Tech Blog :: Tech Blog

Tech Blog


Jul 25 '10 10:28am
Tags

DrupalCampNYC8 Session: A Developer's Arsenal of Productivity Hacks

I'll be presenting a session at DrupalCampNYC in an hour, "A Developer's Arsenal of Productivity Hacks." I'll be covering terminal fu (basics through Bash scripting), Mac tricks, and a bunch of miscellaneous doodads.

Slides are here (powered by S5 and Markdown). Thanks to Markdown they're also visible in a normal post mode.

Jul 24 '10 4:17pm

Database Sync Script with SSH and Drush

This is a script I wrote in 2009 and updated a few months ago, to automate the creation of Bash scripts for synchronizing a database from a remote server to a local server via SSH. It's a single PHP file which presents a form, you fill in the form with the connection and DB info on both sides, and it creates a .sh file that you run to synchronize. (This script does not synchronize anything itself.) The original script used mysqldump and asked for database credentials; the new version uses Drush, so it's only for Drupal but doesn't need DB credentials anymore. (And if you use an SSH key it doesn't need SSH credentials either.) You will need Drush on both sides for it to work.

I'm not hosting this script because I don't want to be responsible for the security of your server information. Instead download the .tar file below and host it on your own server. Check out the code of the .sh script that it creates before you run it so you understand exactly what's going on.

(I'll be mentioning this in my Developer's Arsenal of Productivity Hacks presentation tomorrow at DrupalCampNYC, so stay tuned for that!)

I welcome any and all bug reports, fixes, or other feedback.

Update: Someone pointed out correctly in the comments that Drush already has sql-sync, so I realized I need to explain what this does better. sql-sync makes a remote connection to a database and transfers directly between them. Most of the servers I've worked with, however, do not allow remote login to their database server (except maybe from a separate web server). All the MySql security recommendations I've read say the same thing. So this script will dump the database on the remote server via SSH (run locally on that server), zip the file, transfer it down, and import it. It also backs up both databases which Drush doesn't.
(If Drush does in fact do this, please let me know!)

Update 2: I stand corrected! Thanks to the commenters for enlightening me, Drush does in fact do this. I'll take the script off of here, since it doesn't add anything (except the form maybe). The method is:
drush sql-sync SRC DEST --source-remote-host=XX --source-dump=YY --target-dump=ZZ (and some other options available at drush help sql-sync).

Jul 20 '10 11:38pm
Tags

Need some mass email strategy tips

I'd like to pick your brains if you'll allow me, any ideas/tips/experiences about this would be most welcome:

One of the sites I'm working on needs some mass emailing functionality. The site will involve primarily user-generated content, with nodes representing events, all location-based. We'd like users to be able to subscribe to email notifications (probably daily or weekly digest) of every event created within a specified range from their zip code.

There are a bunch of different ways this could be done, but the main question I don't know much about is whether it's better to use standard Drupal modules like Notifications or Messaging, or better to integrate with a 3rd party mail service like MailChimp or Constant Contact. I imagine with the latter it's easier to send in bulk, less likely for mail to get filtered as spam, easier to track metrics and clickthroughs, etc... but maybe a lot more difficult to set up (or maybe not).

Has anyone done anything like this, where the mail that gets sent out is customized for each user? Any suggestions of how that might be done, or which services are best/worst?

In terms of the email-generation algorithm itself, I'm thinking there are a few ways...

  1. Use cron to run through every user with notifications enabled, and run a bunch of big queries (probably passing through a view with a location proximity filter) that generate each individual users' mail on the fly and send or queue it. Likely to be extremely processing-intensive and not sure if this can scale.
  2. Limit the options for mail to preset geographic areas, and when a node is created, populate the queue for that area's upcoming letter with a reference, so it's all ready to go.
  3. Variation on 2, whenever a node is created, see if any users are interested in being notified about it, and queue it for the user's mail, to be ready to go. (Not sure if the queries can be easily run in this direction.)

I'm mostly curious about the first question -- 3rd party vs pure drupal, and which services -- but if anyone has experience or suggestions about the algorithmic considerations, that would be awesome too.

Thanks!

Jul 20 '10 12:09am

Pseudo-Best Practices and Tool Creep

