4 * Copyright 2003,2004,2005,2007 $ThePhpWikiProgrammingTeam
5 * Copyright 2009 Marc-Etienne Vargenau, Alcatel-Lucent
7 * This file is part of PhpWiki.
9 * PhpWiki is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * PhpWiki is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License along
20 * with PhpWiki; if not, write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 * Display an album of a set of photos with optional descriptions.
27 * @author: Ted Vinke <teddy@jouwfeestje.com>
28 * Reini Urban (local fs)
29 * Thomas Harding (slides mode, real thumbnails)
33 * src="http://server/textfile" or localfile or localdir
34 * mode=[normal|column|row|thumbs|tiles|list|slide]
40 * align=[center|left|right]
44 * "src": textfile of images or directory of images or a single image (local or remote)
45 * Local or remote e.g. http://myserver/images/MyPhotos.txt or http://myserver/images/
46 * or /images/ or Upload:photos/
47 * Possible content of a valid textfile:
48 * photo-01.jpg; Me and my girlfriend
50 * christmas.gif; Merry Christmas!
52 * Inside textfile, filenames and optional descriptions are separated by
53 * semi-colon on each line. Listed files must be in same directory as textfile
54 * itself, so don't use relative paths inside textfile.
56 * "url": defines the the webpath to the srcdir directory (formerly called weblocation)
61 * - specify picture(s) as parameter(s)
62 * - limit amount of pictures on one page
63 * - use PHP to really resize or greyscale images (only where GD library supports it)
64 * (quite done for resize with "ImageTile.php")
67 * - reading height and width from images with spaces in their names fails.
69 * Fixed album location idea by Philip J. Hollenback. Thanks!
72 class ImageTile extends HtmlElement
75 function image_tile( /*...*/)
77 $el = new HTML ('img');
78 $tag = func_get_args();
79 $path = DATA_PATH . "/ImageTile.php";
80 $params = "<img src=\"$path?url=" . $tag[0]['src'];
81 if (!@empty($tag[0]['width']))
82 $params .= "&width=" . $tag[0]['width'];
83 if (!@empty($tag[0]['height']))
84 $params .= "&height=" . $tag[0]['height'];
85 if (!@empty($tag[0]['width']))
86 $params .= '" width="' . $tag[0]['width'];
87 if (!@empty($tag[0]['height']))
88 $params .= '" height="' . $tag[0]['height'];
90 $params .= '" alt="' . $tag[0]['alt'] . '" />';
91 return $el->raw($params);
95 class WikiPlugin_PhotoAlbum
98 function getDescription()
100 return _("Display a set of photos listed in a text file with optional descriptions.");
103 // Avoid nameclash, so it's disabled. We allow any url.
104 // define('allow_album_location', true);
105 // define('album_location', 'http://kw.jouwfeestje.com/foto/redactie');
106 // define('album_default_extension', '.jpg');
107 // define('desc_separator', ';');
109 function getDefaultArguments()
111 return array('src' => '', // textfile of image list, or local dir.
112 'url' => '', // if src=localfs, url prefix (webroot for the links)
113 'mode' => 'normal', // normal|thumbs|tiles|list
114 // "normal" - Normal table which shows photos full-size
115 // "thumbs" - WinXP thumbnail style
116 // "tiles" - WinXP tiles style
117 // "list" - WinXP list style
118 // "row" - inline thumbnails
119 // "column" - photos full-size, displayed in 1 column
120 // "slide" - slideshow mode, needs javascript on client
121 'numcols' => 3, // photos per row, columns
122 'showdesc' => 'both', // none|name|desc|both
123 // "none" - No descriptions next to photos
124 // "name" - Only filename shown
125 // "desc" - Only description (from textfile) shown
126 // "both" - If no description found, then filename will be used
127 'link' => true, // show link to original sized photo
128 // If true, each image will be hyperlinked to a page where the single
129 // photo will be shown full-size. Only works when mode != 'normal'
130 'attrib' => '', // 'sort, nowrap, alt'
131 // attrib arg allows multiple attributes: attrib=sort,nowrap,alt
132 // 'sort' sorts alphabetically, 'nowrap' for cells, 'alt' to use
133 // descs instead of filenames in image ALT-tags
134 'bgcolor' => '#eae8e8', // cell bgcolor (lightgrey)
135 'hlcolor' => '#c0c0ff', // highlight color (lightblue)
136 'align' => 'center', // alignment of table
137 'height' => 'auto', // image height (auto|75|100%)
138 'width' => 'auto', // image width (auto|75|100%)
139 // Size of shown photos. Either absolute value (e.g. "50") or
140 // HTML style percentage (e.g. "75%") or "auto" for no special
142 'cellwidth' => 'image', // cell (auto|equal|image|75|100%)
143 // Width of cells in table. Either absolute value in pixels, HTML
144 // style percentage, "auto" (no special action), "equal" (where
145 // all columns are equally sized) or "image" (take height and
146 // width of the photo in that cell).
147 'tablewidth' => false, // table (75|100%)
148 'p' => false, // "displaythissinglephoto.jpg"
149 'h' => false, // "highlightcolorofthisphoto.jpg"
150 'duration' => 6, // in slide mode, in seconds
151 'thumbswidth' => 80 //width of thumbnails
155 // descriptions (instead of filenames) for image alt-tags
159 * @param string $argstr
160 * @param WikiRequest $request
161 * @param string $basepage
164 function run($dbi, $argstr, &$request, $basepage)
167 extract($this->getArgs($argstr, $request));
169 $attributes = $attrib ? explode(",", $attrib) : array();
173 // check all parameters
174 // what type do we have?
177 $src = $request->getArg('pagename');
178 $error = $this->fromLocation($src, $photos);
180 $error = $this->fromFile($src, $photos, $url);
183 return $this->error($error);
186 if ($numcols < 1) $numcols = 1;
187 if ($align != 'left' && $align != 'center' && $align != 'right') {
190 if (count($photos) == 0) {
191 return HTML::raw('');
194 if (in_array("sort", $attributes))
201 if ($mode == "column") {
206 // set some fixed properties for each $mode
207 if ($mode == 'thumbs' || $mode == 'tiles') {
208 $attributes = array_merge($attributes, "alt");
209 $attributes = array_merge($attributes, "nowrap");
210 $cellwidth = 'auto'; // else cell won't nowrap
211 if ($width == 'auto') $width = 70;
212 } elseif ($mode == 'list') {
215 if ($width == 'auto') $width = 50;
216 } elseif ($mode == 'slide') {
219 $numcols = count($photos);
221 while (list($key, $value) = each($photos)) {
222 list($x, $y, $s, $t) = @getimagesize($value['src']);
223 if ($height != 'auto') $y = $this->newSize($y, $height);
224 if ($width != 'auto') $y = round($y * $this->newSize($x, $width) / $x);
225 if ($x > $cell_width) $cell_width = $x;
226 if ($y > $tableheight) $tableheight = $y;
230 unset ($x, $y, $s, $t, $key, $value, $keep);
234 $duration = 1000 * $duration;
235 if ($mode == 'slide')
236 $row->pushContent(JavaScript("
238 function display_slides() {
240 cell0 = document.getElementsByName('wikislide' + j);
241 cell = document.getElementsByName('wikislide' + i);
242 if (cell0.item(0) != null)
243 cell0.item(0).style.display='none';
244 if (cell.item(0) != null)
245 cell.item(0).style.display='block';
247 if (cell.item(0) == null) i = 0;
248 setTimeout('display_slides()',$duration);
250 display_slides();"));
252 while (list($key, $value) = each($photos)) {
253 if ($p && basename($value["name"]) != "$p") {
256 if ($h && basename($value["name"]) == "$h") {
257 $color = $hlcolor ? $hlcolor : $bgcolor;
261 // $params will be used for each <img > tag
262 $params = array('src' => $value["name"],
263 'src_tile' => $value["name_tile"],
264 'alt' => ($value["desc"] != "" and in_array("alt", $attributes))
266 : basename($value["name"]));
267 if (!@empty($value['location']))
268 $params = array_merge($params, array("location" => $value['location']));
275 $value["desc"] = basename($value["name"]);
280 if (!$value["desc"]) $value["desc"] = basename($value["name"]);
284 // FIXME: get getimagesize to work with names with spaces in it.
285 // convert $value["name"] from webpath to local path
286 $size = @getimagesize($value["name"]); // try " " => "\\ "
287 if (!$size and !empty($value["src"])) {
288 $size = @getimagesize($value["src"]);
290 trigger_error("Unable to getimagesize(" . $value["name"] . ")",
294 $newwidth = $this->newSize($size[0], $width);
295 if ($width != 'auto' && $newwidth > 0) {
296 $params = array_merge($params, array("width" => $newwidth));
298 if (($mode == 'thumbs' || $mode == 'tiles' || $mode == 'list')) {
299 if (!empty($size[0])) {
300 $newheight = round($newwidth * $size[1] / $size[0]);
301 $params['width'] = $newwidth;
302 $params['height'] = $newheight;
303 } else $newheight = '';
304 if ($height == 'auto') $height = 150;
306 $newheight = $this->newSize($size[1], $height);
307 if ($height != 'auto' && $newheight > 0) {
308 $params = array_merge($params, array("height" => $newheight));
314 'class' => 'photoalbum cell align-center top',
315 'bgcolor' => "$color");
316 if ($cellwidth != 'auto') {
317 if ($cellwidth == 'equal') {
318 $newcellwidth = round(100 / $numcols) . "%";
319 } elseif ($cellwidth == 'image') {
320 $newcellwidth = $newwidth;
322 $newcellwidth = $cellwidth;
324 $cell = array_merge($cell, array("width" => $newcellwidth));
326 if (in_array("nowrap", $attributes)) {
327 $cell = array_merge($cell, array("nowrap" => "nowrap"));
329 //create url to display single larger version of image on page
330 $url = WikiURL($request->getPage(),
331 array("p" => basename($value["name"])))
333 . basename($value["name"]);
335 $b_url = WikiURL($request->getPage(),
336 array("h" => basename($value["name"])))
338 . basename($value["name"]);
340 ? HTML::a(array("href" => "$url"), basename($value["desc"]))
341 : basename($value["name"]);
343 if ($mode == 'normal' || $mode == 'slide') {
344 if (!@empty($params['location'])) $params['src'] = $params['location'];
345 unset ($params['location'], $params['src_tile']);
346 $url_image = $link ? HTML::a(array("id" => basename($value["name"]),
347 "href" => "$url"), HTML::img($params))
348 : HTML::img($params);
351 if (!@empty ($params['src_tile']))
352 $params['src'] = $params['src_tile'];
353 unset ($params['location'], $params['src_tile']);
354 $url_image = $link ? HTML::a(array("id" => basename($value["name"]),
356 ImageTile::image_tile($params))
357 : HTML::img($params);
362 if (!@empty($params['location'])) $params['src'] = $params['location'];
363 unset ($params['location'], $params['src_tile']);
364 $url_image = $link ? HTML::a(array("id" => basename($value["name"]),
365 "href" => "$b_url"), HTML::img($params))
366 : HTML::img($params);
369 $url_text = HTML::a(array("id" => basename($value["name"])),
371 // here we use different modes
372 if ($mode == 'tiles') {
375 HTML::div(array('class' => 'top'), $url_image),
376 HTML::div(array('class' => 'bottom'),
377 HTML::div(array('class' => 'boldsmall'),
380 HTML::div(array('class' => 'gensmall'),
386 } elseif ($mode == 'list') {
387 $desc = ($showdesc != 'none') ? $value["desc"] : '';
389 HTML::td(array("class" => "top",
391 "bgcolor" => $color),
392 HTML::div(array('class' => 'boldsmall'), ($url_text))));
394 HTML::td(array("class" => "top",
396 "bgcolor" => $color),
397 HTML::div(array('class' => 'gensmall'),
405 HTML::td(array("class" => "top",
407 "bgcolor" => $color),
408 HTML::div(array('class' => 'gensmall'), $desc)));
410 } elseif ($mode == 'thumbs') {
411 $desc = ($showdesc != 'none') ?
412 HTML::p(HTML::a(array("href" => "$url"),
417 // FIXME: no HtmlElement for fontsizes?
418 // rurban: use ->setAttr("style","font-size:small;")
419 // but better use a css class
420 HTML::div(array('class' => 'gensmall'), $desc)
422 } elseif ($mode == 'normal') {
423 $desc = ($showdesc != 'none') ? HTML::p($value["desc"]) : '';
427 // FIXME: no HtmlElement for fontsizes?
428 HTML::div(array('class' => 'gensmall'), $desc)
430 } elseif ($mode == 'slide') {
431 if ($newwidth == 'auto' || !$newwidth)
432 $newwidth = $this->newSize($size[0], $width);
433 if ($newwidth == 'auto' || !$newwidth)
434 $newwidth = $size[0];
435 if ($newheight != 'auto') $newwidth = round($size[0] * $newheight / $size[1]);
436 $desc = ($showdesc != 'none') ? HTML::p($value["desc"]) : '';
438 $cell = array('style' => 'display: block; '
439 . 'position: absolute; '
441 . 'margin-left: -' . round($newwidth / 2) . 'px;'
442 . 'text-align: center; '
443 . 'vertical-align: top',
444 'name' => "wikislide" . $count);
446 $cell = array('style' => 'display: none; '
447 . 'position: absolute ;'
449 . 'margin-left: -' . round($newwidth / 2) . 'px;'
450 . 'text-align: center; '
451 . 'vertical-align: top',
452 'name' => "wikislide" . $count);
453 if ($align == 'left' || $align == 'right') {
455 $cell = array('style' => 'display: block; '
456 . 'position: absolute; '
457 . $align . ': 50px; '
458 . 'vertical-align: top',
459 'name' => "wikislide" . $count);
461 $cell = array('style' => 'display: none; '
462 . 'position: absolute; '
463 . $align . ': 50px; '
464 . 'vertical-align: top',
465 'name' => "wikislide" . $count);
470 HTML::div(array('class' => 'gensmall'), $desc)
473 } elseif ($mode == 'row') {
474 $desc = ($showdesc != 'none') ? HTML::p($value["desc"]) : '';
476 HTML::table(array("style" => "display: inline",
477 'class' > "photoalbum row"),
478 HTML::tr(HTML::td($url_image)),
479 HTML::tr(HTML::td(array("class" => "gensmall",
480 "style" => "text-align: center; "
481 . "background-color: $color"),
485 return $this->error(fmt("Invalid argument: %s=%s", 'mode', $mode));
488 // no more images in one row as defined by $numcols
489 if (($key + 1) % $numcols == 0 ||
490 ($key + 1) == count($photos) ||
494 $html->pushcontent(HTML::div($row));
496 $html->pushcontent(HTML::tr($row));
497 $row->setContent('');
502 $table_attributes = array(
503 "class" => "photoalbum",
504 "width" => $tablewidth ? $tablewidth : "100%");
506 if (!empty($tableheight))
507 $table_attributes = array_merge($table_attributes,
508 array("height" => $tableheight));
510 $html = HTML::table($table_attributes, $html);
512 return HTML::div(array("align" => $align), $html);
516 * Calculate the new size in pixels when the original size
517 * with a value is given.
519 * @param integer $oldSize Absolute no. of pixels
520 * @param mixed $value Either absolute no. or HTML percentage e.g. '50%'
521 * @return integer New size in pixels
523 function newSize($oldSize, $value)
525 if (trim(substr($value, strlen($value) - 1)) != "%") {
528 $value = str_replace("%", "", $value);
529 return round(($oldSize * $value) / 100);
533 * fromLocation - read only one picture from fixed album_location
534 * and return it in array $photos
536 * @param string $src Name of page
537 * @param array $photos
538 * @return string Error if fixed location is not allowed
540 function fromLocation($src, &$photos)
542 /*if (!allow_album_location) {
543 return $this->error(_("Fixed album location is not allowed. Please specify parameter src."));
546 if (!IsSafeURL($src)) {
547 return $this->error(_("Bad url in src: remove all of <, >, \""));
549 $photos[] = array("name" => $src, //album_location."/$src".album_default_extension,
555 * fromFile - read pictures & descriptions (separated by ;)
556 * from $src and return it in array $photos
558 * @param string $src path to dir or textfile (local or remote)
559 * @param array $photos
560 * @param string $webpath
561 * @return string Error when bad url or file couldn't be opened
563 function fromFile($src, &$photos, $webpath = '')
566 if (preg_match("/^Upload:(.*)$/", $src, $m)) {
567 $src = getUploadFilePath() . $m[1];
568 $webpath = getUploadDataPath() . $m[1];
570 //there has a big security hole... as loading config/config.ini !
571 if (!preg_match('/(\.csv|\.jpg|\.jpeg|\.png|\.gif|\/)$/', $src)) {
572 return $this->error(_("File extension for csv file has to be '.csv'"));
574 if (!IsSafeURL($src)) {
575 return $this->error(_("Bad url in src: remove all of <, >, \""));
577 if (preg_match('/^(http|ftp|https):\/\//i', $src)) {
578 $contents = url_get_contents($src);
582 if (string_ends_with($src, "/"))
583 $src = substr($src, 0, -1);
585 if (!file_exists($src) and @file_exists(PHPWIKI_DIR . "/$src")) {
586 $src = PHPWIKI_DIR . "/$src";
588 // check if src is a directory
589 if (file_exists($src) and filetype($src) == 'dir') {
592 foreach (array('jpeg', 'jpg', 'png', 'gif') as $ext) {
593 $fileset = new fileSet($src, "*.$ext");
594 $list = array_merge($list, $fileset->getFiles());
596 // convert dirname($src) (local fs path) to web path
599 // assume relative src. default: "themes/Hawaiian/images/pictures"
600 $webpath = DATA_PATH . '/' . $src_bak;
602 foreach ($list as $file) {
603 // convert local path to webpath
604 $photos[] = array("src" => $file,
605 "name" => $webpath . "/$file",
606 "name_tile" => $src . "/$file",
607 "src" => $src . "/$file",
612 // check if $src is an image
613 foreach (array('jpeg', 'jpg', 'png', 'gif') as $ext) {
614 if (preg_match("/\.$ext$/", $src)) {
615 if (!file_exists($src) and @file_exists(PHPWIKI_DIR . "/$src"))
616 $src = PHPWIKI_DIR . "/$src";
617 if ($web_location == 1 and !empty($contents)) {
618 $photos[] = array("src" => $src,
625 if (!file_exists($src))
626 return $this->error(fmt("Unable to find src=ā%sā", $src));
627 $photos[] = array("src" => $src,
628 "name" => "../" . $src,
635 if ($web_location == 0) {
636 $fp = @fopen($src, "r");
638 return $this->error(fmt("Unable to read src=ā%sā", $src));
640 while ($data = fgetcsv($fp, 1024, ';')) {
641 if (count($data) == 0 || empty($data[0])
642 || preg_match('/^#/', $data[0])
643 || preg_match('/^[[:space:]]*$/', $data[0])
646 if (empty($data[1])) $data[1] = '';
647 $photos[] = array("name" => dirname($src) . "/" . trim($data[0]),
648 "location" => "../" . dirname($src) . "/" . trim($data[0]),
649 "desc" => trim($data[1]),
650 "name_tile" => dirname($src) . "/" . trim($data[0]));
654 } elseif ($web_location == 1) {
655 //TODO: check if the file is an image
656 $contents = preg_split('/\n/', $contents);
657 while (list($key, $value) = each($contents)) {
658 $data = preg_split('/\;/', $value);
659 if (count($data) == 0 || empty($data[0])
660 || preg_match('/^#/', $data[0])
661 || preg_match('/^[[:space:]]*$/', $data[0])
664 if (empty($data[1])) $data[1] = '';
665 $photos[] = array("name" => dirname($src) . "/" . trim($data[0]),
666 "src" => dirname($src) . "/" . trim($data[0]),
667 "desc" => trim($data[1]),
668 "name_tile" => dirname($src) . "/" . trim($data[0]));
679 // c-hanging-comment-ender-p: nil
680 // indent-tabs-mode: nil