<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-9135439</id><updated>2012-01-13T03:29:52.075-08:00</updated><category term='suphp'/><category term='mvc'/><category term='flash'/><category term='javascript'/><category term='fuse'/><category term='php'/><category term='comparison'/><category term='practices'/><category term='jim'/><category term='security'/><category term='rails'/><category term='development'/><category term='keller'/><category term='scriptaculous'/><category term='framework'/><category term='cake'/><category term='cakePHP'/><category term='context'/><category term='rich text'/><category term='safe mode'/><title type='text'>Jim Keller</title><subtitle type='html'>Jim Keller's (Context, LLC) technology and web development blog, with information and thoughts on PHP, Model View Controller (MVC) architecture, Javascript, AJAX, and more</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>49</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-9135439.post-1270131716295725283</id><published>2009-11-03T12:09:00.001-08:00</published><updated>2009-11-03T12:18:02.618-08:00</updated><title type='text'>compiling apache 2.2.14 on CentOS 5</title><content type='html'>wrangled with this a bit today. One of our clients needed the upgrade for PCI compliance, and CentOS still has 2.2.8 as the latest version in YUM. &lt;br /&gt;&lt;br /&gt;I couldn't find any decent RPMs, so I decided to compile from scratch by first downloading &amp; extracting the source from apache.org. &lt;br /&gt;&lt;br /&gt;The ./configure command for installing apache 2.2.14 on CentOS is below. The --disable lines you see are there because CentOS installs the default modules separately, which is probably a good thing. The rest of the options I found by reverse engineering the directory structure of the existing apache install.&lt;br /&gt;&lt;br /&gt;To compile from source, you may need to first run: yum groupinstall 'Development Tools'&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&lt;br /&gt;./configure \&lt;br /&gt;        --prefix=/etc/httpd \&lt;br /&gt;        --exec-prefix=/usr \&lt;br /&gt;        --bindir=/usr/bin \&lt;br /&gt;        --sbindir=/usr/sbin \&lt;br /&gt;        --mandir=/usr/share/man \&lt;br /&gt;        --libdir=/usr/lib64 \&lt;br /&gt;        --sysconfdir=/etc/httpd/conf \&lt;br /&gt;        --includedir=/usr/local/include/httpd \&lt;br /&gt;        --libexecdir=/etc/httpd/modules \&lt;br /&gt;        --datadir=/var/www \&lt;br /&gt;        --with-mpm=prefork \&lt;br /&gt;        --with-devrandom \&lt;br /&gt;        --disable-auth \&lt;br /&gt;        --disable-cgi \&lt;br /&gt;        --disable-cgid \&lt;br /&gt;        --disable-mime \&lt;br /&gt;        --disable-env \&lt;br /&gt;        --disable-setenvif \&lt;br /&gt;        --disable-negotiation \&lt;br /&gt;        --disable-alias \&lt;br /&gt;        --disable-actions \&lt;br /&gt;        --disable-autoindex \&lt;br /&gt;        --disable-include \&lt;br /&gt;        --disable-dir \&lt;br /&gt;        --disable-userdir \&lt;br /&gt;        --disable-status \&lt;br /&gt;        --disable-authn-file \&lt;br /&gt;        --disable-authn-default \&lt;br /&gt;        --disable-authz-default \&lt;br /&gt;        --disable-authz-user \&lt;br /&gt;        --disable-authz-host \&lt;br /&gt;        --disable-authz-groupfile \&lt;br /&gt;        --disable-auth-basic \&lt;br /&gt;        --disable-asis \&lt;br /&gt;        --disable-log-config&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Note that I also had to then comment out the following lines, possibly because I didn't --enable-ldap during the compile. I don't need LDAP though, so I just commented out:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;#LoadModule ldap_module modules/mod_ldap.so&lt;br /&gt;#LoadModule authnz_ldap_module modules/mod_authnz_ldap.so&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-1270131716295725283?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/1270131716295725283/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=1270131716295725283' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/1270131716295725283'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/1270131716295725283'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2009/11/compiling-apache-2214-on-centos-5.html' title='compiling apache 2.2.14 on CentOS 5'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-8041499564237542552</id><published>2009-04-21T21:24:00.001-07:00</published><updated>2009-04-22T09:26:05.315-07:00</updated><title type='text'>installing ruby, rails, and redmine on Apache &amp; FreeBSD 6</title><content type='html'>I will elaborate on this if anyone needs it, but for now I just wanted to post my notes from this evening when I got ruby, rails, and redmine running on FreeBSD 6 with Apache 2.2:&lt;br /&gt;&lt;br /&gt;1. install ruby &lt;br /&gt;&lt;br /&gt;#cd /usr/ports/lang/ruby18/&lt;br /&gt;#make &amp;&amp; make install&lt;br /&gt;&lt;br /&gt;2. install rails 2.2.2&lt;br /&gt;#cd /usr/ports/www/rubygem-rails&lt;br /&gt;#make &amp;&amp; make install&lt;br /&gt;&lt;br /&gt;3. install ruby-iconv (rake script for redmine won't run without this)&lt;br /&gt;cd /usr/ports/converters/ruby-iconv/&lt;br /&gt;#make &amp;&amp; make install&lt;br /&gt;&lt;br /&gt;4. install mysql gem&lt;br /&gt;#/usr/local/bin/gem install mysql &lt;br /&gt;&lt;br /&gt;5. install passenger &lt;br /&gt;#cd /usr/ports/www/rubygem-passenger&lt;br /&gt;#make &amp;&amp; make install&lt;br /&gt;&lt;br /&gt;6. add these lines to your httpd.conf:&lt;br /&gt;&lt;br /&gt;LoadModule passenger_module /usr/local/lib/ruby/gems/1.8/gems/passenger-2.1.3/ext/apache2/mod_passenger.so&lt;br /&gt;PassengerRoot /usr/local/lib/ruby/gems/1.8/gems/passenger-2.1.3&lt;br /&gt;PassengerRuby /usr/local/bin/ruby18 &lt;br /&gt;&lt;br /&gt;7. install SVN (we'll need it to export the redmine trunk)&lt;br /&gt;#cd /usr/ports/devel/subversion&lt;br /&gt;#make &amp;&amp; make install&lt;br /&gt;#rehash&lt;br /&gt;&lt;br /&gt;8. export redmine trunk (note: since we have ruby 2.2.2, we'll need the trunk, not the stable version of redmine.) ** see note below&lt;br /&gt;&lt;br /&gt;svn export http://redmine.rubyforge.org/svn/trunk/ redmine&lt;br /&gt;&lt;br /&gt;9. follow instructions at: http://www.redmine.org/wiki/redmine/RedmineInstall&lt;br /&gt;10. copy redmine/public/dispatch.fcgi.example to redmine/public/dispatch.fcgi&lt;br /&gt;11. -set up redmine vhost:&lt;br /&gt;&lt;br /&gt;#------------------------------------&lt;br /&gt;# redmine.example.com&lt;br /&gt;#----------------------------------&lt;br /&gt;&amp;lt;VirtualHost 192.168.1.100:80&amp;gt;&lt;br /&gt;ServerName redmine.example.com&lt;br /&gt;DocumentRoot /home/www/redmine/public&lt;br /&gt;&lt;br /&gt;&amp;lt;/VirtualHost&amp;gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Now you should be good to go at redmine.example.com!&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;NOTE:&lt;/b&gt;&lt;br /&gt;The version of trunk I exported seemed to have some bugs, so I ended up installing rails 2.1.2 and going with the stable version of redmine by doing:&lt;br /&gt;&lt;br /&gt;#gem install rails --version='=2.1.2'&lt;br /&gt;#svn export http://redmine.rubyforge.org/svn/branches/0.8-stable redmine&lt;br /&gt;&lt;br /&gt;Thanks to &lt;a href="http://matschaffer.com/"&gt;Mat Schaffer&lt;/a&gt; for the help on that one&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-8041499564237542552?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/8041499564237542552/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=8041499564237542552' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/8041499564237542552'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/8041499564237542552'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2009/04/installing-ruby-rails-and-redmine-on.html' title='installing ruby, rails, and redmine on Apache &amp; FreeBSD 6'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-530036360589181201</id><published>2009-01-06T18:08:00.000-08:00</published><updated>2009-01-06T18:11:59.601-08:00</updated><title type='text'>sIFR flash is in front of dropdown menus</title><content type='html'>While using &lt;a href="http://wiki.novemberborn.net/sifr/"&gt;sIFR&lt;/a&gt; to get nice, clean, scalable fonts on the web, I noticed that because sIFR uses flash replacement, the replaced text was appearing on top of (e.g. z-index) the dropdown navigation menus the site had, which was not the desired behavior. The simple fix was adding: sWmode: "opaque" as one of the options to the sIFR.replaceElement method.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-530036360589181201?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/530036360589181201/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=530036360589181201' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/530036360589181201'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/530036360589181201'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2009/01/sifr-flash-is-in-front-of-dropdown.html' title='sIFR flash is in front of dropdown menus'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-1801713162392388317</id><published>2008-10-24T15:45:00.000-07:00</published><updated>2008-10-24T16:04:58.277-07:00</updated><title type='text'>Stop undeliverable spoofed spam using maildrop and PHP</title><content type='html'>A common problem among email providers is the growing practice of spammers using legitimate email addresses in the From: header of their messages, which means that unsuspecting users get flooded with potentially thousands of undeliverable messages due to the fact that many of the To: addresses on the spammers' lists are no longer valid.&lt;br /&gt;A few of our customers had this issue, so I devised a quick way to filter out the undeliverable messages via Maildrop and php.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;1. Set Postfix to pass mail through Maildrop&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;After installing &lt;a href="http://www.courier-mta.org/maildrop/"&gt;maildrop&lt;/a&gt; (it should be readily available in your *nix distro's packaging system) Make sure Postfix is set up to pass mail to Maildrop by putting the following line in your master.cf file:&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;maildrop unix - n n - - pipe&lt;br /&gt;flags=DRhu user=vmail argv=/usr/local/bin/maildrop -d ${recipient} ${recipient}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;(Note: adjust maildrop according to your environment - see &lt;a href="http://www.postfix.org/MAILDROP_README.html"&gt;http://www.postfix.org/MAILDROP_README.html&lt;/a&gt;)&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;2. Place the following code in /usr/local/etc/mail/maildrop_filters.php&lt;/span&gt;&lt;br /&gt;&lt;pre class="code" style="height: 400px;"&gt;&lt;br /&gt;&lt;?php&lt;br /&gt;&lt;br /&gt;ini_set('display_errors', 'off');&lt;br /&gt;error_reporting(0);&lt;br /&gt;&lt;br /&gt;//---------------------------------------&lt;br /&gt;&lt;br /&gt;$recipients = array(&lt;br /&gt;                'recipient1@domain1.com',&lt;br /&gt;                'recipient2@domain2.com'&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;$bad_subjects = array(&lt;br /&gt;        'Undelivered Mail',&lt;br /&gt;        'Delivery Status Notification',&lt;br /&gt;        'Undeliverable mail',&lt;br /&gt;        'Returned Mail',&lt;br /&gt;        'Delayed Mail',&lt;br /&gt;        'Delivery Failure',&lt;br /&gt;        'Warning: could not send message',&lt;br /&gt;        'Warning: message'&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;//---------------------------------------&lt;br /&gt;// Don't edit below here&lt;br /&gt;//---------------------------------------&lt;br /&gt;$sender_address = $argv[1];&lt;br /&gt;$recipient_address = $argv[2];&lt;br /&gt;&lt;br /&gt;$stdin = fopen('php://stdin', 'r');&lt;br /&gt;$msg = '';&lt;br /&gt;$header = '';&lt;br /&gt;&lt;br /&gt;while ( $buf = fread($stdin, 500) ) {&lt;br /&gt;        $msg .= $buf;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;$msg_lines = explode("\n", $msg);&lt;br /&gt;&lt;br /&gt;if ( is_array($msg_lines) ) {&lt;br /&gt;        foreach( $msg_lines as $cur_line ) {&lt;br /&gt;&lt;br /&gt;                $cur_line = trim($cur_line);&lt;br /&gt;&lt;br /&gt;                if ( $cur_line == '' ) {&lt;br /&gt;                        break;&lt;br /&gt;                }&lt;br /&gt;&lt;br /&gt;                $header .= $cur_line . "\n";&lt;br /&gt;&lt;br /&gt;        }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;foreach( $recipients as $recipient ) {&lt;br /&gt;&lt;br /&gt;        if ( preg_match('/^To\:\s*\&lt;?' . preg_quote($recipient) . '/m', $header) ) {&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;                foreach( $bad_subjects as $subj ) {&lt;br /&gt;&lt;br /&gt;                        if ( preg_match("/^Subject:\s*" . preg_quote($subj) . "/im", $header) ) {&lt;br /&gt;                                exit(1);&lt;br /&gt;                        }&lt;br /&gt;                }&lt;br /&gt;        }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;echo $msg;&lt;br /&gt;exit(0);&lt;br /&gt;&lt;br /&gt;?&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;&lt;br /&gt;3. Add the following to your maildroprc file, which will probably be in /etc, /etc/mail, or /usr/local/etc/:&lt;/span&gt;&lt;br /&gt;&lt;pre class="code" style="height: 220px;"&gt;&lt;br /&gt;    RECIPIENT_ADDRESS="$1"&lt;br /&gt; &lt;br /&gt;    exception {&lt;br /&gt;        xfilter "/usr/local/bin/php /usr/local/etc/mail/maildrop_filters.php \"${SENDER}\" \"${RECIPIENT_ADDRESS}\""&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    if ( $RETURNCODE == 1 )&lt;br /&gt;    {&lt;br /&gt;        log "${OUTER_INDENT} Message for ${RECIPIENT_ADDRESS} discarded. Returncode was ${RETURNCODE}"&lt;br /&gt;        EXITCODE=0&lt;br /&gt;        exit&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;4. Edit the $recipients array&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Go back into /usr/local/etc/mail/maildrop_filters.php and edit the $recipients array as necessary - this is where you put the addresses that are being bombarded with the undeliverable mail messages:&lt;br /&gt;&lt;pre style="height: 150px;" class="code"&gt;&lt;br /&gt;$recipients = array(&lt;br /&gt;                'recipient1@domain1.com',&lt;br /&gt;                'recipient2@domain2.com',&lt;br /&gt;                'recipient3@domain3.com',&lt;br /&gt;);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Additionally, you can add or remove subjects from the $bad_subjects array as required. Note that the filter matches subjects that *start* with the items listed in $bad_subjects, so "Undeliverable:" will match "Undeliverable: Mailbox not found" and "Undeliverable: Mailbox is full", and so on.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-1801713162392388317?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/1801713162392388317/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=1801713162392388317' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/1801713162392388317'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/1801713162392388317'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2008/10/stop-undeliverable-spoofed-spam-using.html' title='Stop undeliverable spoofed spam using maildrop and PHP'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-6906857865544948767</id><published>2008-10-22T09:31:00.000-07:00</published><updated>2008-10-22T09:41:09.175-07:00</updated><title type='text'>SwiftFile - Send files securely on the web</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://www.swiftfile.net"&gt;&lt;img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer;" src="http://2.bp.blogspot.com/_bNKq67NO758/SP9V4gfWhZI/AAAAAAAAAAc/edZBtawsBac/s320/swiftfile-ss.jpg" alt="" id="BLOGGER_PHOTO_ID_5260017319074497938" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;As the latest in a series of webtools that &lt;a href="http://www.contextsolutions.net"&gt;Context &lt;/a&gt;is launching, we've decided to offer an extremely &lt;a href="http://www.swiftfile.net"&gt;easy, free, secure solution for uploading and sending files&lt;/a&gt;. SwiftFile.net offers a simple interface to upload and password protect files, making them unreadable even to the site administrator. Our goal was not to compete with filesharing giants like sendspace, mediafire, or rapidshare, but rather to offer a way to send sensitive documents very quickly and very securely. You can read the &lt;a href="https://www.swiftfile.net/faq.html"&gt;SwiftFile FAQ&lt;/a&gt; for information about how the data is stored and encrypted. Give it a try - it's fast, easy, and free. Additionally, thanks to the &lt;a href="http://www.phpfuse.net"&gt;Fuse PHP framework&lt;/a&gt;, the site was put together from start to finish in 2 modest work days. We're hoping that a side effect of these tools will be to showcase the versatility and ease of use of the framework, which is now gaining ground in the PHP MVC community.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-6906857865544948767?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/6906857865544948767/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=6906857865544948767' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/6906857865544948767'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/6906857865544948767'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2008/10/swiftfile-send-files-securely-on-web.html' title='SwiftFile - Send files securely on the web'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_bNKq67NO758/SP9V4gfWhZI/AAAAAAAAAAc/edZBtawsBac/s72-c/swiftfile-ss.jpg' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-3104515890138325960</id><published>2008-10-19T15:51:00.000-07:00</published><updated>2008-10-19T15:52:40.124-07:00</updated><title type='text'>suphp directory not owned by user</title><content type='html'>Installing suPHP on Apache 2 tonight, I was surprised that it was complaining about the parent directory for my document root not being owned by the specific user who owned the scripts. I had it owned by the apache user, www, and it turns out it (the parent directory which, for me, was /home/www) has to be owned by root in order for suPHP to traverse it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-3104515890138325960?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/3104515890138325960/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=3104515890138325960' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/3104515890138325960'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/3104515890138325960'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2008/10/suphp-directory-not-owned-by-user.html' title='suphp directory not owned by user'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-1747499472863065401</id><published>2008-09-04T21:47:00.000-07:00</published><updated>2008-09-04T22:48:05.529-07:00</updated><title type='text'>Why you should do your PHP development in FUSE</title><content type='html'>Let's face it - there are a lot of development frameworks out there, especially for PHP. CodeIgnitor and CakePHP come to mind immediately as leading PHP frameworks, but in this article I'm going to give some compelling reasons as to why you should be doing your PHP development with the &lt;a href="http://www.phpfuse.net/"&gt;Fuse Framework&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Fuse is a Model/View/Controller framework for PHP. If you're not familiar with MVC architecture, you may want to start with my other post, &lt;a href="http://jimkeller.blogspot.com/2008/02/mvc-and-you-partners-in-freedom.html"&gt;MVC and you: Partners in Freedom&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;1. FUSE is easy to get running&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  The installation scripts included with Fuse guarantee that you'll have a working install a few minutes after downloading. To get an idea of just how easy it is to get started with a Fuse project, have a look at the video I've posted &lt;a href="http://www.youtube.com/watch?v=cX0eIdWZQhQ&amp;amp;feature=related"&gt;here&lt;/a&gt; .&lt;br /&gt;  Fuse was built so that PHP developers don't have to feel like they were learning an entirely different language in order to make use of the framework. The syntax and structure follow normal PHP conventions, and everything from the method names to parameter order was designed to be as intuitive as possible.&lt;br /&gt;Let's face it - everyone wants to use the newest, best, and most appropriate tool for the job. However, developers will often shy away from doing things differently because of the learning curve. They fear that introducing new technologies or methodologies will increase their project timeframe and decrease productivity. Fuse was built with a deadline-oriented mentality in mind: you can get started quickly, and you don't have to know every in and out of the framework to start building your project.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&lt;span style="font-size:130%;"&gt;2. Data access has never been easier&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;    Fuse's data modeling makes accessing your data easier than you ever thought possible. The &lt;a href="http://phpfuse.net/wiki/index.php?title=Generating_Models%2C_Controllers%2C_and_Views"&gt;management scripts&lt;/a&gt; will give you create, read, update, and delete access right off the bat, and customizing your queries is as simple as can be. Let's say you want to list your products, but also include their category_name, which lives in the product_categories table. In Fuse, all you have to do is open your ProductController and add this line:&lt;br /&gt;&lt;br /&gt;&lt;pre class="controller_code" style="height: 50px;"&gt;&lt;br /&gt;public $list_options = array( 'include' =&gt; 'product_categories' );&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;That's it. Now when you iterate through your products, you can use the variable &amp;lt;{category_name}&amp;gt; in your template to display the category name for your product. Let's say we only want to display 20 categories? Try this:&lt;br /&gt;&lt;pre class="controller_code" style="height: 50px;"&gt;&lt;br /&gt;public $list_options = array( 'include' =&gt; 'product_categories', 'limit' =&gt; 20 );&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Fuse offers a whole slew of data methods just like these, and your queries can be as simple or as complicated as you need them to be. You will rarely have to write a query, but if you want to, Fuse even offers an &lt;a href="http://phpfuse.net/wiki/index.php?title=Using_the_FuseSQLQuery_Object"&gt;object to take the headache out of writing queries&lt;/a&gt; . And, as always, if you just want to hand code a query the old fashioned way, Fuse will never stop you. A core principal of Fuse is that it's designed to aid the programmer, not force him or her into the methodologies that we've deemed best.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;3. A simple but extremely powerful templating engine&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Fuse contains its own robust, intuitive templating engine that allows you to truly separate your code from your presentation. As with everything else, the templating system was designed to be intuitive, and "designer friendly". Want to loop through the products we fetched above? Try this:&lt;br /&gt;&lt;br /&gt;&lt;pre class="view_code" style="height: 100px;"&gt;&amp;lt;{ITERATOR products}&amp;gt;&lt;br /&gt;name: &amp;lt;{name}&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;category: &amp;lt;{category_name}&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;&amp;lt;br /&amp;gt;&lt;br /&gt;&amp;lt;{/ITERATOR}&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Want to apply a function to one of your fields? How about we only display the first 200 characters of our description:&lt;br /&gt;&lt;pre class="view_code" style="height: 100px;"&gt;&amp;lt;{ITERATOR products}&amp;gt;&lt;br /&gt;name: &amp;lt;{name}&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;category: &amp;lt;{category_name}&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;description: &amp;lt;{ print(substr(description, 0, 200)) }&amp;gt;&lt;br /&gt;&amp;lt;br /&amp;gt;&lt;br /&gt;&amp;lt;{/ITERATOR}&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Maybe our products can be in more than one category, and we want to fetch them?&lt;br /&gt;&lt;br /&gt;After adding a one line method to our Product model that looks like this:&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;public function get_categories() {&lt;br /&gt;return $this-&gt;product_categories-&gt;fetch_all();&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;We can do:&lt;br /&gt;&lt;pre class="view_code" style="height: 100px;"&gt;&lt;br /&gt;&amp;lt;{ITERATOR products}&amp;gt;&lt;br /&gt;Name: &amp;lt;{name}&amp;gt;&lt;br /&gt;Categories:&lt;br /&gt;&amp;lt;{ITERATOR get_categories()}&amp;gt;&lt;br /&gt;   &amp;lt;{name}&amp;gt;&lt;br /&gt;&amp;lt;{/ITERATOR}&amp;gt;&lt;br /&gt;&amp;lt;{/ITERATOR}&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Yes, &amp;lt;{name}&amp;gt; will know that when you're in the products loop, you want the product name and when you're in the product categories loop, you want the category name.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;span style="font-weight: bold;"&gt;4. Searching your data has &lt;span style="font-style: italic;"&gt;definitely&lt;/span&gt; never been easier.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Your client tells you that they want to be able to search records with any combination of title, date, description, id, and author. You know this is going to be an annoying task of using if/then statements to build a search query. Unless you're using Fuse, where you can do it all with a few lines of code.&lt;br /&gt;&lt;br /&gt;The FuseDataController object, which handles all of the wheeling and dealing your app does with the database, has builtin search capabilities that will automatically sanitize data, generate the search query, and maintain the search session across pages. All you have to do is tell it which fields a user can search on, and it's ready to go. You can search fields values directly, with wildcards on either side, between two date intervals, or even have Fuse automatically parse strings like "restaurant AND (mexican OR Thai)" simply by setting your 'filter_type' to 'parsed_boolean'. The search capabilities are discussed &lt;a href="http://phpfuse.net/wiki/index.php?title=Searching_data"&gt;here&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;span style="font-weight: bold;"&gt;5. Builtin management of photos for albums, user profiles, etc.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Let's say you're building a site that allows users to create a profile, add photo albums, then add photos to those albums. You're going to want several different sizes - a full sized image, a thumbnail for browsing, and a tiny thumbnail for a contact sheet type of display. You also want to watermark each image with your site's logo. This can be a pretty tedious task if you're not using the &lt;a href="http://phpfuse.net/wiki/index.php?title=Using_FusePhotoController"&gt;FusePhotoController&lt;/a&gt;, which should have you up and running in about a half hour if you're slow.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;span style="font-weight: bold;"&gt;6. Simple but fully featured, scalable user management and ACL&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;A lot of sites need user authentication. I've seen a lot of custom implementation in my time, and because of the complexity of doing it right, mostly they rely on a very basic user scheme that allows little or no granularity or scalability when it comes to setting permissions. Fuse has a simple to implement, but fully granular and scalable user authentication and permissions scheme built right in. Need to associate users with groups that all have different permissions? No problem. Have a user who's in the "editors" group, but shouldn't have access to delete an article? Just add a restriction for that user. Need to set it up so that when a user tries to access a restricted page, they are asked to login, then redirected back to the original page on success? It's already done. Password encryption? SHA-1, MD5, and crypt() are all supported. I could go on. You can read about it on your own &lt;a href="http://phpfuse.net/wiki/index.php?title=Authenticating_Controller_Actions"&gt;here&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;span style="font-weight: bold;"&gt;7. Integration with existing non-Fused projects&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;     I mentioned above that Fuse endeavors to never prevent the developer from doing what he or she needs to do. If you have a project that's already done in standard, inline PHP, Fuse can go right alongside your existing code without upsetting any of the existing functionality. In fact, if you include the Fuse bootstrap in one of your existing php scripts, you can add Fuse functionality to that script without having to edit any of the existing code. &lt;br /&gt;&lt;br /&gt;     I have several projects right now that were handed to me as inline PHP, and I put Fuse right on top of it without having to re-code a single line of the existing project. However, I can now use Fuse moving forward for new features and updated functionality.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;span style="font-weight: bold;"&gt;Conclusion&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;There you have it. Just a selected list of reasons you should switch to Fuse. Today. For the project you're working on. Right now. Make things easier on yourself. I've introduced a lot of people to MVC development and Fuse and, without fail, every single one has said, after just one project, that they could never possibly go back to their old methodologies of inline scripting and manually writing every query.&lt;br /&gt;&lt;br /&gt;Comments welcome.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-1747499472863065401?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/1747499472863065401/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=1747499472863065401' title='15 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/1747499472863065401'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/1747499472863065401'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2008/09/why-you-should-do-your-php-development.html' title='Why you should do your PHP development in FUSE'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>15</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-2618695836562626923</id><published>2008-08-28T20:54:00.000-07:00</published><updated>2008-08-28T20:58:01.328-07:00</updated><title type='text'>exchange defragment stuck or frozen</title><content type='html'>I needed to defragment an Exchange 2000 mailbox store tonight because it had reached the 16GB limit. I started the process, let it run to about 5%, then walked away for about an hour. I came back, and it hadn't moved. I thought, "how long does it take to defragment the mailbox store??". I checked the temp files that the defragmenter was generating and noticed they hadn't been modified in an hour. Long story short: for some reason, clicking the mouse in the command window where the defragmenter is running will cause it to pause, and you can restart it by hitting F5. &lt;br /&gt;&lt;br /&gt;Thanks to the following link for the F5 info: http://www.eggheadcafe.com/software/aspnet/31768198/how-do-i-know-how-long-th.aspx&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-2618695836562626923?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/2618695836562626923/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=2618695836562626923' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/2618695836562626923'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/2618695836562626923'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2008/08/exchange-2000-defragment-stuck-or.html' title='exchange defragment stuck or frozen'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-5067106064114966050</id><published>2008-08-07T20:14:00.000-07:00</published><updated>2008-08-07T20:16:19.090-07:00</updated><title type='text'>CS-Cart images not displayed after server move / mysqldump</title><content type='html'>if you are using CS-Cart and you've copied the database using a mysqldump ,the binary data for the images may not have been migrated properly. You need to make sure to use the --hex-blob switch when dumping the database. More information on the --hex-blob switch can be found &lt;a href="http://dev.mysql.com/doc/refman/5.0/en/mysqldump.html#option_mysqldump_hex-blob"&gt;here&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-5067106064114966050?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/5067106064114966050/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=5067106064114966050' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/5067106064114966050'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/5067106064114966050'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2008/08/cs-cart-images-not-displayed-after.html' title='CS-Cart images not displayed after server move / mysqldump'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-4464380703603777900</id><published>2008-08-06T21:50:00.001-07:00</published><updated>2008-08-06T21:52:35.832-07:00</updated><title type='text'>ExpressionEngine flv upload file type not allowed</title><content type='html'>To allow FLV files to be uploaded in ExpressionEngine without getting the file type not allowed message, add the following to /system/lib/mimes.php in the $mimes array:&lt;br /&gt;&lt;br /&gt;&lt;pre class="code"&gt;&lt;br /&gt;'flv' =&gt; 'video/x-flv'&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;So, the end of your $mimes array will end up looking like:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt; 'aif' =&gt; 'audio/x-aiff',&lt;br /&gt; 'aac' =&gt; 'audio/aac',&lt;br /&gt; 'flv' =&gt; 'video/x-flv'&lt;br /&gt;);&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-4464380703603777900?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/4464380703603777900/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=4464380703603777900' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/4464380703603777900'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/4464380703603777900'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2008/08/expressionengine-flv-upload-file-type.html' title='ExpressionEngine flv upload file type not allowed'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-527420850990891971</id><published>2008-07-26T12:52:00.000-07:00</published><updated>2008-08-26T18:38:32.869-07:00</updated><title type='text'>PHP class for Payflow Pro transactions</title><content type='html'>The Payflow Pro PHP sample code was a bit messy, so I decided to wrap it in a nice object to make the entire transaction process very easy. This class will be included as part of the &lt;a href="http://www.phpfuse.net"&gt;FUSE PHP framework&lt;/a&gt;, but I also created a standalone version below:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;PayFlowTransaction.class.php:&lt;/span&gt;&lt;br /&gt;&lt;pre class="code" style="background-color: #F1F1F1; overflow: auto; width: 450px; height: 450px;"&gt;&lt;br /&gt;class PayFlowTransaction {&lt;br /&gt;&lt;br /&gt; const HTTP_RESPONSE_OK = 200;&lt;br /&gt; const KEY_MAP_ARRAY = 'map';&lt;br /&gt; &lt;br /&gt; public $data;&lt;br /&gt; public $headers = array();&lt;br /&gt; public $gateway_retries = 3;&lt;br /&gt; public $gateway_retry_wait = 5; //seconds&lt;br /&gt; public $environment = 'test';&lt;br /&gt; &lt;br /&gt; public $vps_timeout = 45;&lt;br /&gt; public $curl_timeout = 90;&lt;br /&gt; &lt;br /&gt; public $gateway_url_live = 'https://payflowpro.paypal.com';&lt;br /&gt; public $gateway_url_devel = 'https://pilot-payflowpro.paypal.com';&lt;br /&gt; &lt;br /&gt;&lt;br /&gt; public $avs_addr_required = 0;&lt;br /&gt; public $avs_zip_required = 0;&lt;br /&gt; public $cvv2_required = 0;&lt;br /&gt; public $fraud_protection = false;&lt;br /&gt; &lt;br /&gt; public $raw_response;&lt;br /&gt; //public $response;&lt;br /&gt; public $response_arr = array();&lt;br /&gt; &lt;br /&gt; public $txn_successful = null;&lt;br /&gt; public $raw_result;&lt;br /&gt; &lt;br /&gt; public $debug = false;&lt;br /&gt; &lt;br /&gt; public function __construct() {&lt;br /&gt;  &lt;br /&gt;  $this-&gt;load_config();&lt;br /&gt;  &lt;br /&gt;  &lt;br /&gt; }&lt;br /&gt; &lt;br /&gt; public function load_config() {&lt;br /&gt;  &lt;br /&gt;  if ( defined('PAYFLOWPRO_USER') ) {&lt;br /&gt;   $this-&gt;data['USER'] = constant('PAYFLOWPRO_USER');&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;  if ( defined('PAYFLOWPRO_PWD') ) {&lt;br /&gt;   $this-&gt;data['PWD'] = constant('PAYFLOWPRO_PWD');&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  if ( defined('PAYFLOWPRO_PARTNER') ) {&lt;br /&gt;   $this-&gt;data['PARTNER'] = constant('PAYFLOWPRO_PARTNER');&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;  if ( defined('PAYFLOWPRO_VENDOR') ) { &lt;br /&gt;   $this-&gt;data['VENDOR'] = constant('PAYFLOWPRO_VENDOR');&lt;br /&gt;  }&lt;br /&gt;  else {&lt;br /&gt;   if ( isset($this-&gt;data['USER']) ) {&lt;br /&gt;    $this-&gt;data['VENDOR'] = $this-&gt;data['USER'];&lt;br /&gt;   }&lt;br /&gt;   else {&lt;br /&gt;    $this-&gt;data['VENDOR'] = null;&lt;br /&gt;   }&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt; }&lt;br /&gt; &lt;br /&gt; public function __set( $key, $val ) {&lt;br /&gt;  &lt;br /&gt;  $this-&gt;data[$key] = $val;&lt;br /&gt;  &lt;br /&gt; }&lt;br /&gt; &lt;br /&gt; public function __get( $key ) {&lt;br /&gt;  &lt;br /&gt;  if ( isset($this-&gt;data[$key]) ) {&lt;br /&gt;   return $this-&gt;data[$key];&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;  return null;&lt;br /&gt; }&lt;br /&gt; &lt;br /&gt; public function get_gateway_url() {&lt;br /&gt;  &lt;br /&gt;  if ( strtolower($this-&gt;environment) == 'live' ) {&lt;br /&gt;   return $this-&gt;gateway_url_live;&lt;br /&gt;  }&lt;br /&gt;  else {&lt;br /&gt;   return $this-&gt;gateway_url_devel;&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt; }&lt;br /&gt; &lt;br /&gt; public function get_data_string() {&lt;br /&gt;  &lt;br /&gt;  $query = array();&lt;br /&gt;&lt;br /&gt;  if ( !isset($this-&gt;data['VENDOR']) || !$this-&gt;data['VENDOR'] ) {&lt;br /&gt; $this-&gt;data['VENDOR'] = $this-&gt;data['USER'];&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  &lt;br /&gt;  foreach ( $this-&gt;data as $key =&gt; $value) {&lt;br /&gt;   &lt;br /&gt;   if ( $this-&gt;debug ) {&lt;br /&gt;    echo "{$key} = {$value}&lt;br /&gt;";&lt;br /&gt;   }&lt;br /&gt;   &lt;br /&gt;   $query[] = strtoupper($key) . '[' .strlen($value).']='.$value;&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;  return implode('&amp;', $query);&lt;br /&gt;  &lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; public function before_send_transaction() {&lt;br /&gt;  &lt;br /&gt;  $this-&gt;txn_successful = false;&lt;br /&gt;  $this-&gt;raw_response = null; //reset raw result&lt;br /&gt;  $this-&gt;response_arr = array();&lt;br /&gt; } &lt;br /&gt; &lt;br /&gt; public function reset() {&lt;br /&gt;  &lt;br /&gt;  $this-&gt;txn_successful = null;&lt;br /&gt;  $this-&gt;raw_response = null; //reset raw result&lt;br /&gt;  $this-&gt;response_arr = array();&lt;br /&gt;  $this-&gt;data = array();&lt;br /&gt;  $this-&gt;load_config();&lt;br /&gt; } &lt;br /&gt; &lt;br /&gt; &lt;br /&gt; public function send_transaction() {&lt;br /&gt;  &lt;br /&gt;  try { &lt;br /&gt;   &lt;br /&gt;   $this-&gt;before_send_transaction();&lt;br /&gt;    &lt;br /&gt;   $data_string = $this-&gt;get_data_string();&lt;br /&gt;   &lt;br /&gt;      $headers[] = "Content-Type: text/namevalue"; //or text/xml if using XMLPay.&lt;br /&gt;      $headers[] = "Content-Length: " . strlen ($data_string);  // Length of data to be passed &lt;br /&gt;      $headers[] = "X-VPS-Timeout: {$this-&gt;vps_timeout}";&lt;br /&gt;      $headers[] = "X-VPS-Request-ID:" . uniqid(rand(), true);&lt;br /&gt;   $headers[] = "X-VPS-VIT-Client-Type: PHP/cURL";          // What you are using&lt;br /&gt;   &lt;br /&gt;   $headers = array_merge( $headers, $this-&gt;headers );&lt;br /&gt; &lt;br /&gt;   if ( $this-&gt;debug ) {&lt;br /&gt;    echo  __METHOD__ . ' Sending: ' . $data_string . '&lt;br /&gt;';&lt;br /&gt;   }&lt;br /&gt; &lt;br /&gt;      $ch = curl_init();&lt;br /&gt;      curl_setopt($ch, CURLOPT_URL, $this-&gt;get_gateway_url() );&lt;br /&gt;      curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);&lt;br /&gt;      curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);&lt;br /&gt;      curl_setopt($ch, CURLOPT_HEADER, 1);                // tells curl to include headers in response&lt;br /&gt;      curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);        // return into a variable&lt;br /&gt;      curl_setopt($ch, CURLOPT_TIMEOUT, 90);              // times out after 90 secs&lt;br /&gt;      curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);&lt;br /&gt;      curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);        // this line makes it work under https&lt;br /&gt;      curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);        //adding POST data&lt;br /&gt;      curl_setopt($ch, CURLOPT_SSL_VERIFYHOST,  2);       //verifies ssl certificate&lt;br /&gt;      curl_setopt($ch, CURLOPT_FORBID_REUSE, TRUE);       //forces closure of connection when done&lt;br /&gt;      curl_setopt($ch, CURLOPT_POST, 1);          //data sent as POST&lt;br /&gt; &lt;br /&gt;   $i = 0;&lt;br /&gt; &lt;br /&gt;      while ($i++ &lt;= $this-&gt;gateway_retries) {&lt;br /&gt;          &lt;br /&gt;          $result = curl_exec($ch);&lt;br /&gt;          $headers = curl_getinfo($ch);&lt;br /&gt; &lt;br /&gt;          if (array_key_exists('http_code', $headers) &amp;&amp; $headers['http_code'] != self::HTTP_RESPONSE_OK) {&lt;br /&gt;              sleep($this-&gt;gateway_retry_wait);  // Let's wait to see if its a temporary network issue.&lt;br /&gt;          }&lt;br /&gt;          else  {&lt;br /&gt;              // we got a good response, drop out of loop.&lt;br /&gt;              break;&lt;br /&gt;          }&lt;br /&gt;      }  &lt;br /&gt;&lt;br /&gt;      if ( !array_key_exists('http_code', $headers) || $headers['http_code'] != self::HTTP_RESPONSE_OK ) {&lt;br /&gt;    throw new InvalidResponseCodeException;&lt;br /&gt;      }&lt;br /&gt;&lt;br /&gt;   $this-&gt;raw_response = $result;&lt;br /&gt;   &lt;br /&gt;   $result = strstr($result, "RESULT");&lt;br /&gt;   $ret = array();&lt;br /&gt;&lt;br /&gt;      while( strlen($result) &gt; 0 ){&lt;br /&gt;&lt;br /&gt;          $keypos = strpos($result,'=');&lt;br /&gt;          $keyval = substr($result,0,$keypos);&lt;br /&gt; &lt;br /&gt;          // value&lt;br /&gt;          $valuepos = strpos($result,'&amp;') ? strpos($result,'&amp;'): strlen($result);&lt;br /&gt;          $valval = substr($result,$keypos+1,$valuepos-$keypos-1);&lt;br /&gt;&lt;br /&gt;          // decoding the respose&lt;br /&gt;          $ret[$keyval] = $valval;&lt;br /&gt;        &lt;br /&gt;          $result = substr($result, $valuepos+1, strlen($result) );&lt;br /&gt;      }&lt;br /&gt;      &lt;br /&gt;   return $ret;&lt;br /&gt;  }&lt;br /&gt;  catch( Exception $e ) {&lt;br /&gt;   @curl_close($ch);&lt;br /&gt;   throw $e;&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt; &lt;br /&gt; public function response_handler( $response_arr ) {&lt;br /&gt; &lt;br /&gt;  try { &lt;br /&gt;      $result_code = $response_arr['RESULT']; // get the result code to validate.&lt;br /&gt;  &lt;br /&gt;   if ( $this-&gt;debug ) {&lt;br /&gt;    echo __METHOD__ . ' response=' . print_r( $response_arr, true) . '&lt;br /&gt;';&lt;br /&gt;    echo __METHOD__ . ' RESULT=' . $result_code . '&lt;br /&gt;';&lt;br /&gt;   }&lt;br /&gt;   &lt;br /&gt;   if ( $result_code == 0 ) {&lt;br /&gt;&lt;br /&gt;    //&lt;br /&gt;    // Even on zero, still check AVS&lt;br /&gt;    //&lt;br /&gt;          &lt;br /&gt;          if ( $this-&gt;avs_addr_required ) {&lt;br /&gt;     $err_msg = "Your billing (street) information does not match.";&lt;br /&gt;           &lt;br /&gt;           if ( isset($response_arr['AVSADDR'])) {&lt;br /&gt;                if ($response_arr['AVSADDR'] != "Y") {&lt;br /&gt;              throw new AVSException( $err_msg  );&lt;br /&gt;                }&lt;br /&gt;              }&lt;br /&gt;              else {&lt;br /&gt;               if ( $this-&gt;avs_addr_required == 2 ) {&lt;br /&gt;              throw new AVSException( $err_msg );&lt;br /&gt;               }&lt;br /&gt;              }&lt;br /&gt;          }&lt;br /&gt;  &lt;br /&gt;    if ( $this-&gt;avs_zip_required ) {&lt;br /&gt;  &lt;br /&gt;              $err_msg = "Your billing (zip) information does not match. Please re-enter.";&lt;br /&gt;  &lt;br /&gt;           if (isset($nvpArray['AVSZIP'])) {&lt;br /&gt;               if ($nvpArray['AVSZIP'] != "Y") {&lt;br /&gt;       throw new AVSException( $err_msg );&lt;br /&gt;               }&lt;br /&gt;              }&lt;br /&gt;              else {&lt;br /&gt;               if ( $this-&gt;avs_zip_required == 2 ) {&lt;br /&gt;              throw new AVSException( $err_msg );&lt;br /&gt;               }&lt;br /&gt;               &lt;br /&gt;              }&lt;br /&gt;          }&lt;br /&gt;          &lt;br /&gt;          if ( $this-&gt;require_cvv2_match ) {&lt;br /&gt;  &lt;br /&gt;     $err_msg = "Your card code is invalid. Please re-enter.";&lt;br /&gt;           &lt;br /&gt;           if ( array_key_exists('CVV2MATCH', $response_arr) ) {&lt;br /&gt;               if ($response_arr['CVV2MATCH'] != "Y") {&lt;br /&gt;                   throw new CVV2Exception( $err_msg );&lt;br /&gt;               }&lt;br /&gt;              }&lt;br /&gt;              else {&lt;br /&gt;               if ( $this-&gt;require_cvv2_match == 2 ) {&lt;br /&gt;              throw new CVV2Exception( $err_msg );&lt;br /&gt;               }&lt;br /&gt;              }&lt;br /&gt;          }&lt;br /&gt;  &lt;br /&gt;    //&lt;br /&gt;    // Return code was 0 and no AVS exceptions raised&lt;br /&gt;    //&lt;br /&gt;    $this-&gt;txn_successful = true;&lt;br /&gt;    &lt;br /&gt;    parse_str($this-&gt;raw_response, $this-&gt;response_arr);&lt;br /&gt;    return $this-&gt;response_arr;&lt;br /&gt;      }&lt;br /&gt;      else if ($result_code == 1 || $result_code == 26) {&lt;br /&gt;    throw new InvalidCredentialsException( "Invalid API Credentials" );&lt;br /&gt;      }&lt;br /&gt;      else if ($result_code == 12) {&lt;br /&gt;          // Hard decline from bank.&lt;br /&gt;          throw new TransactionDataException( "Your transaction was declined." );&lt;br /&gt;      }&lt;br /&gt;      else if ($result_code == 13) {&lt;br /&gt;          // Voice authorization required.&lt;br /&gt;          throw new TransactionDataException ("Your Transaction is pending. Contact Customer Service to complete your order.");&lt;br /&gt;      }&lt;br /&gt;      else if ($result_code == 23 || $result_code == 24) {&lt;br /&gt;          // Issue with credit card number or expiration date.&lt;br /&gt;         $msg = 'Invalid credit card information: ' . $response_arr['RESPMSG'];&lt;br /&gt;         throw new TransactionDataException ($msg);&lt;br /&gt;      }&lt;br /&gt;  &lt;br /&gt;      // Using the Fraud Protection Service.&lt;br /&gt;      // This portion of code would be is you are using the Fraud Protection Service, this is for US merchants only.&lt;br /&gt;      if ( $this-&gt;fraud_protection ) {&lt;br /&gt;  &lt;br /&gt;          if ($result_code == 125) {&lt;br /&gt;              // 125 = Fraud Filters set to Decline.&lt;br /&gt;              throw new FraudProtectionException ( "Your Transaction has been declined. Contact Customer Service to place your order." );&lt;br /&gt;          }&lt;br /&gt;          else if ($result_code == 126) {&lt;br /&gt;              throw new FraudProtectionException ( "Your Transaction is Under Review. We will notify you via e-mail if accepted." );&lt;br /&gt;          }&lt;br /&gt;          else if ($result_code == 127) {&lt;br /&gt;     throw new FraudProtectionException ( "Your Transaction is Under Review. We will notify you via e-mail if accepted." );&lt;br /&gt;          }&lt;br /&gt;      }&lt;br /&gt;      &lt;br /&gt;      //&lt;br /&gt;      // Throw generic response&lt;br /&gt;      //&lt;br /&gt;      throw new FuseException( $response_arr['RESPMSG'] );&lt;br /&gt;      &lt;br /&gt;      &lt;br /&gt;  }&lt;br /&gt;  catch( Exception $e ) {&lt;br /&gt;   throw $e;&lt;br /&gt;  }&lt;br /&gt;   &lt;br /&gt; } &lt;br /&gt;&lt;br /&gt; public function process() {&lt;br /&gt; &lt;br /&gt;  try { &lt;br /&gt;   return $this-&gt;response_handler($this-&gt;send_transaction());&lt;br /&gt;  }&lt;br /&gt;  catch( Exception $e ) {&lt;br /&gt;   throw $e;&lt;br /&gt;  }&lt;br /&gt; &lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; public function apply_associative_array( $arr, $options = array() ) {&lt;br /&gt;  &lt;br /&gt;  try { &lt;br /&gt;   &lt;br /&gt;   $map_array = array();&lt;br /&gt;     &lt;br /&gt;   if ( isset($options[self::KEY_MAP_ARRAY]) ) {&lt;br /&gt;    $map_array = $options[self::KEY_MAP_ARRAY];&lt;br /&gt;   }&lt;br /&gt;  &lt;br /&gt;   foreach( $arr as $cur_key =&gt; $val ) {&lt;br /&gt;&lt;br /&gt;    if( isset($map_array[$cur_key]) ) {&lt;br /&gt;     $cur_key = $map_array[$cur_key];&lt;br /&gt;    }&lt;br /&gt;    else {&lt;br /&gt;     if ( isset($options['require_map']) &amp;&amp; $options['require_map'] ) {&lt;br /&gt;      continue;&lt;br /&gt;     }&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    $this-&gt;data[strtoupper($cur_key)] = $val;&lt;br /&gt;   &lt;br /&gt;   }&lt;br /&gt;  }&lt;br /&gt;  catch( Exception $e ) {&lt;br /&gt;   throw $e;&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt; }&lt;br /&gt; &lt;br /&gt; &lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;class InvalidCredentialsException extends Exception {&lt;br /&gt; &lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;class GatewayException extends Exception {&lt;br /&gt; &lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;class InvalidResponseCodeException extends GatewayException {&lt;br /&gt; &lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;class TransactionDataException extends Exception {&lt;br /&gt; &lt;br /&gt;} &lt;br /&gt;&lt;br /&gt;class AVSException extends TransactionDataException {&lt;br /&gt; &lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;class CVV2Exception extends TransactionDataException {&lt;br /&gt; &lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;class FraudProtectionException extends Exception {&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Usage:&lt;/span&gt;&lt;br /&gt;&lt;pre class="code" style="background-color: #F1F1F1; overflow:auto; width: 450px; height: 450px;"&gt;&lt;br /&gt;  &lt;br /&gt;  try { &lt;br /&gt;   &lt;br /&gt;   require_once('PayFlowTransaction.class.php');//assumes it's in the current dir&lt;br /&gt;&lt;br /&gt;   $txn = new PayflowTransaction();&lt;br /&gt;  &lt;br /&gt;   //&lt;br /&gt;   //these are provided by your payflow reseller&lt;br /&gt;   //&lt;br /&gt;   $txn-&gt;PARTNER = 'yourpartnername';&lt;br /&gt;   $txn-&gt;USER = 'yourusername';&lt;br /&gt;   $txn-&gt;PWD= 'yourpassword';&lt;br /&gt;   $txn-&gt;VENDOR = $txn-&gt;USER; //or your vendor name&lt;br /&gt; &lt;br /&gt;    //&lt;br /&gt;   // transaction information&lt;br /&gt;   //&lt;br /&gt;&lt;br /&gt;   $txn-&gt;TENDER = 'C'; //sets to a cc transaction&lt;br /&gt;   $txn-&gt;ACCT = '4111111111111111'; //cc number&lt;br /&gt;   $txn-&gt;TRXTYPE = 'S'; //txn type: sale&lt;br /&gt;   $txn-&gt;AMT = 1.00; //amount: 1 dollar&lt;br /&gt;   $txn-&gt;EXPDATE='0210'; //4 digit expiration date&lt;br /&gt;  &lt;br /&gt; &lt;br /&gt;   $txn-&gt;FIRSTNAME = 'Joe';&lt;br /&gt;   $txn-&gt;LASTNAME = 'Junior Shabadu';&lt;br /&gt;   $txn-&gt;STREET = '123 mystreet';&lt;br /&gt;   $txn-&gt;CITY = 'Philadelphia';&lt;br /&gt;   $txn-&gt;STATE = 'PA';&lt;br /&gt;   $txn-&gt;ZIP = '19115';&lt;br /&gt;   $txn-&gt;COUNTRY = 'US';&lt;br /&gt;  &lt;br /&gt;   //$txn-&gt;debug = true; //uncomment to see debugging information&lt;br /&gt;   //$txn-&gt;avs_addr_required = 1; //set to 1 to enable AVS address checking, 2 to force "Y" response&lt;br /&gt;   //$txn-&gt;avs_zip_required = 1; //set to 1 to enable AVS zip code checking, 2 to force "Y" response&lt;br /&gt;   //$txn-&gt;cvv2_required = 1; //set to 1 to enable cvv2 checking, 2 to force "Y" response&lt;br /&gt;   //$txn-&gt;fraud_protection = true; //uncomment to enable fraud protection&lt;br /&gt;&lt;br /&gt;   $txn-&gt;process();&lt;br /&gt;   &lt;br /&gt;   echo "success: " . $txn-&gt;txn_successful;&lt;br /&gt;   echo "response was: " . print_r( $txn-&gt;response_arr, true );   &lt;br /&gt;&lt;br /&gt;  }&lt;br /&gt;  catch( TransactionDataException $tde ) {&lt;br /&gt;   echo 'bad transaction data ' . $tde-&gt;getMessage();&lt;br /&gt;        }&lt;br /&gt;        catch( InvalidCredentialsException $e ) {&lt;br /&gt;   echo 'Invalid credentials';&lt;br /&gt;  }&lt;br /&gt;  catch( InvalidResponseCodeException $irc ) {&lt;br /&gt;   echo 'bad response code: ' . $irc-&gt;getMessage();&lt;br /&gt;  }&lt;br /&gt;  catch( AVSException $avse ) {&lt;br /&gt;   echo 'AVS error: ' . $avse-&gt;getMessage();&lt;br /&gt;  }&lt;br /&gt;  catch( CVV2Exception $cvve ) {&lt;br /&gt;   echo 'CVV2 error: ' . $cvve-&gt;getMessage();&lt;br /&gt;  }&lt;br /&gt;  catch( FraudProtectionException $fpe ) {&lt;br /&gt;   echo 'Fraud Protection error: ' . $fpe-&gt;getMessage();&lt;br /&gt;  }&lt;br /&gt;        catch( Exception $e ) {&lt;br /&gt;   echo $e-&gt;getMessage();&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;               &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;You will probably want to implement better error checking than what I did in the example. However, you'll still want to display TransactionDataException messages back to the user, because they're usually invalid expiration dates or something similar.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-527420850990891971?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/527420850990891971/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=527420850990891971' title='16 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/527420850990891971'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/527420850990891971'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2008/07/php-class-for-payflow-pro-transactions.html' title='PHP class for Payflow Pro transactions'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>16</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-8003641625403032934</id><published>2008-07-15T17:49:00.000-07:00</published><updated>2008-07-15T17:58:25.034-07:00</updated><title type='text'>jQuery isChildOf - is element a child of / ancestor of element - plugin</title><content type='html'>If anyone knows of an existing jQuery method of determining whether an element is a child of another element, please feel free to post. I couldn't find one, so I developed the plugin below:&lt;br /&gt;&lt;br /&gt;&lt;pre style="height: 300px; overflow: auto; width: 500px; background-color:#EEEEEE;"&gt;&lt;br /&gt;(function($) {&lt;br /&gt;    $.fn.extend({&lt;br /&gt;        isChildOf: function( filter_string ) {&lt;br /&gt;          &lt;br /&gt;          var parents = $(this).parents().get();&lt;br /&gt;         &lt;br /&gt;          for ( j = 0; j &lt; parents.length; j++ ) {&lt;br /&gt;           if ( $(parents[j]).is(filter_string) ) {&lt;br /&gt;      return true;&lt;br /&gt;           }&lt;br /&gt;          }&lt;br /&gt;          &lt;br /&gt;          return false;&lt;br /&gt;        }&lt;br /&gt;    });&lt;br /&gt;})(jQuery); &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The usage is:&lt;br /&gt;&lt;br /&gt;$(element).isChildOf(&lt;i&gt;filter_string&lt;/i&gt;)&lt;br /&gt;&lt;br /&gt;where &lt;i&gt;filter_string&lt;/i&gt; is any of the &lt;i&gt;expr&lt;/i&gt; values that can be passed to JQuery's &lt;a href="http://docs.jquery.com/Traversing/is#expr"&gt;is()&lt;/a&gt; function. You can get more information &lt;a href="http://docs.jquery.com/Traversing/is#expr"&gt;here&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;example:&lt;br /&gt;&lt;pre style="overflow:auto; width: 500px;  background-color:#EEEEEE;"&gt;&lt;br /&gt;&amp;lt;div id="parent2"&amp;gt;&lt;br /&gt;  &amp;lt;div id="parent1"&amp;gt;&lt;br /&gt;    &amp;lt;div id="child"&amp;gt;&lt;br /&gt;    &amp;lt;/div&amp;gt;&lt;br /&gt;  &amp;lt;/div&amp;gt;&lt;br /&gt;&amp;lt;/div&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Alerts "true":&lt;/span&gt;&lt;br /&gt;&lt;pre style="overflow:auto; width: 500px; background-color:#EEEEEE;"&gt;&lt;br /&gt;alert( $('#child').isChildOf('#parent1') );&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Alerts "true":&lt;/span&gt;&lt;br /&gt;&lt;pre style="overflow:auto; width: 500px;  background-color:#EEEEEE;"&gt;&lt;br /&gt;alert( $('#child').isChildOf('#parent2') );&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Alerts "false"&lt;/span&gt;&lt;br /&gt;&lt;pre style="overflow:auto; width: 500px; background-color:#EEEEEE;;"&gt;&lt;br /&gt;alert( $('#child').isChildOf('#notaparent') );&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-8003641625403032934?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/8003641625403032934/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=8003641625403032934' title='8 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/8003641625403032934'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/8003641625403032934'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2008/07/jquery-ischildof-is-element-child-of.html' title='jQuery isChildOf - is element a child of / ancestor of element - plugin'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-561382066017641344</id><published>2008-06-18T10:25:00.000-07:00</published><updated>2008-06-18T10:36:26.761-07:00</updated><title type='text'>File Uploads with FUSE PHP MVC Framework</title><content type='html'>I had a project today that required that PDF files be attached to job orders, and FUSE made the PDF upload a simple task. All I had to do was add an after_add() method in my controller to handle the upload, as shown below. The filename is the id of the job that was just added to the database, with a PDF extension:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;function after_add() {&lt;br /&gt; &lt;br /&gt;     if ( $this-&gt;is_postback() ) { &lt;br /&gt;        FUSE::Require_class('File/FileUpload');&lt;br /&gt; &lt;br /&gt;       $upload = new FileUpload();&lt;br /&gt;       $upload-&gt;file_input_name = 'waiver_file';&lt;br /&gt;       $upload-&gt;destination_dir_create = true;&lt;br /&gt;       $upload-&gt;allow_file_extension( 'pdf' );&lt;br /&gt;   &lt;br /&gt;       $upload-&gt;destination_path = APP_BASE_PATH  . DIRECTORY_SEPARATOR . 'files' &lt;br /&gt;                                  . DIRECTORY_SEPARATOR . $this-&gt;job-&gt;id . ".pdf";   &lt;br /&gt;&lt;br /&gt;       $upload-&gt;process();&lt;br /&gt;   &lt;br /&gt;       parent::after_add();&lt;br /&gt;    }&lt;br /&gt;}  &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Note that I call parent::after_add() once I'm finished so that the regular FuseApplicationController after_add() method can take over and display a "changes saved" message to the user.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-561382066017641344?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/561382066017641344/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=561382066017641344' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/561382066017641344'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/561382066017641344'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2008/06/file-uploads-with-fuse-php-mvc.html' title='File Uploads with FUSE PHP MVC Framework'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-8445716900956985850</id><published>2008-05-21T21:43:00.001-07:00</published><updated>2008-05-21T21:55:55.416-07:00</updated><title type='text'>postfix smtpd connection hangs with no banner</title><content type='html'>I ran into an issue the other day where my Postfix mail server would accept an SMTP connection, but then would just hang without ever sending the initial ESMTP welcome banner. I discovered that because of high mail traffic, I had reached the maximum number of smtpd connections, so the new connection was stuck waiting. Since the server load was fairly low, I just raised the smtpd incoming connection limit. You can do this one of two ways&lt;br /&gt;&lt;br /&gt;First way: change the process limit just for smtpd in master.cf. &lt;br /&gt;Note the 200 in the "maxproc" field (the default is 100)&lt;br /&gt;&lt;pre style="padding: 4px;background-color: #F5F5F5;"&gt;smtp      inet  n       -       n       -       200       smtpd &lt;/pre&gt;&lt;br /&gt;Second way: change default_process_limit in your main.cf file (the default is 100).&lt;br /&gt;This will affect all postfix processes, so be careful about taxing your server.&lt;br /&gt;&lt;br /&gt;&lt;div style="font-family: Courier; padding: 4px;background-color: #F5F5F5;"&gt;default_process_limit=200&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-8445716900956985850?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/8445716900956985850/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=8445716900956985850' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/8445716900956985850'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/8445716900956985850'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2008/05/postfix-smtpd-connection-hangs-with-no.html' title='postfix smtpd connection hangs with no banner'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-1389949383926510194</id><published>2008-05-13T21:49:00.000-07:00</published><updated>2008-05-20T15:37:35.897-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='comparison'/><category scheme='http://www.blogger.com/atom/ns#' term='fuse'/><category scheme='http://www.blogger.com/atom/ns#' term='cakePHP'/><category scheme='http://www.blogger.com/atom/ns#' term='mvc'/><category scheme='http://www.blogger.com/atom/ns#' term='framework'/><category scheme='http://www.blogger.com/atom/ns#' term='php'/><title type='text'>CakePHP vs FUSE MVC framework - PHP</title><content type='html'>Admittedly, I'm a bit biased on the topic since FUSE has been one of my development projects for quite a while, but recently I've been working with CakePHP quite a bit. I enjoy working with Cake, but can't help but think of certain things that FUSE does (in my opinion) a bit better. I've compiled a list of a few items where I think the &lt;a href="http://www.phpfuse.net"&gt;FUSE MVC Framework&lt;/a&gt; has a bit of an advantage on &lt;a href="http://www.cakephp.org"&gt;CakePHP&lt;/a&gt;. &lt;br /&gt;&lt;br /&gt;To be fair, I've also included a few things where FUSE falls short for the moment. Before I begin, I'd also like to add that I think Cake is a great framework, and I've certainly taken cues from my work with Cake to integrate new features into FUSE.&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;div style="padding: 4px; margin-top: 5px; font-weight: bold; background-color: rgb(247, 247, 247); font-size: 160%; font-family: arial;"&gt;Where FUSE Wins&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;span style="font-weight: bold; font-family:Arial; font-size:130;"  &gt;1. The Query Builder object&lt;/span&gt;&lt;br /&gt;&lt;div style="padding-left: 12px;"&gt;&lt;br /&gt;&lt;fieldset&gt;&lt;legend&gt;Fetching the last 10 active employees in CAKE:&lt;/legend&gt;&lt;div class="code"&gt;&lt;pre class="controller_code"&gt;// Controller&lt;br /&gt;$where_conditions[] = 'Employee.active=1';&lt;br /&gt;&lt;br /&gt;$this-&gt;data['Employees'] = $this-&gt;Employee-&gt;findAll(&lt;br /&gt;              $where_conditions,&lt;br /&gt;              null,&lt;br /&gt;              null,&lt;br /&gt;              null,&lt;br /&gt;              10 );&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="code"&gt;&lt;pre class="view_code"&gt;// View&lt;br /&gt;&amp;lt;?php foreach( $this-&amp;gt;data['Employees'] as $employee ) {?&amp;gt;&lt;br /&gt;&amp;lt;div&amp;gt;&lt;br /&gt;&amp;lt;?php e($employee['Employees']['first_name'] . ' ' . $employee['Employees']['last_name'] );?&amp;gt;&lt;br /&gt;&amp;lt;/div&amp;gt;&lt;br /&gt;&amp;lt;?php } ?&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/fieldset&gt;&lt;br /&gt;&lt;fieldset&gt;&lt;legend&gt;The same thing in FUSE:&lt;/legend&gt;&lt;div class="code"&gt;&lt;pre class="controller_code"&gt;//Controller&lt;br /&gt;$options['limit'] = 10;&lt;br /&gt;$options['where'] = 'employees.active=1';&lt;br /&gt;&lt;br /&gt;$this-&gt;active_employees = $this-&gt;employee-&gt;fetch_all( $options );&lt;br /&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="code"&gt;&lt;pre class="view_code"&gt;//View&lt;br /&gt;&lt;br /&gt;&amp;lt;{ITERATOR active_employees}&amp;gt;&lt;br /&gt;&amp;lt;div&amp;gt;&amp;lt;{first_name}&amp;gt; &amp;lt;{last_name}&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;&amp;lt;{/ITERATOR}&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/fieldset&gt;&lt;br /&gt;&lt;div style="font-family: Arial;"&gt;     The query builder is simply an object that, believe it or not, allows you to build queries. Nearly every method in FUSE that fetches data from the database (including aggregate methods like count_all()) can take a query builder object as one of its $options parameters, and it will apply the customization in that object to the query being executed.&lt;br /&gt;     Above is an example of the shorthand syntax for customizing queries. For more complex customization, you can use the SQLQuery object directly, as discussed in the &lt;a href="http://www.phpfuse.net/wiki"&gt;FUSE Wiki&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;/div&gt;&lt;span style=";font-family:arial;font-size:130%;"  &gt;&lt;span style="font-weight: bold;"&gt;2. FUSE has a real templating system&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="padding-left: 12px;"&gt;&lt;fieldset&gt;&lt;legend&gt;Iterating through employees in CakePHP&lt;/legend&gt;&lt;div class="code"&gt;&lt;pre class="view_code"&gt;&amp;lt;table&amp;gt;&lt;br /&gt;&amp;lt;?php&lt;br /&gt;foreach( $this-&amp;gt;data['Results'] as $result ) {&lt;br /&gt;?&amp;gt;&lt;br /&gt;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;?php e($result['Employees']['name']);?&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;&amp;lt;?php&lt;br /&gt;}&lt;br /&gt;?&amp;gt;&lt;br /&gt;&amp;lt;/table&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/fieldset&gt;&lt;br /&gt;&lt;fieldset&gt;&lt;legend&gt;The same thing in FUSE:&lt;/legend&gt;&lt;div class="code"&gt;&lt;pre class="view_code"&gt;&amp;lt;table&amp;gt;&lt;br /&gt;&amp;lt;{ITERATOR employees}&amp;gt;&lt;br /&gt;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;{name}&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;&amp;lt;{/ITERATOR}&amp;gt;&lt;br /&gt;&amp;lt;/table&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/fieldset&gt;&lt;br /&gt;&lt;span style="font-family:arial;"&gt;Cake's templating system is too reminiscent of inline PHP, and is sure to send your graphics guy running in the other direction. The FUSE templating system was designed to be extremely simple but incredibly robust, not to mention made to integrate as seamlessly into HTML as possible.&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;span style="font-weight: bold; font-size: 130%; font-family: Arial;"&gt;3. Helper methods are easier in FUSE&lt;/span&gt;&lt;br /&gt;&lt;div style="padding-left: 12px;"&gt;&lt;br /&gt;&lt;fieldset&gt;&lt;legend&gt;To generate a dropdown box of all users by first name in Cake:&lt;/legend&gt;&lt;div class="code"&gt;&lt;pre class="controller_code"&gt;//Controller&lt;br /&gt;$this-&gt;set('users', $this-&gt;User-&gt;generateList(null, null, null,'{n}.User.id', '{n}.User.first_name'));&lt;br /&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="code"&gt;&lt;pre class="view_code"&gt;// View:&lt;br /&gt;&amp;lt;?php echo $form-&gt;input('SomeModel.user_id', array('class'=&gt;'txt', 'type' =&gt; 'select')); ?&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/fieldset&gt;&lt;br /&gt;&lt;fieldset&gt;&lt;legend&gt;The same thing in FUSE:&lt;/legend&gt;&lt;div class="code"&gt;&lt;pre class="view_code"&gt;&lt;{HTML/FormHelper::Select_all( 'User', 'id', 'first_name' )}&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/fieldset&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;div style="padding-left: 12px; font-family:Arial;"&gt;&lt;span style="font-size: 120%;"&gt;Other compelling reasons to use FUSE&lt;/span&gt; over Cake include:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;FUSE's &lt;a href="http://phpfuse.net/wiki/index.php?title=Authenticating_Controller_Actions"&gt;User authentication and management system &lt;/a&gt;is much more intuitive than Cake's ACL implementation.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;FUSE is also capable of doing nested included tables when fetching data. For example, &lt;a href="http://phpfuse.net/wiki/index.php?title=Data_Model_Fetch#Including_multiple_tables_in_your_data_list"&gt;using the 'include' option to any fetch method&lt;/a&gt;, one can account for the scenario where model A is linked to model B, and model B is linked to model C, but you want to select data from A, B, and C. Currently, in CakePHP, you would have to write the query manually. FUSE allows you to do:&lt;br /&gt;&lt;pre style="background-color: #F1F1F1;"&gt;&lt;br /&gt;$options['include'] = array( 'modelA', 'modelB', 'modelB.modelC' );&lt;br /&gt;$iterator = $this-&gt;fetch_all( $options );&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;br /&gt;&lt;div style="padding: 4px; margin-top: 5px; font-weight: bold; background-color: rgb(247, 247, 247); font-size: 160%; font-family: arial;"&gt;Where CakePHP Wins&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;span style="font-weight: bold; font-size: 130%; font-family: Arial;"&gt;1. CakePHP has a larger user base&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="font-family: Arial; padding-left: 12px;"&gt;Although FUSE has been in development for several years, it was only released to the general public in January of 2008. As a result, its user base is still growing, and CakePHP is a more established framework with more documentation, forums, and third party code dedicated to it. The features of each framework are generally comparable, but the size of the CakePHP community is a huge advantage over FUSE.&lt;/div&gt;&lt;br /&gt;&lt;span style="font-weight: bold; font-size: 130%; font-family: Arial;"&gt;2. CakePHP supports database other than mySQL&lt;/span&gt;&lt;div style="font-family: Arial; padding-left: 12px;"&gt;&lt;br /&gt;FUSE 1.1 only has a database object for mySQL, but MSSQL, PostgreSQL and SQLite classes are in the works. Additionally, FUSE will not support using data from multiple databases in one project until 1.1.1.&lt;/div&gt;&lt;br /&gt;&lt;span style="font-weight: bold; font-size: 130%; font-family: Arial;"&gt;3. CakePHP has more direct AJAX integration&lt;/span&gt;&lt;div style="font-family: Arial; padding-left: 12px;"&gt;&lt;br /&gt;FUSE supports a variety of AJAX options and makes it very easy to use JQuery, but as of version 1.1, it is not as well integrated as some of Cake's AJAX functionality. &lt;/div&gt;&lt;br /&gt;&lt;span style="font-weight: bold; font-size: 130%; font-family: Arial;"&gt;4. Your comment here&lt;/span&gt;&lt;div style="font-family: Arial; padding-left: 12px;"&gt;&lt;br /&gt;I know I'm going to get flamed for leaving the "Where Cake wins" section so short, so I figured I'd offer the opportunity for readers to add their own favorite things about CakePHP by commenting on this entry&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-1389949383926510194?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/1389949383926510194/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=1389949383926510194' title='10 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/1389949383926510194'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/1389949383926510194'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2008/05/cakephp-vs-fuse-mvc-framework-for-php.html' title='CakePHP vs FUSE MVC framework - PHP'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>10</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-1886253157099036936</id><published>2008-03-27T21:25:00.001-07:00</published><updated>2008-04-07T23:38:38.134-07:00</updated><title type='text'>VB.net - reading extended ascii text file - StreamReader changes extended ascii to question marks</title><content type='html'>Ran into an issue today reading a text file in Visual Basic .net. The file had extended ascii characters (special "curly quotes" to be exact) that were being read in as question marks because they were outside of the normal ascii character set. The solution was to set the text encoding to "default" in the StreamReader constructor, which is apparently the setting used for "Windows ANSI" text encoding:&lt;br /&gt;&lt;br /&gt;             Dim fileReader As System.IO.StreamReader&lt;br /&gt;           Dim filePath as String&lt;br /&gt;&lt;br /&gt;             filePath = "C:\path\to\file.txt"&lt;br /&gt;           fileReader = New System.IO.StreamReader(filePath, System.Text.Encoding.Default)&lt;br /&gt;&lt;br /&gt;           While fileReader.Peek &lt;&gt; -1&lt;br /&gt;&lt;br /&gt;               curLine = fileReader.ReadLine&lt;br /&gt;                   ' do something with curLine&lt;br /&gt;               End While&lt;br /&gt;&lt;br /&gt;               fileReader.close()&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-1886253157099036936?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/1886253157099036936/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=1886253157099036936' title='10 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/1886253157099036936'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/1886253157099036936'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2008/03/vbnet-reading-text-file-with.html' title='VB.net - reading extended ascii text file - StreamReader changes extended ascii to question marks'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>10</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-2806476732474945964</id><published>2008-03-19T22:23:00.000-07:00</published><updated>2008-03-19T23:29:06.383-07:00</updated><title type='text'>A simple Javascript slideshow</title><content type='html'>I built this tool for a site I was working on, then realized it might be  helpful to other people, so I thought I'd share it. The code below  allows for a simple fading javascript slideshow that can be easily made  by dynamic by your PHP or ASP (etc...) scripts. Included in the zip file is a working  example slideshow along with the .js files you'll need to use it on your own site.&lt;br /&gt;&lt;br /&gt;You can view the slideshow in action at this link:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.phpfuse.net/JS_Slideshow/example/example.html"&gt;http://www.phpfuse.net/JS_Slideshow/example/example.html&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;You can download the code and example at the link below. Enjoy!&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.phpfuse.net/JS_Slideshow/Simple_Javascript_Slideshow.zip"&gt;http://www.phpfuse.net/JS_Slideshow/Simple_Javascript_Slideshow.zip&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-2806476732474945964?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/2806476732474945964/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=2806476732474945964' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/2806476732474945964'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/2806476732474945964'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2008/03/simple-javascript-slideshow.html' title='A simple Javascript slideshow'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-6303012148217139263</id><published>2008-02-18T00:02:00.000-08:00</published><updated>2008-03-05T10:46:15.862-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='jim'/><category scheme='http://www.blogger.com/atom/ns#' term='fuse'/><category scheme='http://www.blogger.com/atom/ns#' term='context'/><category scheme='http://www.blogger.com/atom/ns#' term='keller'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='mvc'/><category scheme='http://www.blogger.com/atom/ns#' term='cake'/><category scheme='http://www.blogger.com/atom/ns#' term='php'/><title type='text'>MVC and you: Partners in Freedom! (Why developers should be using  Model View Controller architecture)</title><content type='html'>&lt;p class="MsoNormal" style="text-indent: 0.5in;"&gt;&lt;o:p&gt; &lt;/o:p&gt;“Model/View/Controller”. To some people, it seems to be nothing but a buzzword – an ambiguous term tossed around development forums and blogs; an idea that’s vague and confusing to anyone who hasn’t actually delved into MVC development. To others, MVC is an obvious way of organizing application structure, and there’s nothing new or exciting about it. And finally there are those of us who stumbled into MVC enlightenment almost by accident and realized, wide-eyed and drop-jawed, that it’s just the “right” way to build applications.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal" style="text-indent: 0.5in;"&gt;Consider me part of the third group. It wasn’t all that long ago that a Ruby on Rails-enthusiast friend of mine insisted that I start moving toward MVC development for the multitude of PHP projects I’m involved in. He knew I was working on a new PHP framework, and, thankfully, he was persistent in his endeavor to get me to delve into MVC sooner rather than later. My framework had the DB abstraction layer. It had the template system and the form validator and the photo handler and the &lt;st1:city&gt;&lt;st1:place&gt;AJAX&lt;/st1:place&gt;&lt;/st1:city&gt; module. But what was really missing was a way to tie it all together, and once I got about 20 minutes into MVC, I felt disappointed in myself for not having seen the light earlier. It just makes sense to develop within the MVC structure, and in this article I’m going to first give an introduction to what that structure is, then touch on a few reasons you should be using it, and finally describe a few hurdles you can expect to have to overcome while getting accustomed to it. &lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;b style=""&gt;&lt;span style="font-size:130%;"&gt;What is Model / View / Controller architecture ?&lt;/span&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;b style=""&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p class="MsoNormal" style="text-indent: 0.5in;"&gt;Model/View/Controller, or MVC, is essentially just a way of structuring application code. The idea is not new (it’s been around since 1979 according to Wikipedia), but it’s only happened fairly recently that MVC started to gain widespread acceptance as a viable way to develop web-based applications. A lot of the charge toward MVC-based web development was led by Rails, the Ruby framework that has gained significant momentum in the past few years. &lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=""&gt;            &lt;/span&gt;MVC-based web development is based primarily on the “M”, the “V”, and the “C” themselves – the data Models, the presentation (or View), and the interface between them – the real workhorse - the Controller. Each component has a specific role, which I’ve outlined below:&lt;/p&gt;  &lt;p class="MsoNormal" style="margin-left: 0.75in; text-indent: -0.25in;"&gt;&lt;!--[if !supportLists]--&gt;&lt;span style=""&gt;1.&lt;span style=""&gt;      &lt;/span&gt;&lt;/span&gt;&lt;!--[endif]--&gt;The data Model is used to store and retrieve information from the database. In most MVC frameworks, a data model class will exist for each of your database tables. Because the model extends a parent class that has very robust, often clever functionality, you can often gain application-level access to your data (e.g. $employee-&gt;first_name) with only a few lines of code. Data models can also be related to one another just as their underlying tables are related, which provides even greater functionality when accessing data across tables (e.g. $company-&gt;employees-&gt;fetch_all()). This extensible, customizable method for mapping tables to objects allows you to access your database without necessarily having to write SQL. Basic read and write operations are performed through simple class members and methods, and even more complicated queries can be done without having to manually write SQL, though that option is always available. &lt;/p&gt;  &lt;p class="MsoNormal" style="margin-left: 0.75in; text-indent: -0.25in;"&gt;&lt;!--[if !supportLists]--&gt;&lt;span style=""&gt;2.&lt;span style=""&gt;      &lt;/span&gt;&lt;/span&gt;&lt;!--[endif]--&gt;The “View” in Mode/View/Controller is the presentation layer that contains the content the end user will see. In web applications, the view will contain your actual HTML tags. Using the features of the templating system included within the application framework, you can add dynamic functionality to the HTML output while still keeping the application code and the presentation separate. Such a separation allows for cleaner code in both the scripting language and the HTML, and it allows designers and programmers to have more independent control over their respective parts of the application development.&lt;/p&gt;  &lt;p class="MsoNormal" style="margin-left: 0.75in; text-indent: -0.25in;"&gt;&lt;!--[if !supportLists]--&gt;&lt;span style=""&gt;3.&lt;span style=""&gt;      &lt;/span&gt;&lt;/span&gt;&lt;!--[endif]--&gt;The Controller is, as mentioned above, the workhorse of the application. The code in the controller is what’s actually run when a page is loaded, and it handles the “pull and push” – taking data from the database via the data models, passing that data to the view, then rendering the output. In most web-based MVC frameworks, specific URLs are attached to specific controller and methods through a routing file, so, for instance, “/Blog/View/123” might call the view() method of a BlogController object and pass it the id of 123. &lt;/p&gt;    &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;span style="font-size:14;"&gt;&lt;span style=""&gt;&lt;/span&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;b style=""&gt;&lt;span style="font-size:14;"&gt;&lt;span style="font-size:130%;"&gt;Sounds pretty complicated. Why switch from my tried and true methods of inline scripting?&lt;/span&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=""&gt;            &lt;/span&gt;One of the greatest pitfalls a developer can succumb to is using only the language, tools, or methods he or she already knows in order to accomplish a task, rather than seeking out and utilizing the best tools for a particular project. A little bit of time spent overcoming the learning curve can often introduce efficiencies, expand your skillset, and actually save a significant amount of time in the long run. &lt;/p&gt;  &lt;p class="MsoNormal" style="text-indent: 0.5in;"&gt;Many web developers have fallen into the trap of writing what can really be described as a collection of scripts, rather than developing a cohesive application, because the former is the way most of us were introduced to web development. The most common architecture used in, for instance, PHP development, doesn’t really qualify as an architecture at all. Most commonly, PHP-driven sites are comprised of a handful of individual script files that each handle incoming data from web forms or display data pulled from a database, but together lack any kind of standard structure. While this method is quick and dirty and works well for smaller applications, it simply does not lend itself to creating scalable, modular solutions that can be easily maintained and updated even as the size of the application grows. &lt;/p&gt;  &lt;p class="MsoNormal" style="text-indent: 0.5in;"&gt;MVC provides a very structured but very versatile approach to web development, lending itself to being immediately scalable while providing simple interfaces to common functionality. Consider how often your application needs to perform the same types of operations on different data – often this is referred to as CRUD: Create, Read, Update, Delete. Entire websites and web-based applications can often be accurately described as offering only these four features, operating on different data. Without a framework to provide CRUD functionality, the developer has to hand write his INSERT, SELECT, UPDATE, and DELETE queries manually, not to mention coding tedious input validation methods to ensure security and data integrity. However, any MVC framework worth its salt will provide all of that functionality in just a few lines of code. Interested yet?&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;b style=""&gt;&lt;span style="font-size:130%;"&gt;Ok, you’ve convinced me, but what kind of learning curve can I expect?&lt;/span&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;b style=""&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;b style=""&gt;&lt;span style=""&gt;            &lt;/span&gt;&lt;/b&gt;The first thing you should know about MVC development is that everything is done through objects, so a solid understanding of object orientation will be extremely helpful in moving forward with any MVC framework. Additionally, HTML output is handled via templates (views) – you will almost never see HTML and PHP in the same file when doing MVC development. Instead, data is passed to the template (sometimes automagically, sometimes manually) and is accessed through special syntax (e.g. &lt;{myvar}&gt;)&lt;span style=""&gt;  &lt;/span&gt;in the template itself. The separation of application code and presentation sometimes throws people off initially, but the benefits of this method become apparent very quickly. Finally, with MVC, you will not see your .php files in the URI the way you’re used to seeing them. Instead, you define a route that points a URI to a specific method in a specific controller. For instance, mysite.com/BlogEntries/List might call the show_list() method of your BlogEntryController. Not only does this kind of URI routing offer almost limitless customization as to what your URI will look like, it can also be used as an SEO (Search Engine Optimization) tool. &lt;/p&gt;    &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;br /&gt;&lt;b style=""&gt;&lt;span style="font-size:130%;"&gt;Which framework do you recommend?&lt;/span&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;b style=""&gt;&lt;span style=""&gt;            &lt;/span&gt;&lt;/b&gt;Personally, I use FUSE (&lt;a href="http://www.phpfuse.net/"&gt;http://www.phpfuse.net&lt;/a&gt;). However, that’s a bit of a shameless plug, since FUSE is my own framework. (See the video below for how you can build a database-driven web app in about 5 minutes :-) Having worked with other frameworks, I honestly believe that FUSE is the easiest MVC framework for PHP, especially as far as initial entry is concerned. There are a slew of other great frameworks available, however, including Cake and Symfony for PHP, Rails for Ruby, Grails for Java, and many, many more.&lt;/p&gt;&lt;br /&gt;&lt;div style="margin-top: 5px;"&gt;&lt;br /&gt;&lt;object height="355" width="425"&gt;&lt;param name="movie" value="http://www.youtube.com/v/cX0eIdWZQhQ&amp;amp;rel=1"&gt;&lt;param name="wmode" value="transparent"&gt;&lt;embed src="http://www.youtube.com/v/cX0eIdWZQhQ&amp;amp;rel=1" type="application/x-shockwave-flash" wmode="transparent" height="355" width="425"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-6303012148217139263?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/6303012148217139263/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=6303012148217139263' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/6303012148217139263'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/6303012148217139263'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2008/02/mvc-and-you-partners-in-freedom.html' title='MVC and you: Partners in Freedom! (Why developers should be using  Model View Controller architecture)'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-5935427427056548065</id><published>2008-02-01T15:41:00.001-08:00</published><updated>2008-02-01T15:47:01.533-08:00</updated><title type='text'>subversion "working copy is corrupt" when trying to commit</title><content type='html'>I was having an issue today with a new SVN repository when trying to commit a bunch of files and folders I had copied in to the now-versioned project directory. Most of the files would commit, but a few would fail with "working copy is corrupt". I discovered while browsing the project directory (outside of my IDE, which hides .svn folders) that a few leftover ".svn" folders existed because some of the folders I copied in had come from another versioned project directory. I deleted the .svn folders, re-committed, and everything was ok!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-5935427427056548065?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/5935427427056548065/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=5935427427056548065' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/5935427427056548065'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/5935427427056548065'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2008/02/subversion-working-copy-is-corrupt-when.html' title='subversion &quot;working copy is corrupt&quot; when trying to commit'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-3799847188467381696</id><published>2008-01-25T17:12:00.001-08:00</published><updated>2008-01-25T19:38:42.361-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='practices'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='flash'/><title type='text'>Five Things Web Developers Should Stop Doing</title><content type='html'>This may not come as a surprise, but I spend a lot of time on the Internet. Whether it’s browsing around for my own enjoyment or diligently working on a web-based application, I end up seeing both the end result and the inner workings of a lot of other peoples’ development work. And while a large majority of design elements are ultimately a matter of preference, there are certain web development techniques and implementation choices that I find myself shaking my head at, and I’d like to address a few of them here. The following is a list of five things that, in my opinion, web developers should simply stop doing.   &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;b style=""&gt;&lt;span style="font-size:130%;"&gt;1 – Including application code and HTML in the same file&lt;/span&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=""&gt;            &lt;/span&gt;Although many web scripting languages are tailored for alternating between application code and HTML by use of special tags, the failings of this architecture become apparent fairly quickly when developing robust web applications. Not only does this inline scripting method create messy, oftentimes confusing code, but it can discourage effective use of functions and introduce difficulty when delegating the roles of designer and programmer to different people who may not share one another’s skill sets. The answer here is to use a templating system to separate the application code from the HTML presentation. Templating functionality is widely available for any web development language, and is an integral part of pretty much any development framework (e.g. Ruby on Rails, CakePHP, FUSE). &lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;b style=""&gt;&lt;span style="font-size:130%;"&gt;2 – Embedding video with a technology other than Flash Full Motion Video&lt;/span&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=""&gt;            &lt;/span&gt;Until Flash FMV became widely available, a common method for video playback on websites involved encoding multiple versions of the same video, then asking the user which player he or she preferred to use (e.g. RealPlayer, Windows Media, or Quicktime). This was always a necessary evil, as developers needed to ensure that the site content was available to all visitors. However, presenting potentially confusing video preference questions to the user can often lead to abandonment, not to mention that encoding, uploading, and linking multiple versions of the same video can be a time consuming process.&lt;span style=""&gt;  &lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal" style="text-indent: 0.5in;"&gt;Thanks to the introduction of full motion video capabilities in Adobe Flash, which has shipped alongside the most popular browsers for several years, developers now have some level of certainty that at least one video player will be available to the majority of users. Additionally, Flash FMV prevents the need to spawn an external application for playback, which is another scenario that can lead to abandonment if the site visitor is unsure of how to answer their browser’s security questions. &lt;/p&gt;  &lt;p class="MsoNormal" style="text-indent: 0.5in;"&gt;Although sites such as YouTube have unfortunately given many people the impression that Flash FMV is only capable of low quality videos with poorly synced audio, this is simply not the case. Adobe has even launched a “Flash HD gallery” (available at &lt;a href="http://www.adobe.com/products/hdvideo/hdgallery/"&gt;http://www.adobe.com/products/hdvideo/hdgallery/&lt;/a&gt; ) that showcases Flash’s HD playback abilities. However, most embedded videos (news clips, etc) are short, small clips that download quickly, so even a medium or low quality encode will suffice. If your specific needs dictate that you must leverage the more advanced features of players like Quicktime or Windows Media, then you will have to use what best suits your end goal, but otherwise, stick with Flash.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;b style=""&gt;&lt;span style="font-size:130%;"&gt;3 – Implementing Flash pieces that introduce custom UI elements&lt;/span&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;b style=""&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=""&gt;            &lt;/span&gt;Flash is a phenomenal addition to any developer’s toolkit, and well designed Flash pieces can significantly enhance both the aesthetics and functionality of a website or web-based application. However, one thing that many Flash developers fail to steer clear of is overusing Flash where it’s not necessary, to the point of introducing custom user interface elements that can end up hampering usability. As an example, consider something that’s unfortunately fairly common – a Flash-based block of text with a scrollbar that is also implemented within the Flash piece itself. Not only is this an unnecessary use of Flash, since the same effect can be accomplished with fairly simple CSS, but you may be alienating visitors who simply aren’t tech savvy enough to adjust their understanding of the browser’s UI elements on the fly. It may not be apparent to some users that they’re even looking at a scrollbar, especially if the bar is stylized or implemented in such a way that it doesn’t behave like the standard scrollbar. Your visitor is used to the way their browser functions and how they use its features to browse the web, so your best bet is not to alter basic UI elements. &lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;b style=""&gt;&lt;span style="font-size:130%;"&gt;4 – Using long query strings where they’re not necessary&lt;/span&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=""&gt;            &lt;/span&gt;Most web applications rely on the URI’s query string to bring in relevant data that is acted upon by the application code, but poor design choices often cause the query string to grow to unreasonable, unnecessary lengths. Long query strings can severely hamper the ease with which users can link to particular pages on your site, so you could very well be losing visitors because they didn’t quite get the full query string when a friend copy &amp;amp; pasted it over to them. &lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=""&gt;            &lt;/span&gt;The first thing to do to clean up your query strings is simply to use small identifiers for both variables and values. Try to use numeric IDs instead of long text strings to identify a specific resource, and keep your variable names short. You should also avoid passing data that could easily be extracted from data you already have. For instance, don’t pass both an item name and item id through the query string – you can just pass the item id and pull the name from the database. &lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=""&gt;            &lt;/span&gt;If you want to go a bit further in cleaning up or even eliminating query strings, look into URI rewriting. URI rewriting is a fairly simple process by which the friendly URI the user sees (e.g. /Blog/2008/01) is transparently translated into something more useful on the server side (e.g. blog_list.php?year=2008&amp;amp;month=01). Nearly all Model-View-Controller frameworks (Ruby on Rails, FUSE, CakePHP, etc.) have advanced techniques for rewriting the URI on the fly. &lt;/p&gt;&lt;span style=";font-family:&amp;quot;;font-size:12;"  &gt;&lt;/span&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;br /&gt;&lt;p class="MsoNormal"&gt;&lt;b style=""&gt;&lt;span style="font-size:130%;"&gt;5 – Sizing images by means of the width and height attributes of the img  tag&lt;/span&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=""&gt;            &lt;/span&gt;This one should be a no-brainer, but I still see it fairly regularly. While it’s quick and easy to force an image down to certain size by using the &lt;img src="" /&gt; tag, you’re doing yourself a disservice in at least two ways by utilizing this technique. The first problem with this method of image sizing is that web browsers aren’t particularly good at shrinking or enlarging images. The browser doesn’t do any kind of resampling, so you often get a pixelated version of your original image, even when shrinking it. The second issue with sizing images by way of the browser is that you may be wasting a lot of bandwidth. An image that’s 1000 pixels by 800 pixels has a much larger filesize than one that’s 200 by 160 pixels, so if you’re forcing it to appear at the smaller size anyway, you’d be transferring a lot of extra data for no reason by resizing it on the client side. To resize images to the size you need, use any of the widely available free tools or websites that allow you to do so. &lt;/p&gt;    &lt;p class="MsoNormal"&gt;&lt;o:p&gt;&lt;br /&gt;&lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal" style="text-indent: 0.5in;"&gt;So there you have it – just a few things I’ve seen during the course of my Web travels that I personally think should be done away with. Especially as development trends continue to shift toward more user-friendly, AJAX-enabled “Web 2.0” applications, it’s important to remember to leave behind techniques that, although familiar, have either been deprecated or were never great ideas in the first place. &lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-3799847188467381696?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/3799847188467381696/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=3799847188467381696' title='13 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/3799847188467381696'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/3799847188467381696'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2008/01/five-things-web-developers-should-stop.html' title='Five Things Web Developers Should Stop Doing'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>13</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-8104847932307690608</id><published>2008-01-25T16:57:00.000-08:00</published><updated>2008-01-25T17:01:11.554-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='suphp'/><category scheme='http://www.blogger.com/atom/ns#' term='safe mode'/><category scheme='http://www.blogger.com/atom/ns#' term='fuse'/><category scheme='http://www.blogger.com/atom/ns#' term='security'/><category scheme='http://www.blogger.com/atom/ns#' term='php'/><title type='text'>PHP Security in a shared hosting environment</title><content type='html'>Since its inception in 1994 as a set of basic development components, PHP has grown into one of the web’s most powerful development engines, having since been installed on literally millions of servers worldwide. And although PHP offers both the versatility and the built-in functionality to run in a reasonably secure fashion, most of those servers are configured in such a way that PHP scripts are at high risk for compromise.   &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal" style="text-indent: 0.5in;"&gt;Most PHP-enabled webservers are configured in such a way that the mod_php Apache module is loaded along with Apache itself, thereby allowing HTTP requests to be passed through the PHP engine, which preprocesses the data before it is sent to the client.&lt;span style=""&gt;  &lt;/span&gt;While this configuration provides a simple, efficient way to get PHP up and running, it raises security issues when working in the most common webserver environment: shared virtual hosting. &lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal" style="text-indent: 0.5in;"&gt;Generally, it is unnecessary and wasteful to dedicate an entire server to hosting just one website. Since most sites demand only a small fraction of a server’s available resources, it is more common to have one server be home to a large number of virtual hosts. A virtual host is simply a configuration entry that points requests for a specific URL (for instance, &lt;a href="http://www.devshed.com/"&gt;www.google.com&lt;/a&gt;) to a particular directory (for instance, /home/www/mydomain.com). &lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal" style="text-indent: 0.5in;"&gt;The shared hosting model, though economical, immediately presents a security concern, since the HTTP server (for instance, Apache or Microsoft IIS) needs to have a considerable amount of control over the files and directories that are to be served to the client. If your application offers the ability to upload files posted through web forms, the problem is further compounded since the HTTP server now needs write permission on the destination directory. In the common virtual hosting configuration discussed above, if the HTTP server has write permission to that directory, then any user running a PHP script on that same server can also write to the directory. Obviously, this presents a major security concern. However, there are steps that can be implemented, as a server administrator or as a user, that will eliminate or mitigate the security issues, or at least isolate individual users so that a script exploit on one host cannot easily affect other hosts on the same server. In this article, I will discuss a few methods for more securely configuring PHP, and will offer some security-conscious techniques to use when writing applications. For the sake of convenience, I have grouped the article into three categories: Configuration directives and environment settings that can only be changed by a server administrator, a basic overview of PHP wrapping for the application developer, and general practices for securing PHP code. Even if you are not administrating your own server, I recommend reading through the first section in order to gain an understanding of the problem so that you can know what to expect from your web host. This article makes the assumption that your environment has PHP running on a Linux/Unix variant, with Apache acting as the HTTP server. &lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;span style=""&gt;&lt;br /&gt;&lt;/span&gt;  &lt;p class="MsoNormal"&gt;&lt;u&gt;&lt;span style="font-size:14;"&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;From the administrator’s perspective: Configuring your shared PHP environment&lt;/span&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/u&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style="font-size:14;"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style="font-size:14;"&gt;&lt;span style=""&gt;          &lt;/span&gt;&lt;/span&gt;As the administrator of a virtual hosting server, you have full control over the HTTP server and the PHP engine, which is the ideal condition for tuning and securing your environment. First, let’s talk about separating the PHP interpreter from the Apache server, so that we negate the file permissions problem discussed above.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=""&gt;            &lt;/span&gt;The PHP interpreter can be invoked in three different ways: as an Apache module (discussed above), as a CGI binary, and as a CLI. Since the CLI (Command Line Interface) isn’t relevant for serving web pages, I won’t be addressing it in this article. As mentioned above, the most common (and often default) method for invoking PHP is as an Apache module. However, let’s look at an alternative way of invoking PHP – namely, as a CGI binary. &lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=""&gt;            &lt;/span&gt;&lt;span style=""&gt;            &lt;/span&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style="font-size:14;"&gt;&lt;span style=""&gt;          &lt;/span&gt;&lt;/span&gt;When invoked as a CGI binary, Apache loads the PHP interpreter only when needed, passing necessary input (environment variables, POST data, etc) to the PHP executable, then collecting the output and sending it to the client. In this scenario, the PHP process is separated from the Apache thread, which makes it possible to run the processes as different users, thereby eliminating the permissions problem discussed above. However, by default, PHP is run as the Apache user, so we haven’t yet solved the problem simply by running PHP as a CGI binary. Our next step is to “wrap” PHP so that it is invoked as a user that we specify, not as the Apache user.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style="font-size:100%;"&gt;&lt;b style=""&gt;Note:&lt;/b&gt;&lt;i style=""&gt; Installing PHP as a CGI Binary can introduce other security concerns that are worth being aware of. While PHP is generally secure out of the box, It is advisable to take a look at&lt;span style="text-decoration: underline;"&gt; &lt;/span&gt;&lt;a href="http://us.php.net/manual/en/security.cgi-bin.php"&gt;http://us.php.net/manual/en/security.cgi-bin.php&lt;/a&gt;.&lt;/i&gt;&lt;/span&gt;&lt;span style="font-size:10;"&gt;&lt;span style=""&gt;  &lt;/span&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style="font-size:10;"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=""&gt;            &lt;/span&gt;While Apache does have its own mechanism, suEXEC, for wrapping CGI programs, I will not be discussing it in this article. Instead, we’re going to look at another open source package: suPHP. Written by Sebastian Marsching, suPHP is a fairly simple Apache module that nicely wraps the PHP binary “in order to change the UID of the process executing the PHP interpreter” (suphp.org). &lt;o:p&gt;&lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style="font-size:14;"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style="font-size:14;"&gt;&lt;span style=""&gt;          &lt;/span&gt;&lt;/span&gt;In my experience, installing suPHP has always been a fairly pleasant (as far as these things go) endeavor. You will need to refer to the suPHP instructions at &lt;a href="http://www.suphp.org/"&gt;http://www.suphp.org&lt;/a&gt; for installation information for your particular UNIX distribution, but as a lightweight application that makes use of Apache’s dynamic module API, installation of suPHP should be trivial. &lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal" style="margin-left: 0.5in;"&gt;There are many articles on using PHP’s ini directives to help lockdown your &lt;/p&gt;  &lt;p class="MsoNormal"&gt;server, and although that is not the focus of this article, I would now like to briefly touch on a few directives you should be aware of. &lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=""&gt;            &lt;/span&gt;First, be sure to enable PHP’s safe_mode. Although some older applications have trouble running with safe_mode enabled, most have been updated to account for this directive, and its benefits are simply too numerous to ignore, especially if you are not&lt;span style=""&gt;  &lt;/span&gt;able to use a PHP wrapper such as suPHP. Safe mode will be removed in PHP 6 in favor of alternative methods of implementing file and directory security, but for now, you should leave it enabled. &lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal" style="text-indent: 0.5in;"&gt;Next, if you cannot implement suPHP or another PHP wrapper, it’s a good idea to set open_basedir in all of your virtual hosts. Set “php_admin_value open_basedir /path/to/vhost/root” as a directive in your virtual host configuration to ensure that PHP is restricted from reading any files outside of the virtual host’s document root. &lt;/p&gt;  &lt;p class="MsoNormal" style="text-indent: 0.5in;"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal" style="margin-left: 0.5in;"&gt;Finally, have a look at the disable_functions directive. While you do want to&lt;/p&gt;  &lt;p class="MsoNormal"&gt;make sure that your security procedures don’t prevent your users’ applications from running as they should, it’s often the case that few, if any, users will need any of the more potentially hazardous functions such as passthru(), exec(), and shell_exec(). If it is the case that none of your users need these functions, it’s a good idea to disable them. &lt;/p&gt;  &lt;p class="MsoNormal" style="text-indent: 0.5in;"&gt;&lt;span style=""&gt; &lt;/span&gt;(Note: In the event that only one user or application needs these functions, suPHP allows you to specify individual php.ini files for specific virtual hosts, which offers a middle ground between allowing these functions globally and restricting applications that need to use them for legitimate purposes.)&lt;/p&gt;  &lt;p class="MsoNormal" style="text-indent: 0.5in;"&gt;&lt;b style=""&gt;&lt;span style="font-size:14;"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;u&gt;&lt;span style="font-size:14;"&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;From the developer’s perspective: Why do I want a PHP wrapper?&lt;/span&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/u&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;b style=""&gt;&lt;span style="font-size:14;"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=""&gt;            &lt;/span&gt;Why do you want suPHP, or a PHP wrapper at all? Let’s look at a very common example – uploading images. &lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=""&gt;            &lt;/span&gt;It’s a common condition that an application needs to accept image uploads via the web, and many developers operating in a shared hosting environment have run into the problem where, on the first try, PHP displays a “permission denied” error when trying to move the uploaded file into its destination directory. Our User vs. HTTP server permission problem is back again, where the HTTP server - user “www” or “apache” -does not have the proper permissions to write to a directory owned by the developer’s account. The common solution to this problem is to set the permissions on the destination directory to 777, giving all users system-wide read, write, and execute access. While this does work and your uploads can now flow freely, you’ve just ensured that any other user on the system – there are probably hundreds – could very easily issue an “rm –rf&lt;span style=""&gt;  &lt;/span&gt;/path/to/your/uploads”, which would quickly and effectively delete everything in the directory. While I personally like to think that there exists camaraderie between users on the same server, this probably isn’t true, and you also have to consider that someone else’s account may have been compromised (probably by a lack of input checking on an upload form – more on that below). &lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=""&gt;            &lt;/span&gt;With suPHP (or another PHP wrapper) enabled, you are free to leave your upload destination directories with the same permission as your other web-accessible files – namely that only your user account has write access, and the Apache user can read files and traverse directories. In fact, if your user account is in the same group as the Apache user, you can set these directories to have permission of 750, which is much more restrictive than a wide-open 777.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal" style="text-indent: 0.5in;"&gt;The primary downside to using a PHP wrapper is that there is in fact a performance hit, since the PHP interpreter has to be invoked for every request, rather than being started as part of the Apache server. However, in my experience, the performance decrease is generally unnoticeable. If your application is extremely performance-critical, you will want to run benchmarks before deciding to use a wrapped PHP environment, or consider graduating to your own dedicated server where you can ensure that only you and your developers have any kind of access to your application files. In the dedicated server scenario, the security concerns of using PHP as an Apache module are largely mitigated. ( Note, however, that if one site on your dedicated server is exploited in a mod_php setup, other sites or files will most likely be vulnerable as well, whereas a server running suPHP with PHP’s safe_mode enabled and open_basedir directive configured will generally be able to jail the attack to one virtual host’s document root)&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;u&gt;&lt;span style="font-size:14;"&gt;&lt;o:p&gt;&lt;span style="text-decoration: none;"&gt; &lt;/span&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/u&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;&lt;u&gt;General practices for PHP application security&lt;/u&gt;&lt;/span&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style="font-size:14;"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;At this point, I’d like to briefly go over just a few coding techniques you can use to increase the security of your application. Please understand that this is by no means a comprehensive list, and simply adhering to the suggestions below does not ensure that your application is secure. However, you should make it a point to be security conscious when writing code, rather than trusting your environment to eliminate or mitigate any potential attacks on your application.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;1. Sanity-check your data&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=""&gt;            &lt;/span&gt;- This is probably the simplest and most effective way of preventing exploits in your application. Sanity checking just means that if you’re expecting the user to enter a number, make sure you actually received a number. If you’re expecting a string with alphanumeric characters only, verify that that’s what you got. Also, never trust this kind of validation to javascript only, as javascript can easily be disabled on the client side. Finally, never pass user data directly to an SQL query without validating it first. While PHP’s magic_quotes mechanism is great for helping to prevent SQL injection attacks (an attack where a user can enter data in such a way as to run their own arbitrary queries), again you should not rely exclusively on the environment for your application security. &lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;2. Check the type and extension of uploaded files.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=""&gt;            &lt;/span&gt;- Allowing file uploads is inherently risky, but very often it’s a necessary part of an application. PHP allows you to gain a lot of information about uploaded files before they’re ever written to their final destination, so make use of the information contained in the $_FILES array to ensure that you’re getting the type of file you’re expecting. A basic way of validating the file type is simply to ensure that the extension of the file indicates that it is (or is purported to be) the type of file you’re expecting. A common exploit for upload scripts is for an attacker to upload a malicious PHP script to your site, then browse to the uploaded script to gain control of your files. Even a basic check to make sure that, for instance, only files with a .jpg extension are allowed to be uploaded would prevent this type of exploit. However, I also recommend verifying the MIME type of the file, which is contained in the $_FILES array under the key ‘type’, and will look like: “image/jpeg” or “application/pdf”. Be as restrictive as possible – rather than validating against a list of extensions that are NOT allowed (php, exe, etc), check to make sure that the extension and/or MIME type matches a small group of file types that ARE allowed. &lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;3. Use a .php extension for ALL files with PHP code contained in them.&lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style=""&gt;            &lt;/span&gt;Often I come across files in PHP projects that have a .inc extension, because they are meant to be included, not browsed to directly from the web. This is a common condition, but there’s a potential security issue here if those files contain any sensitive data (e.g. database passwords). Because .inc files are not parsed by the PHP interpreter, they can be passed directly to the client side if they’re available via a web request, which would allow anyone to read the php code directly. Hopefully the directory these files reside in is denied read access by webserver rules (see below), but even so, there’s no sense in risking an accident where the user ends up being able to browse directly to the file. Use .inc.php. &lt;/p&gt;  &lt;p class="MsoNormal"&gt;&lt;span style="font-size:14;"&gt;&lt;o:p&gt; &lt;/o:p&gt;&lt;/span&gt;&lt;/p&gt;  &lt;p class="MsoNormal"&gt;4. Make use of your webserver’s access control rules (e.g. .htaccess)&lt;/p&gt;  &lt;p class="MsoNormal" style="text-indent: 0.5in;"&gt;- Even if all the php files in your application have a .php extension as discussed in #3, you should still make use of your HTTP server’s access control to prohibit any files in sensitive directories from being served via the web. For instance, if you keep your database passwords in “include/db.inc.php”, this file, and all files in the include/ directory should be prevented from being served via the web. Even though the .php extension will ensure that client-side users can’t read the code if the PHP interpreter is functioning, there is the potential condition that the HTTP server has loaded without the PHP interpreter. Botched upgrades or configuration errors can sometimes cause this condition, and in the event that someone browses to a PHP file without the PHP interpreter ever having been loaded, they will again see the code just as you do when editing the files. In Apache, disabling directory access is usually as simple as creating a file called .htaccess (note the leading period) in the directory, then adding the line: “Deny from All” (no quotes) to that file and saving it. &lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-8104847932307690608?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/8104847932307690608/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=8104847932307690608' title='38 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/8104847932307690608'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/8104847932307690608'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2008/01/php-security-in-shared-hosting.html' title='PHP Security in a shared hosting environment'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>38</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-2212091412650332713</id><published>2007-12-28T13:40:00.000-08:00</published><updated>2007-12-28T14:47:31.404-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='rich text'/><category scheme='http://www.blogger.com/atom/ns#' term='fuse'/><category scheme='http://www.blogger.com/atom/ns#' term='scriptaculous'/><category scheme='http://www.blogger.com/atom/ns#' term='rails'/><category scheme='http://www.blogger.com/atom/ns#' term='mvc'/><category scheme='http://www.blogger.com/atom/ns#' term='php'/><title type='text'>My favorite web tools of 2007</title><content type='html'>As the year comes to a close, I thought it would be fitting to reflect on what development tools helped make 2007 easier for me. These tools weren't necessarily created in 2007, but as someone who has a tendency to want to re-invent the wheel, I previously found myself overlooking great third-party products and ideas, so consider this a thank you to those people who contribute great, free code to the community.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;1. Scriptaculous (&lt;a aiotarget="false" aiotitle="http://script.aculo.us" href="http://script.aculo.us/"&gt;http://script.aculo.us&lt;/a&gt;)&lt;/span&gt;&lt;br /&gt;Provides: Easy drag and drop, animation, and AJAX functionality&lt;br /&gt;&lt;br /&gt;Building on the &lt;a href="http://prototypejs.org/"&gt;Prototype&lt;/a&gt; Javascript framework, Thomas Fuchs, with the help of a community of open source contributors, has given us an easy-to-use but extremely powerful toolkit for some of the more tedious Javascript tasks we face - namely UI features (e.g. drag and drop) and AJAX functionality. Those of us who have been around long enough to have had to work with positioning and controlling user interface elements know the headaches involved in getting your event handlers to fire as you expect them or tracking the x and y coordinates of the mouse. In fact, I suspect the #1 most common reason for keyboard smashing and monitor punching among developers goes something like "graphic element expanded beyond its container instead of properly wrapping to the next line".&lt;br /&gt;With Scriptaculous, implementing drag and drop, including sortable lists, is about as easy as it can feasibly be, and the AJAX-related classes are a godsend. Even someone with only moderate experience in javascript should be able to implement advanced UI features and AJAX functionality without much trouble. Scriptaculous is well integrated with &lt;a href="http://www.rubyonrails.org/"&gt;Ruby on Rails&lt;/a&gt;, and I expect that it will soon be driving a lot of the AJAX functionality in my own PHP MVC framework, &lt;a href="http://www.phpfuse.net/"&gt;FUSE&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;2. The Free Rich Text Editor (&lt;a href="http://www.freerichtexteditor.com/"&gt;http://www.freerichtexteditor.com/&lt;/a&gt;)&lt;br /&gt;&lt;/span&gt;Provides: a full WYSIWYG editor, a la blogger's "compose" feature&lt;br /&gt;&lt;br /&gt;Definitely file this under "things I would NOT like to have to write from scratch"! Released under the &lt;a href="http://creativecommons.org/licenses/by/2.5/"&gt;Creative Commons License&lt;/a&gt;&lt;script&gt;&lt;/script&gt; (thankfully), this rich text editor control allows you to easily implement full WYSIWYG functionality into your web application. With a wide range of editing functions, from bulleted lists and visual color selection to drag and drop image placement, it just doesn't get any better than this. A simple .js configuration file provides you with customization options, and once you add in a few  tags, you're all set. Custom WYSIWYG in under ten minutes. This one has been integrated into &lt;a href="http://www.phpfuse.net/"&gt;FUSE&lt;/a&gt; since the day I found it!&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_bNKq67NO758/R3V1MjGTbqI/AAAAAAAAAAM/U3E9gnMQ_pI/s1600-h/richtext.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://1.bp.blogspot.com/_bNKq67NO758/R3V1MjGTbqI/AAAAAAAAAAM/U3E9gnMQ_pI/s320/richtext.jpg" alt="" id="BLOGGER_PHOTO_ID_5149150607408590498" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;3. Model / View / Controller architecture&lt;/span&gt;&lt;br /&gt;Provides: Sanity&lt;br /&gt;&lt;br /&gt;Ok, this isn't exactly a "tool", and the idea has certainly been around for a long time (since 1979 according to &lt;a href="http://en.wikipedia.org/wiki/Model-view-controller#History"&gt;Wikipedia)&lt;/a&gt; , but MVC has finally started to gain real acceptance in the web development community to the point where it's becoming the standard for large scale,  web-based applications. Due in some part to the popularization of Ruby on Rails, along with other MVC frameworks like Cake, Symfony(, or FUSE), web developers have finally started realizing that procedural scripting, especially the sort that mixes application code and presentation (e.g. PHP and HTML in the same file),  isn't the best method for creating scalable, manageable applications. At this point, most MVC developers will look at you with a true sense of confusion and disappointment if you suggest ANY other way of developing a web app, but it seems that the majority of web programmers are still stuck in the "Chapter 1 of 'Building your first PHP applicatoin'" mentality, mixing procedural code in with HTML and packaging it all up in ugly URLs with long query strings. Get with the &lt;span style="font-style: italic;"&gt;program&lt;/span&gt;, guys!&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;So there you have it - some things that helped me keep what's left of my sanity for the past year. I may add to the list if I think of something that really needs to be mentioned, but the three items listed were part of my daily development life in '07, and I suspect that all of them will keep me company for '08 as well.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-2212091412650332713?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/2212091412650332713/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=2212091412650332713' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/2212091412650332713'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/2212091412650332713'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2007/12/my-favorite-web-tools-of-2007.html' title='My favorite web tools of 2007'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_bNKq67NO758/R3V1MjGTbqI/AAAAAAAAAAM/U3E9gnMQ_pI/s72-c/richtext.jpg' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-117032490622251543</id><published>2007-02-01T01:47:00.000-08:00</published><updated>2007-02-01T02:28:56.450-08:00</updated><title type='text'>Managing include files in Javascript</title><content type='html'>As I continue to write Javascript libraries for use on various projects, I decided it was time to write a simple include() function so that I don't have to manually add superfluous &amp;lt;script&amp;gt;&amp;lt;/script&amp;gt; tags that I may or may not  need on any given page.&lt;br /&gt;&lt;br /&gt;Using the XMLHttpRequestObject, I was able to create an asynchronous request to fetch the source of my .js file. Then I create a new script object and set the .text or .innerHTML attribute of that object (depending on browser - IE requires .text) to the fetched source. So far I've tested it in IE 6 and FF 1.5.&lt;br /&gt;&lt;br /&gt;Usage for including "/script/myscript.js":&lt;br /&gt;Note: in reality, the include() call would be done from inside another .js file.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;head&amp;gt;&lt;br /&gt;&amp;lt;script language=&amp;quot;Javascript&amp;quot; src=&amp;quot;ScriptManager.js&amp;quot; type=&amp;quot;text/javascript&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;&amp;lt;script language=&amp;quot;Javascript&amp;quot; type=&amp;quot;text/javascript&amp;quot;&amp;gt;&lt;br /&gt;ScriptManager.script_base_link = '/script'; //set up our base link&lt;br /&gt;//ScriptManager.script_file_extension = '.js'; //defaults to .js&lt;br /&gt;ScriptManager.ErrorHandler.silent = false; //set to true or remove in production&lt;br /&gt;&lt;br /&gt;try { &lt;br /&gt; ScriptManager.include( 'myscript' );&lt;br /&gt;}&lt;br /&gt;catch (e) {&lt;br /&gt; ScriptManager.ErrorHandler.handle_error( e );&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&amp;lt;/head&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And that's all.&lt;br /&gt;The code for ScriptManager.js is below.&lt;br /&gt;Copy &amp; paste this into ScriptManager.js:&lt;br /&gt;&lt;div style="width: 740px; height: 300px; margin-top: 5px; overflow:auto; border: 1px solid #A1A1A1;"&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;function ErrorHandler() {&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;ErrorHandler.handle_error = function ( e ) {&lt;br /&gt;&lt;br /&gt; try {&lt;br /&gt;  ErrorHandler.show_error( e.message );&lt;br /&gt; }&lt;br /&gt; catch(e) {&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;ErrorHandler.show_error = function( message ) {&lt;br /&gt;&lt;br /&gt; try {&lt;br /&gt;  if ( !ErrorHandler.silent ) {&lt;br /&gt;   alert( message );&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt; catch(e) {&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function HTTPRequest() {&lt;br /&gt;&lt;br /&gt; //&lt;br /&gt; // Properties&lt;br /&gt; //&lt;br /&gt; this.request_object = null;&lt;br /&gt; this.async = true;&lt;br /&gt; this.onreadystatechange = null;&lt;br /&gt;&lt;br /&gt; //&lt;br /&gt; // Methods&lt;br /&gt; //&lt;br /&gt; this.get_request_object = get_request_object;&lt;br /&gt; this.send = send;&lt;br /&gt; this.is_busy = is_busy; &lt;br /&gt; this.abort = abort;&lt;br /&gt; this.get_readyState   = get_readyState;&lt;br /&gt; this.get_status       = get_status;&lt;br /&gt; this.get_responseText = get_responseText;&lt;br /&gt;&lt;br /&gt; function get_request_object() {&lt;br /&gt;&lt;br /&gt;  try {&lt;br /&gt;&lt;br /&gt;   if ( !this.request_object ) {  &lt;br /&gt; &lt;br /&gt;    var http_request;&lt;br /&gt;&lt;br /&gt;    if ( typeof(XMLHttpRequest) != 'undefined' ) {&lt;br /&gt;     http_request = new XMLHttpRequest();&lt;br /&gt;    }&lt;br /&gt;    else {&lt;br /&gt; &lt;br /&gt;     try {&lt;br /&gt;      http_request = new ActiveXObject('Msxml2.XMLHTTP');&lt;br /&gt;     }&lt;br /&gt;     catch(inner_e) {&lt;br /&gt;&lt;br /&gt;      try {&lt;br /&gt;       http_request = new ActiveXObject('Microsoft.XMLHTTP');&lt;br /&gt;      }&lt;br /&gt;      catch(inner_e2) {&lt;br /&gt;      }&lt;br /&gt;     } &lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    this.request_object = http_request;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   return this.request_object;&lt;br /&gt;  }&lt;br /&gt;  catch(e) {&lt;br /&gt;   throw e;&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; function get_readyState() {&lt;br /&gt;&lt;br /&gt;  try {&lt;br /&gt;   if ( request_obj = this.get_request_object() ) {&lt;br /&gt;    return request_obj.readyState;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   return null;&lt;br /&gt;  }&lt;br /&gt;  catch( e ) {&lt;br /&gt;   throw e;&lt;br /&gt;  }   &lt;br /&gt;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; function get_status() {&lt;br /&gt;&lt;br /&gt;  try {&lt;br /&gt;   if ( request_obj = this.get_request_object() ) {&lt;br /&gt;    return request_obj.status;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   return null;&lt;br /&gt;  }&lt;br /&gt;  catch( e ) {&lt;br /&gt;   throw e;&lt;br /&gt;  }   &lt;br /&gt;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; function get_responseText() {&lt;br /&gt;&lt;br /&gt;  try {&lt;br /&gt;   if ( request_obj = this.get_request_object() ) {&lt;br /&gt;    return request_obj.responseText;&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   return null;&lt;br /&gt;  }&lt;br /&gt;  catch( e ) {&lt;br /&gt;   throw e;&lt;br /&gt;  }   &lt;br /&gt;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; function send( which_url ) {&lt;br /&gt;&lt;br /&gt;  try {&lt;br /&gt;   var http_request_obj;&lt;br /&gt;&lt;br /&gt;   if ( !(http_request_obj = this.get_request_object()) ) {&lt;br /&gt;    throw 'Your web browser does not support this function.';&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   if ( this.is_busy() ) {&lt;br /&gt;    this.abort();&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   if ( this.onreadystatechange ) {&lt;br /&gt;    http_request_obj.onreadystatechange = this.onreadystatechange;&lt;br /&gt;   }&lt;br /&gt;   http_request_obj.open("GET", which_url, this.async);&lt;br /&gt;     http_request_obj.send(null);&lt;br /&gt;&lt;br /&gt;  }&lt;br /&gt;  catch(e) {&lt;br /&gt;   throw e;&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;&lt;br /&gt; }&lt;br /&gt; &lt;br /&gt; function is_busy() {&lt;br /&gt;&lt;br /&gt;  try {&lt;br /&gt;   if ( request_obj = this.get_request_object() ) {&lt;br /&gt;&lt;br /&gt;    switch ( request_obj.readyState ) {&lt;br /&gt;     case 1,2,3:&lt;br /&gt;      return true;&lt;br /&gt;      break;&lt;br /&gt;     default:&lt;br /&gt;      return 0;&lt;br /&gt;      break;&lt;br /&gt;    }&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   return 0;&lt;br /&gt;  }&lt;br /&gt;  catch(e) {&lt;br /&gt;   throw e;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; function abort() {&lt;br /&gt;&lt;br /&gt;  try {&lt;br /&gt;   if ( this.request_object ) {&lt;br /&gt;    this.request_object.onreadystatechange = null;&lt;br /&gt;    this.request_object.abort();&lt;br /&gt;   }&lt;br /&gt;  }&lt;br /&gt;  catch(e) {&lt;br /&gt;   throw e;&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;HTTPRequest.READYSTATE_COMPLETED = 4;&lt;br /&gt;HTTPRequest.STATUS_OK = 200;&lt;br /&gt;&lt;br /&gt;////&lt;br /&gt;//// ScriptManager&lt;br /&gt;////&lt;br /&gt;function ScriptManager() {&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;ScriptManager.include = function( file_name ) {&lt;br /&gt;&lt;br /&gt; try {&lt;br /&gt;  var file_path;&lt;br /&gt;  var head;&lt;br /&gt;  var body;&lt;br /&gt;  var request = new HTTPRequest();&lt;br /&gt;  var script_obj;&lt;br /&gt;&lt;br /&gt;  script_obj = document.createElement('script');&lt;br /&gt;  script_obj.setAttribute('type', 'text/javascript');&lt;br /&gt;&lt;br /&gt;  if ( !ScriptManager.has_script_file_extension(file_name) ) {&lt;br /&gt;   file_name = file_name + ScriptManager.script_file_extension;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;   file_path = ScriptManager.script_base_link + '/' + file_name;&lt;br /&gt;&lt;br /&gt;  request.async = false;&lt;br /&gt;  request.send( file_path );&lt;br /&gt;&lt;br /&gt;  if ( typeof(script_obj.text) != 'undefined' ) {&lt;br /&gt;   script_obj.text = request.get_responseText();&lt;br /&gt;  }&lt;br /&gt;  else if ( typeof(script_obj.innerHTML) != 'undefined' ) {&lt;br /&gt;   script_obj.innerHTML = request.get_responseText();&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  if ( head = document.getElementsByTagName('head')[0] ) {&lt;br /&gt;   head.appendChild(script_obj);&lt;br /&gt;  }&lt;br /&gt;  else if ( body = document.getElementsByTagName('body')[0] ) {&lt;br /&gt;   body.appendChild(script_obj);&lt;br /&gt;  }&lt;br /&gt;  else {&lt;br /&gt;   throw 'ScriptManager::include requires a  or  tag';&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt; }&lt;br /&gt; catch ( e ) {&lt;br /&gt;  ScriptManager.ErrorHandler.handle_error(e);&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;ScriptManager.has_script_file_extension = function( file_name ) {&lt;br /&gt;&lt;br /&gt; try {&lt;br /&gt;  var ext = ScriptManager.script_file_extension;&lt;br /&gt;&lt;br /&gt;  if ( ext ) {&lt;br /&gt;                   if ( file_name.substring(file_name.length - ext.length, file_name.length) == ext ) {&lt;br /&gt;                           return true;&lt;br /&gt;                   }&lt;br /&gt;  }&lt;br /&gt; &lt;br /&gt;  return 0;&lt;br /&gt; }&lt;br /&gt; catch ( e ) {&lt;br /&gt;  throw e;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;ScriptManager.script_base_link = '';&lt;br /&gt;ScriptManager.script_file_extension = '.js';&lt;br /&gt;&lt;br /&gt;ScriptManager.ErrorHandler = ErrorHandler;&lt;br /&gt;ScriptManager.ErrorHandler.silent = true;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;div style="display:none;"&gt;&lt;br /&gt;How to include files in javascript&lt;br /&gt;javascript including files&lt;br /&gt;javascript require file&lt;br /&gt;javascript include()&lt;br /&gt;javascript xmlhttprequest include &lt;br /&gt;script object&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-117032490622251543?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/117032490622251543/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=117032490622251543' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/117032490622251543'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/117032490622251543'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2007/02/managing-include-files-in-javascript.html' title='Managing include files in Javascript'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-116419154279120240</id><published>2006-11-22T02:05:00.000-08:00</published><updated>2006-11-22T02:44:18.850-08:00</updated><title type='text'>Getting Java JVM and midp SSH to run on a Motorola Q</title><content type='html'>I bought a Motorola Q a few days ago with the full intention of using it for emergency SSH access when I'm not in front of a computer. I was a little disappointed to find that getting a terminal app with SSH wasn't as easy as I had expected, but I got it working nonetheless.&lt;br /&gt;&lt;br /&gt;If you want something quick for SSH or Telnet, try &lt;a href="http://www.codebrowser.org"&gt;zaTelnet&lt;/a&gt; at the link below. It installed fine on my Q (make sure you download the version for SmartPhone running Windows Mobile 5), though it does seem a bit slow while working. It's freeware, but it's not open source, which makes me a bit wary. However, it does seem to work fairly well:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.codebrowser.org/"&gt;http://www.codebrowser.org/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;If you're looking to get a Java VM installed so you can run MidpSSH, it's more involved, but not too bad, and then you have a Java VM for other Midlets.&lt;br /&gt;&lt;br /&gt;First, you need the JVM for the SmartPhone, which is still in the "evaluation" stage from IBM Websphere, but it seems pretty stable. You have to sign up for an IBM account (free, just annoying) to get to the download, but this is the link:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www14.software.ibm.com/webapp/download/preconfig.jsp?id=2006-04-06+13%3A40%3A41.975747R&amp;S_TACT=104CBW71&amp;amp;S_CMP="&gt;http://www14.software.ibm.com/webapp/download/preconfig.jsp?id=2006-04-06+13%3A40%3A41.975747R&amp;S_TACT=104CBW71&amp;amp;S_CMP=&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;after you signup (and wait a few minutes for your account to become active, apparently), follow the steps below:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;1.&lt;/span&gt; download the executable file listed under "CLDC 1.1/MIDP 2.0 for Windows Mobile 5.0 Smartphone Edition/ARM". The filename will begin with ibm-weme-wm50-sp-arm-midp20. Also download install.pdf under the same heading.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;2.&lt;/span&gt; run the file and complete the install wizard.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;3. &lt;/span&gt;Once installed, browse to C:\Program Files\IBM\WEME\runtimes\61\wm50-arm-sp-midp20&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;4. &lt;/span&gt;in there you will find a zip file called weme-wm50-sp-arm-midp20_6.1.0.20060727-102926.zip&lt;br /&gt;&lt;span style="font-style: italic;"&gt;(Note: the filename might have a slightly different version number after midp20_, but it's the only zip file in the directory)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;5. &lt;/span&gt;Extract the zip, then follow the instructions in the install.pdf file and you should be good to go.&lt;br /&gt;&lt;span style="font-style: italic;"&gt;(Note: The bin/, lib/, and examples/ directories referred to in the install file are the ones from the extracted .zip file, *not* from the _jvm directory.)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The install.pdf file details how to install their example app, but you can follow the same steps to install &lt;a href="http://www.xk72.com/midpssh/"&gt;midPSSH&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-116419154279120240?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/116419154279120240/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=116419154279120240' title='94 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/116419154279120240'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/116419154279120240'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2006/11/getting-java-jvm-and-midp-ssh-to-run.html' title='Getting Java JVM and midp SSH to run on a Motorola Q'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>94</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-116314439307291026</id><published>2006-11-09T23:18:00.000-08:00</published><updated>2008-03-07T10:32:34.905-08:00</updated><title type='text'>"mySQL server has gone away" in PHP when using mySQL stored procedures</title><content type='html'>There seems to be a lot of conflicting information floating around about stored procedures in PHP, and I spent a bit of time myself grappling with a "mysql server has gone away" error when using the CALL statement to run a stored procedure. I think I've managed to get things squared away, both in the code and in my head, as to the caveats of mySQL 5 stored procedures in PHP. Here's the lowdown:&lt;br /&gt;&lt;br /&gt;Per mySQL's own documentation for the C API of mysql_next_result:&lt;br /&gt;"each CALL returns a result to indicate the call status, in addition to any results sets that might be returned by statements executed within the procedure."&lt;br /&gt;&lt;br /&gt;This means that you should &lt;span style="font-weight: bold;"&gt;use mysqli_multi_query() when calling your stored procedure&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;Additionally, this means that you cannot simply call mysqli_store_result() to store your buffered result in a variable then move on to your next query, which is perfectly reasonable for normal queries. Even for procedures that return a single resultset, you need to make sure you iterate through the "call status" result even though you won't be using it for anything. In fact, this status result seems to be kind of nebulous in that you can't use_result() or store_result() on it (as far as I can tell), but you need to iterate through it (via next_result()) before executing another query.&lt;br /&gt;&lt;br /&gt;My problem with the way this all works is that since all of my procedures only returned a single result set, I wanted to be able to access them exactly as I would any other query (including storing the result so I could use it later), without having to manually iterate through this extra status result each time I called a procedure. Below is what I came up with.&lt;br /&gt;&lt;br /&gt;Note: I'm using the procedural functions for the mysqli interface because people who are just getting acquainted with mysqli will recognize the syntax more easily, but you should definitely look into the object oriented approach for using mysqli functions.&lt;br /&gt;&lt;br /&gt;&lt;pre style="color: black;"&gt;&lt;br /&gt;function &amp;amp;procedure_get_result( $procedure_call ) {&lt;br /&gt;&lt;br /&gt; //&lt;br /&gt; //note: error handling has been removed from this function&lt;br /&gt; //for the sake of the example, but you'll want to check the&lt;br /&gt; //return values of functions like mysqli_connect(), etc...&lt;br /&gt; //&lt;br /&gt;&lt;br /&gt; $result_count = 0;&lt;br /&gt; $result_loop_silence = 0;&lt;br /&gt; $false_ret = false;&lt;br /&gt;&lt;br /&gt; $dbh = mysqli_connect("localhost", "my_user", "my_password", "my_dbname");&lt;br /&gt;&lt;br /&gt; //&lt;br /&gt; // if the word CALL was included in the parameter, just strip it out.&lt;br /&gt; // We'll add it later.&lt;br /&gt; //&lt;br /&gt; if ( substr(strtoupper($procedure_call), 0, 4) == 'CALL' ) {&lt;br /&gt;   $procedure_call = substr( $procedure_call, 4 );&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; $procedure_call = trim($procedure_call);&lt;br /&gt;&lt;br /&gt; $query = "CALL {$procedure_call}";&lt;br /&gt;&lt;br /&gt; if ( mysqli_multi_query($dbh, $query)  ) {&lt;br /&gt;&lt;br /&gt;   $result = mysqli_store_result($dbh); //this is the result we want!&lt;br /&gt;&lt;br /&gt;   while ( mysqli_more_results($dbh) ) {&lt;br /&gt;&lt;br /&gt;     //&lt;br /&gt;     // iterate through the call status result set&lt;br /&gt;     //&lt;br /&gt;     if ( $result_count &gt; 0 ) {&lt;br /&gt;       //&lt;br /&gt;       // this function only supports a 0th and a 1st result set&lt;br /&gt;       // so show a warning if we have more&lt;br /&gt;       //&lt;br /&gt;       if ( !$result_loop_silence ) {&lt;br /&gt;         trigger_error( 'Too many results returned for ' . __METHOD__ . '. Use multi query.', E_USER_WARNING );&lt;br /&gt;         $result_loop_silence = 1;&lt;br /&gt;       }&lt;br /&gt;     }&lt;br /&gt;&lt;br /&gt;     mysqli_next_result($dbh);&lt;br /&gt;&lt;br /&gt;     if ( $status_result = mysqli_use_result($dbh) ) {&lt;br /&gt;&lt;br /&gt;       //&lt;br /&gt;       // this never seems to get executed with&lt;br /&gt;       // CALL that only return one result set plus the&lt;br /&gt;       // status resultset, but we'll leave it in&lt;br /&gt;       //&lt;br /&gt;     &lt;br /&gt;       $status_result-&gt;close();&lt;br /&gt;       mysqli_free_result($status_result);&lt;br /&gt;                             &lt;br /&gt;     }&lt;br /&gt;&lt;br /&gt;     $result_count++;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; return $result;&lt;br /&gt;&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt;  return null;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-116314439307291026?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/116314439307291026/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=116314439307291026' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/116314439307291026'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/116314439307291026'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2006/11/mysql-server-has-gone-away-in-php-when.html' title='&quot;mySQL server has gone away&quot; in PHP when using mySQL stored procedures'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-116071207253100884</id><published>2006-10-12T20:58:00.000-07:00</published><updated>2006-11-12T17:32:27.096-08:00</updated><title type='text'>mySQL natural number ordering</title><content type='html'>Had a situation today where I needed to order a list that looked like "DISTRICT 1, DISTRICT 2...DISTRICT 10, DISTRICT 11". Ordering this field with a simple ORDER BY groupField ASC puts "DISTRICT 10" and "DISTRICT 11" before "DISTRICT 2", which is not what I wanted. Also, the  field wasn't always going to be setup as single string, a space, and a number, so I couldn't just SUBSTRING out the trailing number. Found the solution in the mySQL forums:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;ORDER BY LENGTH(groupField) ASC, groupField ASC&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;now the ordering is as you'd expect from a user standpoint.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-116071207253100884?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/116071207253100884/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=116071207253100884' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/116071207253100884'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/116071207253100884'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2006/10/mysql-natural-number-ordering.html' title='mySQL natural number ordering'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-115830344621757134</id><published>2006-09-14T23:51:00.001-07:00</published><updated>2006-09-14T23:57:26.226-07:00</updated><title type='text'>IE doesn't show option text when dynamically creating an optgroup</title><content type='html'>I dealt with a bit of a headache tonight trying to figure out why Internet Explorer 6 wouldn't display any option text if I created an option within an optgroup dynamically (using Javascript).  It turns out that you have to set the innerText property of the option before appending it to the optgroup. Just setting the label or text properties of the option is not enough, you need to set innerText, like so:&lt;br /&gt;&lt;br /&gt;select_obj = document.getElementById('my_select_box_id');&lt;br /&gt;optgroup_obj = document.createElement('optgroup');&lt;br /&gt;option_obj = document.createElement('option');&lt;br /&gt;&lt;br /&gt;option_obj.label  = 'option label';&lt;br /&gt;option_obj.value = 'option value';&lt;br /&gt;&lt;br /&gt;if ( typeof(option_obj.innerText) != 'undefined' ) {&lt;br /&gt;     option_obj.innerText = 'option label';&lt;br /&gt;}&lt;br /&gt;else {&lt;br /&gt;     option_obj.text = 'option label';&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;optgroup_obj.appendChild(option_obj);   &lt;br /&gt;select_obj.appendChild( optgroup_obj );&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-115830344621757134?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/115830344621757134/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=115830344621757134' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/115830344621757134'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/115830344621757134'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2006/09/ie-doesnt-show-option-text-when_14.html' title='IE doesn&apos;t show option text when dynamically creating an optgroup'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-115309529227206896</id><published>2006-07-16T17:13:00.000-07:00</published><updated>2006-07-16T17:14:52.283-07:00</updated><title type='text'>IMP Error connecting to IMAP server. 22 : Invalid argument.</title><content type='html'>IMP was giving me this error today when trying to login. Turns out that since I had the IMAP server set to use SSL (the default), PHP needs openSSL *compiled statically*, not just the openSSL extension. To fix this in FreeBSD using ports, I just did:&lt;br /&gt;&lt;br /&gt;cd /usr/ports/lang/php4&lt;br /&gt;make config&lt;br /&gt;  -&gt; check the "Build OpenSSL statically" option&lt;br /&gt;make clean&lt;br /&gt;make&lt;br /&gt;make deinstall&lt;br /&gt;make reinstall&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-115309529227206896?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/115309529227206896/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=115309529227206896' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/115309529227206896'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/115309529227206896'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2006/07/imp-error-connecting-to-imap-server-22.html' title='IMP Error connecting to IMAP server. 22 : Invalid argument.'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-115308744303606508</id><published>2006-07-16T15:01:00.000-07:00</published><updated>2006-07-16T15:04:03.046-07:00</updated><title type='text'>horde sidebar menu hangs on login after turba configuration</title><content type='html'>I was installing horde today on a new server, and noticed that after doing all of my configs, the sidebar would hang pretty much indefinitely when trying to login. I poked around for a bit and eventually found that turba was the issue. A little googling and a mailing list post led me to the information that the problem is the LDAP source in the turba/config/sources.php file. I don't use LDAP, so I commented out the LDAP source by changing:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;if ( Util::extensionExists('ldap')) {&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;to&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;if ( false &amp;&amp;amp; Util::extensionExists('ldap')) {&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;in &lt;span style="font-weight: bold;"&gt;config/turba/sources.php&lt;/span&gt; and it now works perfectly.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-115308744303606508?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/115308744303606508/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=115308744303606508' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/115308744303606508'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/115308744303606508'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2006/07/horde-sidebar-menu-hangs-on-login.html' title='horde sidebar menu hangs on login after turba configuration'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-114440005324316647</id><published>2006-04-07T01:50:00.000-07:00</published><updated>2006-04-07T01:57:39.826-07:00</updated><title type='text'>InnoDB table creation fails after an improperly dropped table</title><content type='html'>I was playing around with InnoDB on my development server, somehow screwed things up pretty bad, and had to delete some .frm files from the db directory manually. However, I found that even though some of my tables didn't exist in show tables, I couldn't recreate them. the mySQL client was spitting out fairly ambiguous InnoDB errors (can't rename table, etc), and it was confusing me quite a bit. I even found that I could create the tables as myISAM, but once I tried to change engine to InnoDB, it wouldn't let me. I even tried dropping the entire database and recreating it, but the InnoDB data dictionary still had information about those tables in that database! I found some more insight by using the mySQL command "show engine innoDB status;", which told me the last foreign key errors. Turns out the foreign key constraints were still applied to my table even though the table didn't actually exist. I tried dropping the keys by name, but that didn't work. It didn't issue any errors, but it also didn't do anything. Ultimately I had to&lt;span style="font-weight: bold;"&gt; recreate all the keys as they had originally appeared (matching the name and type exactly), then drop the table properly. &lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-114440005324316647?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/114440005324316647/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=114440005324316647' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/114440005324316647'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/114440005324316647'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2006/04/innodb-table-creation-fails-after.html' title='InnoDB table creation fails after an improperly dropped table'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-114326372722204899</id><published>2006-03-24T21:11:00.000-08:00</published><updated>2006-03-24T21:18:52.096-08:00</updated><title type='text'>Windows Explorer freezes when video files are in the folder you're viewing</title><content type='html'>I recently started having a problem where any time there was an avi file in the folder I was viewing, explorer would freeze up and have to be shutdown from task manager. Over at annoyances.org, I found a handy fix that took care of the problem right away. Here it is:&lt;br /&gt;&lt;br /&gt;NOTE: before doing this, you should make a backup of your registry by going to start-&gt;run, typing &lt;span style="font-weight: bold;"&gt;regedt32 &lt;/span&gt;, then going to File &gt; Export and saving the registry file somewhere safe.&lt;br /&gt;&lt;br /&gt;1. Click Start&lt;br /&gt;2. Click Run&lt;br /&gt;3. Type: regedt32&lt;br /&gt;4. Browse to the registry key (the things that look like folders on the left side of the screen)  HKEY_CLASSES_ROOT\SystemFileAssociations\.avi\shellex\PropertyHandler.&lt;br /&gt;5. Delete the Key on the right hand side called "default" by right-clicking and hitting "Delete".&lt;br /&gt;&lt;br /&gt;That should be it. If that doesn't work, the following command might help, but it will disable all AVI thumbnail preview capability:&lt;br /&gt;&lt;br /&gt;go to Start &gt; Run, type:  &lt;span style="font-weight: bold;"&gt;REGSVR32 /U SHMEDIA.DLL&lt;/span&gt;  and press OK. To undo this change, simply go to Start &gt; Run, type:  &lt;span style="font-weight: bold;"&gt;REGSVR32 SHMEDIA.DLL&lt;/span&gt;  and press OK.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-114326372722204899?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/114326372722204899/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=114326372722204899' title='22 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/114326372722204899'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/114326372722204899'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2006/03/windows-explorer-freezes-when-video.html' title='Windows Explorer freezes when video files are in the folder you&apos;re viewing'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>22</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-113389322967285685</id><published>2005-12-06T10:15:00.000-08:00</published><updated>2005-12-06T10:20:29.900-08:00</updated><title type='text'>ACT 2005 Install fails on SQLHotfix818.exe on Small Business Server 2003</title><content type='html'>This problem showed up while I was migrating a client onto a new SBS 2003 server. The ACT install would just about complete, then complain about an invalid exception in SQLHotfix818.exe at the very end. Subsequently trying to start ACT would cause an "Object reference not set to instance of object" error. I tried a lot of things, but here's what ended up working:&lt;br /&gt;&lt;br /&gt;1. Go to Add/Remove Programs and remove any references to MS SQL Desktop Engine (including SharePoint and SBSMonitoring, you can reinstall these if you need them. If you actively use either, make sure you have a backup!).&lt;br /&gt;&lt;br /&gt;2. Rename C:\Program Files\Microsoft SQL Server to something else, so the ACT install can't find it.&lt;br /&gt;&lt;br /&gt;3. Make sure the SYSTEM user has full access to the C:\ drive (per ACT KB article #16478)&lt;br /&gt;&lt;br /&gt;4. Make sure all services that start with MSSQL$ are stopped (Start -&gt; Administrative Tools -&gt; Services)&lt;br /&gt;&lt;br /&gt;5. If ACT is installed, uninstall it.&lt;br /&gt;&lt;br /&gt;6. Now install ACT, and you should be good to go.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-113389322967285685?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/113389322967285685/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=113389322967285685' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/113389322967285685'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/113389322967285685'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2005/12/act-2005-install-fails-on.html' title='ACT 2005 Install fails on SQLHotfix818.exe on Small Business Server 2003'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-113383736335011316</id><published>2005-12-05T18:44:00.000-08:00</published><updated>2005-12-05T18:50:34.993-08:00</updated><title type='text'>Horde/IMP won't create sent mail folder. This is what the server said: Invalid mailbox name</title><content type='html'>I was setting up the new version of &lt;a href="http://www.horde.org/imp/"&gt;IMP&lt;/a&gt; today, and found that I couldn't create folders from webmail. I knew it wasn't an IMAP permissions issue, because I could create &amp; subscribe to folders with no problems from my IMAP client. It turns out that with &lt;a href="http://www.inter7.com/courierimap/"&gt;Courier-IMAP&lt;/a&gt;, the IMAP server I prefer, you need to set the following options in your server confing (probably /usr/local/www/horde/imp/config/servers.php):&lt;br /&gt;&lt;br /&gt;under $servers['imap'] (or whatever server you're using - 'imap' is the default - change the namespace and folder lines to read:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;'folders' =&gt; 'INBOX.',&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;'namespace' =&gt; '',&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Note the trailing dot after INBOX. Then you have to LOG OUT AND LOG BACK IN before the changes will take effect. The docs suggest that these options should be the other way around for Courier-IMAP, but it doesn't seem to work that way.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-113383736335011316?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/113383736335011316/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=113383736335011316' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/113383736335011316'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/113383736335011316'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2005/12/hordeimp-wont-create-sent-mail-folder.html' title='Horde/IMP won&apos;t create sent mail folder. This is what the server said: Invalid mailbox name'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-112742775014003789</id><published>2005-09-22T15:15:00.000-07:00</published><updated>2005-09-22T15:22:30.146-07:00</updated><title type='text'>Changing file associations in windows as a non administrator user</title><content type='html'>It seems that non-admin users on Windows 2000 and Windows XP can't edit their own file associations. This becomes a problem when you have a nonstandard association that needs to be in place for everyone in the office/organization. I grappled with this problem a bit, but eventually found that I could write a simple VBscript to edit the current user's registry settings to add in my own file assocations for a given extension. The code is below, simply save this script with a vbs extension (example: fileAssn.vbs) and add it as a login script for any users who need the association:&lt;br /&gt;&lt;br /&gt;Option Explicit&lt;br /&gt;&lt;br /&gt;Dim objShell&lt;br /&gt;set objShell = WScript.CreateObject ("WScript.Shell")&lt;br /&gt;&lt;br /&gt;'----------------------------------------------------------------&lt;br /&gt;' Don't edit above here&lt;br /&gt;'&lt;br /&gt;' Below is an example of associating PDF files with AcroRd32.exe&lt;br /&gt;' You can change this to whatever you need, and just&lt;br /&gt;' copy and paste the line over again to add more associations.&lt;br /&gt;'-----------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;addFileAssociation ".pdf", "AcroRd32.exe"&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;'-------------------------&lt;br /&gt;' Don't edit below here&lt;br /&gt;'-------------------------&lt;br /&gt;Sub addFileAssociation( fileExt, whichApp )&lt;br /&gt;&lt;br /&gt;    If ( Left(fileExt, 1) &lt;&gt; "." ) Then&lt;br /&gt;        fileExt = "." &amp; fileExt&lt;br /&gt;    End If&lt;br /&gt;&lt;br /&gt;    objShell.RegWrite "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\" &amp; fileExt &amp;amp; "\Application", whichApp&lt;br /&gt;&lt;br /&gt;End Sub&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-112742775014003789?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/112742775014003789/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=112742775014003789' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/112742775014003789'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/112742775014003789'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2005/09/changing-file-associations-in-windows.html' title='Changing file associations in windows as a non administrator user'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-112702278212118225</id><published>2005-09-17T22:47:00.000-07:00</published><updated>2005-09-17T22:53:02.126-07:00</updated><title type='text'>passing username to spamc through maildrop with postfix</title><content type='html'>I'm setting up a purely virtual postfix/mysql mail server, and I wanted to implement spamassassin to filter spam. Initially I had intended to use procmail, but postfix doesn't currently support procmail for virtual users, so I had to go with maildrop. All I wanted was to enable the line:&lt;br /&gt;&lt;br /&gt;xfilter "/usr/local/bin/spamc -u USERNAME"&lt;br /&gt;&lt;br /&gt;and have the username changed to the recipient for the mail being processed. After stumbling through some changes in master.cf and trying different maildrop variables, I found that the easiest way to get the username to the script was simply to pass ${recipient} as a parameter to maildrop in master.cf, like this:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;maildrop  unix  -       n       n       -       -       pipe&lt;/span&gt;&lt;br /&gt; &lt;span style="font-weight: bold;"&gt;   flags=DRhu user=vmail argv=/usr/local/bin/maildrop -d ${recipient} ${recipient}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;(yes, ${recipient} should be there twice.) This way, I can always pull the recipient email address directly into the maildroprc file by referencing $1. So my xfilter line became:&lt;br /&gt;&lt;br /&gt; &lt;span style="font-weight: bold;"&gt;xfilter "/usr/local/bin/spamc -u $1"&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;then I just fleshed out my maildroprc script (i'd post it here but it's pretty specific to my setup, and other howtos on maildrop/postfix are available) to handle what happens if a mail is tagged as spam.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-112702278212118225?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/112702278212118225/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=112702278212118225' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/112702278212118225'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/112702278212118225'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2005/09/passing-username-to-spamc-through.html' title='passing username to spamc through maildrop with postfix'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-112240397562340652</id><published>2005-07-26T11:50:00.000-07:00</published><updated>2005-07-26T11:52:55.630-07:00</updated><title type='text'>Windows XP automatically tries to search when you click a folder</title><content type='html'>A friend of mine came to me with this issue, and after a bit of searching, I came across http://windowsxp.mvps.org/searchwindow.htm . Running &lt;span style="font-weight: bold;"&gt;"regsvr32 /i shell32.dll"&lt;/span&gt;, as suggested by that site, fixed his problem, so I figured I'd repost it here, since it took me a while to find:&lt;br /&gt;&lt;br /&gt;The following is from http://windowsxp.mvps.org/searchwindow.htm:&lt;br /&gt;&lt;p class="normaltext"&gt;If you double-click a drive or folder, Search Companion may start and the drive or folder may not open. 'Search' will be the top most in the right-click context menu for a folder or drive. This usually happens if you've edited the  &lt;b&gt;File Folder&lt;/b&gt;  or &lt;b&gt;Drive&lt;/b&gt; via the Folder Options File Types dialog, in order  to add a context-menu item such as "Command Prompt here", "Print Directory"  feature for folders or drives.&lt;/p&gt;  &lt;p class="normaltext"&gt;References:&lt;br /&gt;&lt;a target="_blank" href="http://support.microsoft.com/?kbid=321379"&gt;http://support.microsoft.com/?kbid=321379&lt;/a&gt;&lt;br /&gt;&lt;a target="_blank" href="http://support.microsoft.com/?kbid=321186"&gt;http://support.microsoft.com/?kbid=321186&lt;/a&gt;&lt;/p&gt;  &lt;h4&gt;Quick fix&lt;/h4&gt;  &lt;p class="normaltext"&gt;Click Start, Run and  type this command:&lt;/p&gt;  &lt;blockquote&gt; &lt;p class="normaltext"&gt;&lt;b&gt;regsvr32 /i  shell32.dll&lt;/b&gt;&lt;br /&gt;(Tip from David Candy)&lt;/p&gt; &lt;/blockquote&gt;  &lt;p class="normaltext"&gt;To fix the problem  manually, open Registry Editor and navigate to:&lt;/p&gt;  &lt;blockquote&gt; &lt;blockquote&gt; &lt;p class="normaltext"&gt; HKEY_CLASSES_ROOT\Directory\shell&lt;/p&gt; &lt;p class="normaltext"&gt;-and-&lt;/p&gt; &lt;p class="normaltext"&gt; HKEY_CLASSES_ROOT\Drive\shell&lt;/p&gt; &lt;/blockquote&gt; &lt;/blockquote&gt;  &lt;ul&gt; &lt;li&gt; &lt;p class="normaltext"&gt;In the right-pane, locate and click the  (Default) value under the keys mentioned above &lt;/p&gt;&lt;/li&gt;&lt;li&gt; &lt;p class="normaltext"&gt;Click Modify on the Edit menu.   &lt;/p&gt;&lt;/li&gt;&lt;li&gt; &lt;p class="normaltext"&gt;Type the word &lt;b&gt;none&lt;/b&gt; in the Value data box, and then click OK.  &lt;/p&gt;&lt;/li&gt;&lt;li&gt; &lt;p class="normaltext"&gt;Exit Registry Editor. &lt;/p&gt;&lt;/li&gt; &lt;/ul&gt; &lt;span style="font-weight: bold;"&gt;&lt;/span&gt;&lt;b&gt;&lt;span style="font-weight: bold;"&gt;&lt;/span&gt;&lt;br /&gt;&lt;/b&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-112240397562340652?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/112240397562340652/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=112240397562340652' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/112240397562340652'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/112240397562340652'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2005/07/windows-xp-automatically-tries-to.html' title='Windows XP automatically tries to search when you click a folder'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-112240302706472578</id><published>2005-07-26T11:26:00.000-07:00</published><updated>2005-07-26T11:37:07.070-07:00</updated><title type='text'>Windows (WSH) Shell Login scripts won't run after allowing specified programs</title><content type='html'>In an attempt to lock down some workstations that were having spyware trouble, I made use of the Windows 2000 "Run only allowed windows applications" option in the Group Policy Object. [Note: this feature is expanded in Windows Server 2003 as "Software Restrictions Policies" in the GPO editor, but the idea is the same]. Since the network users only use a handful of applications ( MS Word, Internet Explorer, etc ), I was able to specify these as allowed programs, disallowing everything else. However, even if I specified my login scripts in the "allowed programs" list as my_script.vbs,  they still weren't running.  On a hunch, I added wscript.exe, the interpreter for VBS scripts, to the allowed program list, and everything ran fine after that. I also allowed sndvol32.exe (which will allow users to access the sound mixer to change their volume), calc.exe, notepad.exe, mspaint.exe, and a few other various windows "accessories" that everyone should have access to.&lt;br /&gt;&lt;br /&gt;Bottom line:&lt;br /&gt;Can't get VBS scripts to run due to having specified an allowed list of windows applications? Try adding wscript.exe to the list.&lt;br /&gt;&lt;br /&gt;Users restricted from changing their volume settings for the same reason? Try adding sndvol32.exe.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-112240302706472578?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/112240302706472578/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=112240302706472578' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/112240302706472578'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/112240302706472578'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2005/07/windows-wsh-shell-login-scripts-wont.html' title='Windows (WSH) Shell Login scripts won&apos;t run after allowing specified programs'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-111775327409878617</id><published>2005-06-02T15:54:00.000-07:00</published><updated>2005-06-02T16:05:51.300-07:00</updated><title type='text'>Proftpd can't find libmysqlclient.so.14 inside chroot jail</title><content type='html'>I was trying to make the switch to purely virtual ftp users in Proftpd, but noticed that the jailed Proftpd binary (created with my &lt;a href="http://jim.centerfuse.net/projects/proftpd_chroot/"&gt;Proftpd Chroot script&lt;/a&gt;) was having trouble finding the mySQL libraries. No problem, I thought, it tells me what file it can't find, so I'll just copy them into their respective homes underneath the chroot jail. The first two went fine, libm.so and libz.so, but even after copying libmysqlclient.so.14 from /usr/local/lib/mysql/ to /usr/chroot/proftpd/usr/local/lib/mysql, which is underneath my chroot jail, Proftpd still couldn't find it on startup. After tinkering with the file location and doing some google searching, I found a thread related to shared objects that suggested using &lt;a href="http://www.liacs.nl/%7Ewichert/strace/"&gt;strace&lt;/a&gt; on a binary file to determine where exactly it's looking for a particular shared object. Aha! Proftpd running under the jail, for reasons I haven't yet figured out, wants libmysqlclient.so.14 to be in /usr/lib. No mySQL sub directory or anything, just in the standard /usr/lib directory. After moving the file there, proftpd started up like a charm.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://jim.centerfuse.net/projects/proftpd_chroot/"&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-111775327409878617?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/111775327409878617/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=111775327409878617' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/111775327409878617'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/111775327409878617'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2005/06/proftpd-cant-find-libmysqlclientso14.html' title='Proftpd can&apos;t find libmysqlclient.so.14 inside chroot jail'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-110966896462103457</id><published>2005-03-01T01:20:00.000-08:00</published><updated>2005-03-01T01:22:44.623-08:00</updated><title type='text'>mysqldump out of memory error 5</title><content type='html'>Tonight I was trying to mysqldump some large tables, but kept getting "mysqldump out of memory error 5". I knew I had dumped tables larger than the ones I was dumping, so I wasn't sure what was going on. After poking around with mySQL settings in my.cnf to no avail, I decided to try optimize table. That seemed to fix the problem. The reason it came to mind is because I had purged about 100,000 rows of the problematic table earlier in the day and thought maybe it needed cleanup&lt;br /&gt;&lt;br /&gt;bottom line:&lt;br /&gt;mysqldump giving "out of memory" error, try:&lt;br /&gt;optimze table myTable&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-110966896462103457?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/110966896462103457/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=110966896462103457' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/110966896462103457'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/110966896462103457'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2005/03/mysqldump-out-of-memory-error-5.html' title='mysqldump out of memory error 5'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-110965882755023369</id><published>2005-02-28T22:29:00.000-08:00</published><updated>2005-02-28T22:33:47.550-08:00</updated><title type='text'>Windows MSI installer won't install</title><content type='html'>I was trying to install Veritas Backupexec 9.1 tonight, and it kept failing with "backupexec failed to install msde".  The Veritas site said to try to install manually, and I found that I couldn't execute the MSI file at all. It simply did nothing when I clicked the file, but that was a problem I had seen before. Security settings on MSI files have to be very leniant for some reason, so I simply granted "everyone" full access to the install files. I'm not sure which user actually needs access in order for the install to run properly (maybe SYSTEM?), but since security isn't going to be important on files I'm deleting, I just granted EVERYONE full access to the entire folder. You should not, however, make a habit of doing this on files you don't want your entire organization to have access to.&lt;br /&gt;&lt;br /&gt;Bottom Line:&lt;br /&gt;clicking an MSI file seems to do absolutely nothing? Try granting full access permissions to the windows group "EVERYONE" and see if things change. If security is important on the MSI file, take away the full access as soon as you are finished installing.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-110965882755023369?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/110965882755023369/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=110965882755023369' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/110965882755023369'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/110965882755023369'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2005/02/windows-msi-installer-wont-install.html' title='Windows MSI installer won&apos;t install'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-110738169646045604</id><published>2005-02-02T13:58:00.000-08:00</published><updated>2005-02-02T14:01:36.460-08:00</updated><title type='text'>PHP Sessions stop working after mod_php4 upgrade to php 4.3.10</title><content type='html'>Last night I upgraded PHP to php 4.3.10 from 4.3.9. Everything seemed to have gone well, but today people complained about some scripts not working. I did some digging and found that session_start() would stop the script cold with no error messages.  I was rather confused for a while, then decided to just recompile the session extension ( /usr/ports/www/php4-session/make &amp;&amp;amp; make install ) and things seemed to work after that. Now I'm going to recompile all extensions in case something else was broken in the process. Hopefully I won't be breaking anything myself.&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-110738169646045604?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/110738169646045604/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=110738169646045604' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/110738169646045604'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/110738169646045604'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2005/02/php-sessions-stop-working-after.html' title='PHP Sessions stop working after mod_php4 upgrade to php 4.3.10'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-110295623196961222</id><published>2004-12-13T08:42:00.000-08:00</published><updated>2004-12-13T08:43:51.970-08:00</updated><title type='text'>Found Windows 2000 Sound Drivers for E-Machine Etower 533id2</title><content type='html'>Came across these drivers after some searching, thought someone else might find the link useful. This machine (Etower 533id2) uses the Crystal CS4281 Chipset.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.opendrivers.com/freedownload/217909/Crystal-CS4281-Sound-Driver-Windows.html"&gt;http://www.opendrivers.com/freedownload/217909/Crystal-CS4281-Sound-Driver-Windows.html&lt;/a&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-110295623196961222?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/110295623196961222/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=110295623196961222' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/110295623196961222'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/110295623196961222'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2004/12/found-windows-2000-sound-drivers-for-e.html' title='Found Windows 2000 Sound Drivers for E-Machine Etower 533id2'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-110257838518013505</id><published>2004-12-08T23:43:00.000-08:00</published><updated>2009-01-24T13:10:52.603-08:00</updated><title type='text'>Selecting an age from a birthday date field in mySQL</title><content type='html'>I added this to the mySQL manual a while back, but it seems to have been filtered out in more recent versions of the docs. Although I prefer to do my arithmetic in PHP, this query will determine an age from a date field birthDate in mySQL.&lt;br /&gt;&lt;br /&gt;SELECT YEAR(NOW()) - YEAR(birthDate) - IF ( MONTH(NOW()) &lt; MONTH(birthDate), 1, 0 ) - IF ( MONTH(NOW()) = MONTH(birthDate) AND DAYOFMONTH(NOW()) &lt; DAYOFMONTH(birthDate), 1, 0) AS age&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-110257838518013505?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/110257838518013505/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=110257838518013505' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/110257838518013505'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/110257838518013505'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2004/12/selecting-age-from-birthday-date-field.html' title='Selecting an age from a birthday date field in mySQL'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-110193517392236045</id><published>2004-12-01T13:02:00.000-08:00</published><updated>2004-12-01T13:54:56.750-08:00</updated><title type='text'>FreeBSD thinks vpopmail isn't installed even when it is</title><content type='html'>I grappled with this problem a little bit today, where FreeBSD kept insisting on installing vpopmail from other ports even though it was already installed. The port install would fail, however, because FreeBSD won't install over another package without making "reinstall". I couldn't figure out why it thought vpopmail was not installed until I froze the screen when it was checking, and I noticed that it looks for /usr/local/vpopmail/lib/libvpopmail.a. Since I install vpopmail to /home/vpopmail, that file was in /home/vpopmail/lib/ !&lt;br /&gt;I just did:&lt;br /&gt;&lt;br /&gt;mkdir -p /usr/local/vpopmail/lib&lt;br /&gt;ln -s /home/vpopmail/lib/libvpopmail.a /usr/local/vpopmail/lib/libvpopmail.a&lt;br /&gt;&lt;br /&gt;and the problem went away.&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-110193517392236045?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/110193517392236045/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=110193517392236045' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/110193517392236045'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/110193517392236045'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2004/12/freebsd-thinks-vpopmail-isnt-installed.html' title='FreeBSD thinks vpopmail isn&apos;t installed even when it is'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-110177057627125893</id><published>2004-11-29T14:29:00.000-08:00</published><updated>2004-11-29T15:22:56.270-08:00</updated><title type='text'>Installing vpopmail to a directory other than the default</title><content type='html'>I was setting up a FreeBSD machine with &lt;a href="http://www.tnpi.biz/internet/mail/toaster/index.shtml"&gt;Matt's Mail Toaster&lt;/a&gt; today, and I couldn't seem to get vpopmail to install anywhere except for /usr/local/vpopmail, even though I specified in toaster-watcher.conf that the vpopmail directory should be /home/vpopmail. After some digging, I found that the vpopmail install checks the home directory of the vpopmail user in order to find out where it should install to. So, I did&lt;br /&gt;&lt;br /&gt;chsh vpopmail&lt;br /&gt;&lt;br /&gt;and changed the home directory to /home/vpopmail.  To get it to work seamlessly, though, I also had to set PREFIX=/home before doing the vpopmail install, so the line was:&lt;br /&gt;&lt;br /&gt;env PREFIX=/home toaster_setup.pl -s vpopmail&lt;br /&gt;&lt;br /&gt;or, if you're using the ports on your own:&lt;br /&gt;env PREFIX=/home make install&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-110177057627125893?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/110177057627125893/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=110177057627125893' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/110177057627125893'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/110177057627125893'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2004/11/installing-vpopmail-to-directory-other.html' title='Installing vpopmail to a directory other than the default'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-110136285990945141</id><published>2004-11-24T22:03:00.000-08:00</published><updated>2004-11-24T22:11:27.226-08:00</updated><title type='text'>Capitalizing the first word of every sentence in PHP</title><content type='html'>Someone asked me tonight for a function that capitalized the first word of every sentence in PHP. I thought I could do it with a one line preg_replace, but it didn't want to let me call strtoupper or ucfirst on backreferenced variables for some reason. I didn't really look into it, but I wrote this function instead. It's not necessarily as efficient as I'd like it to be, but it works, and I thought someone might be able to use it.&lt;br /&gt;&lt;br /&gt;$text = 'Here is a sentence. here is one not capitalized. and another';&lt;br /&gt;$fixed_text = uc_sentence_start( $text );&lt;br /&gt;echo $fixed_text;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;function uc_sentence_start( $value ) {&lt;br /&gt;&lt;br /&gt;    preg_match_all( '/(\.\s*)([a-z])/m', $value, $matches, PREG_OFFSET_CAPTURE );&lt;br /&gt;&lt;br /&gt;    for ($i=0; $i&lt; count($matches[0]); $i++) {&lt;br /&gt;&lt;br /&gt;        $match_pos = $matches[0][$i][1];&lt;br /&gt;        $dot_space = $matches[1][$i][0];&lt;br /&gt;        $char      = $matches[2][$i][0];&lt;br /&gt;              &lt;br /&gt;        $replacement = $dot_space . strtoupper( $char );&lt;br /&gt;              &lt;br /&gt;        $value = substr_replace( $value, $replacement, $match_pos, strlen($replacement) );&lt;br /&gt;           }&lt;br /&gt;       &lt;br /&gt;           return $value;&lt;br /&gt;}&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-110136285990945141?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/110136285990945141/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=110136285990945141' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/110136285990945141'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/110136285990945141'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2004/11/capitalizing-first-word-of-every.html' title='Capitalizing the first word of every sentence in PHP'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-110116273387414294</id><published>2004-11-22T14:28:00.000-08:00</published><updated>2004-11-22T14:32:13.873-08:00</updated><title type='text'>Mappoint North America can't run</title><content type='html'>Just something I've picked up along the way. If you're seeing the following message when trying to use the MapPoint VB control (MappointControl.ocx):&lt;br /&gt;&lt;br /&gt;"MapPoint North America can't run because it is not registered on your system, or it can't be found. Install MapPoint North America and try again."&lt;br /&gt;&lt;br /&gt;Try going to start -&gt; run and typing (including quotes):&lt;br /&gt;&lt;br /&gt;"C:\Program Files\Microsoft MapPoint\Mappoint.exe" /regserver&lt;br /&gt;&lt;br /&gt;If you have Mappoint installed to a different location than the default, replace the location above with the correct path. However, make sure that while the path stays in quotes, the /regserver is outside the quotes and separated from the path with a space, as above.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-110116273387414294?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/110116273387414294/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=110116273387414294' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/110116273387414294'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/110116273387414294'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2004/11/mappoint-north-america-cant-run.html' title='Mappoint North America can&apos;t run'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-110114466136299635</id><published>2004-11-22T09:23:00.000-08:00</published><updated>2004-11-22T09:32:34.510-08:00</updated><title type='text'>Apache make_sock errors</title><content type='html'>While trying to resolve a latency issue today, I restarted apache and kept seeing these errors:&lt;br /&gt;&lt;br /&gt;[Mon Nov 22 12:12:57 2004] [info] (2)No such file or directory: make_sock: for port 443, setsockopt: (SO_ACCEPTFILTER)&lt;br /&gt;[Mon Nov 22 12:12:57 2004] [info] (2)No such file or directory: make_sock: for port 80, setsockopt: (SO_ACCEPTFILTER)&lt;br /&gt;&lt;br /&gt;The port seemed to be bound but wouldn't accept any connections. After running "sockstat" a lot and scratching my head, something occurred to me. I normally have an ipfw throttle pipe running just to prevent an attack or sudden popularity increase in a  website or image from sucking up thousands of dollars of bandwidth. I *thought* I had disabled it with "ipfw pipe flush" to troubleshoot the latency problem, but then I remembered that you need to issue two commands to fully clear the ipfw throttling rules:&lt;br /&gt;&lt;br /&gt;ipfw pipe flush&lt;br /&gt;ipfw flush&lt;br /&gt;&lt;br /&gt;That solved the problem with Apache not accepting socket connections. &lt;br /&gt;&lt;br /&gt;(As of this writing, the latency is still there, but I'm growing more and more certain that it's a problem somwhere upstream.)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-110114466136299635?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/110114466136299635/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=110114466136299635' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/110114466136299635'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/110114466136299635'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2004/11/apache-makesock-errors.html' title='Apache make_sock errors'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-9135439.post-110102148362093383</id><published>2004-11-20T22:40:00.000-08:00</published><updated>2004-12-01T17:14:06.506-08:00</updated><title type='text'>Proftpd mySQL Authentication &amp; Quotas on FreeBSD</title><content type='html'>Tonight I tried setting up Proftpd with mySQL authentication on a development box running FreeBSD 4.10. I got it to work using the steps below, but some security concerns arise from using mySQL user accounts without a system account behind them. Since this setup uses one FTP account to create user home directories and upload files, a compromise to this FTP user would cause the attacker to gain access to all FTP user home directories. I guess it just depends on how much you trust the DefaultRoot directive in Proftpd. I run Proftpd in its own chroot environment (&lt;a href="http://jim.centerfuse.net/projects/proftpd_chroot/"&gt; http://jim.centerfuse.net/projects/proftpd_chroot/&lt;/a&gt; ) in addition to using DefaultRoot, so I'm used to feeling pretty safe with my Proftpd install. Anyway, here's how I did the install/configuration&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;1&lt;/span&gt;&lt;span style="font-weight: bold;"&gt;. &lt;/span&gt;&lt;span style="font-size:130%;"&gt;install proftpd-mysql from the ports with WITH_QUOTA set:&lt;/span&gt;&lt;br /&gt;cd /usr/ports/ftp/proftpd-mysql/&lt;br /&gt;env WITH_QUOTA=yes make&lt;br /&gt;env WITH_QUOTA=yes make install&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;2.&lt;/span&gt;&lt;span style="font-size:130%;"&gt; Add the global proftpd user &amp; Proftpd group to your system.&lt;/span&gt;&lt;br /&gt;I used uid &amp;amp; gid 5500 simply because that's what was used at one of the sites I was referencing (listed below).&lt;br /&gt;&lt;br /&gt;pw groupadd -n Proftpd -g 5500&lt;br /&gt;pw useradd proftpd -u 5500 -g Proftpd -s /sbin/nologin -d /dev/null -c "Proftpd User"&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;3.&lt;/span&gt;&lt;span style="font-size:130%;"&gt; Create the mySQL database&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;create database proftpd;&lt;br /&gt;grant all on proftpd.* to 'proftpd'@'localhost' identified by 'password'&lt;br /&gt;&lt;br /&gt;( change 'password' to something secret! )&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;4.&lt;/span&gt;&lt;span style="font-size:130%;"&gt; Create the mySQL tables for the users &amp; quota&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;create table proftpdUsers (&lt;br /&gt;&lt;br /&gt; sqlUID int unsigned auto_increment not null,&lt;br /&gt; userName varchar(30) not null unique,&lt;br /&gt; passwd varchar(80) not null,&lt;br /&gt; uid int unsigned not null unique,&lt;br /&gt; gid int unsigned not null,&lt;br /&gt; homedir tinytext,&lt;br /&gt; shell tinytext,&lt;br /&gt; primary key(sqlUID)&lt;br /&gt;&lt;br /&gt;) ;&lt;br /&gt;&lt;br /&gt;create table proftpdGroups (&lt;br /&gt;&lt;br /&gt; sqlGID int unsigned auto_increment not null,&lt;br /&gt; groupName varchar(30) not null unique,&lt;br /&gt; gid int unsigned not null unique,&lt;br /&gt; members tinytext,&lt;br /&gt; primary key(sqlGID)&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;CREATE TABLE proftpdQuotaLimits (&lt;br /&gt;name VARCHAR(30),&lt;br /&gt;quota_type ENUM("user", "group", "class", "all") NOT NULL,&lt;br /&gt;per_session ENUM("false", "true") NOT NULL,&lt;br /&gt;limit_type ENUM("soft", "hard") NOT NULL,&lt;br /&gt;bytes_in_avail FLOAT NOT NULL,&lt;br /&gt;bytes_out_avail FLOAT NOT NULL,&lt;br /&gt;bytes_xfer_avail FLOAT NOT NULL,&lt;br /&gt;files_in_avail INT UNSIGNED NOT NULL,&lt;br /&gt;files_out_avail INT UNSIGNED NOT NULL,&lt;br /&gt;files_xfer_avail INT UNSIGNED NOT NULL&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;CREATE TABLE proftpdQuotaTallies (&lt;br /&gt;name VARCHAR(30) NOT NULL,&lt;br /&gt;quota_type ENUM("user", "group", "class", "all") NOT NULL,&lt;br /&gt;bytes_in_used FLOAT NOT NULL,&lt;br /&gt;bytes_out_used FLOAT NOT NULL,&lt;br /&gt;bytes_xfer_used FLOAT NOT NULL,&lt;br /&gt;files_in_used INT UNSIGNED NOT NULL,&lt;br /&gt;files_out_used INT UNSIGNED NOT NULL,&lt;br /&gt;files_xfer_used INT UNSIGNED NOT NULL&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;5.&lt;/span&gt; &lt;span style="font-size:130%;"&gt;Add a test user to the proftpd database&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;(assumes /home/ftp is where you keep your ftp users. Otherwise, change the homedir location). This is certainly not a necessary step, but you should probably check to see if your configuration is working. You can delete this user later.&lt;br /&gt;&lt;br /&gt;insert into proftpdUsers values ( 0, 'test', 'test', 5500, 5500, '/home/ftp/test', '/sbin/nologin' );&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;6. &lt;/span&gt;&lt;span style="font-size:130%;"&gt;Set your proftpd configuration to use the mySQL authentication and quotas:&lt;/span&gt;&lt;br /&gt;(NOTE: this is not a complete configuration file, it's basically just the default config file with mySQL auth &amp; quotas added, but note that the User and Group directives are the user &amp;amp; group we added in step 2. )&lt;br /&gt;&lt;br /&gt;MaxInstances            30&lt;br /&gt;&lt;br /&gt;# Set the user and group under which the server will run.&lt;br /&gt;User                proftpd&lt;br /&gt;Group                Proftpd&lt;br /&gt;&lt;br /&gt;# To cause every FTP user to be "jailed" (chrooted) into their home&lt;br /&gt;# directory, uncomment this line.&lt;br /&gt;DefaultRoot ~&lt;br /&gt;&lt;br /&gt;# Normally, we want files to be overwriteable.&lt;br /&gt;AllowOverwrite        on&lt;br /&gt;&lt;br /&gt;# Bar use of SITE CHMOD by default&lt;br /&gt;&amp;lt;Limit SITE_CHMOD&amp;gt;&lt;br /&gt;DenyAll&lt;br /&gt;&amp;lt;/Limit&amp;gt;&lt;br /&gt;&lt;br /&gt;DenyAll&lt;br /&gt;&lt;br /&gt;# The passwords in MySQL are encrypted using CRYPT&lt;br /&gt;SQLAuthTypes            Plaintext Crypt&lt;br /&gt;SQLAuthenticate         users* groups*&lt;br /&gt;&lt;br /&gt;# used to connect to the database&lt;br /&gt;# databasename@host database_user user_password&lt;br /&gt;SQLConnectInfo  proftpd@localhost proftpd yourdatabasepassword&lt;br /&gt;&lt;br /&gt;# Here we tell ProFTPd the names of the database columns in the "usertable"&lt;br /&gt;# we want it to interact with. Match the names with those in the db&lt;br /&gt;SQLUserInfo     proftpdUsers userName passwd uid gid homedir shell&lt;br /&gt;&lt;br /&gt;# Here we tell ProFTPd the names of the database columns in the "grouptable"&lt;br /&gt;# we want it to interact with. Again the names match with those in the db&lt;br /&gt;SQLGroupInfo    proftpdGroups groupName gid members&lt;br /&gt;&lt;br /&gt;# set min UID and GID - otherwise these are 999 each&lt;br /&gt;SQLMinID        5000&lt;br /&gt;&lt;br /&gt;#============&lt;br /&gt;# User quotas&lt;br /&gt;# ===========&lt;br /&gt;QuotaEngine on&lt;br /&gt;QuotaDirectoryTally on&lt;br /&gt;QuotaDisplayUnits Mb&lt;br /&gt;QuotaShowQuotas on&lt;br /&gt;&lt;br /&gt;# create a user's home directory on demand if it doesn't exist&lt;br /&gt;SQLHomedirOnDemand on&lt;br /&gt;&lt;br /&gt;SQLNamedQuery get-quota-limit SELECT "name, quota_type, per_session, limit_type, bytes_in_avail, bytes_out_avail, bytes_xfer_avail, files_in_avail, files_out_avail, files_xfer_avail FROM proftpdQuotaLimits WHERE name = '%{0}' AND quota_type = '%{1}'"&lt;br /&gt;&lt;br /&gt;SQLNamedQuery get-quota-tally SELECT "name, quota_type, bytes_in_used, bytes_out_used, bytes_xfer_used, files_in_used, files_out_used, files_xfer_used FROM proftpdQuotaTallies WHERE name = '%{0}' AND quota_type = '%{1}'"&lt;br /&gt;&lt;br /&gt;SQLNamedQuery update-quota-tally UPDATE "bytes_in_used = bytes_in_used + %{0}, bytes_out_used = bytes_out_used + %{1}, bytes_xfer_used = bytes_xfer_used + %{2}, files_in_used = files_in_used + %{3}, files_out_used = files_out_used + %{4}, files_xfer_used = files_xfer_used + %{5} WHERE name = '%{6}' AND quota_type = '%{7}'" proftpdQuotaTallies&lt;br /&gt;&lt;br /&gt;SQLNamedQuery insert-quota-tally INSERT "%{0}, %{1}, %{2}, %{3}, %{4}, %{5}, %{6}, %{7}" proftpdQuotaTallies&lt;br /&gt;&lt;br /&gt;QuotaLimitTable sql:/get-quota-limit&lt;br /&gt;QuotaTallyTable sql:/get-quota-tally/update-quota-tally/insert-quota-tally&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Thanks to the following sites for being great references, and providing most of the information in this post:&lt;br /&gt;&lt;a href="http://www.khoosys.net/single.htm?ipg=848"&gt;http://www.khoosys.net/single.htm?ipg=848&lt;/a&gt;&lt;br /&gt;&lt;a href="http://www.castaglia.org/proftpd/modules/mod_quotatab_sql.html"&gt;http://www.castaglia.org/proftpd/modules/mod_quotatab_sql.html&lt;/a&gt;&lt;br /&gt;         &lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/9135439-110102148362093383?l=jimkeller.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimkeller.blogspot.com/feeds/110102148362093383/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=9135439&amp;postID=110102148362093383' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/110102148362093383'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/9135439/posts/default/110102148362093383'/><link rel='alternate' type='text/html' href='http://jimkeller.blogspot.com/2004/11/proftpd-mysql-authentication-quotas-on.html' title='Proftpd mySQL Authentication &amp; Quotas on FreeBSD'/><author><name>Jim Keller</name><uri>http://www.blogger.com/profile/04379106179273487649</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry></feed>