Someone wrote to me recently asking if I could help them set up a Drupal development environment for an outsourced dev team. I don't have time and declined the job, but continued a brief correspondence about the requested project requirements, which I thought were interesting:.

The requirements included (each a full installation of a 3rd party software package):

Whoa.

This is the list of tools that you'll find by googling "best practices development environment" or something like that. But it's massive tool creep. Several problems I see here:

  • Toolsets need to grow organically. Dumping 10 enormous tools on a brand new team (outsourced/remote/global, let alone in the same office) is like throwing a 1000 page HR Handbook on a new employee's desk.
  • Most of these can be [ironically] outsourced to 3rd party services. There are several excellent Git hosters for example; there's no need to host a repo yourself (unless you have some crazy security requirements).
  • Tons of redundancy. Both SimpleTest and Selenium can do client-side unit testing. SimpleTest also does server-side; ditch Selenium.
  • Each of these tools requires maintenance, upgrades, TLC. The team is hired to develop a product, not maintain other companies' software, why not focus on the essentials?
  • Capistrano is great. I suggested they use Webistrano, however, and write a 5-line Ruby recipe that tells Drush to run SimpleTest before deploying. No need for Hudson anymore.
  • Issue Tracking, Wiki, and Forum? I worked on a team with Jira and Basecamp and that was more than enough for critical details to get lost. "Did you see my message from last week?" - "Which tool did you put it in?" - "I think Basecamp, but the specs are in Jira, and the wireframes in DropBox, and the timesheet in Google Docs" ... recipe for confusion.
  • This is a Drupal project. I asked several of the best Drupal developers I know if they've heard of Hudson; they hadn't. (I only have because I worked near a Java team that demo'd it for me. I didn't think it would be helpful for my workflow.) Having a list of tools like this on the job description for developers is likely to be a turnoff. Again, focus on essentials.

So I wish this project all the best. But I think they'd be much better off starting with some simple, well-known tools: Drush, SimpleTest, Webistrano OR Aegir (not both), Basecamp OR Jira OR Atrium. Better to over-use one really good tool than underuse half a dozen tools, in my opinion.

Thoughts, comments, rebuttals?

Jul 16 '10 11:05pm
Tags

CSS sprites made easy with LessCSS

I'm working on a project now that involves building a Drupal theme from scratch from a designer's PSDs. It's something I don't do often - the CSS I write is usually either in a modified stock theme or cut up by someone else. So I haven't had much experience creating CSS sprites from scratch before.

Basically a sprite is a single image file containing other images that appear on the site. Each individual image (a button, logo, background, whatever) is then written in CSS with the sprite as the background image, a background-position, and a fixed width and height, so each slice of the big image appears where it's needed. The main benefit is fewer HTTP requests, so the site loads faster. (With broadband it's often not the loading itself that takes time but rather the requesting of each item.) It's also easier to manage a few sprites than dozens of little files, and it's just elegant.

The tools I'm using in this case are Photoshop and LessCSS. I know Photoshop decently well but am not an expert. LessCSS is a recent addition to my workflow, and it basically makes coding CSS a lot more efficient and intuitive with nesting, "mixins," variables, calculations, and other little shortcuts. I use Less.app for Mac, so I just save my .less files in Textmate, and they get compiled (and minified) automatically.

I'm building the sprite with a simple photoshop grid of 60px horizontally and vertically. I align each picture with the top and/or left line of a grid square. So for each picture I just have to know: 1) the grid position (not pixel position), 2) pixel width and height.

In Less this works out like this:

@mastersprite-row: -60px;
 
#mastersprite {
  background-image: url('/path-to-theme/images/master-sprite.png');
  background-repeat: no-repeat;
  background-position: 0px 0px;
}

Then for the image at the 4th position down on the grid (the first being at 0px), I do:

#element {
  #mastersprite;
  background-position: 0 @mastersprite-row*3;
  width: [X]px;
  height: [Y]px;
}

And it just works. Beautiful.

Also part of this theming process but not related to sprites: the client had a font they wanted to use, in OpenType Font (.otf) format. I uploaded it to Font Squirrel's Font-Face Generator and got a fully browser-compatible font package, including a CSS file which I simply @import'd into my .less file and voila, the font worked.

