<?php

/**
 * Photo Set to KML
 * http://www2.adamfranco.com/photosetToKML.php
 * http://www.adamfranco.com/?p=43
 *
 * Features:
 *
 *    - Generate a KML file from a Flickr photo set
 *    - Directly open the KML file in Google Maps
 *    - Choose what size image to include in the placemark description for each photo.
 *    - Optionaly draw a path (line) from photo to photo ordered in one of several ways: 
 *        - by date taken
 *        - by date uploaded
 *        - by set order
 *      Useful for making a quick and dirty map of a trip.
 *
 *
 * Requirements:
 *
 *    - PHP 5.2 or greater    http://www.php.net
 *    - PEAR Flickr API        http://code.iamcal.com/php/flickr/readme.htm
 *
 *
 * Change-log:
 *
 * - 2007-08-27
 *        - Now uses htmlspecialchars() to clean titles instead of htmlentities(), 
 *          the latter of which was causing excessive translation of German 
 *          characters. Thanks <a href='http://www.ogleearth.com/'>Stefan Geens</a>, for 
 *          pointing this out.
 *        - Form now generates valid XHTML 1.0 strict.
 *        - Now can use image thumbnails instead of camera icons. Thanks for the idea
 *          Nicolas Hoizey.
 *
 * - 2007-08-24
 *        - Now escapes ampersands in titles and descriptions.
 *
 * - 2007-08-23
 *        - First Release
 *
 *
 * @since 11/7/06
 * @package com.adamfranco.flickr
 * 
 * @copyright Copyright &copy; 2006, Adam Franco
 * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License (GPL)
 *
 * @version $Id$
 */ 

ini_set('display_errors'1);
require_once 
'Flickr/API.php';

/**
 * Photosets are Flickr's version of slideshows.
 * 
 * @since 11/7/06
 * @package com.adamfranco.flickr
 * 
 * @copyright Copyright &copy; 2006, Adam Franco
 * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License (GPL)
 *
 * @version $Id$
 */
class Photoset 
    
