Blog

Archive for the ‘open source’ Category

UPDATED: New Facebook Phonebook Script

Posted on:Monday, July 19th, 2010 by Ashish Datta

I realized this morning that Anonymous Coward’s Facebook Phonebook Greasemonkey script broke awhile back so I decided to rewrite it from scratch.

The original instructions for how to install the script are available here.

I updated the original Userscripts page with the new script so you can download it here.

Once again, this probably breaks your Facebook TOS so I can’t vouch for the safety of your account if you do decide to do this.

QR Bookmarklet

Posted on:Sunday, June 13th, 2010 by Ashish Datta

I got tired of having to find the same website (mostly recipes) on my phone after looking at them on my workstation or laptop so I decided to whip together a bookmarklet to throw a Google powered QR code on any page.

The bookmarklet will just slap a QR code image with the current page’s URL (window.location) so that you can open the page on your phone. ps. Barcode Scanner for Android will automatically open the URL in a browser.

Without further ado, QR Code Bookmarklet

jQuery.trigger weirdness

Posted on:Thursday, June 10th, 2010 by Ashish Datta

Earlier today, I was trying to use jQuery to trigger the submission of a form after a radio button was clicked. The form tag looked something like:

...form..

So for a regular submit button:


Everything works fine, you’ll see the alert() and the form won’t submit because of the return false.

I ran into issues when I tried to trigger() the submit event with jQuery. I was trying to trigger the submit() event on the form via jQuery. The problem I ran into, was that the saysomething() function was getting called, but the “return false” seemed to have no effect.

The final form looked something like:

For some reason, if you submit the form via a jQuery trigger the form submits even though saysomething() gets called. I’m not sure if this is the expected behavior but it was certainly something of a shock. Anyway, a live version of the form is running here.

Run jQuery each() serially

Posted on:Tuesday, May 18th, 2010 by Ashish Datta

jQuery.each() is pretty sweet but earlier today I wanted to run some animations across a set of three elements and since the animate() calls are non-blocking everything was happening at the same time. What I wanted to do was have the functions execute in a serial fashion (1 after the other).

I poked around and it doesn’t look like there’s a native way to do this. After a bit I decided to just whip something up and see how it works. Here’s what I had originally:

$("#splashStream .snippetbox").each( function(index){
	$(this).animate( {opacity: 0}, 1000, function(){
		$(this).html( $("#hiddenSplashDiv .snippetbox:eq(" + index + ")").html() );
		$(this).animate( {opacity: 1}, 2000 );
	});
});

That ran fine but everything happened at the same time. The modified serial code looks like:


                  var hasCallbackCompleted = [ true ];
                  $("#splashStream .snippetbox").each( function(index){

                      var f = arguments.callee;
                      var args = arguments;
                      var t = this;

                      if( !hasCallbackCompleted[ index ] ){
                        window.setTimeout( function(){ f.apply(t, args); }, 5 );
                        return true;
                      }

                      hasCallbackCompleted[ index + 1 ] = false;
                      $(this).animate( {opacity: 0}, 1000, function(){
                          $(this).html( $("#hiddenSplashDiv .snippetbox:eq(" + index + ")").html() );
                          $(this).animate( {opacity: 1}, 2000, function(){
                            hasCallbackCompleted[ index + 1 ] = true;
                          });
                      });

                  });

Basically, what it does is after the first element, the code will delay execution of the each() function until the hasCallbackCompleted flag is set for the correct element.

jQuery blank() modified for password fields

Posted on:Wednesday, April 28th, 2010 by Ashish Datta

We’ve been using Jeff Hui’s very awesome jquery.blank plugin for sometime over at Setfive HQ. What blank() is allow you to basically move the labels for text inputs into the input themselves (to save space). We use this technique frequently for login boxes in headers since it’s easier not to have to stick in labels next to text boxes.

The problem is, you can’t use blank() on a password field since a password field won’t display clear text (obviously). To get around this, I’ve always manually stuck in a “shadow” text box next to the password field and toggled the text box or password box in order to make blank() work correctly.

Anyway, I finally got tired of doing this so I decided to patch the plugin to do this automatically. Jeff incorporated the code back into blank() and it’s available on GitHub here.

Happy coding.

sfSCMIgnoresTaskPlugin for Symfony released, windows compatible!

Posted on:Friday, March 19th, 2010 by Matt Daum

Recently I received an email from Davert.  He noted that the sfSCMIgnoresTaksPlugin would not work on Windows as Windows has a different directory separator.  He sent over a patch which uses PHP’s DIRECTORY_SEPARATOR instead of the coded “/”.  This makes the plugin compatible on Windows.  Thanks Davert.  You can read more about the plugin and download the most recent release at http://www.symfony-project.org/plugins/sfSCMIgnoresTaskPlugin.

