Picasa Import Drupal module

The goal of this article is to show step by step how to create new Drupal module providing following functionality:

  • synchronizes photos from Picasa web album into local database 
  • displays photos in image gallery using jquery gallerific plugin
  • displays photos locations on thee map
  • displays photo detail using jquery lightbox plugin
     

Create picasa_import.info file

name = Picasa import
description = Displays images from picasa web album with location on the map
core = 7.x 
 

Create pisaca_import.install file 

The file should contain code defining database table picasa_import. The table will be automatically created after enabling Picasa Import module on your Drupal site.
 

/**
 * Implementation of hook_uninstall().
 */
function picasa_import_uninstall() {
    drupal_uninstall_schema('picasa_import');
}
/**
 * Implementation of hook_schema().
 */
function picasa_import_schema() {
    $schema['picasa_import'] = array(
        'fields' => array(
            'piid' => array(
                'type' => 'serial',
                'unsigned' => TRUE,
                'not null' => TRUE,
            ),
            'nid' => array(
                'type' => 'int',
                'unsigned' => TRUE,
                'not null' => TRUE,
            ),
            'user_id' => array(
                'type' => 'varchar',
                'length' => 50,
                'not null' => TRUE,
            ),
            'album_id' => array(
                'type' => 'varchar',
                'length' => 50,
                'not null' => TRUE,
            ),
            'photo_id' => array(
                'type' => 'varchar',
                'length' => 50,
                'not null' => TRUE,
            ),
            'name' => array(
                'type' => 'varchar',
                'length' => 250,
                'not null' => TRUE,
            ),
            'thumb_small_url' => array(
                'type' => 'varchar',
                'length' => 250,
                'not null' => FALSE,
            ),
            'thumb_small_width' => array(
                'type' => 'int',
                'unsigned' => TRUE,
                'not null' => FALSE,
            ),
            'thumb_small_height' => array(
                'type' => 'int',
                'unsigned' => TRUE,
                'not null' => FALSE,
            ),
            'thumb_medium_url' => array(
                'type' => 'varchar',
                'length' => 250,
                'not null' => FALSE,
            ),
            'thumb_medium_width' => array(
                'type' => 'int',
                'unsigned' => TRUE,
                'not null' => FALSE,
            ),
            'thumb_medium_height' => array(
                'type' => 'int',
                'unsigned' => TRUE,
                'not null' => FALSE,
            ),
            'thumb_big_url' => array(
                'type' => 'varchar',
                'length' => 250,
                'not null' => FALSE,
            ),
            'thumb_big_width' => array(
                'type' => 'int',
                'unsigned' => TRUE,
                'not null' => FALSE,
            ),
            'thumb_big_height' => array(
                'type' => 'int',
                'unsigned' => TRUE,
                'not null' => FALSE,
            ),
            'photo_url' => array(
                'type' => 'varchar',
                'length' => 250,
                'not null' => FALSE,
            ),
            'photo_width' => array(
                'type' => 'int',
                'unsigned' => TRUE,
                'not null' => FALSE,
            ),
            'photo_height' => array(
                'type' => 'int',
                'unsigned' => TRUE,
                'not null' => FALSE,
            ),
            'caption' => array(
                'type' => 'varchar',
                'length' => 250,
                'not null' => FALSE,
            ),
            'longitude' => array(
                'type' => 'varchar',
                'length' => 50,
                'not null' => FALSE,
            ),
            'latitude' => array(
                'type' => 'varchar',
                'length' => 50,
                'not null' => FALSE,
            ),
        ),
        'indexes' => array(
            'nid' => array('nid'),
            'photo_id' => array('photo_id'),
        ),
        'primary key' => array('piid'),
    );
    return $schema;
}

Create picasa_import.module file

This file is the main part of the Picasa Import module. It uses Zend GData API for communication with Picasa Web Album. So the first thing we need to done is to intialize Zend framework.
The Zend GData library can be downloaded from http://framework.zend.com/download/webservices. Place the library in sites/all/modules/picasa_import/Zend directory and initialize framework with following code:

// initialize Zend framework

require_once DRUPAL_ROOT.DIRECTORY_SEPARATOR.drupal_get_path('module', 'picasa_import').DIRECTORY_SEPARATOR.'Zend/Loader.php';
$zend_library_path = DRUPAL_ROOT.DIRECTORY_SEPARATOR.drupal_get_path('module', 'picasa_import');
set_include_path(get_include_path().PATH_SEPARATOR.$zend_library_path);
Zend_Loader::loadClass('Zend_Gdata');
Zend_Loader::loadClass('Zend_Gdata_AuthSub');
Zend_Loader::loadClass('Zend_Gdata_Photos');
Zend_Loader::loadClass('Zend_Gdata_Photos_UserQuery');
Zend_Loader::loadClass('Zend_Gdata_Photos_AlbumQuery');
Zend_Loader::loadClass('Zend_Gdata_Photos_PhotoQuery');
Zend_Loader::loadClass('Zend_Gdata_App_Extension_Category');
Zend_Loader::loadClass('Zend_Gdata_ClientLogin'); 