extends FlickrObject
{
    
    
/**
     * @var string $id;
     * @access public
     * @since 8/21/07
     */
    
public $id;
    
    
/**
     * @var integer $currentPhoto;  
     * @access private
     * @since 8/21/07
     */
    
private $currentPhoto 0;
    
    
/**
     * @var integer $currentPageNumber;  
     * @access private
     * @since 8/21/07
     */
    
private $currentPageNumber 0;
    
    
/**
     * @var object XML_TreeNode $currentPage;  
     * @access private
     * @since 8/21/07
     */
    
private $currentPage;
    
    
/**
     * @var object Photo $primaryPhoto;  
     * @access private
     * @since 8/21/07
     */
    
private $primaryPhoto;
    
    
/**
     * @var array $properties;  
     * @access private
     * @since 8/21/07
     */
    
private $properties = array();
    
    
/**
     * @var boolean $loaded;  
     * @access private
     * @since 8/21/07
     */
    
private $loaded false;

    
/**
     * Constructor
     * 
     * @param object Flickr_API $api
     * @param string $id The Photoset Id
     * @return void
     * @access public
     * @since 11/7/06
     */
    
public function __construct (Flickr_API $api$id) {
        if (!
preg_match('/^[0-9]+$/'$id))
            throw new 
Exception("Invalid photo set id, '$id', passed.");
        
        
$this->api $api;
        
$this->id $id;
        
$this->currentPhoto 0;
        
$this->currentPageNumber 0;
    }
    
    
/**
     * Answer one of our properties
     * 
     * @param string $name
     * @return mixed
     * @access public
     * @since 8/21/07
     */
    
public function __get ($name) {
        
$this->loadInfo();
        if (!isset(
$this->properties[$name]))
            throw new 
Exception("Cannot access unknown property, '$name'.");
        
        return 
$this->properties[$name];
    }
    
    
/**
     * Answer true if this attribute is set
     * 
     * @param string $name
     * @return boolean
     * @access public
     * @since 8/21/07
     */
    
public function __isset ($name) {
        
$this->loadInfo();
        if (isset(
$this->properties[$name]))
            return 
true;
        else
            return 
false;
    }
    
    
/**
     * Set one of our properties
     * 
     * @param string $name
     * @return void
     * @access private
     * @since 8/21/07
     */
    
private function __set ($name$val) {        
        
$this->properties[$name] = $val;
    }
    
    
/**
     * Unset one of our properties
     * 
     * @param string $name
     * @return void
     * @access private
     * @since 8/21/07
     */
    
private function __unset ($name) {        
        unset(
$this->properties[$name]);
    }
    
    
/**
     * Load the info
     * 
     * @return void
     * @access private
     * @since 11/7/06
     */
    
private function loadInfo () {
        if (!
$this->loaded) {
            
$photoset $this->callMethod('flickr.photosets.getInfo'
                array(
'photoset_id' => $this->id));
            
//             print "<pre>".htmlentities($photoset->ownerDocument->saveXML($photoset))."</pre>";
            
            
$this->owner $photoset->getAttribute('owner');
            
$this->numPhotos $photoset->getAttribute('photos');
            
            
$this->title "";
            if (
$node $this->getChild($photoset'title'))
                if (
$node->firstChild)
                    
$this->title $node->firstChild->data;
                
            
            
$this->description "";
            if (
$node $this->getChild($photoset'description'))
                if (
$node->firstChild)
                    
$this->description $node->firstChild->data;
                
            
            
$this->loaded true;
            
//             print "myproperties: "; var_dump($this->properties);
        
}
    }
    
    
/**
     * Answer true if this photoset has more photos
     * 
     * @return boolean
     * @access public
     * @since 11/7/06
     */
    
function hasNextPhoto () {
        return (
$this->currentPhoto $this->numPhotos);
    }
    
    
/**
     * Answer the next photo
     * 
     * @return mixed object or false
     * @access public
     * @since 11/7/06
     */
    
function nextPhoto () {
        if (!
$this->hasNextPhoto())
            return 
false;
        
        
// Load a new page if needed
        
if (!isset($this->currentPage
            || (
$this->currentPhoto &&  $this->currentPhoto 100 == 0))
        {
            
$this->currentPageNumber++;
            
$this->currentPage $this->callMethod('flickr.photosets.getPhotos'
                array(
                    
'photoset_id' => $this->id,
                    
'per_page' => 100,
                    
'page' => $this->currentPageNumber
                
));
            
//            print "<hr/>";
//            print "<pre>";
//            print_r($this->_currentPage);
//            print "</pre>";
        
}
        
        
$photoNode $this->currentPage->getElementsByTagName('photo')->item($this->currentPhoto 100);
        
$photo = new Photo($this->api
                            
$photoNode->getAttribute('id'),
                            
$photoNode->getAttribute('server'),
                            
$photoNode->getAttribute('secret'),
                            
$photoNode->getAttribute('title'));
        
        if (
$photoNode->getAttribute('isprimary'))
            
$this->primaryPhoto $photo;
        
        
$this->currentPhoto++;
        return 
$photo;
    }
    
    
/**
     * Accept a visitor
     * 
     * @param ref object $visitor
     * @return mixed
     * @access public
     * @since 11/7/06
     */
    
function acceptVisitor $visitor ) {
        
$result $visitor->visitPhotoset($this);
        return 
$result;
    }
}

/**
 * A Photo.
 * 
 * @since 11/7/06
 * @package com.adamfranco.flickr
 * 
 * @copyright Copyright &copy; 2006, Adam Franco
 * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License (GPL)
 *
 * @version $Id$
 */
class Photo
    
