Table of Contents
Photo Organizer Webservice
By Luud Heck, April 2007
NOTE: at this moment this page is little more than a dump of some things I've tested and proven to work. I have made further advances already and will update this page as soon as possible.
Introduction
I've long wanted to include photos in my web pages without having to copy them over from PO. It is much nicer when you can include the images straight from PO. In a way this has long been possible by abusing the image.display.php file in your page. However, it is a rather rigid method and only allows to include a single photo at a time.
Below I explain how I publish my websites with MediaWiki and include Photo Organizer photos and albums in my wiki pages. The crux is that the information about photos and albums will be provided by Photo Organiser as a SOAP based webservice.
MediaWiki
Homepages in MediaWiki
MediaWiki can be very useful for maintaining a website, even if your website is not an encyclopedia like wikipidia. Here are some examples and resources to show you how:
- A List Apart (This explains the technique used in the monobook mediawiki skin.)
The above in nice and all, but what has it to do with Photo Organizer?
Well, assuming we are creating our own webpages in MediaWiki, how are we going to include our photos and albums from Photo Organizer in our pages? In case of MediaWiki, this is not very hard. All we have to do is create an extension for MediaWiki that drags in the photos and albums from Photo Organizer. The good thing is that Photo Organizer and MediaWiki do not need to be on the same host. But we come back to that later.
MediaWiki Extension
Creating a MediaWiki extension is quite easy and explained in the MediaWiki documentation.
For our purpose I extended the MediaWiki markup with the <po> tag. So including Photo Organizer in a wiki page will look like:
= My holiday trip = Hello all, here are some photos from my holiday trip: <po mode="album" style="thumbnail" id="163" />
All we have to do is include a simple <po> element with some attributes telling our extension what to include (in this case an album with id 163 in thumbnail view).
Here is an example MediaWiki extension for Photo Organizer. This example just generates some fixed html and does not retrieve any information from the Photo Organizer webservice yet. It is just to show how extend the wiki markup.
<?php /* * Photo Organizer Extension for MediaWiki. * * Copyright (C) 2007 Luud Heck * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; version 2 * of the License. * * This extension is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * */ /* * Purpose * ------- * * The purpose of this extension is to allow the integrations of individual * photos or complete albums stored in Photo Organizer into MediaWiki. * * * Function * -------- * * The photos are served by Photo Organizer. The PO installation does not need * to be on the same webserver as the wiki. * * The extension will retrieve the reference information from the PO server * through a SOAP connection and generate HTML to insert into the wiki page. * * The extension will define the new tag "po" in the form * <po> some text </po> * the function registered by the extension gets the text between the * tags as input and can transform it into arbitrary HTML code. * Note: The output is not interpreted as WikiText but directly * included in the HTML output. So Wiki markup is not supported. * * * Activating the extension * ------------------------ * * To activate the extension, include it at the end of LocalSettings.php: * * require_once("extensions/PhotoOrganizer.php"); * * * Usage * ----- * */ $wgExtensionFunctions[] = "wfPhotoOrganizerExtension"; /* * Register the Photo Organizer Extension */ function wfPhotoOrganizerExtension() { global $wgParser; /* Register the extension with the WikiText parser. * * The first parameter is the name of the new tag. * In this case it defines the tag <po> ... </po> * * The second parameter is the callback function for * processing the text between the tags. */ $wgParser->setHook( "po", "renderPhotoOrganizer" ); } /* * The callback function for converting the input text to HTML output * * This extension will simply discard any text in the <po> tag, i.e. $input * will be ignored. * * However, the attributes of the tag are parsed as provided in the $argv * parameter. * * The following paramters are accepted: * * mode Specifies the type of PO inclusion. * Possible values: * photo: include a single photo * album: include a complete album * style Specifies the style to use for rendering. * Possible values: * thumbnail: photos are displayed as thumbnail images * preview : photos are displayes as preview * (photo mode only) * list : photos are only displayed by their name * (album mode only) * id Specifies the database reference id of the photo or album. * * A typical use of the Photo Organizer tag will look like: * * <po mode="photo" style="preview" id="163" /> */ function renderPhotoOrganizer( $input, $argv, &$parser ) { /* * Disable caching for this extension as we want the latest information * that is available from Photo Organizer. */ $parser->disableCache(); /* * For testing, just render a heading 1 and display the attributes */ $output = "<h1>Photo Organizer Extension</h1>\n"; $output .= "<pre>\n"; $output .= print_r( $argv, true ); $output .= "</pre>\n"; /* * Make sure we are alone: */ $output .= "<div style=\"clear:both;\"></div>\n"; /* * Start the containing box for the album */ $output .= "<div class=\"po_album\">\n"; /* * Float all thumbnails inside the album div * * Getting images centered vertically is not too trivial. * * Images are inline elements. The css way would be to set the display * style of the container div to table-cell and use the vertical-align * property set to middle. However, not all browsers support this * correctly (note: IE). A solution is to embed each image in a single * cell table. This table inherits it's size from it's container div and * can have the td element's vertical-align property set to middle. * * The html will now look as: * * <div class="po_thumnnail" style="width:200px; height:200px"> * <table class="po_thumbnail"> * <tr class="po_thumbnail"> * <td class="po_thumbnail"> * <img class="po_thumbnail" src="..." /> * </td> * </tr> * </table> * </div> * * And the CSS for this will look like * * .po_thumnail { * } * * div.po_thumbnail { * float: left; * border: 1px solid gray; * padding: 3px; * margin: 3px; * } * * table.po_thumbnail, tr.po_thumbnail, td.po_thumbnail { * * border: 0px; * margin: 0px; * padding: 0px; * widht: 100%; * height: 100%; * } * * td.po_thumnail { * text-align: center; * vertical-align: middle; * } */ for ( $i=1; $i<40; $i++ ) { $output .= "<div class=\"po_thumbnail\" style=\"width:176px; height:176px;\">"; $output .= "<table class=\"po_thumbnail\"><tr class=\"po_thumbnail\"><td class=\"po_thumbnail\">"; $output .= "<img class=\"po_thumbnail\" src=\"http://localhost/po/image.display.php?image=$i&size=1\"/>"; $output .= "</td></tr></table>"; $output .= "</div>\n"; } /* * Make the album div grow to the size of it's contents */ $output .= "<div style=\"clear:both;\"></div>\n"; /* * Close the album div */ $output .= "</div>\n"; return $output; } ?>
Here is the CSS file that needs to be imported in the wiki skin CSS file (use the import directive and place it at the start of the main.css file in the monobook skin for example).
/* * Photo Organizer Extension Styling */ /* * Do not float the album container, but keep it an inline element! */ div.po_album { float: none; width: 100%; /* background-color: yellow; border: 1px solid red; */ clear:both; } .po_thumbnail { margin: 0px; padding: 0px; } /* * Make sure to float the thumnails (left or right). */ div.po_thumbnail { float: left; border: 0px solid gray; padding: 3px; margin: 3px; } table.po_thumbnail, tr.po_thumnail, td.po_thumnail { /* border: 1px solid red; */ border: 0px; padding: 0px; margin: 0px; width: 100%; height: 100%; } td.po_thumbnail { text-align: center; vertical-align: middle; } img.po_thumbnail { border: 1px solid black; }
Photo Organizer SOAP interface
Having the basics of creating a MediaWiki extension for Photo Organizer working, lets look at how we can make this extension retrieve information from the Photo Organizer database.
PHP-SOAP
Recent versions of PHP include good support for the SOAP protocol in the form of the PHP-SOAP extension of PHP.
Now, before you start experimenting, take this TIP and follow it!
Make sure to disable WSDL caching in both client and server, and check every time that it is still there!
ini_set('soap.wsdl_cache_enabled', '0');
I thought I had it disabled, but trying things and starting over once in a while resulted in not having WSDL caching disabled in my client. This oversight has cost me a couple of evenings getting thing to work right.
Next tip:
Validate your WSDL file!
If you have Mono installed on your Linux box you can use the wsdl command.
Here is an example static serverice that generates some kind of Photo Organizer album information. This is just a demonstration, not a functional solution for PO yet. Just put in in a directory wsdl-po on your PHP with SOAP enabled webserver and it should work if you browse to the client.php file.
photoorganizer.wsdl
<?xml version='1.0' encoding='UTF-8' ?> <definitions name='PhotoOrganizer' targetNamespace='urn:PhotoOrganizer' xmlns='http://schemas.xmlsoap.org/wsdl/' xmlns:tns='urn:PhotoOrganizer' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:soap='http://schemas.xmlsoap.org/wsdl/soap/' xmlns:wsdl='http://schemas.xmlsoap.org/wsdl/' xmlns:soapenc='http://schemas.xmlsoap.org/soap/encoding/'> <types xmlns='http://schemas.xmlsoap.org/wsdl/'> <schema xmlns='http://www.w3.org/2001/XMLSchema' targetNamespace='urn:PhotoOrganizer'> <complexType name="photoInfo"> <all> <element name="id" type="xsd:int"/> <element name='title' type='xsd:string'/> </all> </complexType> <complexType name="photoAlbum"> <complexContent> <restriction base="soapenc:Array"> <attribute ref="soapenc:arrayType" wsdl:arrayType="tns:photoInfo[]"/> </restriction> </complexContent> </complexType> </schema> </types> <message name='getAlbumRequest'> <part name='id' type='xsd:int'/> </message> <message name='getAlbumResponse'> <part name='album' type='tns:photoAlbum'/> </message> <portType name='PhotoOrganizerPort'> <operation name='getAlbum'> <input message='tns:getAlbumRequest'/> <output message='tns:getAlbumResponse'/> </operation> </portType> <binding name='PhotoOrganizerBinding' type='tns:PhotoOrganizerPort'> <soap:binding style='rpc' transport='http://schemas.xmlsoap.org/soap/http'/> <operation name='getAlbum'> <soap:operation soapAction='urn:PhotoOrganizer#getAlbum'/> <input> <soap:body namespace='urn:PhotoOrganizer' use='encoded' encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/> </input> <output> <soap:body namespace='urn:PhotoOrganizer' use='encoded' encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/> </output> </operation> </binding> <service name='PhotoOrganizerService'> <port name='PhotoOrganizerPort' binding='tns:PhotoOrganizerBinding'> <soap:address location='http://localhost/wsdl-po/server.php'/> </port> </service> </definitions>
common.php
<?php $wsdlfile = "photoorganizer.wsdl"; $photoClassMap = array ( "photoInfo" => "C_PhotoInfo" ); ?>
photoinfo.php
<?php /** * The C_PhotoInfo class contains details for a single photo. */ class C_PhotoInfo { /** @var int */ public $id; /** @var string */ public $title; } ?>
photoserver.php
<?php /** * Manages remote requests for Photo Organizer * * This is the server for our Photo Organizer WebService. */ class C_PhotoOrganizerService { /** * Returns the list of photos in the specified album. * * @return photoInfoList[] */ public function getAlbum($id) { $l_PhotoInfoList = array(); // Let's make up some test data: $l_PhotoInfo = new C_PhotoInfo(); $l_PhotoInfo->id = 42; $l_PhotoInfo->title = 'Photo Title 1 in album ['.$id.'].'; $l_PhotoInfoList[0] = $l_PhotoInfo; $l_PhotoInfo = new C_PhotoInfo(); $l_PhotoInfo->id = 1024; $l_PhotoInfo->title = 'Photo Title 2 in album ['.$id.'].'; $l_PhotoInfoList[1] = $l_PhotoInfo; return $l_PhotoInfoList; // return $l_PhotoInfo; } } ?>
server.php
<?php include_once "photoinfo.php"; include_once "photoserver.php"; include_once "common.php"; ini_set('soap.wsdl_cache_enabled', '0'); $s = new SoapServer( 'photoorganizer.wsdl', array( 'trace' => 1, 'classmap' => $photoClassMap ) ); $s->setClass('C_PhotoOrganizerService'); $s->handle(); ?>
client.php
<html> <head> </head> <body> <pre> <?php include_once "photoinfo.php"; include_once "common.php"; class client { private $link; public function __construct($wsdl) { global $photoClassMap; $this->link = new SoapClient( $wsdl, array( 'trace' => 1, 'classmap' => $photoClassMap ) ); } public function getAlbum($id) { // $l_Album = array(); try { $l_Album = $this->link->getAlbum($id); echo "var_dump:\n=========\n"; var_dump ($l_Album); echo "Iterate array\n=============\n"; // If only one element is returned, an array won't be built if (is_array($l_Album)) { echo "It's an array!"; $i = 1; foreach ($l_Album as $photo) { echo "\n\nPrinting element $i:\n-------------------\n"; print_r ($photo); $i++; } echo "\n"; } else { print_r ($l_Album); } } catch (SoapFault $sf) { print_r ($sf->getMessage()); $this->debug(); } $this->debug(); } public function debug() { echo "Types:\n"; print_r ($this->link->__getTypes()); echo "Request:\n"; echo htmlspecialchars(str_replace('>', ">\n", $this->link->__getLastRequest())), "\n"; echo "Response:\n"; echo htmlspecialchars(str_replace('>', ">\n", $this->link->__getLastResponse())), "\n"; } } ini_set('soap.wsdl_cache_enabled', '0'); echo "Running client...\n\n"; echo "Calling server...\n"; $client = new client($wsdlfile); $client->getAlbum(27); echo "\nDone.\n"; ?> </pre> </body> </html>