The next step is to create form for synchronizing web album photos into local database. When importing new photo new Drupal node with content type picasa_photo is also created. So before invoking the synchronization create new content type picasa_photo. Default settings can be kept. 

function picasa_import_synchronizeform($form, $form_state) {
  $form['settings'] = array(
    '#type' => 'fieldset',
    '#title' => t('Picasa Synchronization'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );
  $form['settings']['picasa_user'] = array(
    '#type' => 'textfield',
    '#title' => t('Picasa User'),
    '#default_value' =>  variable_get('picasa_user', 'picasa user'),    
  );

  $form['settings']['picasa_album'] = array(
    '#type' => 'textfield',
    '#title' => t('Album ID'),
  );
  $form['settings']['submit'] = array('#type' => 'submit', '#value' => t('Synchronize'));
  return $form;
}

function picasa_import_synchronizeform_submit($form, $form_state) {
    drupal_set_time_limit(200);   
    $picasa_user = $form_state['values']['picasa_user'];
    $picasa_album = $form_state['values']['picasa_album'];    

    // Create an authenticated HTTP client
    $service = Zend_Gdata_Photos::AUTH_SERVICE_NAME;
    $user = "your_picassa_user@gmail.com";
    $pass = "some_password";      
    $client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $service);        

    $counterChanged = 0;
    $counterImported = 0;
    $startIndex = 0;
    $photosInAlbum = -1;    

    // Because Zend GData library is memory hog and retrieving all photos in one call should cause memory problems
    // we will call picasa web in iterations. This is workaround for common exception with message Fatal error: Allowed memory size of 16777216 bytes exhausted (tried to allocate 71 bytes) in /opt/ZendFramework/release-1.8.3/library/Zend/Gdata/App/Base.php on line 430

    while (true) {           
        if ($photosInAlbum != -1) {
            $startIndex = $startIndex + 100;
            if ($startIndex > $photosInAlbum) {
                break;
            }
        }
        $photos = new Zend_Gdata_Photos($client);
        $query = new Zend_Gdata_Photos_AlbumQuery();
        $query->setUser($picasa_user);
        $query->setAlbumId($picasa_album);
        $query->setMaxResults(100);
        $query->setStartIndex($startIndex);
        $albumFeed = $photos->getAlbumFeed($query);
        $userId = $albumFeed->getGphotoUser()->getText();
        $photosInAlbum = $albumFeed->getGphotoNumPhotos()->getText();
        foreach ($albumFeed as $entry) {
            if ($entry instanceof Zend_Gdata_Photos_PhotoEntry) {
                $where = $entry->getGeoRssWhere();
                $longitude = null;
                $latitude = null;
                if ($where) {
                    //gps location is provided
                    $pos = $where->getPoint()->getPos();
                    $pos = explode(" ", $pos); // Split out the space-separated latitude and longitude values
                    $latitude = $pos[0];
                    $longitude = $pos[1];
                }

                // picasa web album photo ID
                $photoId = $entry->getGphotoId();

                //picasa web album uploaded file name
                $photoTitle = $entry->getTitleValue();

                //caption on photo in picasa web album
                $photoDescription = $entry->getMediaGroup()->getDescription()->getText();
                $photoDescription = str_replace("'","\'",$photoDescription);
                $photoDescription = str_replace("&","&",$photoDescription);

                //thumbnails
                $thumbs = $entry->getMediaGroup()->getThumbnail();
                $thumbSmallUrl = $thumbs[0]->getUrl();
                $thumbSmallWidth = $thumbs[0]->getWidth();
                $thumbSmallHeight = $thumbs[0]->getHeight();
                $thumbMediumUrl = $thumbs[1]->getUrl();
                $thumbMediumWidth = $thumbs[1]->getWidth();
                $thumbMediumHeight = $thumbs[1]->getHeight();                $thumbBigUrl = $thumbs[2]->getUrl();
                $thumbBigWidth = $thumbs[2]->getWidth();
                $thumbBigHeight = $thumbs[2]->getHeight();

                //photo url
                $contents = $entry->getMediaGroup()->getContent();
                $photoUrl = $contents[0]->getUrl();
                $photoWidth = $contents[0]->getWidth();
                $photoHeight = $contents[0]->getHeight();

                //check if entry with given photo_id already exists
                $result = db_select('picasa_import', 'pi')->fields('pi')
                    ->condition('photo_id',$photoId,'=')->condition('album_id',$albumFeed->getGphotoId(),'=')->condition('user_id',$userId,'=')
                    ->execute()->fetchAll();

                if ($result) {
                    foreach ($result as $record) {
                        //update entry in database if some photo attribute has changed

                        if ($photoTitle != $record->name || $photoDescription != $record->caption
                                || $longitude != $record->longitude || $latitude != $record->latitude
                                || $thumbSmallUrl != $record->thumb_small_url || $thumbMediumUrl != $record->thumb_medium_url || $thumbBigUrl != $record->thumb_big_url
                                || $thumbSmallWidth != $record->thumb_small_width || $thumbMediumWidth != $record->thumb_medium_width || $thumbBigWidth != $record->thumb_big_width
                                || $thumbSmallHeight != $record->thumb_small_height || $thumbMediumHeight != $record->thumb_medium_height || $thumbBigHeight != $record->thumb_big_height

                                || $photoUrl != $record->photo_url || $photoWidth != $record->photo_width || $photoHeight != $record->photo_height  ) {

                            //update entry in database
                            db_update('picasa_import')->fields(
                                array('name' => $photoTitle, 'caption' => $photoDescription,
                                        'thumb_small_url' => $thumbSmallUrl, 'thumb_medium_url' => $thumbMediumUrl, 'thumb_big_url' => $thumbBigUrl,
                                        'thumb_small_width' => $thumbSmallWidth, 'thumb_medium_width' => $thumbMediumWidth, 'thumb_big_width' => $thumbBigWidth,
                                        'thumb_small_height' => $thumbSmallHeight, 'thumb_medium_height' => $thumbMediumHeight, 'thumb_big_height' => $thumbBigHeight,
                                        'photo_url' => $photoUrl, 'photo_width' => $photoWidth,'photo_height' => $photoHeight,
                                        'longitude' => $longitude, 'latitude' => $latitude,
                                    )
                                 )
                                ->condition('photo_id',$photoId,'=')->condition('album_id',$albumFeed->getGphotoId(),'=')->condition('user_id',$userId,'=')
                                ->execute();

                            //update node title
                            $node = node_load($record->nid);
                            $node->title = $photoDescription;
                            node_save($node);

                            //increase counter
                            $counterChanged = $counterChanged + 1;
                        }
                    }
                }
                else {
                    //create node
                    $node = new stdClass();
                    $node->type = 'picasa_photo';
                    node_object_prepare($node);
                    $node->title = $photoDescription;
                    $node->language = LANGUAGE_NONE;
                    node_save($node);

                    //insert entry
                    $piid = db_insert('picasa_import')->fields(
                        array('nid' => $node->nid,'user_id' => $userId,'album_id' => $albumFeed->getGphotoId(),
                              'photo_id' => $photoId, 'name' => $photoTitle, 'caption' => $photoDescription,
                              'thumb_small_url' => $thumbSmallUrl, 'thumb_medium_url' => $thumbMediumUrl, 'thumb_big_url' => $thumbBigUrl,
                              'thumb_small_width' => $thumbSmallWidth, 'thumb_medium_width' => $thumbMediumWidth, 'thumb_big_width' => $thumbBigWidth,
                              'thumb_small_height' => $thumbSmallHeight, 'thumb_medium_height' => $thumbMediumHeight, 'thumb_big_height' => $thumbBigHeight,
                              'photo_url' => $photoUrl, 'photo_width' => $photoWidth,'photo_height' => $photoHeight,
                              'longitude' => $longitude, 'latitude' => $latitude, )
                            )->execute();

                    //increase counter
                    $counterImported = $counterImported + 1;
                }
            }
        }
    }

    drupal_set_message(t('Photos from Picasa Web Album @picasa_album have been synchronized.', array('@picasa_album' => $picasa_album)));
    drupal_set_message(t('Number of imported photos: @counterImported, number of changed photos: @counterChanged, number of photos in album:
counterAll ',
            array('@counterImported' => $counterImported, '@counterChanged' => $counterChanged,'@counterAll' => $photosInAlbum)));
}