extends FlickrObject
{

    
/**
     * @var string $serverId; The Id of the server the photo should be fetched from 
     * @access private
     * @since 8/21/07
     */
    
private $serverId;
    
    
/**
     * @var string $secret;  
     * @access private
     * @since 8/21/07
     */
    
private $secret;
    
    
/**
     * @var string $id;  
     * @access private
     * @since 8/21/07
     */
    
private $id;
    
    
/**
     * @var array $properties;  
     * @access private
     * @since 8/21/07
     */
    
private $properties = array();
    
    
/**
     * @var boolean $loaded;  
     * @access private
     * @since 8/21/07
     */
    
private $loaded false;

    
/**
     * Constructor
     * 
     * @param <##>
     * @return <##>
     * @access public
     * @since 11/7/06
     */
    
public function __construct (Flickr_API $api$id$serverId null$secret null$title null$origFormat null) {
        if (!
preg_match('/^[0-9]+$/'$id))
            throw new 
Exception("Invalid photo set id, '$id', passed.");
        
        
$this->api $api;
        
$this->id $id;
        
        if (
$serverId)
            
$this->serverId $serverId;
        if (
$secret)
            
$this->secret $secret;
        if (
$title)
            
$this->title $title;
        if (
$origFormat)
            
$this->origFormat $origFormat;
    }
    
        
/**
     * Answer one of our properties
     * 
     * @param string $name
     * @return mixed
     * @access public
     * @since 8/21/07
     */
    
public function __get ($name) {
        
$this->loadInfo();
        if (!isset(
$this->properties[$name]))
            throw new 
Exception("Cannot access unknown property, '$name'.");
        
        return 
$this->properties[$name];
    }
    
    
/**
     * Answer true if this attribute is set
     * 
     * @param string $name
     * @return boolean
     * @access public
     * @since 8/21/07
     */
    
public function __isset ($name) {
        
$this->loadInfo();
        if (isset(
$this->properties[$name]))
            return 
true;
        else
            return 
false;
    }
    
    
/**
     * Set one of our properties
     * 
     * @param string $name
     * @return void
     * @access private
     * @since 8/21/07
     */
    
private function __set ($name$val) {        
        
$this->properties[$name] = $val;
    }
    
    
/**
     * Unset one of our properties
     * 
     * @param string $name
     * @return void
     * @access private
     * @since 8/21/07
     */
    
private function __unset ($name) {        
        unset(
$this->properties[$name]);
    }
    
    
/**
     * Answer the URL of the image
     * 
     * @param string $size [square, thumb, small, medium, large, original]
     * @return string
     * @access public
     * @since 11/7/06
     */
    
function getImageUrl ($size 'small') {
        if (!isset(
$this->serverId) || !isset($this->secret))
            
$this->loadInfo();
            
        switch (
$size) {
            case 
'medium':
                return 
'http://static.flickr.com/'.$this->serverId.'/'
                    
.$this->id.'_'.$this->secret.'.jpg';
            case 
'original':
                
$this->loadInfo();
                return 
'http://static.flickr.com/'.$this->serverId.'/'
                    
.$this->id.'_'.$this->secret.'_o.'.$this->origFormat;
            case 
'square':
                
$key 's';
                break;
            case 
'thumb':
                
$key 't';
                break;
            case 
'small':
                
$key 'm';
                break;
            case 
'large':
                
$key 'b';
                break;
        }
        
        return 
'http://static.flickr.com/'.$this->serverId.'/'
                    
.$this->id.'_'.$this->secret.'_'.$key.'.jpg';
    }
    
    
/**
     * Load the info
     * 
     * @return void
     * @access private
     * @since 11/7/06
     */
    
private function loadInfo () {
        if (!
$this->loaded) {
            
$this->loaded true;
            
            
$photo $this->callMethod('flickr.photos.getInfo'
                array(
'photo_id' => $this->id));
            
//            print "<hr/>";
//            print "<pre>";
//            print_r($photo);
//            print "</pre>";
            
            // License
            
$this->licenseId $photo->getAttribute('license');
            
$this->serverId $photo->getAttribute('server');
            
$this->secret $photo->getAttribute('secret');
            
$this->origFormat $photo->getAttribute('originalformat');
            
            
// Owner info
            
if ($node $this->getChild($photo'owner')) {
                
$this->ownerId $node->getAttribute('nsid');
                
$this->ownerUserName $node->getAttribute('username');
                
$this->ownerRealName $node->getAttribute('realname');
                
$this->ownerLocation $node->getAttribute('location');
            }
            
            
// Title
            
if ($node $this->getChild($photo'title')) {
                if (
$node->firstChild)
                    
$this->title $node->firstChild->data;
            }
            
            if (!isset(
$this->title))
                
$this->title '';
            
            
            
// Description
            
if ($node $this->getChild($photo'description')) {
                if (
$node->firstChild)
                    
$this->description $node->firstChild->data;
            }
            
            if (!isset(
$this->description))
                
$this->description '';
            
            
// Authorizations
            
if ($node $this->getChild($photo'visibility')) {
                
$this->isPublic $node->getAttribute('ispublic');
                
$this->isFriend $node->getAttribute('isfriend');
                
$this->isFamily $node->getAttribute('isfamily');
            }
            
            
// Dates
            
if ($node $this->getChild($photo'dates')) {
                
$this->datePosted $node->getAttribute('posted');
                
$this->dateLastUpdated $node->getAttribute('lastupdate');
                
$this->dateTaken $node->getAttribute('taken');
            }
            
            
// Tags
            
$tags = array();
            if (
$node $this->getChild($photo'tags')) {
                foreach (
$node->childNodes as $tagNode) {
                    if (
$tagNode->nodeType == XML_ELEMENT_NODE)
                        
$tags[$tagNode->firstChild->data] = $tagNode->getAttribute('raw');
                }
            }
            
$this->tags $tags;
            
            
// Location
            
if ($node $this->getChild($photo'location')) {
                
$this->latitude $node->getAttribute('latitude');
                
$this->longitude $node->getAttribute('longitude');
                
$this->locationAccuracy $node->getAttribute('accuracy');
            }
            
            
// GeoPerms
            
if ($node $this->getChild($photo'geoperms')) {
                
$this->geoIsPublic $node->getAttribute('ispublic');
                
$this->geoIsContact $node->getAttribute('iscontact');
                
$this->geoIsFriend $node->getAttribute('isfriend');
                
$this->geoIsFamily $node->getAttribute('isfamily');
            }
            
            
// Urls
            
if ($node $this->getChild($photo'urls')) {
                
$this->photoPageUrl $node->getElementsByTagName('url')->item(0)->firstChild->data;
            }            
        }
    }
    
    
/**
     * Accept a visitor
     * 
     * @param ref object $visitor
     * @return mixed
     * @access public
     * @since 11/7/06
     */
    
function acceptVisitor $visitor ) {
        
$result $visitor->visitPhoto($this);
        return 
$result;
    }
}