Jul 10 '10 1:00am
Tags

Learning AppleScript: Creating a "Do Not Disturb" Toggle

On a whim I decided to start learning AppleScript tonight. AppleScript is one of the great features of OSX, and allows scripts to communicate pretty much with any [decently built] application.

The first use case I could think of was a little toggle for quiet time, a "Do Not Disturb" toggle. It has 2 modes, Quiet and Noisy. In Quiet mode it puts Mailplane into its own "Do Not Disturb" mode, logs out of iChat, closes Skype, turns off Growl, and turns on Caffeine (assuming this quiet time also involves concentrated looking at the screen). Noisy mode is normal; flipping that switch logs back into iChat, and turns Mailplane and Growl back on. (It doesn't toggle all the same things; I don't always have Skype on and Caffeine shouldn't necessarily be turned off. More on that below.)

Here's the code:

-- figure out: how can it REMEMBER from one run to the next (e.g. were caffeine, ichat on?)
 
set appName to "DoNotDisturb Toggle"  -- for display later
 
-- register with Growl
tell application "GrowlHelperApp"
	set the allNotificationsList to {"toggle-announce"}
	copy allNotificationsList to enabledNotificationsList
	register as application ¬
		appName all notifications allNotificationsList ¬
		default notifications enabledNotificationsList
end tell
 
display dialog "Noisy or Quiet?" buttons {"Noisy", "Silent"}
set doMode to button returned of result
 
if doMode is "Noisy" then
	tell application "Mailplane" to set doNotDisturb to false
 
	tell application "GrowlHelperApp"
		launch
		notify with name "toggle-announce" title appName ¬
			description "Going back to Noisy mode" application name appName
	end tell
 
	tell application "iChat" to log in # what if it wasn't before?
 
else -- Quiet
	tell application "GrowlHelperApp"
		notify with name "toggle-announce" title appName ¬
			description "Going into Quiet mode" application name appName
		quit
	end tell
 
	tell application "Mailplane" to set doNotDisturb to true
 
	tell application "Caffeine" to turn on # (turn on for quiet but don't turn off on noisy)
 
	tell application "iChat" to log out
	tell application "Skype" to quit
 
end if

I turned it into an Application (File->Save As), so it's easy to run from the Dock.

What I need to figure out is how it can remember states from one run to the other: it should only log back into iChat, for instance, if it was logged in before going into Quiet mode. Does anyone know?

Jul 9 '10 7:27pm

[retweet] @cgbeaman: Google Launches App to Let Users Share Open Parking Spots: http://bit.ly/aNjAAs via @addthis

Jul 9 '10 1:55pm

MapQuest tries to reclaim "Just MapQuest it" by embracing open-source OpenStreetMap to jumpstart new system http://bit.ly/bxCgwZ

Jul 9 '10 10:33am

Google changes China search page from redirect to link (to HK), gets its license renewed. http://bit.ly/aiyBc3

Jul 9 '10 10:05am

Guardian is syndicating full articles (w/ads+tracking) to bloggers for free - v interesting model! http://bit.ly/an7R87 (via @AntlerAgency)

Jul 8 '10 11:47am

Jared Spool's D4D Keynote, "What Makes a Design Intuitive?"

Jared Spool's awesome keynote from D4D Boston on "What Makes a Design Intuitive?" is now up on MIT's TechTV, enjoy:

Jul 7 '10 10:22pm
Tags

Identify differences between Drupal databases with Drush, mysqldump, and diff

(This code will be part of my DrupalCamp NYC session on A Developer's Toolkit of Productivity Hacks.)

I had to do a brute-force deployment today, synchronizing functionality and content in 2 directions between a development and live site that split months ago. So I needed to see exactly what changed on each site's database to plan the deployment process. This scriptlet, run on the server with both sites, helped a lot.

(You'll want to do all this from a directory outside the webroot because you don't want the public to download SQL dumps.)

• Set up shortcuts to each site, as user 'admin', passing all prompts (not crucial for the rest but generally useful):

DEV="drush --root=/path/to/dev/site -u admin -y "
PROD="drush --root=/path/to/live/site -u admin -y "

• Make shortcuts to mysqldump each site's database, sorting by primary key and with full insert's - so each record is its own line and can be matched to the other. (You can use $DEV sql-connect and $PROD sql-connect, and copy/paste from there:

PRODDUMP="mysqldump --order-by-primary --skip-extended-insert -hHOST -uUSER -pPASS PRODDB "
DEVDUMP="mysqldump --order-by-primary --skip-extended-insert -hHOST -uUSER -pPASS DEVDB "

• Now get a list of all your tables. (This assumes one site has a more complete set):
$DEV sql-query "show tables" > tables.log

(Alternately, you could do this for both tables by combining them with >>, piping (|) to uniq and back to the file, for the full list from both sides.)

• Now get each table from development and production, and diff them. (The semicolons allow this to be consolidated to 1 line; I separate it here for readability.)

mkdir tables;
cat tables.log | while read TABLE; do 
  $DEVDUMP $TABLE > tables/${TABLE}-dev.sql;
  $PRODDUMP $TABLE > tables/${TABLE}-prod.sql; 
  diff -u tables/${TABLE}-prod.sql tables/${TABLE}-dev.sql > tables/${TABLE}.diff;
  echo "Done with $TABLE";
 done

• Now delete the files with no differences, they're just clutter. Those seemed to be 10 lines long, so I useD that as the magic number:

cat tables.log | while read TABLE; do 
  LCOUNT=`cat tables/${TABLE}.diff | wc -l`; 
  echo "$TABLE : $LCOUNT";
    if [[ $LCOUNT -lt 11 ]]; then 
      rm tables/${TABLE}.diff && rm tables/${TABLE}-dev.sql && rm tables/${TABLE}-prod.sql && echo "DELETED $TABLE"; 
    fi;  
 done

You can probably also ignore the cache and session tables. But this will give you a good view of what nodes, variables, users, and so on have changed.

If anyone wants to turn this into a Drush plugin (a la drush sql-diff PROD DEV --destination=tables), please do!

What kind of hacks, shortcuts, and tricks do you use in your development workflow? Let me know so I can include them in the session!

Jun 30 '10 10:07pm
Tags

Drupal Fix: Ajax and Secure Pages

Drupal's Secure Pages module is great for enforcing HTTPs/SSL on particular parts of a site. But it's known to have a problem with Ajax, especially (for my purposes) autocomplete fields in node forms. The solution is to toggle https:// in $base_url so it's not seen as cross-domain, and that's most easily done by putting this line in settings.php (changing mysite.com as needed):

$base_url = ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS']=='on') ? 'https://' : 'http://') . 'mysite.com';

Jun 30 '10 9:42pm

Drupal extension for Firebug and Chrome

Over on the EchoDitto Labs blog, Ethan has a post about the Firebug for Drupal module and its partner Firefox or Chrome extensions. Super cool and definitely going on my list of productivity hacks for DrupalCampNYC.
Jun 28 '10 12:05am
Tags

Debugging PHP with XAMPP, MacGDPp, and Textmate

Technosophos has a great tutorial on setting up a PHP debugging environment with XDebug and MAMP. I'm using XAMPP and it works the same way, just change the path where xdebug.so goes.

However, the Textmate part - using the xdebug.file_link_format parameter - doesn't seem to be working. Apparently others are having the same problem, possibly Snow Leopard-related, not sure if there's a solution. It's not necessary for the debugger to work, however, just a convenient way to view the error-causing code.

Jun 24 '10 11:20pm
Tags

Drupal trick: Embed a view anywhere with views_embed_view

I learned a new Drupal API function today: views_embed_view() to programmatically display a view inside another template (for example). Previously I had a custom function that loaded, executed, and rendered a view, several more lines of code than this.
I'm using it to pull a "Other Articles by Author" block into the side of a node template (the author is an argument):
echo views_embed_view('other_articles', 'block_1', $node->uid);
(Could also do the same in a preprocessor, $vars['other_articles'] = ...

For those who prefer not to write any code (and I've been increasingly impressed lately at Drupal designers who build amazing sites without any custom code), the Views Attach module (different from Views attachments) can pull Views into nodes. But for the code-inclined like myself, it can't be simpler than one line.

Jun 21 '10 10:59pm
Tags

DrupalCamp NYC Session Proposal: A Developer's Toolkit of Productivity Hacks

I'll be going to DrupalCampNYC on July 24-25, and I proposed to do a session called "A Developer's Arsenal of Productivity Hacks.". I have a bunch of my own but filling 50 minutes will require contributions! If you have any tips or tricks that improve your development workflow - terminal shortcuts, IDE plugins, shell scripts, magic wands, whatever it is - I'd love to include them in the session (giving full credit to each contributor).

What's in your arsenal?

Jun 21 '10 11:50am

How to use the latest version of Pressflow Drupal with Drush Make

Pressflow is a performance-tuned, MySql-specific distribution of Drupal maintained by Four Kitchens. Drush Make is a way of automatically compiling the files needed to build a big Drupal site.

I posted this issue on Pressflow's Launchpad queue (after raising the question with their team at D4D):

I'm using Drush Make to build my sites and I'd like Pressflow to be the default core. It's easy to put projects[] = drupal in the make file and it gets the latest 6.x from drupal.org. But how do I get the latest Pressflow? Currently I have:

projects[drupal][download][url] = "http://launchpad.net/pressflow/6.x/6.16.77/+download/pressflow-6.16.77.tar.gz"
projects[drupal][download][type] = "get"

For every Pressflow upgrade, I have to change the path there manually, which mostly defeats the purpose of Drush Make (and is a barrier to using Pressflow). It would be great if I could simply put in http://launchpad.net/pressflow/6.x/6.x-latest.tar.gz and it would always fetch the latest version. Or better yet, have projects[] = pressflow instead of "drupal," but I don't want to press my luck...

Thank you!

David Strauss (creator of Pressflow) replied:

Two options:

* Use Drush Make's support for Bazaar, and use lp:pressflow
* Use this URL to our build server: http://files.pressflow.org/pressflow-6-current.tar.gz

Nice!

Jun 21 '10 11:31am

Drupal hack: Working around Ubercart's disconnected catalog/taxonomy paths

One of my current projects involves an inherited Ubercart (Drupal ecommerce) store. It's set up with the standard Catalog module and products are broken down by content type and taxonomy. Unfortunately, Ubercart's catalog paths (mapped in this case to catalog/[category]) overlap/conflict with the taxonomy/term/% paths for the product categories. They have to be themed separately, and menu links point to one or the other, so effectively half the product-listing pages on the site are orphaned and/or unwanted.

I posed this question at an Ubercart session at Design4Drupal Boston this weekend, and the proposed solution was to disable the UC Catalog module. That's probably a good idea if I'm building it from scratch, but on this project I'd like to avoid restructuring the whole legacy site if possible. So I came up with this rather messy (but sufficiently functional) hack, to redirect one group to the other for consistency (comments in the code). You'll want to change 'store' to whatever path your Pathauto is using:

function MYMODULE_store_init() {
  // stopgap fix for disconnected taxonomy/term/% and catalog/% paths
  // (redirect first to second)
  // @TODO figure out a better solution
  $menu_trail = menu_get_active_trail();   // current page's breadcrumb trail
  $menu_item = is_array($menu_trail) ? array_pop($menu_trail) : array();   // current page's menu item
  $url_parts = explode('/', drupal_get_path_alias($_GET['q']));    // URL as seen by user
  // dsm($menu_item);    // debugging with Devel
  // dsm($url_parts);
 
  if (isset($menu_item['path']) && $menu_item['path'] === 'taxonomy/term/%'   // is a term page
    && is_numeric($menu_item['page_arguments'][0])    // has a term #
    && $url_parts[0]==='store'    // where Pathauto puts the store, dif for each site
    && count($url_parts)===2) {     // a store category, not a product page or other sub-page
 
      if ($term = taxonomy_get_term($menu_item['page_arguments'][0])) {    // is a valid term
        // dsm($term);
        // redirect to catalog category page of same term id
        drupal_goto('catalog/' . $menu_item['page_arguments'][0]);
      }
  }
}

I need to test this some more, but for a stopgap solution that avoids restructuring an existing site, it seems to work.
I'd be interested in hearing if people have thoughts on this method, or what would be a better way.

Jun 20 '10 6:03pm

End of an awesome Design For Drupal Boston conference, learned a lot and met great people. #d4d