2 rcs_id('$Id: PhotoAlbum.php,v 1.15 2007-01-07 18:46:05 rurban Exp $');
4 Copyright 2003,2004,2005,2007 $ThePhpWikiProgrammingTeam
6 This file is part of PhpWiki.
8 PhpWiki is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
13 PhpWiki is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with PhpWiki; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 * Display an album of a set of photos with optional descriptions.
26 * @author: Ted Vinke <teddy@jouwfeestje.com>
27 * Reini Urban (local fs)
28 * Thomas Harding (slides mode, real thumbnails)
32 * src="http://server/textfile" or localfile or localdir
33 * mode=[normal|column|row|thumbs|tiles|list|slide]
39 * align=[center|left|right]
43 * "src": textfile of images or directory of images or a single image (local or remote)
44 * Local or remote e.g. http://myserver/images/MyPhotos.txt or http://myserver/images/
45 * or /images/ or Upload:photos/
46 * Possible content of a valid textfile:
47 * photo-01.jpg; Me and my girlfriend
49 * christmas.gif; Merry Christmas!
51 * Inside textfile, filenames and optional descriptions are seperated by
52 * semi-colon on each line. Listed files must be in same directory as textfile
53 * itself, so don't use relative paths inside textfile.
55 * "url": defines the the webpath to the srcdir directory (formerly called weblocation)
60 * - specify picture(s) as parameter(s)
61 * - limit amount of pictures on one page
62 * - use PHP to really resize or greyscale images (only where GD library supports it)
63 * (quite done for resize with "ImageTile.php")
66 * - reading height and width from images with spaces in their names fails.
68 * Fixed album location idea by Philip J. Hollenback. Thanks!
71 class ImageTile extends HtmlElement
74 function image_tile (/*...*/) {
75 $el = new HTML ('img');
76 $tag = func_get_args();
77 $path = DATA_PATH . "/ImageTile.php";
78 $params = "<img src=\"$path?url=". $tag[0]['src'];
79 if (!@empty($tag[0]['width']))
80 $params .= "&width=" . $tag[0]['width'];
81 if (!@empty($tag[0]['height']))
82 $params .= "&height=" . $tag[0]['height'];
83 if (!@empty($tag[0]['width']))
84 $params .= '" width="' . $tag[0]['width'];
85 if (!@empty($tag[0]['height']))
86 $params .= '" height="' . $tag[0]['height'];
88 $params .= '" alt="' . $tag[0]['alt'] . '" />';
89 return $el->raw ($params);
93 class WikiPlugin_PhotoAlbum
97 return _("PhotoAlbum");
100 function getDescription () {
101 return _("Displays a set of photos listed in a text file with optional descriptions");
104 function getVersion() {
105 return preg_replace("/[Revision: $]/", '',
106 "\$Revision: 1.15 $");
109 // Avoid nameclash, so it's disabled. We allow any url.
110 // define('allow_album_location', true);
111 // define('album_location', 'http://kw.jouwfeestje.com/foto/redactie');
112 // define('album_default_extension', '.jpg');
113 // define('desc_separator', ';');
115 function getDefaultArguments() {
116 return array('src' => '', // textfile of image list, or local dir.
117 'url' => '', // if src=localfs, url prefix (webroot for the links)
118 'mode' => 'normal', // normal|thumbs|tiles|list
119 // "normal" - Normal table which shows photos full-size
120 // "thumbs" - WinXP thumbnail style
121 // "tiles" - WinXP tiles style
122 // "list" - WinXP list style
123 // "row" - inline thumbnails
124 // "column" - photos full-size, displayed in 1 column
125 // "slide" - slideshow mode, needs javascript on client
126 'numcols' => 3, // photos per row, columns
127 'showdesc' => 'both', // none|name|desc|both
128 // "none" - No descriptions next to photos
129 // "name" - Only filename shown
130 // "desc" - Only description (from textfile) shown
131 // "both" - If no description found, then filename will be used
132 'link' => true, // show link to original sized photo
133 // If true, each image will be hyperlinked to a page where the single
134 // photo will be shown full-size. Only works when mode != 'normal'
135 'attrib' => '', // 'sort, nowrap, alt'
136 // attrib arg allows multiple attributes: attrib=sort,nowrap,alt
137 // 'sort' sorts alphabetically, 'nowrap' for cells, 'alt' to use
138 // descs instead of filenames in image ALT-tags
139 'bgcolor' => '#eae8e8', // cell bgcolor (lightgrey)
140 'hlcolor' => '#c0c0ff', // highlight color (lightblue)
141 'align' => 'center', // alignment of table
142 'height' => 'auto', // image height (auto|75|100%)
143 'width' => 'auto', // image width (auto|75|100%)
144 // Size of shown photos. Either absolute value (e.g. "50") or
145 // HTML style percentage (e.g. "75%") or "auto" for no special
147 'cellwidth'=> 'image', // cell (auto|equal|image|75|100%)
148 // Width of cells in table. Either absolute value in pixels, HTML
149 // style percentage, "auto" (no special action), "equal" (where
150 // all columns are equally sized) or "image" (take height and
151 // width of the photo in that cell).
152 'tablewidth'=> false, // table (75|100%)
153 'p' => false, // "displaythissinglephoto.jpg"
154 'h' => false, // "highlightcolorofthisphoto.jpg"
155 'duration' => 6, // in slide mode, in seconds
156 'thumbswidth' => 80 //width of thumbnails
159 // descriptions (instead of filenames) for image alt-tags
161 function run($dbi, $argstr, &$request, $basepage) {
163 extract($this->getArgs($argstr, $request));
165 $attributes = $attrib ? explode(",", $attrib) : array();
169 // check all parameters
170 // what type do we have?
173 $src = $request->getArg('pagename');
174 $error = $this->fromLocation($src, $photos);
176 $error = $this->fromFile($src, $photos, $url);
179 return $this->error($error);
182 if ($numcols < 1) $numcols = 1;
183 if ($align != 'left' && $align != 'center' && $align != 'right') {
186 if (count($photos) == 0) return;
188 if (in_array("sort", $attributes))
195 if ($mode == "column") {
200 // set some fixed properties for each $mode
201 if ($mode == 'thumbs' || $mode == 'tiles') {
202 $attributes = array_merge($attributes, "alt");
203 $attributes = array_merge($attributes, "nowrap");
204 $cellwidth = 'auto'; // else cell won't nowrap
205 if ($width == 'auto') $width = 70;
206 } elseif ($mode == 'list') {
209 if ($width == 'auto') $width = 50;
210 } elseif ($mode == 'slide' ) {
213 $numcols = count($photos);
215 while (list($key, $value) = each($photos)) {
216 list($x,$y,$s,$t) = @getimagesize($value['src']);
217 if ($height != 'auto') $y = $this->newSize($y, $height);
218 if ($width != 'auto') $y = round($y * $this->newSize($x, $width) / $x);
219 if ($x > $cell_width) $cell_width = $x;
220 if ($y > $tableheight) $tableheight = $y;
224 unset ($x,$y,$s,$t,$key,$value,$keep);
228 $duration = 1000 * $duration;
229 if ($mode == 'slide')
230 $row->pushContent(JavaScript("
232 function display_slides() {
234 cell0 = document.getElementsByName('wikislide' + j);
235 cell = document.getElementsByName('wikislide' + i);
236 if (cell0.item(0) != null)
237 cell0.item(0).style.display='none';
238 if (cell.item(0) != null)
239 cell.item(0).style.display='block';
241 if (cell.item(0) == null) i = 0;
242 setTimeout('display_slides()',$duration);
244 display_slides();"));
246 while (list($key, $value) = each($photos)) {
247 if ($p && basename($value["name"]) != "$p") {
250 if ($h && basename($value["name"]) == "$h") {
251 $color = $hlcolor ? $hlcolor : $bgcolor;
255 // $params will be used for each <img > tag
256 $params = array('src' => $value["name"],
257 'src_tile' => $value["name_tile"],
259 'alt' => ($value["desc"] != "" and in_array("alt", $attributes))
261 : basename($value["name"]));
262 if (!@empty($value['location']))
263 $params = array_merge($params, array("location" => $value['location']));
270 $value["desc"] = basename($value["name"]);
275 if (!$value["desc"]) $value["desc"] = basename($value["name"]);
279 // FIXME: get getimagesize to work with names with spaces in it.
280 // convert $value["name"] from webpath to local path
281 $size = @getimagesize($value["name"]); // try " " => "\\ "
282 if (!$size and !empty($value["src"])) {
283 $size = @getimagesize($value["src"]);
285 trigger_error("Unable to getimagesize(".$value["name"].")",
289 $newwidth = $this->newSize($size[0], $width);
290 if ($width != 'auto' && $newwidth > 0) {
291 $params = array_merge($params, array("width" => $newwidth));
293 if (($mode == 'thumbs' || $mode == 'tiles' || $mode == 'list')) {
294 if (!empty($size[0])) {
295 $newheight = round ($newwidth * $size[1] / $size[0]);
296 $params['width'] = $newwidth;
297 $params['height'] = $newheight;
298 } else $newheight = '';
299 if ($height == 'auto') $height=150;
302 $newheight = $this->newSize($size[1], $height);
303 if ($height != 'auto' && $newheight > 0) {
304 $params = array_merge($params, array("height" => $newheight));
309 $cell = array('align' => "center",
311 'class' => 'photoalbum cell',
312 'bgcolor' => "$color");
313 if ($cellwidth != 'auto') {
314 if ($cellwidth == 'equal') {
315 $newcellwidth = round(100/$numcols)."%";
316 } else if ($cellwidth == 'image') {
317 $newcellwidth = $newwidth;
319 $newcellwidth = $cellwidth;
321 $cell = array_merge($cell, array("width" => $newcellwidth));
323 if (in_array("nowrap", $attributes)) {
324 $cell = array_merge($cell, array("nowrap" => "nowrap"));
326 //create url to display single larger version of image on page
327 $url = WikiURL($request->getPage(),
328 array("p" => basename($value["name"])))
330 . basename($value["name"]);
332 $b_url = WikiURL($request->getPage(),
333 array("h" => basename($value["name"])))
335 . basename($value["name"]);
337 ? HTML::a(array("href" => "$url"), basename($value["desc"]))
338 : basename($value["name"]);
340 if ($mode == 'normal' || $mode == 'slide') {
341 if(!@empty($params['location'])) $params['src'] = $params['location'];
342 unset ($params['location'],$params['src_tile']);
343 $url_image = $link ? HTML::a(array("id" => basename($value["name"])),
344 HTML::a(array("href" => "$url"), HTML::img($params))) : HTML::img($params);
347 if (!@empty ($params['src_tile']))
348 $params['src'] = $params['src_tile'] ;
349 unset ($params['location'],$params['src_tile']);
350 $url_image = $link ? HTML::a(array("id" => basename($value["name"])),
351 HTML::a(array("href" => "$url"),
352 ImageTile::image_tile($params))) : HTML::img($params);
357 if(!@empty($params['location'])) $params['src'] = $params['location'];
358 unset ($params['location'],$params['src_tile']);
359 $url_image = $link ? HTML::a(array("id" => basename($value["name"])),
360 HTML::a(array("href" => "$b_url"), HTML::img($params))) : HTML::img($params);
363 $url_text = HTML::a(array("id" => basename($value["name"])),
365 // here we use different modes
366 if ($mode == 'tiles') {
369 HTML::div(array('valign' => 'top'), $url_image),
370 HTML::div(array('valign' => 'bottom'),
371 HTML::span(array('class'=>'boldsmall'),
374 HTML::span(array('class'=>'gensmall'),
380 } elseif ($mode == 'list') {
381 $desc = ($showdesc != 'none') ? $value["desc"] : '';
383 HTML::td(array("valign" => "top",
385 "bgcolor" => $color),
386 HTML::span(array('class'=>'boldsmall'),($url_text))));
388 HTML::td(array("valign" => "top",
390 "bgcolor" => $color),
391 HTML::span(array('class'=>'gensmall'),
399 HTML::td(array("valign" => "top",
401 "bgcolor" => $color),
402 HTML::span(array('class'=>'gensmall'),$desc)));
404 } elseif ($mode == 'thumbs') {
405 $desc = ($showdesc != 'none') ?
406 HTML::p(HTML::a(array("href" => "$url"),
411 // FIXME: no HtmlElement for fontsizes?
412 // rurban: use ->setAttr("style","font-size:small;")
413 // but better use a css class
414 HTML::span(array('class'=>'gensmall'),$desc)
416 } elseif ($mode == 'normal') {
417 $desc = ($showdesc != 'none') ? HTML::p($value["desc"]) : '';
421 // FIXME: no HtmlElement for fontsizes?
422 HTML::span(array('class'=>'gensmall'),$desc)
424 } elseif ($mode == 'slide') {
425 if ($newwidth == 'auto' || !$newwidth)
426 $newwidth = $this->newSize($size[0],$width);
427 if ($newwidth == 'auto' || !$newwidth)
428 $newwidth = $size[0];
429 if ($newheight != 'auto') $newwidth = round($size[0] * $newheight / $size[1]);
430 $desc = ($showdesc != 'none') ? HTML::p($value["desc"]) : '';
432 $cell=array('style' => 'display: block; '
433 . 'position: absolute; '
435 . 'margin-left: -'.round($newwidth / 2).'px;'
436 . 'text-align: center; '
437 . 'vertical-align: top',
438 'name' => "wikislide".$count);
440 $cell=array('style' => 'display: none; '
441 . 'position: absolute ;'
443 . 'margin-left: -'.round($newwidth / 2).'px;'
444 . 'text-align: center; '
445 . 'vertical-align: top',
446 'name' => "wikislide".$count);
447 if ($align == 'left' || $align == 'right') {
449 $cell=array('style' => 'display: block; '
450 .'position: absolute; '
452 .'vertical-align: top',
453 'name' => "wikislide".$count);
455 $cell=array('style' => 'display: none; '
456 .'position: absolute; '
458 .'vertical-align: top',
459 'name' => "wikislide".$count);
464 HTML::span(array('class'=>'gensmall'), $desc)
467 } elseif ($mode == 'row') {
468 $desc = ($showdesc != 'none') ? HTML::p($value["desc"]) : '';
470 HTML::table(array("style" => "display: inline",
471 'class' > "photoalbum row"),
472 HTML::tr(HTML::td($url_image)),
473 HTML::tr(HTML::td(array("class" => "gensmall",
474 "style" => "text-align: center; "
475 ."background-color: $color"),
479 return $this->error(fmt("Invalid argument: %s=%s", 'mode', $mode));
482 // no more images in one row as defined by $numcols
483 if ( ($key + 1) % $numcols == 0 ||
484 ($key + 1) == count($photos) ||
487 $html->pushcontent(HTML::span($row));
489 $html->pushcontent(HTML::tr($row));
490 $row->setContent('');
495 $table_attributes = array("border" => 0,
498 "class" => "photoalbum",
499 "width" => $tablewidth ? $tablewidth : "100%");
501 if (!empty($tableheight))
502 $table_attributes = array_merge($table_attributes,
503 array("height" => $tableheight));
505 $html = HTML::table($table_attributes, $html);
507 return HTML::div(array("align" => $align), $html);
511 * Calculate the new size in pixels when the original size
512 * with a value is given.
514 * @param integer $oldSize Absolute no. of pixels
515 * @param mixed $value Either absolute no. or HTML percentage e.g. '50%'
516 * @return integer New size in pixels
518 function newSize($oldSize, $value) {
519 if (trim(substr($value,strlen($value)-1)) != "%") {
522 $value = str_replace("%", "", $value);
523 return round(($oldSize*$value)/100);
527 * fromLocation - read only one picture from fixed album_location
528 * and return it in array $photos
530 * @param string $src Name of page
531 * @param array $photos
532 * @return string Error if fixed location is not allowed
534 function fromLocation($src, &$photos) {
535 /*if (!allow_album_location) {
536 return $this->error(_("Fixed album location is not allowed. Please specify parameter src."));
539 if (! IsSafeURL($src)) {
540 return $this->error(_("Bad url in src: remove all of <, >, \""));
542 $photos[] = array ("name" => $src, //album_location."/$src".album_default_extension,
547 * fromFile - read pictures & descriptions (separated by ;)
548 * from $src and return it in array $photos
550 * @param string $src path to dir or textfile (local or remote)
551 * @param array $photos
552 * @return string Error when bad url or file couldn't be opened
554 function fromFile($src, &$photos, $webpath='') {
556 if (preg_match("/^Upload:(.*)$/", $src, $m)) {
557 $src = getUploadFilePath() . $m[1];
558 $webpath = getUploadDataPath() . $m[1];
560 //there has a big security hole... as loading config/config.ini !
561 if (!preg_match('/(\.csv|\.jpg|\.jpeg|\.png|\.gif|\/)$/',$src)) {
562 return $this->error(_("File extension for csv file has to be '.csv'"));
564 if (! IsSafeURL($src)) {
565 return $this->error(_("Bad url in src: remove all of <, >, \""));
567 if (preg_match('/^(http|ftp|https):\/\//i', $src)) {
568 $contents = url_get_contents($src);
572 if (string_ends_with($src,"/"))
573 $src = substr($src,0,-1);
575 if (!file_exists($src) and @file_exists(PHPWIKI_DIR . "/$src")) {
576 $src = PHPWIKI_DIR . "/$src";
578 // check if src is a directory
579 if (file_exists($src) and filetype($src) == 'dir') {
582 foreach (array('jpeg','jpg','png','gif') as $ext) {
583 $fileset = new fileSet($src, "*.$ext");
584 $list = array_merge($list, $fileset->getFiles());
586 // convert dirname($src) (local fs path) to web path
589 // assume relative src. default: "themes/Hawaiian/images/pictures"
590 $webpath = DATA_PATH . '/' . $src_bak;
592 foreach ($list as $file) {
593 // convert local path to webpath
594 $photos[] = array ("src" => $file,
595 "name" => $webpath . "/$file",
596 "name_tile" => $src . "/$file",
597 "src" => $src . "/$file",
602 // check if $src is an image
603 foreach (array('jpeg','jpg','png','gif') as $ext) {
604 if (preg_match("/\.$ext$/", $src)) {
605 if (!file_exists($src) and @file_exists(PHPWIKI_DIR . "/$src"))
606 $src = PHPWIKI_DIR . "/$src";
607 if ($web_location == 1 and !empty($contents)) {
608 $photos[] = array ("src" => $src,
615 if (!file_exists($src))
616 return $this->error(fmt("Unable to find src='%s'", $src));
617 $photos[] = array ("src" => $src,
618 "name" => "../".$src,
625 if ($web_location == 0) {
626 $fp = @fopen($src, "r");
628 return $this->error(fmt("Unable to read src='%s'", $src));
630 while ($data = fgetcsv($fp, 1024, ';')) {
631 if (count($data) == 0 || empty($data[0])
632 || preg_match('/^#/',$data[0])
633 || preg_match('/^[[:space:]]*$/',$data[0]))
635 if (empty($data[1])) $data[1] = '';
636 $photos[] = array ("name" => dirname($src)."/".trim($data[0]),
637 "location" => "../".dirname($src)."/".trim($data[0]),
638 "desc" => trim($data[1]),
639 "name_tile" => dirname($src)."/".trim($data[0]));
643 } elseif ($web_location == 1) {
644 //TODO: check if the file is an image
645 $contents = preg_split('/\n/',$contents);
646 while (list($key,$value) = each($contents)) {
647 $data = preg_split('/\;/',$value);
648 if (count($data) == 0 || empty($data[0])
649 || preg_match('/^#/',$data[0])
650 || preg_match('/^[[:space:]]*$/',$data[0]))
652 if (empty($data[1])) $data[1] = '';
653 $photos[] = array ("name" => dirname($src)."/".trim($data[0]),
654 "src" => dirname($src)."/".trim($data[0]),
655 "desc" => trim($data[1]),
656 "name_tile" => dirname($src)."/".trim($data[0]));
662 // $Log: not supported by cvs2svn $
663 // Revision 1.14 2005/10/12 06:19:07 rurban
664 // protect unsafe calls
666 // Revision 1.13 2005/09/26 06:39:55 rurban
667 // re-add lost mode=column|row. by Thomas Harding
669 // Revision 1.12 2005/09/20 19:34:51 rurban
670 // slide and thumbs mode by Thomas Harding
673 // Revision 1.14 2005/09/19 23:49:00 tharding
674 // added slide mode, correct url retrieving with url_get_contents
676 // Revision 1.13 2005/09/17 18:17:00 tharding
677 // add resized thumbnails (see ImageTile.php at top-level)
678 // comment url_get_contents (fopen can open a web location)
680 // Revision 1.11 2004/12/06 19:50:05 rurban
681 // enable action=remove which is undoable and seeable in RecentChanges: ADODB ony for now.
682 // renamed delete_page to purge_page.
683 // enable action=edit&version=-1 to force creation of a new version.
684 // added BABYCART_PATH config
685 // fixed magiqc in adodb.inc.php
686 // and some more docs
688 // Revision 1.10 2004/12/01 19:34:13 rurban
689 // Cleanup of CONSTANT pollution.
690 // renamed weblocation to url.
692 // use fixed ";" CSV seperator
693 // fix substr_replace usage bug.
695 // Revision 1.9 2004/07/08 20:30:07 rurban
696 // plugin->run consistency: request as reference, added basepage.
697 // encountered strange bug in AllPages (and the test) which destroys ->_dbi
699 // Revision 1.8 2004/06/01 15:28:01 rurban
700 // AdminUser only ADMIN_USER not member of Administrators
701 // some RateIt improvements by dfrankow
702 // edit_toolbar buttons
704 // Revision 1.7 2004/05/03 20:44:55 rurban
705 // fixed gettext strings
706 // new SqlResult plugin
707 // _WikiTranslation: fixed init_locale
709 // Revision 1.6 2004/04/18 00:19:30 rurban
710 // better default example with local src, don't require weblocation for
711 // the default setup, better docs, fixed ini_get => get_cfg_var("allow_url_fopen"),
712 // no HttpClient lib yet.
714 // Revision 1.5 2004/03/09 12:10:23 rurban
715 // fixed getimagesize problem with local dir.
717 // Revision 1.4 2004/02/28 21:14:08 rurban
718 // generally more PHPDOC docs
719 // see http://xarch.tu-graz.ac.at/home/rurban/phpwiki/xref/
720 // fxied WikiUserNew pref handling: empty theme not stored, save only
721 // changed prefs, sql prefs improved, fixed password update,
722 // removed REPLACE sql (dangerous)
723 // moved gettext init after the locale was guessed
724 // + some minor changes
726 // Revision 1.3 2004/02/27 08:03:35 rurban
727 // Update from version 1.2 by Ted Vinke
728 // implemented the localdir support
730 // Revision 1.2 2004/02/17 12:11:36 rurban
731 // added missing 4th basepage arg at plugin->run() to almost all plugins. This caused no harm so far, because it was silently dropped on normal usage. However on plugin internal ->run invocations it failed. (InterWikiSearch, IncludeSiteMap, ...)
733 // Revision 1.1 2003/01/05 04:21:06 carstenklapp
734 // New plugin by Ted Vinke (sf tracker patch #661189)
742 // c-hanging-comment-ender-p: nil
743 // indent-tabs-mode: nil