silverstripe-picasaweb 

silverstripe-picasaweb Commit Details

Date:2013-05-03 00:04:18 (7 years 11 months ago)
Author:Nicola Fontana
Branch:master
Commit:d1f1582329e6f47f72307e0014adc092ea872f6f
Parents: e76ae3596448eb8a4c7797eaeb3e78803d2f04ea
Message:Added album and tested photo generation

Changes:
Atemplates/Picasaweb_album.ss (full)
Atemplates/Picasaweb_photo.ss (full)
M_config.php (1 diff)
Mcode/Picasaweb.php (2 diffs)
Mtemplates/Picasaweb_image.ss (1 diff)

File differences

_config.php
11
22
3
43
5
6
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php
// TODO: Object::add_extension('SiteConfig', 'PicasawebConfig');
Object::add_extension('Page', 'PicasawebExtension');
ShortcodeParser::get()->register('picasa', array('Picasaweb', 'shortcode'));
/**
* @package silverstripe-picasaweb
*
* Provides different ways to interface a SilverStripe based project to
* the PicasaWeb service using developer API v.2.
*
* There are two kind of Picasaweb objects: photos and albums. They are
* identified by different links. An album is a Picasaweb link without a
* fragment identifier, such as https://picasaweb.google.com/109188999413540243075/ADGCairoCanvasSoftwareLibrary.
* A photo is a Picasaweb album link with a fragment identifier appended
* such as https://picasaweb.google.com/109188999413540243075/ADGCairoCanvasSoftwareLibrary#5859399409666509250.
*
* The editing can be enhanced to include a [picasa] short-code, so
* [picasa]https://picasaweb.google.com/109188999413540243075/ADGCairoCanvasSoftwareLibrary[/picasa]
* will be rendered as an album.
*
* <code>
* ShortcodeParser::get()->register('picasa', array('Picasaweb', 'shortcode'));
* </code>
*
* A specific page type can be augmented by providing a onBeforeWrite()
* callback so that the user can just paste a Picasaweb link inside the
* HTML code to get a specific photo or album element. Be careful: in
* this case you CANNOT use the Picasaweb link in your templates
* otherwise the expansion will be triggered recursively.
*
* <code>
* Object::add_extension('Page', 'PicasawebExtension');
* </code>
*
* The expansion of the links happens before storing the HTML code in
* the database, avoiding further Picasaweb requests on page hits.
*
*
* @todo implement an Object::add_extension('SiteConfig', 'PicasawebConfig')
* for customizing the global Picasaweb environment, that is
* photo and/or album rendering activation.
*/
code/Picasaweb.php
22
33
44
5
5
6
67
78
8
9
109
11
12
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
13119
14
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
15135
16
136
137
138
139
140
141
142
143
144
145
17146
18147
19
20
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
21178
22179
23180
24
181
25182
26
27
28
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
29260
30261
31
262
32263
33264
34
35
265
266
267
268
269
270
36271
37
38
272
273
274
39275
40
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
41296
42297
43
298
44299
45300
46301
......
65320
66321
67322
68
323
324
69325
70326
71327
72
73
328
329
74330
75
331
76332
77333
class Picasaweb {
public static $render_image = true;
public static $render_photo = true;
public static $render_album = true;
const IMAGE_REGEX = '#https://picasaweb.google.com/\S*/photo/\S*#';
const ALBUM_REGEX = '#https://picasaweb.google.com/\S*#';
private static function renderImage($url) {
$viewer = new SSViewer('Picasaweb_image');
/**
* Get a SimpleXMLElement from the wild.
*
* Fetches the resource found at $url (supposedly an XML file)
* and convert it to the SimpleXMLElement representation of the
* whole tree.
*
* @param String $url The URL of the resource.
* @return SimpleXMLElement The DOM of the resource.
*/
private static function xmlFetch($url) {
$xml = file_get_contents($url);
if (empty($xml))
return null;
// I have only one life...
$xml = str_replace(' xmlns=', ' fakens=', $xml);
return simplexml_load_string($xml);
}
/**
* Pick a specific set of fields from a SimpleXMLElement.
*
* Gets the values of a set of fields and returns the result in an
* ArrayData. The values are collected from a child of $element or
* from an attribute of $element with the requested field name.
*
* The field set is specified in $fields. If it is a simple array
* value ('value') this value is used as either as the key to look
* for in $element and as key to store the result in. If it is a
* key-value pair ('key' => 'value') 'key' is used as index for
* accessing $element and 'value' will be the key in the result.
*
* @param SimpleXMLElement $element The source element.
* @param Array $fields The fields to retrieve.
* @return ArrayData The result.
*/
private static function compoundData(SimpleXMLElement $element, Array $fields) {
$data = new ArrayData(array());
foreach ($fields as $src => $dst) {
// Do not overwrite yet defined fields
if ($data->hasField($dst))
continue;
// Try to get the value from the children and fallback
// to the attributes if not found there
$key = is_string($src) ? $src : $dst;
$node = isset($element->$key) ? $element->$key : null;
if (is_null($node) && isset($element->attributes()->$key))
$node = $element->attributes()->$key;
// Set in the data, if found
isset($node) and $data->setField($dst, (string) $node);
}
return $data;
}
/**
* Return the metadata of an image.
*
* Convenient shortcut to return typical image fields,
* such as src, width and height, from an XML element.
* The data is collected with self::compoundData(), so
* either children and attributes are considered.
*
* @param SimpleXMLElement $element The source element.
* @return ArrayData Image data.
*/
private static function imageMetadata(SimpleXMLElement $element) {
$fields = array('src', 'url' => 'src', 'width', 'height', 'size');
return self::compoundData($element, $fields);
}
/**
* Return the first match of an XPath pattern.
*
* Given a specific XPath, returns the first value or
* an empty string if the XPath does not match anything.
*
* @param SimpleXMLElement $element The source element.
* @param String An xpath pattern.
* @return String The first match.
*/
private static function xquery(SimpleXMLElement $element, $xpath) {
$result = $element->xpath($xpath);
if (! is_array($result))
return '';
return (string) reset($result);
}
/**
* Return the metadata of a photo.
*
* Gets some metadata from a photo. The XML element is
* expected to be an XML photo entry as returned by the
* PicasaWeb API version 2.
*
* @param SimpleXMLElement $element The source element.
* @return ArrayData Photo metadata.
*/
private static function photoMetadata(SimpleXMLElement $element) {
if (empty($element))
return null;
$gphoto = $element->children('http://schemas.google.com/photos/2007');
$media = $element->children('http://search.yahoo.com/mrss/')->group;
$data = new ArrayData(array(
'url' => $url
'self' => self::xquery($element, 'link[@rel="self"]/@href'),
'canonical' => self::xquery($element, 'link[contains(@rel,"#canonical")]/@href'),
'photoid' => (string) $gphoto->id,
'albumid' => (string) $gphoto->albumid,
'published' => (string) $element->published,
'title' => (string) $element->title,
'summary' => (string) $element->summary,
'credit' => (string) $media->credit,
'keywords' => (string) $media->keywords,
'timestamp' => (string) $gphoto->timestamp,
// src is usually not defined in org (I suppose for copyright
// issues) but it is included here nonetheless
'org' => self::imageMetadata($gphoto),
'normal' => self::imageMetadata($media->content)
));
return $data->renderWith($viewer);
// Add any thumbnail element found, naming the items "thumbnail$n"
// with $n starting from 1
$n = 1;
foreach ($media->thumbnail as $element) {
$data->setField("thumbnail$n", self::imageMetadata($element));
++$n;
}
return $data;
}
private static function renderAlbum($url) {
return 'Rendering the album';
/**
* Return the metadata of an album.
*
* Gets some metadata from an album. The XML element is
* expected to be an XML album entry as returned by the
* PicasaWeb API version 2.
*
* @param SimpleXMLElement $element The source element.
* @return ArrayData Album data.
*/
static function albumMetadata(SimpleXMLElement $element) {
if (empty($element))
return null;
$gphoto = $element->children('http://schemas.google.com/photos/2007');
return new ArrayData(array(
'self' => self::xquery($element, 'link[@rel="self"]/@href'),
'userid' => (string) $gphoto->user,
'icon' => (string) $element->icon,
'title' => (string) $element->title,
'subtitle' => (string) $element->subtitle,
'author_name' => (string) $element->author->name,
'author_uri' => (string) $element->author->uri,
'name' => (string) $gphoto->name,
'location' => (string) $gphoto->location,
'timestamp' => (string) $gphoto->timestamp,
'numphotos' => (string) $gphoto->numphotos,
'nickname' => (string) $gphoto->nickname
));
}
/**
* Renders the given URL as a Picasaweb image or album.
* Check if a string is an ID.
*
* @param url
* The string returned is captured from print_r(). It is
* intended to be used for debugging purpose.
* Helper function to check if the given $str is a valid Picasaweb
* ID. A string is considered an ID if it contains only digits.
*
* @param String $str The string to check.
* @return Boolean true if $str contains only digits.
*/
private static function isId($str) {
return strlen($str) == strspn($str, '0123456789');
}
public static function photoUrl($user, $photo, $album = null) {
// At least user and photo are required to access a photo entry
if (! isset($user, $photo))
return null;
$user = "user/$user";
$photo = self::isId($photo) ? "photoid/$photo" : "photo/$photo";
$url = 'http://picasaweb.google.com/data/entry/api';
// The album is optional
if (isset($album)) {
$album = self::isId($album) ? "albumid/$album" : "album/$album";
$url = "$url/$user/$album/$photo";
} else {
$url = "$url/$user/$photo";
}
return $url;
}
public static function albumUrl($user, $album) {
// Both user and album are required to access an album
if (! isset($user, $album))
return null;
$user = "user/$user";
$album = self::isId($album) ? "albumid/$album" : "album/$album";
return "http://picasaweb.google.com/data/feed/api/$user/$album";
}
public static function photo($url) {
$tree = self::xmlFetch($url);
if (empty($tree))
return null;
return self::photoMetadata($tree);
}
public static function album($url) {
$tree = self::xmlFetch($url);
if (empty($tree))
return null;
$data = self::albumMetadata($tree);
if (empty($data))
return null;
// Add the photo list
$items = new ArrayList();
foreach ($tree->entry as $element)
$items->push(self::photoMetadata($element));
$data->setField('items', $items);
return $data;
}
/**
* Render Picasaweb photo or album.
*
* Given a Picasaweb URL, renders it as an HTML chunk that
* represents the album or a specific photo. The type of element
* depends on the format of the URL: if it has a fragment identifier
* it is a photo, otherwise it is an album.
*
* In case of error (the Picasaweb server is down, the API has
* changed or $url is invalid), null is returned.
*
* @param String $url The URL to render.
* @returns A ready to use HTML chunk or null on errors.
* @returns String A ready to use HTML chunk or null on errors.
*/
public static function render($url) {
if (self::$render_image && preg_match(self::IMAGE_REGEX, $url))
return self::renderImage($url);
// Try to clean up a bit the Picasaweb URL mess
$url = preg_replace('"\?[^#]*"', '', $url);
$match = array();
if (preg_match('"^https?://picasaweb.google.com/(\S*?)/(\S*?)(#(\S*?))?$"', $url, $match) != 1)
return null;
if (self::$render_album && preg_match(self::ALBUM_REGEX, $url))
return self::renderAlbum($url);
$user = @$match[1];
$album = @$match[2];
$photo = @$match[4];
return null;
if (empty($user) || empty($album)) {
// Both user and album are required
return null;
} elseif (empty($photo)) {
$viewer = new SSViewer('Picasaweb_album');
$data = self::album(self::albumUrl($user, $album));
} else {
$viewer = new SSViewer('Picasaweb_photo');
$data = self::photo(self::photoUrl($user, $photo, $album));
}
if (is_null($data))
return null;
// $url cannot be embedded as is: if included in a template it
// will be expanded recursively. This is the rationale behind
// the following scheme stripping.
$data->setField('url', preg_replace('"https?://"', '', $url));
return $data->renderWith($viewer);
}
public static function shortcode($arguments, $url = '') {
public static function shortcode($arguments, $url) {
$result = self::render($url);
if (is_null($result)) {
$url = htmlentities($url, ENT_COMPAT, 'UTF-8');
function onBeforeWrite() {
parent::onBeforeWrite();
if (empty($this->owner->Content))
$content = $this->owner->Content;
if (empty($content))
return;
// Pass every link to the callback: self::renderer() will
// modify only PicasaWeb links
$this->owner->Content = preg_replace_callback('#https?://\S*#',
// modify only valid PicasaWeb links
$this->owner->Content = preg_replace_callback('"https?://[-A-Za-z0-9+&@#/%?=~_()|!:,.;]*"',
'self::renderer',
$this->owner->Content);
$content);
}
};
templates/Picasaweb_album.ss
1
2
3
4
5
6
7
<% if items %>
<ul class="album"><% loop items %>
<li>
<% include Picasaweb_image %>
</li><% end_loop %>
</ul>
<% end_if %>
templates/Picasaweb_image.ss
1
2
3
4
1
<div style="float: right;" class="caption">
<a href="http://lh4.ggpht.com/-C5nYltm_9lY/UDYslCJB6AI/AAAAAAAADdg/75nHsCgFisE/000.jpeg" title="Coppa Ernesto della Torre 2012: Marco Pedrini e Mauro Bosio" style="float: none;" class="picasa2"><img height="192" width="288" alt="Coppa Ernesto della Torre 2012: Marco Pedrini e Mauro Bosio" src="http://lh4.ggpht.com/-C5nYltm_9lY/UDYslCJB6AI/AAAAAAAADdg/75nHsCgFisE/s288/000.jpeg"></a>
<p class="caption-title">Coppa Ernesto della Torre 2012: Marco Pedrini e Mauro Bosio</p>
</div>
<a href="$normal.src.ATT" title="$summary.ATT" class="picasa2"><% with thumbnail3 %><img width="$width" height="$height" alt="$Top.summary.ATT" title="$Top.url" src="$src"><% end_with %></a>
templates/Picasaweb_photo.ss
1
2
3
4
<div class="photo">
<% include Picasaweb_image %>
<p class="caption-title">$summary</p>
</div>

Archive Download the corresponding diff file

Branches