LimeSurvey with load balancers, fixing the user sessions.

Posted on:Friday, March 12th, 2010 by Matt Daum

For a client we’ve been working with recently it came to our attention that they needed more frontend servers to keep up with the traffic for their surveys. They use LimeSurvey which is powerful open source survey platform. We set the client up in the cloud to scale as necessary with a load balancer in front. This is when we noticed the problem that LimeSurvey doesn’t work well when a user is bouncing between different frontend servers. LimeSurvey keeps all the user’s session attributes on the local server and not in the database. After googling around for a while, we found other people also had this problem before, and no one had really solved it. We figured we would.

We didn’t feel like doing a ton of extra work to reinvent the wheel in terms of storing the session in the database. We snagged most of the code straight from the Symfony “storage” module which handles it’s session management if you want to store the user sessions in a database. After a quick few modifications, we got it up:

/**
 * Taken from parts the Symfony package "storage" module
 * @author Ashish Datta ashish@setfive.com
 * @author Matt Daum matt@setfive.com
 * Setfive Consulting, LLC
 */                                                     

class setfiveSession {

  private $options = array();
  private $sessionIdRegenerated;

  public function initialize()
  {
    $this->options =  array('db_table' => 'session',
                            'db_id_col'   => 'sess_id',
                            'db_data_col' => 'sess_data',
                            'db_time_col' => 'sess_time');

    session_set_save_handler(array($this, 'sessionOpen'),
                             array($this, 'sessionClose'),
                             array($this, 'sessionRead'),
                             array($this, 'sessionWrite'),
                             array($this, 'sessionDestroy'),
                             array($this, 'sessionGC'));    

    // start our session
    // session_start();
  }                                                         

  /**
   * Closes a session.
   *
   * @return boolean true, if the session was closed, otherwise false
   */
  public function sessionClose()
  {
    // do nothing
    return true;
  }                                                                  

  /**
   * Opens a session.
   *
   * @param  string $path  (ignored)
   * @param  string $name  (ignored)
   *
   * @return boolean true, if the session was opened, otherwise an exception is thrown
   *
   * @throws Exception If a connection with the database does not exist or cannot be created
   */
  public function sessionOpen($path = null, $name = null)
  {
    // we're assuming LimeSurvey all ready has db settings and opened a db connection
    return true;
  }                                                                                                

  /**
   * Destroys a session.
   *
   * @param  string $id  A session ID
   *
   * @return bool true, if the session was destroyed, otherwise an exception is thrown
   *
   * @throws Exception If the session cannot be destroyed.
   */
  public function sessionDestroy($id)
  {
    // get table/column
    $db_table  = $this->options['db_table'];
    $db_id_col = $this->options['db_id_col'];                                                      

    // cleanup the session id, just in case
    $id = $this->db_escape($id);           

    // delete the record associated with this id
    $sql = "DELETE FROM $db_table WHERE $db_id_col = '$id'";

    if ($this->db_query($sql))
    {
      return true;
    }                         

    // failed to destroy session
    throw new Exception(sprintf('%s cannot destroy session id "%s" (%s).', get_class($this), $id, mysql_error()));
  }                                                                                                               

  /**
   * Cleans up old sessions.
   *
   * @param  int $lifetime  The lifetime of a session
   *
   * @return bool true, if old sessions have been cleaned, otherwise an exception is thrown
   *
   * @throws Exception If any old sessions cannot be cleaned
   */
  public function sessionGC($lifetime)
  {
    // get table/column
    $db_table    = $this->options['db_table'];
    $db_time_col = $this->options['db_time_col'];                                                                 

    // delete the record older than the authorised session life time
    $lifetime = $this->db_escape($lifetime); // We never know...
    $sql = "DELETE FROM $db_table WHERE $db_time_col + $lifetime < UNIX_TIMESTAMP()";

    if (!$this->db_query($sql))
    {
      throw new Exception(sprintf('%s cannot delete old sessions (%s).', get_class($this), mysql_error()));
    }                                                                                                      

    return true;
  }             