/**
 * General methods needed by all Flickr objects
 * 
 * @since 11/7/06
 * @package com.adamfranco.flickr
 * 
 * @copyright Copyright &copy; 2006, Adam Franco
 * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License (GPL)
 *
 * @version $Id$
 */
class FlickrObject {
    
    
/**
     * @var object Flickr_API $api;  
     * @access private
     * @since 8/21/07
     */
    
protected $api;
        
    
/**
     * Call a method
     * 
     * @param string $method
     * @param array $parameters
     * @return object
     * @access private
     * @since 11/7/06
     */
    
function callMethod ($method$parameters) {
        
$doc $this->api->callMethod($method$parameters);
        if (!
$doc)
            throw new 
FlickrException($this->api);
        
        
$response $doc->getElementsByTagName('rsp')->item(0);
        
        if (
$response && $response->getAttribute('stat') == 'ok') {
//             print "<pre>";
//             ReflectionClass::export($response);
//             print "</pre>";

//             print "<hr/><pre>Method: $method\n\tParams: ".print_r($parameters, true)."\n".($doc->saveXML())."</pre>";
            
            
foreach ($response->childNodes as $child) {
                if (
$child->nodeType == XML_ELEMENT_NODE)
                    return 
$child;
            }
            throw new 
Exception("No node of type zero found in <pre>".htmlentities($doc->saveXML($response))."</pre>");
        } else
            throw new 
FlickrException($this->api"<pre>$method\n".print_r($parameterstrue)."</pre>");
    }
    
    
/**
     * Answer the child with the name given or false if not available
     * 
     * @param object $node
     * @param string name
     * @return mixed
     * @access public
     * @since 11/8/06
     */
    
function getChild $node$name ) {
        foreach (
$node->childNodes as $child) {
            if (
$child->nodeName == $name)
                return 
$child;
        }
        
        
$false false;
        return 
$false;
    }
}

/**
 * This exception also handles api errors.
 * 
 * @since 8/20/07
 * @package com.adamfranco.flickr
 * 
 * @copyright Copyright &copy; 2007, Adam Franco
 * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License (GPL)
 *
 * @version $Id$
 */
class FlickrException
    
extends Exception
{    
    
/**
     * Construct this message.
     * 
     * @param object Flickr_API $api
     * @param string $extraMessage
     * @return void
     * @access public
     * @since 8/21/07
     */
    
public function __construct(Flickr_API $api$extraMessage "") {
        
parent::__construct($api->getErrorMessage().$extraMessage$api->getErrorCode());
    }
}

/**
 * Prints out the photoset as an html page
 * 
 * @since 11/7/06
 * @package com.adamfranco.flickr
 * 
 * @copyright Copyright &copy; 2006, Adam Franco
 * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License (GPL)
 *
 * @version $Id$
 */
