2011-11-10

How to manage iOS Newsstand subscriptions auto downloads

The screen where you manage automatic download settings for subscriptions in the new iOS  Newsstand is hard to find (at least it was for me!) Here is how to get to it on your iPad, iPod Touch, or iPhone.

  • Tap on the Settings app to get to the Settings screen
  • Scroll down list of settings until you see the icon for the Store and tap that
  • There will be a section of the Store settings called "Automatically download new content when on Wi-Fi" and all of your Newsstand subscriptions should be listed there with an on/off slider for each one.
Incidentally, the wording of this screen seems to imply that automatic downloads of subscriptions only happen when on Wi-Fi (i.e. not on cellular), which I hope is true because one issue of the New Yorker would blow more than half my cheapskate 250 MB cellular plan.


2011-11-02

Save money using Google Voice plus a prepaid cellphone

A lot of prepaid mobile phone plans charge you only for the minutes you actually use, and if you don't use a lot of minutes each month it can save you a lot of money (I spend on average less than $10 a month on my T-Mobile prepaid service).  If you get a Google Voice account, and set it up to ring not only your prepaid cell phone but also your home and office phones, then you can give everyone your Google Voice number as your mobile phone number, but answer calls to it on your house or work phone when you are not on the road.

2011-10-11

How to prevent sessions from expiring too quickly on PHP applications


When I started using sessions for authentication in a PHP web application I discovered that users were being logged out after a very short time.  After some research I discovered how sessions work in PHP.
A unique session ID is stored on a cookie on the users computer.  When the user connects to the server the server looks for a file with that unique session ID in its temp folder, and if one exists it pulls variable values from that file.  Every time any user connects to the server and starts a session the server generates a random number and then looks at some settings in php.ini to determine whether or not to clean out the temp folder. When it cleans out the temp folder it throws out session files older than the session.gc_maxlifetime setting in php.ini (set in seconds).  In order to enable keeping users logged in for long periods I set session.gc_maxlifetime = 1814400 in the php.ini.

This works on MAMP installations also. Just edit to /Applications/MAMP/conf/php5.3/php.ini

How to configure Apache in MAMP to only be accessible from the machine it is installed on

For a number of reasons I wanted to run a web application locally on my MacBook.  After some research I decided that the path of least resistance was to install the free MAMP app, which runs an Apache-MySQL-PHP stack on a Max OS X machine without having to do a complex install process.   I got my web app working on MAMP pretty easily, but then I discovered that other machines on my local network could also access the web app if they connected to the right port of the IP address of my MacBook.  That isn't a problem when I am at home or work, but I didn't want the web app exposed when I was on public networks like at a coffee house or the library.  After some research I discovered the solution was to edit the /Applications/MAMP/conf/apache/httpd.conf file to change:

Listen 8888

to

Listen 127.0.0.1:80

This did two things. It changed the port that MAMP uses from the MAMP default of 8888 to 80 (the normal web server port) and specified that only traffic from the local machine would be accepted.  After I made this change and restarted the Apache server I was no longer able to access the web app from other machines on the same network.

2011-01-25

How to automatically mount external hard drive at boot in Ubuntu Lucid Lynx

I have an external USB hard drive connected to my Ubuntu laptop home server that I used to store MythTV recordings and videos.  Ubuntu would mount this USB drive to the mount point I had created when I logged in as my regular user, but not at boot.  I wanted the drive to automatically mount at boot so that after a power failure, or a reboot, I wouldn't have to log in as a user in order to have MythTV work.  After doing some research using Google I tried the following:

  • After logging in as my regular user to make the USB drive mount, I ran mount in a terminal which gave me a listing of all the mounted disks and their mount settings.  I copied down the mount settings for the USB drive and noted which device the USB drive was mounted as (sdb1 in my case).
  • Then I ran ls -l /dev/disk/by-uuid which gave me a listing of the UUID of each mounted partition and I copied down the UUID for the sdb1.  This is the unique identifier for the USB drive.
  • Then I made a backup copy of the fstab file: sudo cp /etc/fstab /etc/fstab.bak
  • Then I opened the fstab file in an editor: sudo nano /etc/fstab
  • Then I added a line to the bottom of the fstab file like this, putting a tab between each value, and using the UUID I noted, the filesystem type I noted, and the mount options I noted:
  • UUID=07955830-0d54-443c-bdba-f111121f6bd3   /media/myth_data    ext4    rw,nosuid,nodev,uhelper=udisks 
  • Then I rebooted and the disk was automatically mounted without me having to log in.

