1<?php
2// force UTF-8 Ø
3$_zp_extra_filetypes = array(); // contains file extensions and the handler class for alternate images
4
5define('WATERMARK_IMAGE', 1);
6define('WATERMARK_THUMB', 2);
7define('WATERMARK_FULL', 4);
8
9/**
10 * Returns a new "image" object based on the file extension
11 *
12 * @param object $album the owner album
13 * @param string $filename the filename
14 * @param bool $quiet set true to supress error messages (used by loadimage)
15 * @return object
16 */
17function newImage($album, $filename, $quiet = false) {
18	global $_zp_extra_filetypes, $_zp_missing_image;
19	if (is_array($filename)) {
20		$xalbum = newAlbum($filename['folder'], true, true);
21		$filename = $filename['filename'];
22	} else {
23		if ($album->isDynamic()) {
24			$xalbum = NULL;
25			foreach ($album->getImages() as $image) {
26				if ($filename == $image['filename']) {
27					$xalbum = newAlbum($image['folder']);
28					break;
29				}
30			}
31		} else {
32			$xalbum = $album;
33		}
34	}
35	if (!is_object($xalbum) || !$xalbum->exists || !isAlbumClass($xalbum)) {
36		if (!$quiet) {
37			$msg = sprintf(gettext('Bad album object parameter to newImage(%s)'), $filename);
38			trigger_error($msg, E_USER_NOTICE);
39		}
40		return $_zp_missing_image;
41	}
42	if ($object = Gallery::validImageAlt($filename)) {
43		$image = New $object($xalbum, $filename, $quiet);
44	} else {
45		if (Gallery::validImage($filename)) {
46			$image = New Image($xalbum, $filename, $quiet);
47		} else {
48			$image = NULL;
49		}
50	}
51	if ($image) {
52		if ($album && $album->isDynamic()) {
53			$image->albumname = $album->name;
54			$image->albumlink = $album->linkname;
55			$image->albumnamealbum = $album;
56		}
57		zp_apply_filter('image_instantiate', $image);
58		if ($image->exists) {
59			return $image;
60		} else {
61			return $_zp_missing_image;
62		}
63	}
64
65	if (!$quiet) {
66		$msg = sprintf(gettext('Bad filename suffix in newImage(%s)'), $filename);
67		trigger_error($msg, E_USER_NOTICE);
68	}
69	return $_zp_missing_image;
70}
71
72/**
73 * Returns true if the object is a zenphoto 'image'
74 *
75 * @param object $image
76 * @return bool
77 */
78function isImageClass($image = NULL) {
79	global $_zp_current_image;
80	if (is_null($image)) {
81		if (!in_context(ZP_IMAGE))
82			return false;
83		$image = $_zp_current_image;
84	}
85	return is_object($image) && ($image->table == 'images');
86}
87
88/**
89 * Image Class
90 * @package core
91 * @subpackage classes\objects
92 */
93class Image extends MediaObject {
94
95	public $filename; // true filename of the image.
96	public $exists = true; // Does the image exist?
97	public $webpath; // The full URL path to the original image.
98	public $localpath; // Latin1 full SERVER path to the original image.
99	public $displayname; // $filename with the extension stripped off.
100	public $album; // An album object for the album containing this image.
101	public $albumname; // The name of the album for which this image was instantiated. (MAY NOT be $this->album->name!!!!).
102	public $albumnamealbum; //	An album object representing the above;
103	public $albumlink; // "rewrite" verwion of the album name, eg. may not have the .alb
104	public $imagefolder; // The album folder containing the image (May be different from the albumname!!!!)
105	protected $index; // The index of the current image in the album array.
106	protected $sortorder; // The position that this image should be shown in the album
107	public $filemtime; // Last modified time of this image
108	public $sidecars = array(); // keeps the list of suffixes associated with this image
109	public $manage_rights = MANAGE_ALL_ALBUM_RIGHTS;
110	public $manage_some_rights = ALBUM_RIGHTS;
111	public $view_rights = ALL_ALBUMS_RIGHTS;
112	// Plugin handler support
113	public $objectsThumb = NULL; // Thumbnail image for the object
114	public $thumbdimensions = null;
115	protected $is_public = null;
116
117	/**
118	 * Constructor for class-image
119	 *
120	 * Do not call this constructor directly unless you really know what you are doing!
121	 * Use instead the function newImage() which will instantiate an object of the
122	 * correct class for the file type.
123	 *
124	 * @param object &$album the owning album
125	 * @param sting $filename the filename of the image
126	 * @return Image
127	 */
128
129	function __construct($album, $filename, $quiet = false) {
130		global $_zp_current_admin_obj;
131		// $album is an Album object; it should already be created.
132		$msg = false;
133		if (!is_object($album) || !$album->exists) {
134			$msg = gettext('Invalid image instantiation: Album does not exist');
135		} else {
136			if (!$this->classSetup($album, $filename) || !file_exists($this->localpath) || is_dir($this->localpath)) {
137				$msg = gettext('Invalid image instantiation: file does not exist');
138			}
139		}
140		if ($msg) {
141			$this->exists = false;
142			if (!$quiet) {
143				trigger_error($msg, E_USER_ERROR);
144			}
145			return;
146		}
147
148		// This is where the magic happens...
149		$album_name = $album->name;
150		$new = $this->instantiate('images', array('filename' => $filename, 'albumid' => $this->album->getID()), 'filename', false, empty($album_name));
151		if ($new || $this->filemtime != $this->get('mtime')) {
152			if ($new) {
153				$this->setTitle($this->displayname);
154			}
155			$this->updateMetaData(); // extract info from image
156			$this->updateDimensions(); // deal with rotation issues
157			$this->set('mtime', $this->filemtime);
158			$this->save();
159			if ($new)
160				zp_apply_filter('new_image', $this);
161		}
162	}
163
164	/**
165	 * (non-PHPdoc)
166	 * @see PersistentObject::setDefaults()
167	 */
168	protected function setDefaults() {
169		global $_zp_gallery;
170		$this->setShow($_zp_gallery->getImagePublish());
171		$this->set('mtime', $this->filemtime);
172		$this->setLastChange();
173		$this->updateDimensions(); // deal with rotation issues
174	}
175
176	/**
177	 * generic "image" class setup code
178	 * Returns true if valid image.
179	 *
180	 * @param object $album the images' album
181	 * @param string $filename of the image
182	 * @return bool
183	 *
184	 */
185	protected function classSetup(&$album, $filename) {
186		if (TEST_RELEASE) {
187			$bt = debug_backtrace();
188			$good = false;
189			foreach ($bt as $b) {
190				if ($b['function'] == "newImage") {
191					$good = true;
192					break;
193				}
194			}
195			if (!$good) {
196				zp_error(gettext('An image object was instantiated without using the newImage() function.'), E_USER_WARNING);
197			}
198		}
199
200		global $_zp_current_admin_obj;
201		$fileFS = internalToFilesystem($filename);
202		if ($filename != filesystemToInternal($fileFS)) { // image name spoof attempt
203			return false;
204		}
205		$this->albumnamealbum = $this->album = &$album;
206		if ($album->name == '') {
207			$this->webpath = ALBUM_FOLDER_WEBPATH . $filename;
208			$this->encwebpath = ALBUM_FOLDER_WEBPATH . rawurlencode($filename);
209			$this->localpath = ALBUM_FOLDER_SERVERPATH . internalToFilesystem($filename);
210		} else {
211			$this->webpath = ALBUM_FOLDER_WEBPATH . $album->name . "/" . $filename;
212			$this->encwebpath = ALBUM_FOLDER_WEBPATH . pathurlencode($album->name) . "/" . rawurlencode($filename);
213			$this->localpath = $album->localpath . $fileFS;
214		}
215		$this->imagefolder = $this->albumlink = $this->albumname = $album->name;
216		$this->filename = $filename;
217		$this->displayname = substr($this->filename, 0, strrpos($this->filename, '.'));
218		if (empty($this->displayname))
219			$this->displayname = $this->filename;
220		$this->comments = null;
221		$this->filemtime = @filemtime($this->localpath);
222		$this->imagetype = strtolower(get_class($this)) . 's';
223		$date = $this->get('date');
224		if (empty($date)) {
225			$this->set('date', strftime('%Y-%m-%d %H:%M:%S', $this->filemtime));
226		}
227		return true;
228	}
229
230	/**
231	 * Returns the image filename
232	 *
233	 * @return string
234	 */
235	function getFileName() {
236		return $this->filename;
237	}
238
239	/**
240	 * Returns true if the file has changed since last time we looked
241	 *
242	 * @return bool
243	 */
244	protected function fileChanged() {
245		$storedmtime = $this->get('mtime');
246		return (empty($storedmtime) || $this->filemtime > $storedmtime);
247	}
248
249	/**
250	 * Returns an array of EXIF data
251	 *
252	 * @return array
253	 */
254	function getMetaData() {
255		global $_zp_exifvars;
256		$exif = array();
257		// Put together an array of EXIF data to return
258		foreach ($_zp_exifvars as $field => $exifvar) {
259			//	only enabled image metadata
260			if ($_zp_exifvars[$field][5]) {
261				$exif[$field] = $this->get($field);
262			}
263		}
264		return $exif;
265	}
266
267	/**
268	 * Parses Exif/IPTC data
269	 *
270	 */
271	function updateMetaData() {
272		global $_zp_exifvars, $_zp_gallery;
273		require_once(dirname(__FILE__) . '/exif/exif.php');
274		$IPTCtags = array(
275						'SKIP'								 => '2#000', //	Record Version										Size:64
276						'ObjectType'					 => '2#003', //	Object Type	Ref										Size:67
277						'ObjectAttr'					 => '2#004', //	Object Attribute Ref							Size:67
278						'ObjectName'					 => '2#005', //	Object name												Size:64
279						'EditStatus'					 => '2#007', //	Edit Status												Size:64
280						'EditorialUpdate'			 => '2#008', //	Editorial Update									Size:2
281						'Urgency'							 => '2#010', //	Urgency														Size:1
282						'SubRef'							 => '2#012', //	Subject	Reference									Size:236
283						'Category'						 => '2#015', //	Category 													Size:3
284						'SuppCategory'				 => '2#020', //	Supplemental category							Size:32
285						'FixtureID'						 => '2#022', //	Fixture	ID 												Size:32
286						'Keywords'						 => '2#025', //	Keywords 													Size:64
287						'ContentLocationCode'	 => '2#026', //	Content	Location Code							Size:3
288						'ContentLocationName'	 => '2#027', //	Content	Location Name							Size:64
289						'ReleaseDate'					 => '2#030', //	Release	Date 											Size:8
290						'ReleaseTime'					 => '2#035', //	Release	Time											Size:11
291						'ExpireDate'					 => '2#037', //	Expiration Date										Size:8
292						'ExpireTime'					 => '2#038', //	Expiration Time										Size:11
293						'SpecialInstru'				 => '2#040', //	Special Instructions							Size:256
294						'ActionAdvised'				 => '2#042', //	Action Advised										Size:2
295						'RefService'					 => '2#045', //	Reference Service									Size:10
296						'RefDate'							 => '2#047', //	Reference Date										Size:8
297						'RefNumber'						 => '2#050', //	Reference Number									Size:8
298						'DateCreated'					 => '2#055', //	Date created											Size:8
299						'TimeCreated'					 => '2#060', //	Time created											Size:11
300						'DigitizeDate'				 => '2#062', //	Digital Creation Date							Size:8
301						'DigitizeTime'				 => '2#063', //	Digital Creation Time							Size:11
302						'OriginatingProgram'	 => '2#065', //	Originating Program								Size:32
303						'ProgramVersion'			 => '2#070', //	Program version										Size:10
304						'ObjectCycle'					 => '2#075', //	Object Cycle											Size:1
305						'ByLine'							 => '2#080', //	ByLine 														Size:32
306						'ByLineTitle'					 => '2#085', //	ByLine Title											Size:32
307						'City'								 => '2#090', //	City															Size:32
308						'SubLocation'					 => '2#092', //	Sublocation												Size:32
309						'State'								 => '2#095', //	Province/State										Size:32
310						'LocationCode'				 => '2#100', //	Country/Primary	Location Code			Size:3
311						'LocationName'				 => '2#101', //	Country/Primary	Location Name			Size:64
312						'TransmissionRef'			 => '2#103', //	Original Transmission Reference		Size:32
313						'ImageHeadline'				 => '2#105', //	Image headline										Size:256
314						'ImageCredit'					 => '2#110', //	Image credit											Size:32
315						'Source'							 => '2#115', //	Source														Size:32
316						'Copyright'						 => '2#116', //	Copyright Notice									Size:128
317						'Contact'							 => '2#118', //	Contact														Size:128
318						'ImageCaption'				 => '2#120', //	Image caption											Size:2000
319						'ImageCaptionWriter'	 => '2#122', //	Image caption writer							Size:32
320						'ImageType'						 => '2#130', //	Image type												Size:2
321						'Orientation'					 => '2#131', //	Image	 rientation									Size:1
322						'LangID'							 => '2#135', //	Language ID												Size:3
323						'Subfile'							 => '8#010' //	Subfile														Size:2
324		);
325		$this->set('hasMetadata', 0);
326		$result = array();
327		if (get_class($this) == 'Image') {
328			$localpath = $this->localpath;
329		} else {
330			$localpath = $this->getThumbImageFile();
331		}
332		$xdate = false;
333
334		if (!empty($localpath)) { // there is some kind of image to get metadata from
335			$exifraw = read_exif_data_protected($localpath);
336			if (isset($exifraw['ValidEXIFData'])) {
337				$this->set('hasMetadata', 1);
338				foreach ($_zp_exifvars as $field => $exifvar) {
339					$exif = NULL;
340					if ($exifvar[5]) { // enabled field
341						if (isset($exifraw[$exifvar[0]][$exifvar[1]])) {
342							$exif = trim(sanitize($exifraw[$exifvar[0]][$exifvar[1]], 1));
343						} else if (isset($exifraw[$exifvar[0]]['MakerNote'][$exifvar[1]])) {
344							$exif = trim(sanitize($exifraw[$exifvar[0]]['MakerNote'][$exifvar[1]], 1));
345						}
346					}
347					$this->set($field, $exif);
348				}
349			}
350			/* check IPTC data */
351			$iptcdata = zp_imageIPTC($localpath);
352			if (!empty($iptcdata)) {
353				$iptc = iptcparse($iptcdata);
354				if ($iptc) {
355					$this->set('hasMetadata', 1);
356					$characterset = $this->getIPTCTag('1#090', $iptc);
357					if (!$characterset) {
358						$characterset = getOption('IPTC_encoding');
359					} else if (substr($characterset, 0, 1) == chr(27)) { // IPTC escape encoding
360						$characterset = substr($characterset, 1);
361						if ($characterset == '%G') {
362							$characterset = 'UTF-8';
363						} else { // we don't know, need to understand the IPTC standard here. In the mean time, default it.
364							$characterset = getOption('IPTC_encoding');
365						}
366					} else if ($characterset == 'UTF8') {
367						$characterset = 'UTF-8';
368					}
369					// Extract IPTC fields of interest
370					foreach ($_zp_exifvars as $field => $exifvar) {
371						if ($exifvar[0] == 'IPTC') {
372							if ($exifvar[5]) { // enabled field
373								$datum = $this->getIPTCTag($IPTCtags[$exifvar[1]], $iptc);
374								$this->set($field, $this->prepIPTCString($datum, $characterset));
375							} else {
376								$this->set($field, NULL);
377							}
378						}
379					}
380					/* iptc keywords (tags) */
381					if ($_zp_exifvars['IPTCKeywords'][5]) {
382						$datum = $this->getIPTCTagArray($IPTCtags['Keywords'], $iptc);
383						if (is_array($datum)) {
384							$tags = array();
385							$result['tags'] = array();
386							foreach ($datum as $item) {
387								$tags[] = $this->prepIPTCString(sanitize($item, 3), $characterset);
388							}
389							$this->setTags($tags);
390						}
391					}
392				}
393			}
394		}
395		/* "import" metadata into Zenphoto fields as makes sense */
396		zp_apply_filter('image_metadata', $this);
397
398		/* iptc date */
399		$date = $this->get('IPTCDateCreated');
400		if (!empty($date)) {
401			if (strlen($date) > 8) {
402				$time = substr($date, 8);
403			} else {
404				/* got date from IPTC, now must get time */
405				$time = $this->get('IPTCTimeCreated');
406			}
407			$date = substr($date, 0, 4) . '-' . substr($date, 4, 2) . '-' . substr($date, 6, 2);
408			if (!empty($time)) {
409				$date = $date . ' ' . substr($time, 0, 2) . ':' . substr($time, 2, 2) . ':' . substr($time, 4, 2);
410			}
411		}
412		/* EXIF date */
413		if (empty($date)) {
414			$date = $this->get('EXIFDateTime');
415		}
416		if (empty($date)) {
417			$date = $this->get('EXIFDateTimeOriginal');
418		}
419		if (empty($date)) {
420			$date = $this->get('EXIFDateTimeDigitized');
421		}
422		if (!empty($date)) {
423			$xdate = $date;
424			$this->setDateTime($date);
425		}
426
427		/* iptc title */
428		$title = $this->get('IPTCObjectName');
429		if (empty($title)) {
430			$title = $this->get('IPTCImageHeadline');
431		}
432		//EXIF title [sic]
433		if (empty($title)) {
434			$title = $this->get('EXIFDescription');
435		}
436		if (!empty($title)) {
437			$this->setTitle($title);
438		}
439
440		/* iptc description */
441		$desc = $this->get('IPTCImageCaption');
442		if (!empty($desc)) {
443   if(getOption('IPTC_convert_linebreaks')) {
444     $desc = nl2br($desc);
445   }
446			$this->setDesc($desc);
447		}
448
449		/* iptc location, state, country */
450		$loc = $this->get('IPTCSubLocation');
451		if (!empty($loc)) {
452			$this->setLocation($loc);
453		}
454		$city = $this->get('IPTCCity');
455		if (!empty($city)) {
456			$this->setCity($city);
457		}
458		$state = $this->get('IPTCState');
459		if (!empty($state)) {
460			$this->setState($state);
461		}
462		$country = $this->get('IPTCLocationName');
463		if (!empty($country)) {
464			$this->setCountry($country);
465		}
466
467		/* iptc credit */
468		$credit = $this->get('IPTCByLine');
469		if (empty($credit)) {
470			$credit = $this->get('IPTCImageCredit');
471		}
472		if (empty($credit)) {
473			$credit = $this->get('IPTCSource');
474		}
475		if (!empty($credit)) {
476			$this->setCredit($credit);
477		}
478
479		/* iptc copyright */
480		$this->setCopyright($this->get('IPTCCopyright'));
481
482		if (empty($xdate)) {
483			$this->setDateTime(strftime('%Y-%m-%d %H:%M:%S', $this->filemtime));
484		}
485		$alb = $this->album;
486		if (!is_null($alb)) {
487			if (!$this->get('owner')) {
488				$this->setOwner($alb->getOwner());
489			}
490			$save = false;
491			if (strtotime($alb->getUpdatedDate()) < strtotime(date('Y-m-d H:i:s'))) {
492				$alb->setUpdatedDate();
493				$alb->setUpdatedDateParents();
494				$save = true;
495			}
496			if (is_null($albdate = $alb->getDateTime()) || ($_zp_gallery->getAlbumUseImagedate() && strtotime($albdate) < strtotime($this->getDateTime()))) {
497				$alb->setDateTime($this->getDateTime()); //  not necessarily the right one, but will do. Can be changed in Admin
498				$save = true;
499			}
500			if ($save) {
501				$alb->save();
502			}
503		}
504	}
505
506	/**
507	 * Fetches a single tag from IPTC data
508	 *
509	 * @param string $tag the metadata tag sought
510	 * @return string
511	 */
512	private function getIPTCTag($tag, $iptc) {
513		if (isset($iptc[$tag])) {
514			$iptcTag = $iptc[$tag];
515			$r = "";
516			$ct = count($iptcTag);
517			for ($i = 0; $i < $ct; $i++) {
518				$w = $iptcTag[$i];
519				if (!empty($r)) {
520					$r .= ", ";
521				}
522				$r .= $w;
523			}
524			return trim($r);
525		}
526		return '';
527	}
528
529	/**
530	 * Fetches the IPTC array for a single tag.
531	 *
532	 * @param string $tag the metadata tag sought
533	 * @return array
534	 */
535	private function getIPTCTagArray($tag, $iptc) {
536		if (array_key_exists($tag, $iptc)) {
537			return $iptc[$tag];
538		}
539		return NULL;
540	}
541
542	/**
543	 * Returns the IPTC data converted into UTF8
544	 *
545	 * @param string $iptcstring the IPTC data
546	 * @param string $characterset the internal encoding of the data
547	 * @return string
548	 */
549	private function prepIPTCString($iptcstring, $characterset) {
550		global $_zp_UTF8;
551		// Remove null byte at the end of the string if it exists.
552		if (substr($iptcstring, -1) === 0x0) {
553			$iptcstring = substr($iptcstring, 0, -1);
554		}
555		$outputset = LOCAL_CHARSET;
556		if ($characterset == $outputset)
557			return $iptcstring;
558		$iptcstring = $_zp_UTF8->convert($iptcstring, $characterset, $outputset);
559		return trim(sanitize($iptcstring, 1));
560	}
561
562	/**
563	 * If there is valid GPS data returns key value array with "long" and "lat" keys
564	 * otherwise an empty array
565	 *
566	 * @since ZenphotoCMS 1.5.8 - Moved/adapted from the offical Zenphoto GoogleMap plugin by Stephen Billard (sbillard) & Vincent Bourganel (vincent3569)
567	 *
568	 * @return array
569	 */
570	function getGeodata() {
571		$gps = array();
572		if (isImageClass($this)) {
573			$exif = $this->getMetaData();
574			if ((!empty($exif['EXIFGPSLatitude'])) && (!empty($exif['EXIFGPSLongitude']))) {
575				$lat_c = explode('.', str_replace(',', '.', $exif['EXIFGPSLatitude']) . '.0');
576				$lat_f = round((float) abs($lat_c[0]) + ($lat_c[1] / pow(10, strlen($lat_c[1]))), 12);
577				if (isset($exif['EXIFGPSLatitudeRef'][0]) && strtoupper($exif['EXIFGPSLatitudeRef'][0]) == 'S') {
578					$lat_f = -$lat_f;
579				}
580				$long_c = explode('.', str_replace(',', '.', $exif['EXIFGPSLongitude']) . '.0');
581				$long_f = round((float) abs($long_c[0]) + ($long_c[1] / pow(10, strlen($long_c[1]))), 12);
582				if (isset($exif['EXIFGPSLongitudeRef'][0]) && strtoupper($exif['EXIFGPSLongitudeRef'][0]) == 'W') {
583					$long_f = -$long_f;
584				}
585				//in case European comma decimals sneaked in
586				$lat_f = str_replace(',', '.', $lat_f);
587				$long_f = str_replace(',', '.', $long_f);
588				if (($long_f > -180 && $long_f < 180) && ($lat_f > -90 && $lat_f < 90)) {
589					return array(
590							'lat' => $lat_f,
591							'long' => $long_f
592					);
593				}
594			}
595			return $gps;
596		}
597	}
598
599	/**
600	 * Update this object's values for width and height.
601	 *
602	 */
603	function updateDimensions() {
604		$discard = NULL;
605		$size = zp_imageDims($this->localpath);
606		$width = $size['width'];
607		$height = $size['height'];
608		if (zp_imageCanRotate()) {
609			// Swap the width and height values if the image should be rotated
610			$splits = preg_split('/!([(0-9)])/', $this->get('EXIFOrientation'));
611			$rotation = $splits[0];
612			switch ($rotation) {
613				case 5:
614				case 6:
615				case 7:
616				case 8:
617					$width = $size['height'];
618					$height = $size['width'];
619					break;
620			}
621		}
622		$this->set('width', $width);
623		$this->set('height', $height);
624	}
625
626	/**
627	 * Returns the width of the image
628	 *
629	 * @return int
630	 */
631	function getWidth() {
632		$w = $this->get('width');
633		if (empty($w)) {
634			$this->updateDimensions();
635			$this->save();
636			$w = $this->get('width');
637		}
638		return $w;
639	}
640
641	/**
642	 * Returns the height of the image
643	 *
644	 * @return int
645	 */
646	function getHeight() {
647		$h = $this->get('height');
648		if (empty($h)) {
649			$this->updateDimensions();
650			$this->save();
651			$h = $this->get('height');
652		}
653		return $h;
654	}
655
656	/**
657	 * Returns an array with widht and height the thumb. Here this is just a wrapper for getWidth() and getHeight()
658	 *
659	 * Child (base) class handlers of non image file formats (e.g. video, textobject) where the actual "image" and the sidecar thumb are not the same
660	 * file need to override this and provide the actual dimensions of the thumb using zp_getImageDims($thumbfile).
661	 * Otherwise thumb generation may be distorted.
662	 *
663	 * @since ZephotoCMS 1.5.8
664	 *
665	 * @return array
666	 */
667	function getThumbDimensions() {
668		if (!is_null($this->thumbdimensions)) {
669			return $this->thumbdimensions;
670		}
671		return $this->thumbdimensions = array(
672				'width' => $this->getWidth(),
673				'height' => $this->getHeight()
674		);
675	}
676
677	/**
678	 * Returns the width of the thumb. Here just the same as getWidth().
679	 *
680	 * @see getThumbDimensions() for specific usage
681	 * @since ZephotoCMS 1.5.8
682	 *
683	 * @return int
684	 */
685	function getThumbWidth() {
686		$dims = $this->getThumbDimensions();
687		return $dims['width'];
688	}
689
690	/**
691	 * Returns the height of the image. Here just the same as getHeight().
692	 *
693	 * @see getThumbDimensions() for specific usage
694	 * @since ZephotoCMS 1.5.8
695	 *
696	 * @return int
697	 */
698	function getThumbHeight() {
699		$dims = $this->getThumbDimensions();
700		return $dims['height'];
701	}
702
703	/**
704	 * Returns 'is_square', 'is_landscape', 'is_portrait' if the original image's widht and height match.
705	 *
706	 * @since Zenphoto 1.5.8
707	 *
708	 * @param string $type 'image' or 'thumb' - the latter may be different on non image "image items"
709	 * @return boolean|string
710	 */
711	function getOrientation($type = 'image') {
712		switch ($type) {
713			default:
714			case 'image':
715				$width = $this->getWidth();
716				$height = $this->getHeight();
717				break;
718			case 'thumb':
719				$width = $this->getThumbWidth();
720				$height = $this->getThumbHeight();
721				break;
722		}
723		if ($width == $height) {
724			return 'is_square';
725		} else if ($width > $height) {
726			return 'is_landscape';
727		} else if ($width < $height) {
728			return 'is_portrait';
729		}
730		return false;
731	}
732
733	/**
734	 * Returns true if the image has landscape orientation
735	 *
736	 * @since Zenphoto 1.5.8
737	 *
738	 * @param string $type 'image' or 'thumb' - the latter may be different on non image "image items"
739	 * @return bool
740	 */
741	function isLandscape($type = 'image') {
742		return $this->getOrientation($type) == 'is_landscape';
743	}
744
745	/**
746	 * Returns true if the image is a square
747	 *
748	 * @since Zenphoto 1.5.8
749	 *
750	 * @param string $type 'image' or 'thumb' - the latter may be different on non image "image items"
751	 * @return bool
752	 */
753	function isSquare($type = 'image') {
754		return $this->getOrientation($type) == 'is_square';
755	}
756
757	/**
758	 * Returns true if the image has portrait orientation
759	 *
760	 * @since Zenphoto 1.5.8
761	 *
762	 * @param string $type 'image' or 'thumb' - the latter may be different on non image "image items"
763	 * @return bool
764	 */
765	function isPortrait($type = 'image') {
766		return $this->getOrientation($type) == 'is_portrait';
767	}
768
769	/**
770	 * Returns the album that holds this image
771	 *
772	 * @return object
773	 */
774	function getAlbum() {
775		return $this->album;
776	}
777
778	/**
779	 * Retuns the folder name of the album that holds this image
780	 *
781	 * @return string
782	 */
783	function getAlbumName() {
784		return $this->albumname;
785	}
786
787	/**
788	 * Returns the location field of the image
789	 *
790	 * @return string
791	 */
792	function getLocation($locale = NULL) {
793		$text = $this->get('location');
794		if ($locale !== 'all') {
795			$text = get_language_string($text, $locale);
796		}
797		$text = unTagURLs($text);
798		return $text;
799	}
800
801	/**
802	 * Stores the location field of the image
803	 *
804	 * @param string $location text for the location
805	 */
806	function setLocation($location) {
807		$this->set('location', $location);
808	}
809
810	/**
811	 * Returns the city field of the image
812	 *
813	 * @return string
814	 */
815	function getCity($locale = NULL) {
816		$text = $this->get('city');
817		if ($locale !== 'all') {
818			$text = get_language_string($text, $locale);
819		}
820		$text = unTagURLs($text);
821		return $text;
822	}
823
824	/**
825	 * Stores the city field of the image
826	 *
827	 * @param string $city text for the city
828	 */
829	function setCity($city) {
830		$this->set('city', tagURLs($city));
831	}
832
833	/**
834	 * Returns the state field of the image
835	 *
836	 * @return string
837	 */
838	function getState($locale = NULL) {
839		$text = $this->get('state');
840		if ($locale !== 'all') {
841			$text = get_language_string($text, $locale);
842		}
843		$text = unTagURLs($text);
844		return $text;
845	}
846
847	/**
848	 * Stores the state field of the image
849	 *
850	 * @param string $state text for the state
851	 */
852	function setState($state) {
853		$this->set('state', tagURLs($state));
854	}
855
856	/**
857	 * Returns the country field of the image
858	 *
859	 * @return string
860	 */
861	function getCountry($locale = NULL) {
862		$text = $this->get('country');
863		if ($locale !== 'all') {
864			$text = get_language_string($text, $locale);
865		}
866		$text = unTagURLs($text);
867		return $text;
868	}
869
870	/**
871	 * Stores the country field of the image
872	 *
873	 * @param string $country text for the country filed
874	 */
875	function setCountry($country) {
876		$this->set('country', tagURLs($country));
877	}
878
879	/**
880	 * Returns the credit field of the image
881	 *
882	 * @return string
883	 */
884	function getCredit($locale = NULL) {
885		$text = $this->get('credit');
886		if ($locale !== 'all') {
887			$text = get_language_string($text, $locale);
888		}
889		$text = unTagURLs($text);
890		return $text;
891	}
892
893	/**
894	 * Stores the credit field of the image
895	 *
896	 * @param string $credit text for the credit field
897	 */
898	function setCredit($credit) {
899		$this->set('credit', tagURLs($credit));
900	}
901
902	/**
903	 * Returns the copyright field of the image
904	 *
905	 * @return string
906	 */
907	function getCopyright($locale = NULL) {
908		$text = $this->get('copyright');
909		if ($locale !== 'all') {
910			$text = get_language_string($text, $locale);
911		}
912		$text = unTagURLs($text);
913		return $text;
914	}
915
916	/**
917	 * Stores the text for the copyright field of the image
918	 *
919	 * @param string $copyright text for the copyright field
920	 */
921	function setCopyright($copyright) {
922		$this->set('copyright', tagURLs($copyright));
923	}
924
925	/**
926	 * Returns the content of the copyright field if set.
927	 * If not it tries the following fallbacks:
928	 *
929	 * - IPTCCopyright field
930	 * - EXIFCopyright field
931	 * - "coypright_image_notice" option
932	 * - Owner
933	 *
934	 * @since ZenphotoCMS 1.5.8
935	 *
936	 * @param string $locale
937	 * @return string|null
938	 */
939	function getCopyrightNotice($locale = null) {
940		$copyright = trim($this->getCopyright($locale));
941		if (!empty($copyright)) {
942			$notice = $copyright;
943		} else {
944			$metadata = $this->getMetaData();
945			if (isset($metadata['IPTCCopyright']) && !empty($metadata['IPTCCopyright'])) {
946				$notice = $metadata['IPTCCopyright'];
947			} else if (isset($metadata['EXIFCopyright']) && !empty($metadata['EXIFCopyright'])) {
948				$notice = $metadata['EXIFCopyright'];
949			} else if (empty($notice)) {
950				$option = trim(getOption('copyright_image_notice'));
951				if (!empty($option)) {
952					$notice = $option;
953				}
954			}
955		}
956		if (!empty(trim($notice))) {
957			$notice = unTagURLs(get_language_string($notice, $locale));
958		}
959		return $notice;
960	}
961
962	/**
963	 * Gets the general option "copyright_image_rightsholder" respectively "copyright_image_rightsholder_custom"
964	 * If set to "none" the following fallbacks are tried.
965	 *
966	 * - EXIFArtist
967	 * - VideoArtist (for multimedia "images")
968	 * – IPTCByLine
969	 * - the owner (fullname if available)
970	 *
971	 * @since ZenphotoCMS 1.5.8
972	 */
973	function getCopyrightRightsholder() {
974		$rightsholder = trim(getOption('copyright_image_rightsholder'));
975		if ($rightsholder && $rightsholder != 'none') {
976			if ($rightsholder == 'custom') {
977				$rightsholder = trim(getOption('copyright_image_rightsholder_custom'));
978			} else {
979				$rightsholder = Administrator::getNameByUser($rightsholder);
980			}
981		} else {
982			$metadata = $this->getMetaData();
983			if (isset($metadata['EXIFArtist']) && !empty($metadata['EXIFArtist'])) {
984				$rightsholder = $metadata['EXIFArtist'];
985			} else if (isset($metadata['VideoArtist']) && !empty($metadata['VideoArtist'])) {
986				$rightsholder = $metadata['VideoArtist'];
987			} else if (isset($metadata['IPTCByLine']) && !empty($metadata['IPTCByLine'])) {
988				$rightsholder = $metadata['IPTCByLine'];
989			}
990		}
991		if (empty($rightsholder)) {
992			$rightsholder = $this->getOwner(true);
993		}
994		return $rightsholder;
995	}
996
997	/**
998	 * Gets the image copyright URL
999	 *
1000	 * @since ZenhphotoCMS 1.5.8
1001	 *
1002	 * @return string
1003	 */
1004	function getCopyrightURL() {
1005		$url = getOption('copyright_image_url');
1006		if ($url) {
1007			if ($url == 'custom') {
1008				return getOption('copyright_image_url_custom');
1009			} else if ($url == 'none') {
1010				return null;
1011			} else {
1012				if (extensionEnabled('zenpage') && ZP_PAGES_ENABLED) {
1013					$pageobj = new ZenpagePage($url);
1014					if ($pageobj->exists) {
1015						return $pageobj->getLink();
1016					}
1017				}
1018			}
1019		}
1020	}
1021
1022	/**
1023   * Permanently delete this image (permanent: be careful!)
1024   * Returns the result of the unlink operation (whether the delete was successful)
1025   * @param bool $clean whether to remove the database entry.
1026   * @return bool
1027   */
1028  function remove() {
1029    $result = false;
1030    if (parent::remove()) {
1031      $result = true;
1032      $filestodelete = safe_glob(substr($this->localpath, 0, strrpos($this->localpath, '.')) . '.*');
1033      foreach ($filestodelete as $file) {
1034        @chmod($file, 0777);
1035        $result = $result && @unlink($file);
1036      }
1037      if ($result) {
1038				$this->setUpdatedDateAlbum();
1039        query("DELETE FROM " . prefix('obj_to_tag') . "WHERE `type`='images' AND `objectid`=" . $this->id);
1040        query("DELETE FROM " . prefix('comments') . "WHERE `type` ='images' AND `ownerid`=" . $this->id);
1041        $cachepath = SERVERCACHE . '/' . pathurlencode($this->album->name) . '/' . $this->filename;
1042        $cachefilestodelete = safe_glob(substr($cachepath, 0, strrpos($cachepath, '.')) . '_*');
1043        foreach ($cachefilestodelete as $file) {
1044          @chmod($file, 0777);
1045          @unlink($file);
1046        }
1047
1048      }
1049    }
1050    clearstatcache();
1051    return $result;
1052  }
1053
1054  /**
1055	 * Moves an image to a new album and/or filename (rename).
1056	 * Returns  0 on success and error indicator on failure.
1057	 * @param Album $newalbum the album to move this file to. Must be a valid Album object.
1058	 * @param string $newfilename the new file name of the image in the specified album.
1059	 * @return int
1060	 */
1061	function move($newalbum, $newfilename = null) {
1062		if (is_string($newalbum))
1063			$newalbum = newAlbum($newalbum, false);
1064		if ($newfilename == null) {
1065			$newfilename = $this->filename;
1066		} else {
1067			if (getSuffix($this->filename) != getSuffix($newfilename)) { // that is a no-no
1068				return 6;
1069			}
1070		}
1071		if ($newalbum->getID() == $this->album->getID() && $newfilename == $this->filename) {
1072			// Nothing to do - moving the file to the same place.
1073			return 2;
1074		}
1075		$newpath = $newalbum->localpath . internalToFilesystem($newfilename);
1076		if (file_exists($newpath)) {
1077			// If the file exists, don't overwrite it.
1078			if (!(CASE_INSENSITIVE && strtolower($newpath) == strtolower($this->localpath))) {
1079				return 2;
1080			}
1081		}
1082		$filename = basename($this->localpath);
1083		@chmod($filename, 0777);
1084		$result = @rename($this->localpath, $newpath);
1085		@chmod($filename, FILE_MOD);
1086		$this->localpath = $newpath;
1087		clearstatcache();
1088		if ($result) {
1089			$filestomove = safe_glob(substr($this->localpath, 0, strrpos($this->localpath, '.')) . '.*');
1090			foreach ($filestomove as $file) {
1091				if (in_array(strtolower(getSuffix($file)), $this->sidecars)) {
1092					$result = $result && @rename($file, stripSuffix($newpath) . '.' . getSuffix($file));
1093				}
1094			}
1095		}
1096		if ($result) {
1097			if (parent::move(array('filename' => $newfilename, 'albumid' => $newalbum->getID()))) {
1098				$this->setUpdatedDateAlbum();
1099				$newalbum->setUpdatedDate();
1100				$newalbum->save();
1101				$newalbum->setUpdatedDateParents();
1102				$this->set('mtime', filemtime($newpath));
1103				$this->save();
1104				return 0;
1105			}
1106		}
1107		return 1;
1108	}
1109
1110	/**
1111	 * Renames an image to a new filename, keeping it in the same album. Convenience for move($image->album, $newfilename).
1112	 * Returns  true on success and false on failure.
1113	 * @param string $newfilename the new file name of the image file.
1114	 * @return bool
1115	 */
1116	function rename($newfilename) {
1117		return $this->move($this->album, $newfilename);
1118	}
1119
1120	/**
1121	 * Copies the image to a new album, along with all metadata.
1122	 *
1123	 * @param string $newalbum the destination album
1124	 */
1125	function copy($newalbum) {
1126		if (is_string($newalbum)) {
1127			$newalbum = newAlbum($newalbum, false);
1128		}
1129		if ($newalbum->getID() == $this->album->getID()) {
1130			// Nothing to do - moving the file to the same place.
1131			return 2;
1132		}
1133		$newpath = $newalbum->localpath . internalToFilesystem($this->filename);
1134		if (file_exists($newpath)) {
1135			// If the file exists, don't overwrite it.
1136			return 2;
1137		}
1138		$filename = basename($this->localpath);
1139		$result = @copy($this->localpath, $newpath);
1140		if ($result) {
1141			$filestocopy = safe_glob(substr($this->localpath, 0, strrpos($this->localpath, '.')) . '.*');
1142			foreach ($filestocopy as $file) {
1143				if (in_array(strtolower(getSuffix($file)), $this->sidecars)) {
1144					$result = $result && @copy($file, $newalbum->localpath . basename($file));
1145				}
1146			}
1147		}
1148		if ($result) {
1149			if ($newID = parent::copy(array('filename' => $filename, 'albumid' => $newalbum->getID()))) {
1150				storeTags(readTags($this->getID(), 'images'), $newID, 'images');
1151				query('UPDATE ' . prefix('images') . ' SET `mtime`=' . filemtime($newpath) . ' WHERE `filename`="' . $filename . '" AND `albumid`=' . $newalbum->getID());
1152				$newalbum->setUpdatedDate();
1153				$newalbum->save();
1154				$newalbum->setUpdatedDateParents();
1155				return 0;
1156			}
1157		}
1158		return 1;
1159	}
1160
1161	/*	 * ** Image Methods *** */
1162
1163	/**
1164	 * Returns a path urlencoded image page link for the image
1165	 *
1166	 * @return string
1167	 */
1168	function getLink() {
1169		if (is_array($this->filename)) {
1170			$albumq = $album = dirname($this->filename['source']);
1171			$image = basename($this->filename['source']);
1172		} else {
1173			$album = $this->albumlink;
1174			$albumq = $this->albumnamealbum->name;
1175			$image = $this->filename;
1176		}
1177		return zp_apply_filter('getLink', rewrite_path(pathurlencode($album) . '/' . urlencode($image) . IM_SUFFIX, '/index.php?album=' . pathurlencode($albumq) . '&image=' . urlencode($image)), $this, NULL);
1178	}
1179
1180	/**
1181	 * Returns a path to the original image in the original folder.
1182	 *
1183	 * @param string $path the "path" to the image. Defaults to the simple WEBPATH
1184	 *
1185	 * @return string
1186	 */
1187	function getFullImage($path = WEBPATH) {
1188		global $_zp_conf_vars;
1189		if ($path == WEBPATH && $_zp_conf_vars['album_folder_class'] == 'external') {
1190			return false;
1191		}
1192		if (is_array($this->filename)) {
1193			$album = dirname($this->filename['source']);
1194			$image = basename($this->filename['source']);
1195		} else {
1196			$album = $this->imagefolder;
1197			$image = $this->filename;
1198		}
1199		return getAlbumFolder($path) . $album . "/" . $image;
1200	}
1201
1202	/**
1203	 * returns URL to the original image
1204	 */
1205	function getFullImageURL() {
1206		return $this->getFullImage(WEBPATH);
1207	}
1208
1209	/**
1210	 * Returns a path to a sized version of the image
1211	 *
1212	 * @param int $size how big an image is wanted
1213	 * @return string
1214	 */
1215	function getSizedImage($size) {
1216		$wmt = getWatermarkParam($this, WATERMARK_IMAGE);
1217		$args = getImageParameters(array($size, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, $wmt), $this->album->name);
1218		return getImageURI($args, $this->album->name, $this->filename, $this->filemtime);
1219	}
1220
1221	/**
1222	 *  Get a custom sized version of this image based on the parameters.
1223	 *
1224	 * @param int $size size
1225	 * @param int $width width
1226	 * @param int $height height
1227	 * @param int $cropw crop width
1228	 * @param int $croph crop height
1229	 * @param int $cropx crop x axis
1230	 * @param int $cropy crop y axis
1231	 * @param bool $thumbStandin set to true to treat as thumbnail
1232	 * @param bool $effects set to desired image effect (e.g. 'gray' to force gray scale)
1233	 * @return string
1234	 */
1235	function getCustomImage($size, $width, $height, $cropw, $croph, $cropx, $cropy, $thumbStandin = false, $effects = NULL) {
1236		if ($thumbStandin < 0) {
1237			$wmt = '!';
1238		} else {
1239			if ($thumbStandin) {
1240				$wmt = getWatermarkParam($this, WATERMARK_THUMB);
1241			} else {
1242				$wmt = getWatermarkParam($this, WATERMARK_IMAGE);
1243			}
1244		}
1245		$args = getImageParameters(array($size, $width, $height, $cropw, $croph, $cropx, $cropy, NULL, $thumbStandin, NULL, $thumbStandin, $wmt, NULL, $effects), $this->album->name);
1246		return getImageURI($args, $this->album->name, $this->filename, $this->filemtime);
1247	}
1248
1249	/**
1250	 * Returns the default sized image HTML
1251	 *
1252	 * @return string
1253	 */
1254	function getContent() {
1255		$class = '';
1256		if (!$this->isPublished()) {
1257			$class .= " not_visible";
1258		}
1259		$album = $this->getAlbum();
1260		$pwd = $album->getPassword();
1261		if (!empty($pwd)) {
1262			$class .= " password_protected";
1263		}
1264		$size = getOption('image_size');
1265		$h = $this->getHeight();
1266		$w = $this->getWidth();
1267		$side = getOption('image_use_side');
1268		$us = getOption('image_allow_upscale');
1269		$dim = $size;
1270
1271		if ($w == 0) {
1272			$hprop = 1;
1273		} else {
1274			$hprop = round(($h / $w) * $dim);
1275		}
1276		if ($h == 0) {
1277			$wprop = 1;
1278		} else {
1279			$wprop = round(($w / $h) * $dim);
1280		}
1281
1282		if (($size && ($side == 'longest' && $h > $w) || ($side == 'height') || ($side == 'shortest' && $h < $w))) {
1283			// Scale the height
1284			$newh = $dim;
1285			$neww = $wprop;
1286		} else {
1287			// Scale the width
1288			$neww = $dim;
1289			$newh = $hprop;
1290		}
1291		if (!$us && $newh >= $h && $neww >= $w) {
1292			$neww = $w;
1293			$newh = $h;
1294		}
1295		$html = '<img src="' . html_encode(pathurlencode($this->getSizedImage($size))) . '" alt="' . html_encode($this->getTitle()) . '"' .
1296						' width="' . $neww . '" height="' . $newh . '"' .
1297						(($class) ? " class=\"$class\"" : "") . " />";
1298		$html = zp_apply_filter('standard_image_html', $html, $this);
1299		return $html;
1300	}
1301
1302	/**
1303	 * Returns the image file name for the thumbnail image.
1304	 *
1305	 * @param string $path override path
1306	 *
1307	 * @return s
1308	 */
1309	function getThumbImageFile() {
1310		return $local = $this->localpath;
1311	}
1312
1313	/**
1314	 * Returns an array of cropping parameters. Used as a "helper" function for various
1315	 * inherited getThumb() methods
1316	 *
1317	 * @param string $type the type of thumb (in case it ever matters in the cropping, now it does not.)
1318	 */
1319	function getThumbCropping($ts, $sw, $sh) {
1320		$cy = $this->get('thumbY');
1321		if (is_null($cy)) {
1322			$custom = $cx = NULL;
1323			$cw = $sw;
1324			$ch = $sh;
1325		} else {
1326			$custom = true;
1327			$cx = $this->get('thumbX');
1328			$cw = $this->get('thumbW');
1329			$ch = $this->get('thumbH');
1330			// upscale to thumb_size proportions
1331			if ($sw == $sh) { // square crop, set the size/width to thumbsize
1332				$sw = $sh = $ts;
1333			} else {
1334				if ($sw > $sh) {
1335					$r = $ts / $sw;
1336					$sw = $ts;
1337					$sh = $sh * $r;
1338				} else {
1339					$r = $ts / $sh;
1340					$sh = $ts;
1341					$sh = $r * $sh;
1342				}
1343			}
1344		}
1345		return array($custom, $cw, $ch, $cx, $cy);
1346	}
1347
1348	/**
1349	 * Get a default-sized thumbnail of this image.
1350	 *
1351	 * @return string
1352	 */
1353	function getThumb($type = 'image') {
1354		$ts = getOption('thumb_size');
1355		if (getOption('thumb_crop')) {
1356			$sw = getOption('thumb_crop_width');
1357			$sh = getOption('thumb_crop_height');
1358			list($custom, $cw, $ch, $cx, $cy) = $this->getThumbCropping($ts, $sw, $sh);
1359			if ($custom) {
1360				$ts = null;
1361			}
1362		} else {
1363			$sw = $sh = $cw = $ch = $cx = $cy = null;
1364		}
1365		return $this->getCustomImage($ts, $sw, $sh, $cw, $ch, $cx, $cy, true);
1366	}
1367
1368	/**
1369	 * Get the index of this image in the album, taking sorting into account.
1370	 * @param bool $use_realalbum If the image is wihtin a dynamic album this is the index within it, set to true to get the index of the actual physical album the image belongs
1371	 * @return int
1372	 */
1373	function getIndex($use_realalbum = false) {
1374		global $_zp_current_search, $_zp_current_album;
1375		$use_realalbum = true;
1376		if ($this->index == NULL) {
1377			if ($use_realalbum) {
1378				$album = $this->getAlbum();
1379			} else {
1380				$album = $this->albumnamealbum;
1381			}
1382			if (!is_null($_zp_current_search) && !in_context(ZP_ALBUM_LINKED) || $album->isDynamic()) {
1383				if ($album->isDynamic()) {
1384					$images = $album->getImages();
1385					for ($i = 0; $i < count($images); $i++) {
1386						$image = $images[$i];
1387						if ($this->filename == $image['filename']) {
1388							$this->index = $i;
1389							break;
1390						}
1391					}
1392				} else {
1393					$this->index = $_zp_current_search->getImageIndex($this->imagefolder, $this->filename);
1394				}
1395			} else {
1396				$images = $this->album->getImages(0);
1397				for ($i = 0; $i < count($images); $i++) {
1398					$image = $images[$i];
1399					if ($this->filename == $image) {
1400						$this->index = $i;
1401						break;
1402					}
1403				}
1404			}
1405		}
1406		return $this->index;
1407	}
1408
1409	/**
1410	 * Returns the next Image.
1411	 *
1412	 * @return object
1413	 */
1414	function getNextImage() {
1415		global $_zp_current_search;
1416		$index = $this->getIndex();
1417		if (!is_null($_zp_current_search) && !in_context(ZP_ALBUM_LINKED)) {
1418			$image = $_zp_current_search->getImage($index + 1);
1419		} else {
1420			$album = $this->albumnamealbum;
1421			$image = $album->getImage($index + 1);
1422		}
1423		return $image;
1424	}
1425
1426	/**
1427	 * Return the previous Image
1428	 *
1429	 * @return object
1430	 */
1431	function getPrevImage() {
1432		global $_zp_current_search;
1433		$index = $this->getIndex();
1434		if (!is_null($_zp_current_search) && !in_context(ZP_ALBUM_LINKED)) {
1435			$image = $_zp_current_search->getImage($index - 1);
1436		} else {
1437			$album = $this->albumnamealbum;
1438			$image = $album->getImage($index - 1);
1439		}
1440		return $image;
1441	}
1442
1443	/**
1444	 * Returns the disk size of the image
1445	 *
1446	 * @return string
1447	 */
1448	function getImageFootprint() {
1449		return filesize($this->localpath);
1450	}
1451
1452	/**
1453	 * Returns the custom watermark name
1454	 *
1455	 * @return string
1456	 */
1457	function getWatermark() {
1458		return $this->get('watermark');
1459	}
1460
1461	/**
1462	 * Set custom watermark
1463	 *
1464	 * @param string $wm
1465	 */
1466	function setWatermark($wm) {
1467		$this->set('watermark', $wm);
1468	}
1469
1470	/**
1471	 * Returns the custom watermark usage
1472	 *
1473	 * @return bool
1474	 */
1475	function getWMUse() {
1476		return $this->get('watermark_use');
1477	}
1478
1479	/**
1480	 * Sets the custom watermark usage
1481	 *
1482	 * @param $use
1483	 */
1484	function setWMUse($use) {
1485		$this->set('watermark_use', $use);
1486	}
1487
1488	/**
1489	 * Gets the owner of the image
1490	 *
1491	 * @param bool $fullname Set to true to get the full name (if the owner is a vaild user of the site and has the full name defined)
1492	 * @return string
1493	 */
1494	function getOwner($fullname = false) {
1495		$owner = $this->get('owner');
1496		if (empty($owner)) {
1497			$owner = $this->album->getOwner();
1498		}
1499		if ($fullname) {
1500			return Zenphoto_Administrator::getNameByUser($owner);
1501		}
1502		return $owner;
1503	}
1504
1505	function setOwner($owner) {
1506		$this->set('owner', $owner);
1507	}
1508
1509	function isMyItem($action) {
1510		$album = $this->album;
1511		return $album->isMyItem($action);
1512	}
1513
1514	/**
1515	 * returns true if user is allowed to see the image
1516	 */
1517	function checkAccess(&$hint = NULL, &$show = NULL) {
1518		$album = $this->getAlbum();
1519		if ($album->isMyItem(LIST_RIGHTS)) {
1520			return $this->isPublished() || $album->albumSubRights() & (MANAGED_OBJECT_RIGHTS_EDIT | MANAGED_OBJECT_RIGHTS_VIEW);
1521		}
1522		return $album->checkforGuest($hint, $show) && $this->isPublished() && $album->isPublished();
1523	}
1524
1525	/**
1526	 * Checks if guest is loggedin for the album
1527	 * @param unknown_type $hint
1528	 * @param unknown_type $show
1529	 */
1530	function checkforGuest(&$hint = NULL, &$show = NULL) {
1531		if (!parent::checkForGuest()) {
1532			return false;
1533		}
1534		$album = $this->getAlbum();
1535		return $album->checkforGuest($hint, $show);
1536	}
1537
1538	/**
1539	 *
1540	 * returns true if there is any protection on the image
1541	 */
1542	function isProtected() {
1543		return $this->checkforGuest() != 'zp_public_access';
1544	}
1545
1546	/**
1547	 * Returns true if this image is published and also its album and all of its parents.
1548	 *
1549	 * @since Zenphoto 1.5.5
1550	 *
1551	 * @return bool
1552	 */
1553	function isPublic() {
1554		if (is_null($this->is_public)) {
1555			if (!$this->isPublished()) {
1556				return $this->is_public = false;
1557			}
1558			$album = $this->getAlbum();
1559			if(!$album->isPublic()) {
1560				return $this->is_public = false;
1561			}
1562			return $this->is_public = true;
1563		} else {
1564			return $this->is_public;
1565		}
1566	}
1567
1568	/**
1569	 * Returns the filesize in bytes of the full image
1570	 *
1571	 * @since ZenphotoCMS 1.5.2
1572	 *
1573	 * @return int|false
1574	 */
1575	function getFilesize() {
1576		$album = $this->getAlbum();
1577		$filesize = filesize($this->getFullImage(SERVERPATH));
1578		return $filesize;
1579	}
1580
1581	/**
1582	 * Sets the current date to the images'album and all of its parent albums recursively
1583	 * @since Zenphoto 1.5.5
1584	 */
1585	function setUpdatedDateAlbum() {
1586		$album = $this->album;
1587		if($album) {
1588			$album->setUpdatedDate();
1589			$album->save();
1590			$album->setUpdatedDateParents();
1591		}
1592	}
1593
1594}
1595
1596/**
1597 * Transient image class
1598 * @package core
1599 * @subpackage classes\objects
1600 */
1601class Transientimage extends Image {
1602
1603	/**
1604	 * creates a transient image (that is, one that is not stored in the database)
1605	 *
1606	 * @param string $image the full path to the image
1607	 * @return transientimage
1608	 */
1609	function __construct($album, $image) {
1610		if (!is_object($album)) {
1611			$album = new AlbumBase('Transient');
1612		}
1613		$this->album = $album;
1614		$this->localpath = $image;
1615		$filename = makeSpecialImageName($image);
1616		$this->filename = $filename;
1617		$this->displayname = stripSuffix(basename($image));
1618		if (empty($this->displayname)) {
1619			$this->displayname = $this->filename['name'];
1620		}
1621		$this->filemtime = @filemtime($this->localpath);
1622		$this->comments = null;
1623		$this->instantiate('images', array('filename' => $filename['name'], 'albumid' => $this->album->getID()), 'filename', true, true);
1624		$this->exists = false;
1625	}
1626
1627}
1628
1629?>
1630