class HtmlVisitor {
    
    
/**
     * @var string $photoSize; 
     * @access private
     * @since 8/22/07
     */
    
private $photoSize 'medium';
    
    
/**
     * Set the photo size
     * 
     * @param string $size
     * @return void
     * @access public
     * @since 8/22/07
     */
    
public function setSize ($size) {
        
$sizes = array('square''thumb''small''medium''large''original');
        if (!
in_array($sizes$size))
            throw new 
Exception("Unknown size, '$size'.");
        
        
$this->photoSize $size;
    }

    
/**
     * Print out the photoset
     * 
     * @return void
     * @access public
     * @since 11/7/06
     */
    
function visitPhotoset $photoset ) {
        print 
"
<html>
<head>
    <title>"
.$photoset->title."</title>
</head>
<body>
        
"
;
        print 
"<h1>".$photoset->title."</h1>" ;
        print 
"(".$photoset->numPhotos." photos)<br/>";
        print 
$photoset->description;
        
        while (
$photoset->hasNextPhoto()) {
            
$photo $photoset->nextPhoto();
            
$photo->acceptVisitor($this);
        }
        print 
"
</body>
</html>"
;
    }
    
    
/**
     * Print out the photo
     * 
     * @return void
     * @access public
     * @since 11/7/06
     */
    
function visitPhoto $photo ) {
        print 
"<h2>".$photo->title."</h2>" ;
        print 
"<img src='".$photo->getImageUrl($this->photoSize)."' /><br/>";
        print 
$photo->description;
        print 
"<pre>";
        foreach (
get_object_vars($photo) as $key => $val)
            print 
"\n\t".$key." = ".$val;
        print 
"</pre>";
    }
}

/**
 * This class overloads some of the PEAR Flickr API methods to return a DomDocument
 * instead of using XML_tree
 * 
 * @since 8/22/07
 * @package com.adamfranco.flickr
 * 
 * @copyright Copyright &copy; 2007, Adam Franco
 * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License (GPL)
 *
 * @version $Id$
 */
class AdamsFlickr_API
    
extends Flickr_API
{
        
    function 
callMethod($method$params = array()){
        
$this->_err_code 0;
        
$this->_err_msg '';
        
        
#
        # create the POST body
        #
        
        
$p $params;
        
$p['method'] = $method;
        
$p['api_key'] = $this->_cfg['api_key'];
        
        if (
$this->_cfg['api_secret']){
        
            
$p['api_sig'] = $this->signArgs($p);
        }
        
        
        
$p2 = array();
        foreach(
$p as $k => $v){
            
$p2[] = urlencode($k).'='.urlencode($v);
        }
        
        
$body implode('&'$p2);
        
        
        
#
        # create the http request
        #
        
        
$req =& new HTTP_Request($this->_cfg['endpoint'], array('timeout' => $this->_cfg['conn_timeout']));
        
        
$req->_readTimeout = array($this->_cfg['io_timeout'], 0);
        
        
$req->setMethod(HTTP_REQUEST_METHOD_POST);
        
$req->addRawPostData($body);
        
        
$req->sendRequest();
        
        
$this->_http_code $req->getResponseCode();
        
$this->_http_head $req->getResponseHeader();
        
$this->_http_body $req->getResponseBody();
        
        if (
$this->_http_code != 200){
        
            
$this->_err_code 0;
        
            if (
$this->_http_code){
                
$this->_err_msg "Bad response from remote server: HTTP status code $this->_http_code";
            }else{
                
$this->_err_msg "Couldn't connect to remote server";
            }
        
            return 
0;
        }
        
        
/*********************************************************
         * Code below has been changed by Adam
         *********************************************************/
        #
        # create xml DomDocument
        # 
        #
        
        
$doc = new DomDocument();
        
$doc->loadXML($this->_http_body);
                
        
        
#
        # check we got an <rsp> element at the root
        #
        
        
if ($doc->documentElement->nodeName != 'rsp'){
        
            
$this->_err_code 0;
            
$this->_err_msg "Bad XML response";
        
            return 
0;
        }
        
        
        
#
        # stat="fail" ?
        #
        
        
if ($doc->documentElement->getAttribute('stat') == 'fail'){
        
            
$error $doc->getElementsByTagName('err')->item(0);
        
            
$this->_err_code $error->getAttribute('code');
            
$this->_err_msg $error->getAttribute('msg');
        
            return 
0;
        }
        
        
        
#
    &