The form can be displayed using drupal_get_form and drupal_render functions:

$form = drupal_get_form('picasa_import_synchronizeform');
drupal_render($form);

After submitting the form feed from Picasa Web Album will be retrieved, parsed using Zend GData library and data inserted into local database.

Now we can retrieve photos in the album using SQL query which is much more effective from the performance point of view then parsing feed from picasa web album. I have chosen gallerific jQuery plugin for rendering pictures in the image gallery. Galleriffic is a jQuery plugin that provides a rich, post-back free experience optimized to handle high volumes of photos while conserving bandwidth. You can download it from  http://www.twospy.com/galleriffic/. All you need to do is to import javascript library, generate gallery HTML code and initialize the gallery by calling the galleriffic initialization function on the thumbnails container as described on the Galleriffic home page. Importing javascript files into HTML page can be done using drupal_add_js function, the best place is the hook_init() method, in our case 

function picasa_import_init() {
    //import javascript libraries for galleriffic library
    drupal_add_js(drupal_get_path('module', 'picasa_import').'/js/galleriffic-2.0/jquery.galleriffic.js');
    drupal_add_js(drupal_get_path('module', 'picasa_import').'/js/galleriffic-2.0/jquery.opacityrollover.js');
    //apply galleriffic css styles
    drupal_add_css(drupal_get_path('module', 'picasa_import').'/css/galleriffic-2.0/galleriffic-picasa.css');
}