  /**
   * Reads a session.
   *
   * @param  string $id  A session ID
   *
   * @return string      The session data if the session was read or created, otherwise an exception is thrown
   *
   * @throws Exception If the session cannot be read
   */
  public function sessionRead($id)
  {                                                                                                           

    // get table/column
    $db_table    = $this->options['db_table'];
    $db_data_col = $this->options['db_data_col'];
    $db_id_col   = $this->options['db_id_col'];
    $db_time_col = $this->options['db_time_col'];                                                             

    // cleanup the session id, just in case
    $id = $this->db_escape($id);           

    // delete the record associated with this id
    $sql = "SELECT $db_data_col FROM $db_table WHERE $db_id_col = '$id'";

    $result = $this->db_query($sql);

    if ($result != false && $this->db_num_rows($result) == 1)
    {
      // found the session
      $data = $this->db_fetch_row($result);                  

      return $data[0];
    }
    else
    {
      // session does not exist, create it
      $sql = "INSERT INTO $db_table ($db_id_col, $db_data_col, $db_time_col) VALUES ('$id', '', UNIX_TIMESTAMP())";
      if ($this->db_query($sql))
      {
        return '';
      }                                                                                                            

      // can't create record
      throw new Exception(sprintf('%s cannot create new record for id "%s" (%s).', get_class($this), $id, mysql_error()));
    }                                                                                                               

  }                                                                                                                 

  /**
   * Writes session data.
   *
   * @param  string $id    A session ID
   * @param  string $data  A serialized chunk of session data
   *
   * @return bool true, if the session was written, otherwise an exception is thrown
   *
   * @throws sfDatabaseException If the session data cannot be written
   */
  public function sessionWrite($id, $data)
  {
    // get table/column
    $db_table    = $this->options['db_table'];
    $db_data_col = $this->options['db_data_col'];
    $db_id_col   = $this->options['db_id_col'];
    $db_time_col = $this->options['db_time_col'];                                   

    // cleanup the session id and data, just in case
    $id   = $this->db_escape($id);
    $data = $this->db_escape($data);                

    // update the record associated with this id
    $sql = "UPDATE $db_table SET $db_data_col='$data', $db_time_col=UNIX_TIMESTAMP() WHERE $db_id_col='$id'";

    if ($this->db_query($sql))
    {
      return true;
    }                         

    // failed to write session data
    throw new Exception(sprintf('%s cannot write session data for id "%s" (%s).', get_class($this), $id, mysql_error()));
  }                                                                                                                 

/**
   * Regenerates id that represents this storage.
   *
   * @param  boolean $destroy Destroy session when regenerating?
   *
   * @return boolean True if session regenerated, false if error
   *
   */
  public function regenerate($destroy = false)
  {
    if ($this->sessionIdRegenerated)
    {
      return;
    }                                                           

    $this->sessionIdRegenerated = true;
    $currentId = session_id();
    $newId = session_id();
    $this->sessionRead($newId);        

    return $this->sessionWrite($newId, $this->sessionRead($currentId));
  }                                                                    

  /**
   * Counts the rows in a query result
   *
   * @param  resource $result  Result of a query
   * @return int Number of rows
   */
  protected function db_num_rows($result)
  {
    return mysql_num_rows($result);
  }                                                                    

  /**
   * Extracts a row from a query result set
   *
   * @param  resource $result  Result of a query
   * @return array Extracted row as an indexed array
   */
  protected function db_fetch_row($result)
  {
    return mysql_fetch_row($result);
  }

  /**
   * Executes an SQL Query
   *
   * @param  string $query  The query to execute
   * @return mixed The result of the query
   */
  protected function db_query($query)
  {
    return @mysql_query($query);
  }

  /**
   * Escapes a string before using it in a query statement
   *
   * @param  string $string  The string to escape
   * @return string The escaped string
   */
  protected function db_escape($string)
  {
    return mysql_real_escape_string($string);
  }

}

$sfSessionHandler = new setfiveSession();
$sfSessionHandler->initialize();

This requires you create a table in your MySQL database called session. Here is a dump of the create statement for the table:

CREATE TABLE IF NOT EXISTS `session` (
  `id` int(11) NOT NULL auto_increment,
  `sess_id` varchar(255) NOT NULL,
  `sess_data` text NOT NULL,
  `sess_time` datetime NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1;

Basically this uses PHP’s session_set_handler function to manipulate how the PHP retrieves, updates, and stores the user’s session. The final touches were to include this class where the user sessions are started in LimeSurvey. We found them in the index.php and sessioncontrol.php files. Include our file from above just before the session_start(); in the code in those two files. In admin/scripts/fckeditor.265/editor/filemanager/connectors/php/upload.php include the file before the first include. Lastly we need to update a couple locations where it does session_regenerate_id and replace it with $sfSessionHandler->regenerate(). You can find these edits in the following three files: admin/usercontrol.php on line 128, admin/login_check.php line 65, and index.php at lines 207 and 215. You should be up and running now, let us know if you have any problems.

Retrieve session timeout in Symfony

Posted on:Tuesday, December 1st, 2009 by Ashish Datta

We were recently working on an application that required users to enter a significant amount of complex data that often meant that they had to look things up in between saves. Users kept running into the problem that their sfGuard sessions would timeout before they were able to click “Save” on the form which in turn caused them to loose all of their hard work. Obviously, this is lame so we decided to add a popup warning users that their session had expired and prompting them to login again before saving their data.

We decided to implement this by using setTimeout in Javascript to pop up a window once the user’s session had expired.

Setting the session length for a Symfony user is easy enough, open up app/config/factories.yml and add the following:

all:
  user:
    class: myUser
    param:
      timeout: 1800 # this is the default but you can change it at will (its in seconds)

As it turns out, the tricky part is how do you access this value inside the application? Un-characteristically, I couldn’t find anything in the Symfony documentation about how to access these variables. For whatever reason, sfConfig::get() doesn’t provide access to the variables in factories.yml.

In order to get that timeout value I used (inside a template):

  $userOptions = $sf_user->getOptions();
  $timeout = $userOptions["timeout"];

Anyway, once I figured that out the rest is pretty straightforward.

After $timeout a Javascript function opens a jQuery UI Dialog box informing the user that their session has expired and presents the standard sfGuard sign in form. I override the onSubmit of this form to perform the request via AJAX in the background (so the user doesn’t loose their data) and then if the credentials are valid the dialog closes and the user can go on their way. If the credentials are invalid, the form re-populates with any errors and the user can correct them to re-login to the app.

Hope everyone had a good Thanksgiving!

jQuery UI $.dialog – on the fly HTML

Posted on:Friday, November 13th, 2009 by Ashish Datta

Wow its been awhile!

We’ve been insanely busy over the last month or so. We launched Setfive Ventures and are anxiously anticipating the launch of both WeGov and OmniStrat in the immediate future. There are also a handful of internal project that should be rolling out before Christmas. Get Excited.

Anyway, the jQuery UI Dialog class is pretty sweet. Basically, it provides a class to display a modal dialog box from a regular old DOM element (a div, span, or whatever.)

One of the thing that isn’t explained well (or at all?) in the documentation is that you can create a dialog with on the fly HTML! I found this out after posting on the Google Group asking why this feature didn’t exist (it does. Ashish fail.)

So if you want to create a dialog with on the fly HTML all you need to do is:

$("<p>Hello World!</p>").dialog();

Pretty sweet.

Adding ORDER BY FIELD to Propel Criterias

Posted on:Tuesday, October 13th, 2009 by Ashish Datta

Every now and then, we use Sphinx to provide full text searching in MySQL InnoDB tables. Sphinx is pretty solid. It’s easy to set up, pretty fast, and easy to deploy.

My one big issue with Sphinx has always been making it play nice with Symfony, specifically Propel. The way Sphinx returns a result set is as an ordered list of [id, weight] for each document it matched. As outlined here the idea is to then hit your MySQL server to return the actual documents and use “ORDER BY FIELD(id, [id list])” to keep them in the right order that you received the list.

The problem is, Propel Criteria objects provide no mechanism to set an ORDER BY FIELD. This is an issue because if you drop Criterias you loose Propel Pagers which generally adds to a lot of duplicated code and is honestly just not very elegant.

Anyway, after some thought I came up with this solution.

If you read through the definition of “Criteria::addDescendingOrderByColumn()”:

	/**
	 * Add order by column name, explicitly specifying descending.
	 *
	 * @param      string $name The name of the column to order by.
	 * @return     Criteria Modified Criteria object (for fluent API)
	 */
	public function addDescendingOrderByColumn($name)
	{
		$this->orderByColumns[] = $name . ' ' . self::DESC;
		return $this;
	}

All it really does is add the second part of the ORDER BY clause to an array which then gets joined up to build the final SQL. Because of this, you can actually just add an element onto the orderByColumns array which will cause Propel to execute an ORDER BY FIELD SQL statement.

To make the magic happen, I sub-classed Criteria and then added a addOrderByField() function to let me add a field to order by as well as a list to order by.


class sfCriteria extends Criteria {

  private $myOrderByColumns = array();

  /**
   * Add an ORDER BY FIELD clause.
   *
   * @param String $name The field to order by.
   * @param Array $elements A list to order the elements by.
   * @return unknown
   */
  public function addOrderByField($name, $elements)
  {
    $this->myOrderByColumns[] = ' FIELD(' . $name . ', ' . join(", ", $elements) . ')';
    return $this;
  }

  public function getOrderByColumns(){
    return array_merge( $this->myOrderByColumns, parent::getOrderByColumns() );
  }
}

To use it, do something like this:

$ids = array(1, 3, 7);
$c = new sfCriteria();
$c->add( SomeModelPeer::ID, $ids, Criteria::IN);
$c->addOrderByField( SomeModelPeer::ID, $ids);
$results = SomeModelPeer::doSelect( $c );

And thats about it. Since sfCriteria is a sub-class of Criteria the code works seamlessly with existing PropelPagers and anything else that expects a Propel Criteria.