2011-01-03

A Mac OS X app to wake a Mac and then open screen sharing

I have a Mac Mini hooked up to our TV and stereo as a HTPC which has a hard-wired Ethernet connection. To save a bit of energy I have it set to go to sleep when it isn't used. My wife wanted to be able to play internet radio stations over the stereo, but she didn't want to have to turn on the TV, get a keyboard, etc. just to start one playing. I showed her how to use one program to wake the Mac Mini, and then how to launch screen sharing through Finder, but it was a lot of clicking. So I started googling for a way to set up a one-click icon that would (1) wake the Mac Mini, and (2) then connect to it by screen sharing.

As to waking the Mac Mini, I found this page that gives an Automator workflow to send the wake on lan magic packet (which will only work if the target computer has a hard wired Ethernet connection) and also open screen sharing:

Wake sleeping Mac with AppleScript and Automator

The guts of this is a PHP script written by Mark Muir to send the magic packet that wakes the sleeping computer. The version of this PHP script at the link has comments explaining how it works. Since I have zero experience with Automator I decided to adapt this to be an AppleScript. Here is what I ended up with:

on run
set command to "/usr/bin/php -r " & quoted form of ("$mac = \"a4:6a:19:d8:d2:24\";
$ip = \"10.10.10.255\";
$mac_bytes = explode(\":\", $mac);
$mac_addr = \"\";
for ($i=0; $i<6; $i++)
$mac_addr .= chr(hexdec($mac_bytes[$i]));
$packet = \"\";
for ($i=0; $i<6; $i++) /*6x 0xFF*/
$packet .= chr(255);
for ($i=0; $i<16; $i++) /*16x MAC address*/
$packet .= $mac_addr;

$port = 9;
$sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
socket_set_option($sock, SOL_SOCKET, SO_BROADCAST, TRUE);
socket_sendto($sock, $packet, strlen($packet), 0, $ip, $port);
socket_close($sock);
")
do shell script command
delay 3
tell application "Screen Sharing"
open location "vnc://MyComputersBonjourName.local"
end tell
end run


To use this yourself do the following:

  • Make sure screen sharing is configured on the target computer and verify it works when you manually connect from the source computer.
  • Go to the target computer and open Network Utility and look up the Hardware Address of the target computers Ethernet network interface. Change the $mac value in the Applescript to be this value.
  • Look up the Bonjour name of the target computer (System Preferences -> Sharing : Computer Name) and substitute it for MyComputersBonjourName in the Applescript.
  • Change the $ip value in the Applescript to be the IP address of your router with 255 substituted for the last digits. For many people this would be 192.168.1.255. Also, 255.255.255.255 should work.
  • Open AppleScript Editor on the source computer (the one you want to connect from) and paste the script in it and then click Compile and then click Run to test it. It should wake the target computer and open screen sharing for it.
  • Save the AppleScript as an app by selecting File -> Save As and then picking application as the file type in the dialog box.
  • Double click on the app to verify it works.
  • Drag the app to the Dock or wherever you want it to live.

2010-12-15

How to run a Python script from Mac OS X Finder

Here is how to run a Python script from the Finder:

  • Make this the first line of your Python script "#!/usr/bin/env python"
  • Change the extension of the script file to ".command" i.e. my_python_script.command
  • In Terminal make the Python script file executable by running "chmod +x my_python_script.command"
  • Now when you double click the Python script in Finder it will open a terminal window and run.

2010-12-08

Checklist for making a home file, print and web server using Ubuntu and an old laptop

A couple years ago I did a detailed set of posts on turning an old laptop into a Ubuntu file, print, and web server. That laptop started having hardware problems so I decided to decommission it and replace it with another recently retired Dell Inspiron 1521 laptop. Here is a checklist of all the steps I did to convert the Windows Dell Inspiron 1521 laptop into a home file, print, and web server. If you are new to Linux you should read my original posts since this checklist assumes you have done stuff like this before.

  • Downloaded and burned CD for Ubuntu LTS 10.04 standard desktop
  • Installed Ubuntu standard desktop 10.04 on Inspiron 1521. I followed the prompts and used the default options.
  • Wrote over existing Windows file system rather than doing dual boot.
  • After Ubuntu was installed I started Synaptic and updated packages
  • Installed openssh-server and samba using Synaptic
  • Opened terminal and ran sudo tasksel to start program that offers a number of options for installing groups of packages for different functions.
  • Selected install LAMP server and followed prompts to install.
  • Using Firefox on new server I went to truecrypt website, downloaded latest Linux version (7a) to the Desktop, extracted file, double clicked it, and followed the prompts to install Truecrypt.
  • Edited /etc/network/interfaces (sudo nano /etc/network/interfaces) to make computer use fixed IP address by adding:
auto eth0
iface eth0 inet static
address 10.10.10.123
netmask 255.255.255.0
gateway 10.10.10.1
  • Rebooted new server to put new static IP address in effect
  • sudo aptitude install ntp (ntpdate was already installed)
  • Set up mount points for encrypted external hard drives:
  • sudo mkdir /media/encrypted1
  • sudo mkdir /media/encrypted2
  • Changed owner of mount points to match what smb.conf will use for them
  • sudo chown nobody:nogroup /media/encrypted1
  • sudo chown nobody:nogroup /media/encrypted2
  • Created directory for my custom scripts for new server: sudo mkdir /home/andy/scripts
  • Mounted new server as volume on remote MacBook using MacFusion (SSHFS)
  • Copied backup of custom scripts over to new server from MacBook
  • Checked ownership and permissions of custom scripts and changed them as needed.
  • Ran my custom script to mount truecrypt volumes (/media/encrypted1 etc)
  • Made these changes to /etc/samba/smb.conf (sudo nano /etc/samba/smb.conf)
  • workgroup = MYWORKGROUP
  • removed semi-colon before interfaces = 127.0.0.0/8 eth0
  • removed # before security = user
  • Added these under Share Definitions
[public]
comment = Public Share
path = /media/encrypted1
read only = No
force create mode = 777
force directory mode = 777
force user = nobody

[backup]
comment = Public Share
path = /media/encrypted2
read only = No
force create mode = 777
force directory mode = 777
force user = nobody

[pictures]
comment = Public Share
path = "/media/encrypted1/Documents/My Pictures"
read only = Yes
guest only = Yes
guest ok = Yes
  • sudo adduser spouse
  • sudo smbpasswd -a andy
  • sudo smbpasswd -a spouse
  • Rebooted new server
  • Ran custom script for mounting truecrypt volumes
  • Mounted samba share from remote MacBook and opened file to make sure everything was working properly.
  • sudo aptitude install phpmyadmin
  • From remote MacBook browsed to http://10.10.10.123/phpmyadmin and logged in as user root
  • Using phpmyadmin created a new user and gave this user all possible privileges on the MySQL database.
  • sudo adduser www
  • sudo nano /etc/apache2/sites-available/default
  • Changed the DocumentRoot value to /home/www/public and saved the file
  • Restarted Apache: sudo services apache2 restart
  • From remote MacBook did SSHFS mount as user www using MacFusion
  • Copied all html/php files from backup to /home/www
  • Used phpMyAdmin to create new user web_app_user with all data (but not structure or database administration) privileges. This is the MySQL user used by the web apps.
  • Used phpmyadmin to create new MySQL databases with same names as were used on the old server (including wordpress).
  • Used phpmyadmin to go into each database and then import the SQL file backup of that database.
  • Edited /etc/php5/apache2/php.ini to change session.gc_maxlifetime = 1440 to 1814400 (this prevents web app users from being logged out of web app shortly after they sign in)
  • Edited blank /etc/apache2/httpd.conf to add "ServerName myserversname". This prevents the annoying "Could not reliably determine the server's fully qualified domain name" message when you start apache2.
  • Restarted apache: sudo service apache2 restart
  • Tested that web applications work.
  • Got crontab setups from old server by doing contab -e as all users that had crontabs and writing down what was there.
  • Changed permissions on custom scripts that would be run by different users via crontab: chmod a+rwx scriptname.sh
  • Created logs directory in /home/andy: chmod -R a+rwx logs
  • Tested all backup scripts on new server.
  • Setup crontabs on new server using crontab -e
  • Configured CUPS for printer (not sure all of this is necessary)
  • sudo nano /etc/cups/cupsd.conf
  • In , and and
  • add:
  • Allow all
  • Commented out "Require user @OWNER @SYSTEM" where ever it appeared except for administrative tasks.
  • Changed:
  • Listen localhost:631
  • to:
  • Listen 631
  • Added the line: DefaultEncryption Never
  • sudo service cups restart
  • Browsed to 10.10.10.120:631 from remote machine
  • Add printer
  • Select HP LaserJet 1012 (HP LaserJet 1012), not the 1012 printer with USB in the name.
  • Name: HP_Shed
  • Description: HP LaserJet 1012
  • Location: Shed
  • Select Model: HP LaserJet 1012 - CUPS+Gutenprint v5.2.5(en)
  • Select default default options
  • Select modify printer just created
  • Click Select Another Make/Manufacturer
  • Select Make: Raw

2010-11-30

Specifying different CSS for landscape and portrait orientations on the iPad

I have a web app that uses jQuery UI buttons. I noticed that on my iPad the buttons worked fine in portrait orientation, but in landscape orientation the buttons, and even regular links in a table, would not work properly when pressed (a different button or link than the one pressed would fire). I determined that this problem was caused by the following tag in my HTML:

<meta name='viewport' content='width=device-width' />

Using this tag causes Safari Mobile (the iPad browser) to zoom in on the page a bit in landscape orientation, making the fonts a bit bigger, which would be fine except that it apparently breaks link and button functionality in some cases. I got the buttons and links to work properly by using this tag instead:

<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no' />

This locks down the page so it doesn't (and can't) zoom in or out.

That fixed my broken buttons and links in landscape orientation, but I had really liked the way the fonts got bigger in landscape orientation. It gave me a way to have two different zoom levels in may app. If I wanted to see more content in a small font I used portrait; if I wanted to see less content in a bigger font I used landscape.

So, I figured out how to get the exact same effect using CSS "media queries." Here is some CSS that first sets styles for the iPad regardless of orientation and then specifies different styles for portrait and landscape orientation:

@media only screen and (max-device-width: 1024px)
{/* This block specifies CSS that only applies to an iPad
max-device-width: 1024px seems to only select the iPad*/

body
{margin-left: 0px;
margin-top: 0px;}

}

@media only screen and (max-device-width: 1024px) and (orientation: portrait)
{/* This block provides CSS that only applies to the iPad in portrait orientation */

body
{font-size: 16px;}

}

@media only screen and (max-device-width: 1024px) and (orientation: landscape)
{/* This CSS kicks in only when an iPad is in landscape mode. It makes the font bigger, the table rows taller, etc */

body
{font-size: 18px;}

}


You can put this block of CSS at the end of the regular stylesheet for a page and it will apply different CSS when the page is viewed on the iPad, allowing you to have a completely different layout on the iPad than you have on a regular browser. What is remarkable to me is how smoothly the styles change when I switch orientation on the iPad; I was afraid there would be lag while the page reformatted but I can't detect any.

2010-11-21

How to avoid typing the full path of a file in Mac OS X

Occasionally in Mac OS X I want to write the full path of a file in a Bash script or something. To avoid typing out the full path (and possibly making errors) just:

  • Go to the file in Finder.
  • Hit Cmd-I to bring up the Get Info window for the file.
  • Block and copy the path from "General > Where:" section of the Get Info box into your text file.
  • Block and copy the file name from the Get Info box.

2010-11-06

Workaround for jQuery .live() event handler not working on Mobile Safari, iPad, iPod Touch, iPhone

I have a web app where I use the jQuery .live() method to attach a click event handler to a table td element (to convert it into a input field when the user clicks the table cell and then post the input using ajax.)  Everything worked properly on all the mainstream browsers (Firefox, Safari, Chrome) but nothing happened when I tapped the table cell when viewing the app on my iPad.  Some Googling led me to this blog post:

jQuery’s live click handler on mobile Safari

This guy discovered that the .live() click event will fire on Mobile Safari (i.e. iPad, iPod Touch, iPhone) when it is attached to certain elements (like an anchor tag), but for some other elements it will only fire if you add onclick="" to the element tag.

I added onclick="" to my td tag for my table cell and voila, tapping on the table cell works now to convert it to an input field like it should.

2010-11-04

The lineage model for hierarchal data in a SQL database

Recently I decided to make a web app to keep track of a number of policies.  There were a number of different policies, all of which had their provisions numbered and organized in an outline hierarchy that often went three or more layers deep.

The first thing I researched was how to structure SQL databases to handle hierarchical data like a product catalog with the products grouped into categories and subcategories. Or a org chart where employees are grouped into divisions and departments. I quickly learned that most people used either the adjacency list model or the nested set model,  both of which are described in detail in an article on the MySQL website: Managing Hierarchical Data in MySQL.  Another detailed article on both models is Storing Hierarchical Data in a Database.

With the adjacency list model you have a table which gives each record a unique ID number, and then you have a "Parent ID" field where you record the ID number of the record's parent in the hierarchy tree.  If you were doing foods, you would have a record for "apples" and the parent ID for that record would be "fruits."  Then when you need to generate an output of the whole hierarchy tree you do SQL that organizes all the records by connecting the various parent ID values.  Some articles I found on this approach include Tree Drawing with the Adjacency List Model  and Hierarchical SQL.  The thing I didn't like about the adjacency list model was that the SQL required to generate a simple display of the data seemed unduly complicated (lots of recursion) and likely to consume a lot of database server processing power.

I never did really grasp the nested set model, so I won't try and explain it. Needless to say it also involved very complicated SQL that seemed likely to put a big drain on a server.

Then I found an article about a variation on the adjacency list model: More Trees and Hierarchies in SQL.  The author's basic idea was to include a Lineage field in each record where you record the ID numbers of the full path to the record in the hierarchy with each ID number separated by a delimiter character.  For example, if you had a product tree with the iPod in it, the Parent ID field would be "MP3 Players" and the Lineage field would be "Electronics/Audio Equipment/MP3 Players."  This makes the SQL to output the basic tree much easier: you just do a simple SELECT and ORDER BY the Lineage field and you are done.

So here is the structure I ended up with using the lineage field model.  For a hierarchy like this:

  • Plants
    • Fruits
      • Apples
      • Bananas
      • Peaches
    • Vegetables
      • Broccoli
      • Asparagus
  • Animals
    • Poultry
      • Chicken
      • Turkey
    • Meat
      • Beef
      • Pork
The table structure would be like this (except you would use ID numbers instead of the full name of each item as the ID):

Node Lineage
Plants /
Animals /
Fruit /Plants/
Vegetables /Plants/
Poultry /Animals/
Meat /Animals/
Apples /Plants/Fruits/
Bananas /Plants/Fruits/
Peaches /Plants/Fruits/
Chicken /Animals/Poultry/
Turkey /Animals/Poultry/
Beef /Animals/Meat/
Pork /Animals/Meat/
Broccoli /Plants/Vegetables/
Asparagus /Plants/Vegetables/

Once you put your hierarchical data into a structure like this then working with it is easy-peasy. Want to display the whole tree?

SELECT * FROM table_Foods ORDER BY concat(Lineage, Node)

This will output the data organized by groups and subgroups. Want to see just the items in the group Plants, organized by subgroups?

SELECT * FROM Foods WHERE concat( Lineage, Node ) LIKE '/Plants%' ORDER BY concat( Lineage, Node )

This will return just the Plants, grouped by Fruits and then by Vegetables.

Want to indent each item in your output based on its depth in the hierarchy? In your PHP code just count the number of slashes in the Lineage field using substr_count() for each record and set the indent accordingly.

I know this approach seems too simple but I have been working with it for a while in a web application and I haven't run into any dead ends yet, and figuring out the SQL for various tasks has been very easy.

2010-10-28

How to make jQuery UI buttons shorter

I have been using jQuery and jQuery UI for a while now and they make it really easy to add fancy features and layout to a web app. However, one thing I didn't like was that the default appearance jQuery UI buttons is too tall for my taste. The jQuery buttons have lots of space above and below the button text, making them look square and blocky to me.

After playing around with it for a while I figured out how to adjust the height of jQuery UI buttons. Simply add the following to your CSS style sheet:

.ui-button-text-only .ui-button-text
{ padding-top: .1em;
padding-left: 1em;
padding-right: 1em;
padding-bottom: .2em; }

Just tweak the top and bottom padding until you like the way the buttons look. I had to make my bottom padding bigger than my top padding to make the button text look vertically centered.

I also had some buttons with little GIF images instead of text. jQuery had the images jammed up against the top of the button by default. To center the images I added this to my CSS stylesheet:

img {margin-bottom: -.25em;}

I could get away with just changing my img tag styles because these little GIFs were the only img tags on the whole page. If you have other img tags on your page you would need to give the button img tags their own class and apply this style to just that class.

2010-10-26

Google Chrome is particular about syntax of script tags

I was tearing my hair out because my jQuery UI code was working properly with Firefox and Safari but not with Google Chrome. When I looked at the Javascript console in Google Chrome I was getting an error like this:

Uncaught TypeError: Object # has no method 'datepicker'

Because it was working in Safari and Firefox, but not in Google Chrome, and because the error message sounded like some problem in the jQuery code and/or my other Javascript I spent hours trying to troubleshoot the problem. However, in the end the problem turned out to be with my script tags to reference the jQuery libraries:

<script src="/jquery-ui-1.8.5.custom/js/jquery-1.4.2.min.js" type="text/javascript"</script>

<script src="/jquery-ui-1.8.5.custom/js/jquery-ui-1.8.5.custom.min.js" type="text/javascript"</script>


Do you see the error in my syntax? I left off the right angle bracket after the type="text/javascript". For some reason Firefox and Safari don't care about that, but Google Chrome does. To make it even odder, Google Chrome didn't give me an error about problems loading a script, but instead an error about the execution of the script.

2010-10-22

Resources about designing web content for iOS devices (iPhone, iPod, iPad)

Apple has a thorough guide to developing web content for Safari on the iPhone, presumably most of it also applies to the iPad:

[iOS] Safari Web Content Guide

I haven't read all of this article yet but the title is promising:

The iPad Web Design and Development Toolbox

How to turn off php magic quotes on nearlyfreespeech.net

I recently set up a web app hosted on nearlyfreespeech.net that uses mysql_real_escape_string to to escape user input. I noticed the other day that an entry in that web app displayed an escaped single quote, which led me to suspect that nearlyfreespeech.net has PHP magic quotes turned on and my user input was being double escaped, which turned out to be correct. Here is how I turned off magic quotes for web app on nearlyfreespeech.net:

  • I connected to my nearlyfreespeech.net account via SSH.
  • I navigated to the public directory of my account (where the html and php files are).
  • At the command prompt I ran nano .htaccess to create a .htaccess file and open it in nano for editing
  • I put this in the .htaccess file: php_flag magic_quotes_gpc off
  • I saved the file, and voila, magic quotes were off!

2010-10-21

How to specify different CSS rules based on device screen size (i.e. for the iPad)

I was working on a web app where I wanted the appearance to be very different on my iPad than it was on my laptop. After some research I found this article:

Detecting device size & orientation in CSS

That describes how you can include code in a CSS stylesheet that will apply designated rules only if the device screen (or browser viewing area) meets certain criteria. For example (from the article):

@media only screen and (max-width: 999px) { /* rules that only apply for canvases narrower than 1000px */}

@media only screen and (device-width: 768px) and (orientation: landscape) { /* rules for iPad in landscape orientation */}

@media only screen and (min-device-width: 320px) and (max-device-width: 480px) { /* iPhone, Android rules here */}


CSS put inside the brackets in these examples would only apply if the specified criteria are met. Also, the CSS rules apparently work on the fly, so if the user resizes their browser screen the appropriate rules will be applied without refreshing.

I also found a reference article on these "media queries" for Firefox. Much of what is in this article applies to other browsers since I believe it is derived from the CSS 3 specification.

Media Queries

Note that this article only uses these media queries to select an alternate style sheet but as far as I can tell you can also use them to apply a block of rules enclosed in curly brackets after the query as shown in the example above and the first article.

There is also an Apple article that includes a section on conditional CSS:

Optimizing Web Content

After doing some experimenting, it appears that once a CSS property is specified the only way to remove it using a media query is to specifically specify a new value for the property. In other words, if you specify a bunch of properties for the ".main" class in your style sheet, and then in your media query block just specify:

@media only screen and (max-width: 999px) { .main {;}}

Then all the rules you specified for .main are still going to apply even when the media criteria is met because they were not individually countermanded.

I experimented around with different media queries to distinguish between my iPad and my laptop and in the end this seemed to work so that the rules were only applied on my iPad, and the rules were applied regardless of my iPads orientation:

@media only screen and (max-device-width: 1024px)
{/*max-device-width: 1024px seems to only select the iPad*/}

2010-10-20

Escaping square brackets in SQL Server queries from PHP

I have a PHP web app that pulls data from an SQL Server database using the ODBC functions. Earlier I come up with a PHP function to escape single quotes in user input by adding a single quote to each single quote (a single quote is the character you use to escape a single quote) as defense against (inadvertent) SQL injection (the web app is behind a firewall).

The other day I accidentally discovered that including text inside a pair of square brackets [like this] in user input that was added to a LIKE clause of a WHERE clause resulted in a huge data dump being returned by SQL Server. I did some Googling and discovered that characters enclosed in square brackets have some special meaning in SQL Server (I don't remember what it was). I first tried updating my PHP function to escape square brackets with single quotes, but that didn't work for some reason. Then I did some more research and discovered that the way to escape a square bracket in SQL Server is to enclose it in square brackets like this [[]. So I updated my PHP function to do this on user input and it worked to stop the data dumps when a user included something like [fred] in their input.

Writing the PHP to do this was tricky because if you just do a straight str_replace on each square bracket the second replace replaces some of the square brackets you added with the first replace and messes it all up. The way I solved this was to write my function to:

  • first replace the left square bracket with an arbitrary three character string that is unlikely to be in user input,
  • then replace on the right square bracket with []],
  • then do a third replace of my arbitrary three character string with [[].
And yes, I know I should be using stored procedures etc, and hackers can get past any escaping routine, etc, but this app is behind a firewall and I am only concerned about accidental SQL injection.

2010-10-03

SQL and sequences

I am working on an application that uses a sequence field to keep records in a specific order.

The first challenge was how to fill in a numbered sequence in the new sequence field without using auto-increment (the data table will contain a number of independent sequences for different sub-sets of records so I need to be able to multiple sequences in the same table).  I found the answer on this blog post:

How to Sequence each Sub-set of Records by David Soussan

I won't repeat that post here, but here is the SQL that I ended up with based on Mr. Soussan's technique.

This first SQL query creates a Temp table with Prov_ID and sequence number.

CREATE TABLE Temp
SELECT
    t1.Prov_ID, COUNT(t1.Prov_Sort_Num) AS sequence,
    t1.Prov_Sort_Num >= t2.Prov_Sort_Num AS flg
FROM
    tbl_Provisions AS t1
INNER JOIN
    tbl_Provisions AS t2 ON t1.Doc_ID = t2.Doc_ID
WHERE
    t1.Doc_ID = 1
GROUP BY
    t1.Doc_ID,
    t1.Prov_Sort_Num, flg
HAVING
    flg = TRUE
ORDER BY
    t1.Prov_Sort_Num

Then this second SQL query updates tbl_Provision using the sequence numbers from the newly created Temp table.
UPDATE
    tbl_Provisions AS t1
JOIN
    Temp AS t2 ON t1.Prov_ID = t2.Prov_ID
SET t1.Prov_Sequence = t2.sequence

The next issue was how to make sure that gaps and duplicates didn't end up in the sequence for each sub-set of records?  I found the solution to the issue of detecting gaps in this blog post:

Sequence gaps in MySQL by Sameer

I still don't fully understand how Sameer's SQL works, but it does indeed work reliably.  Here is the SQL I ended up with based on Sameer's technique:

SELECT
    a.Prov_Sequence + 1 AS start,
    MIN(b.Prov_Sequence) - 1 AS end
FROM
    tbl_Provisions AS a,
    tbl_Provisions AS b
WHERE
    a.Prov_Sequence < b.Prov_Sequence
   AND
      a.Doc_ID=$Doc_ID
   AND
      b.DOC_ID=$Doc_ID
GROUP BY
   a.Prov_Sequence
HAVING
   start < MIN(b.Prov_Sequence)
This returns a two column table with each row giving the beginning of a gap in the Start column and the end of that gap listed in the End column.

2010-08-07

PDFsam works for merging scans of double sided documents

Occasionally I need to scan a double sided document. For a short documents (1-4) I just scan the fronts of the pages and then the backs and then manually put them together using Preview in Mac OS X.  However for longer documents the free PDF Split and Merge, or PDFsam, quickly merges these types of scans:

http://www.pdfsam.org/