So the image gallery is done. Next step is to display locations of photos on the map. 

Add following line to the picasa_import_init() function. This will import javascript library for google maps.

drupal_add_js('http://maps.google.com/maps/api/js?sensor=false','external'); 

For displaying location of the photo on the map we will call javascript function setMarkers(). 

function setMarkers(map, locations, imgdir) {
  // Add markers to the map
  // Marker sizes are expressed as a Size of X,Y
  // where the origin of the image (0,0) is located
  // in the top left of the image.
  // Origins, anchor positions and coordinates of the marker
  // increase in the X direction to the right and in
  // the Y direction down.
  var image = new google.maps.MarkerImage(imgdir+'/marker.png',
      // This marker is 20 pixels wide by 32 pixels tall.
      new google.maps.Size(20, 32),
      // The origin for this image is 0,0.
      new google.maps.Point(0,0),
      // The anchor for this image is the base of the flagpole at 0,32.
      new google.maps.Point(0, 32));
  var shadow = new google.maps.MarkerImage(imgdir+'/marker_shadow.png',
      // The shadow image is larger in the horizontal dimension
      // while the position and offset are the same as for the main image.
      new google.maps.Size(37, 32),
      new google.maps.Point(0,0),
      new google.maps.Point(0, 32));
      // Shapes define the clickable region of the icon.
      // The type defines an HTML <area> element 'poly' which
      // traces out a polygon as a series of X,Y points. The final
      // coordinate closes the poly by connecting to the first coordinate.
  var shape = {
      coord: [1, 1, 1, 20, 18, 20, 18 , 1],
      type: 'poly'
  };
  var markers = new Array();
  for (var i = 0; i < locations.length; i++) {
    var flag = locations[i];
    var myLatLng = new google.maps.LatLng(flag[1], flag[2]);
    markers[i] = new google.maps.Marker({
        position: myLatLng,
        map: map,
        shadow: shadow,
        icon: image,
        shape: shape,
        title: flag[0],
        zIndex: flag[3]
    });
   google.maps.event.addListener(markers[i], 'click', function() {
markerClick(this); 

    });
  } 

  function markerClick( mark )  {
      for ( var m = 0; m < markers.length; ++m )  {
        if ( markers[m] == mark ) {  
             document.location=locations[m][4];
             return;
        }
     }
  }
}

The setMarkers() function first argument is reference to the google.maps.Map object, second argument is array of marker descriptors, third argument is path to image directory, where marker.png and marker_shadow.png are stored. Marker descriptor is an array 5 values (photo caption, latitude, longitude, index and url to the node with photo detail. The google map is initialized calling javascript function gmap_photos_initialize(). This should be done after all HTML code is loaded using jQuery(document).ready() function.

drupal_add_js("function gmap_photos_initialize(aLocs, aZoom, aCenterLat, aCenterLng ) { \n".
                    " var latlng = new google.maps.LatLng(aCenterLat,aCenterLng);\n".
                    " var myOptions = {zoom: aZoom, center: latlng, mapTypeId: google.maps.MapTypeId.ROADMAP }; \n".
                    " var map = new google.maps.Map(document.getElementById(\"map_canvas\"), myOptions); \n".
                    "       setMarkers(map, aLocs, \"".$GLOBALS['base_url']."/".drupal_get_path('module', 'picasa_import')."/images\"); \n".
                    "} \n","inline"); 

The last step is to display detail of the photo.

As mentioned above, during synchronization process for every newly imported photo the new node is created. The node url is stored in the database with other photo's metadata. When clicking on the marker in the map or on the thumbnail in the image gallery, the page with image detail is opened. The photo in the original size will be displayed using jQuery lightBox plugin. jQuery lightBox plugin is simple, elegant, unobtrusive, no need extra markup and is used to overlay images on the current page through the power and flexibility of jQuery´s selector. The library can be downloaded from http://leandrovieira.com/projects/jquery/lightbox/ and initialized standard way as follows:

//import javascript library for lightbox
drupal_add_js(drupal_get_path('module', 'picasa_import').'/js/lightbox/jquery.lightbox-0.5.js');
drupal_add_css(drupal_get_path('module', 'picasa_import').'/js/lightbox/jquery.lightbox-0.5.css');

Add new comment

Filtered HTML

  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Author: katalpa.cz@gmail.com, powered by Drupal Valid XHTML + RDFa