1<?php
2
3
4// force UTF-8 Ø
5
6define('IMAGE_SORT_DIRECTION', getOption('image_sortdirection'));
7define('IMAGE_SORT_TYPE', getOption('image_sorttype'));
8
9Gallery::addAlbumHandler('alb', 'dynamicAlbum');
10
11/**
12 * Wrapper instantiation function for albums. Do not instantiate directly
13 * @param string $folder8 the name of the folder (inernal character set)
14 * @param bool $cache true if the album should be fetched from the cache
15 * @param bool $quiet true to supress error messages
16 * @return Album
17 */
18function newAlbum($folder8, $cache = true, $quiet = false) {
19	global $_zp_albumHandlers;
20	$suffix = getSuffix($folder8);
21	if (!$suffix || !array_key_exists($suffix, $_zp_albumHandlers) || is_dir(ALBUM_FOLDER_SERVERPATH . internalToFilesystem($folder8))) {
22		return new Album($folder8, $cache, $quiet);
23	} else {
24		return new $_zp_albumHandlers[$suffix]($folder8, $cache, $quiet);
25	}
26}
27
28/**
29 * Returns true if the object is a zenphoto 'album'
30 *
31 * @param object $album
32 * @return bool
33 */
34function isAlbumClass($album = NULL) {
35	global $_zp_current_album;
36	if (is_null($album)) {
37		if (!in_context(ZP_ALBUM))
38			return false;
39		$album = $_zp_current_album;
40	}
41	return is_object($album) && ($album->table == 'albums');
42}
43
44/**
45 * Album Base Class
46 * @package core
47 * @subpackage classes\objects
48 */
49class AlbumBase extends MediaObject {
50
51	public $name; // Folder name of the album (full path from the albums folder)
52	public $linkname; // may have the .alb suffix stripped off
53	public $localpath; // Latin1 full server path to the album
54	public $exists = true; // Does the folder exist?
55	public $images = null; // Full images array storage.
56	public $parent = null; // The parent album name
57	public $parentalbum = null; // The parent album's album object (lazy)
58	public $parentalbums = null; // Array of objects of parent albums (lazy)
59	public $sidecars = array(); // keeps the list of suffixes associated with this album
60	public $manage_rights = MANAGE_ALL_ALBUM_RIGHTS;
61	public $manage_some_rights = ALBUM_RIGHTS;
62	public $view_rights = ALL_ALBUMS_RIGHTS;
63	protected $subalbums = null; // Full album array storage.
64	protected $index;
65	protected $lastimagesort = NULL; // remember the order for the last album/image sorts
66	protected $lastsubalbumsort = NULL;
67	protected $albumthumbnail = NULL; // remember the album thumb for the duration of the script
68	protected $subrights = NULL; //	cache for album subrights
69	protected $num_allalbums = null; // count of all subalbums of all sublevels
70	protected $num_allimages = null; // count of all images of all sublevels
71	protected $is_public = null;
72
73	function __construct($folder8, $cache = true) {
74		$this->linkname = $this->name = $folder8;
75		$this->instantiate('albums', array('folder' => $this->name), 'folder', false, true);
76		$this->exists = false;
77	}
78
79	/**
80	 * Sets default values for a new album
81	 *
82	 * @return bool
83	 */
84	protected function setDefaults() {
85		global $_zp_gallery;
86		if (TEST_RELEASE) {
87			$bt = debug_backtrace();
88			$good = false;
89			foreach ($bt as $b) {
90				if ($b['function'] == "newAlbum") {
91					$good = true;
92					break;
93				}
94			}
95			if (!$good) {
96				zp_error(gettext('An album object was instantiated without using the newAlbum() function.'), E_USER_WARNING);
97			}
98		}
99// Set default data for a new Album (title and parent_id)
100		$parentalbum = NULL;
101		$this->setShow($_zp_gallery->getAlbumPublish());
102		$this->set('mtime', time());
103		$this->setLastChange();
104		$title = trim($this->name);
105		$this->set('title', sanitize($title, 2));
106		return true;
107	}
108
109	/**
110	 * Returns the folder on the filesystem
111	 *
112	 * @return string
113	 */
114	function getFileName() {
115		return $this->name;
116	}
117
118	/**
119	 * Returns the folder on the filesystem
120	 *
121	 * @return string
122	 */
123	function getFolder() {
124		return $this->name;
125	}
126
127	/**
128	 * Returns The parent Album of this Album. NULL if this is a top-level album.
129	 *
130	 * @return object
131	 */
132	function getParent() {
133		if (is_null($this->parentalbum)) {
134			$slashpos = strrpos($this->name, "/");
135			if ($slashpos) {
136				$parent = substr($this->name, 0, $slashpos);
137				$parentalbum = newAlbum($parent, true, true);
138				if ($parentalbum->exists) {
139					return $parentalbum;
140				}
141			}
142		} else if ($this->parentalbum->exists) {
143			return $this->parentalbum;
144		}
145		return NULL;
146	}
147
148	/**
149	 * Gets an array of parent album objects
150	 *
151	 * @since Zenphoto 1.5.5
152	 *
153	 * @return array|null
154	 */
155	function getParents() {
156		if (is_null($this->parentalbums)) {
157			$parents = array();
158			$album = $this;
159			while (!is_null($album = $album->getParent())) {
160				array_unshift($parents, $album);
161			}
162			return $this->parentalbums = $parents;
163		} else {
164			return $this->parentalbums;
165		}
166	}
167
168
169	function getParentID() {
170		return $this->get('parentid');
171	}
172
173	/**
174	 * Returns the place data of an album
175	 *
176	 * @return string
177	 */
178	function getLocation($locale = NULL) {
179		$text = $this->get('location');
180		if ($locale !== 'all') {
181			$text = get_language_string($text, $locale);
182		}
183		$text = unTagURLs($text);
184		return $text;
185	}
186
187	/**
188	 * Stores the album place
189	 *
190	 * @param string $place text for the place field
191	 */
192	function setLocation($place) {
193		$this->set('location', tagURLs($place));
194	}
195
196	/**
197	 * Returns either the subalbum sort direction or the image sort direction of the album
198	 *
199	 * @param string $what 'image_' if you want the image direction,
200	 *        'album' if you want it for the album
201	 *
202	 * @return string
203	 */
204	function getSortDirection($what = 'image') {
205		global $_zp_gallery;
206		if ($what == 'image') {
207			$direction = $this->get('image_sortdirection');
208			$type = $this->get('sort_type');
209		} else {
210			$direction = $this->get('album_sortdirection');
211			$type = $this->get('subalbum_sort_type');
212		}
213		if (empty($type)) {
214// using inherited type, so use inherited direction
215			$parentalbum = $this->getParent();
216			if (is_null($parentalbum)) {
217				if ($what == 'image') {
218					$direction = IMAGE_SORT_DIRECTION;
219				} else {
220					$direction = $_zp_gallery->getSortDirection();
221				}
222			} else {
223				$direction = $parentalbum->getSortDirection($what);
224			}
225		}
226		return $direction;
227	}
228
229	/**
230	 * Returns the sort type of the album images
231	 * Will return a parent sort type if the sort type for this album is empty
232	 *
233	 * @return string
234	 */
235	function getSortType($what = 'image') {
236		global $_zp_gallery;
237		if ($what == 'image') {
238			$type = $this->get('sort_type');
239		} else {
240			$type = $this->get('subalbum_sort_type');
241		}
242		if (empty($type)) {
243			$parentalbum = $this->getParent();
244			if (is_null($parentalbum)) {
245				if ($what == 'image') {
246					$type = IMAGE_SORT_TYPE;
247				} else {
248					$type = $_zp_gallery->getSortType();
249				}
250			} else {
251				$type = $parentalbum->getSortType($what);
252			}
253		}
254		return $type;
255	}
256
257	/**
258	 * sets sort directions for the album
259	 *
260	 * @param bool $val the direction
261	 * @param string $what 'image_sortdirection' if you want the image direction,
262	 *        'album_sortdirection' if you want it for the album
263	 */
264	function setSortDirection($val, $what = 'image') {
265		if ($what == 'image') {
266			$this->set('image_sortdirection', (int) ($val && true));
267		} else {
268			$this->set('album_sortdirection', (int) ($val && true));
269		}
270	}
271
272	/**
273	 * Stores the sort type for the album
274	 *
275	 * @param string $sorttype the album sort type
276	 * @param string $what 'Description'image' or 'album'
277	 */
278	function setSortType($sorttype, $what = 'image') {
279		if ($what == 'image') {
280			$this->set('sort_type', $sorttype);
281		} else {
282			$this->set('subalbum_sort_type', $sorttype);
283		}
284	}
285
286	/**
287	 * Returns the DB key associated with the image sort type
288	 *
289	 * @param string $sorttype the sort type
290	 * @return string
291	 */
292	function getImageSortKey($sorttype = null) {
293		if (is_null($sorttype)) {
294			$sorttype = $this->getSortType();
295		}
296		return lookupSortKey($sorttype, 'filename', 'images');
297	}
298
299	/**
300	 * Returns the DB key associated with the subalbum sort type
301	 *
302	 * @param string $sorttype subalbum sort type
303	 * @return string
304	 */
305	function getAlbumSortKey($sorttype = null) {
306		if (empty($sorttype)) {
307			$sorttype = $this->getSortType('album');
308		}
309		return lookupSortKey($sorttype, 'sort_order', 'albums');
310	}
311
312	/**
313	 * Returns all folder names for all the subdirectories.
314	 *
315	 * @param string $page  Which page of subalbums to display.
316	 * @param string $sorttype The sort strategy
317	 * @param string $sortdirection The direction of the sort
318	 * @param bool $care set to false if the order does not matter
319	 * @param bool $mine set true/false to override ownership
320	 * @return array
321	 */
322	function getAlbums($page = 0, $sorttype = null, $sortdirection = null, $care = true, $mine = NULL) {
323		if ($page == 0) {
324			return $this->subalbums;
325		} else {
326			$albums_per_page = max(1, getOption('albums_per_page'));
327			return array_slice($this->subalbums, $albums_per_page * ($page - 1), $albums_per_page);
328		}
329	}
330
331	/**
332	 * Returns the count of direct child subalbums
333	 *
334	 * @return int
335	 */
336	function getNumAlbums() {
337		return count($this->getAlbums(0, NULL, NULL, false));
338	}
339
340	/**
341	 * Returns the count of all subalbums of all sublevels
342	 * Note that dynamic albums are not counted
343	 *
344	 * @since Zenphoto 1.5.2
345	 */
346	function getNumAllAlbums() {
347		if (!is_null($this->num_allalbums)) {
348			return $this->num_allalbums;
349		} else {
350			$count = $this->getNumAlbums();
351			$subalbums = $this->getAlbums();
352			foreach ($subalbums as $folder) {
353				$subalbum = newAlbum($folder);
354				if (!$subalbum->isDynamic()) {
355					$count += $subalbum->getNumAllAlbums();
356				}
357			}
358			return $count;
359		}
360	}
361
362	/**
363	 * Returns a of a slice of the images for this album. They will
364	 * also be sorted according to the sort type of this album, or by filename if none
365	 * has been set.
366	 *
367	 * @param string $page  Which page of images should be returned. If zero, all images are returned.
368	 * @param int $firstPageCount count of images that go on the album/image transition page
369	 * @param string $sorttype optional sort type
370	 * @param string $sortdirection optional sort direction
371	 * @param bool $care set to false if the order of the images does not matter
372	 * @param bool $mine set true/false to override ownership
373	 *
374	 * @return array
375	 */
376	function getImages($page = 0, $firstPageCount = 0, $sorttype = null, $sortdirection = null, $care = true, $mine = NULL) {
377// Return the cut of images based on $page. Page 0 means show all.
378		if ($page == 0) {
379			return $this->images;
380		} else {
381// Only return $firstPageCount images if we are on the first page and $firstPageCount > 0
382			if (($page == 1) && ($firstPageCount > 0)) {
383				$pageStart = 0;
384				$images_per_page = $firstPageCount;
385			} else {
386				if ($firstPageCount > 0) {
387					$fetchPage = $page - 2;
388				} else {
389					$fetchPage = $page - 1;
390				}
391				$images_per_page = max(1, getOption('images_per_page'));
392				$pageStart = (int) ($firstPageCount + $images_per_page * $fetchPage);
393			}
394			return array_slice($this->images, $pageStart, $images_per_page);
395		}
396	}
397
398	/**
399	 * Returns the number of images in this album (not counting its subalbums)
400	 *
401	 * @return int
402	 */
403	function getNumImages() {
404		if (is_null($this->images)) {
405			return count($this->getImages(0, 0, NULL, NULL, false));
406		}
407		return count($this->images);
408	}
409
410	/**
411	 * Returns the number of images in this album and subalbums of all levels
412	 * Note that dynamic albums are not counted.
413	 *
414	 * @since Zenphoto 1.5.2
415	 */
416	function getNumAllImages() {
417		if (!is_null($this->num_allimages)) {
418			return $this->num_allimages;
419		} else {
420			$count = $this->getNumImages();
421			$subalbums = $this->getAlbums();
422			foreach ($subalbums as $folder) {
423				$subalbum = newAlbum($folder);
424				if (!$subalbum->isDynamic()) {
425					$count += $subalbum->getNumAllImages();
426				}
427			}
428			return $count;
429		}
430	}
431
432	/**
433	 * Returns an image from the album based on the index passed.
434	 *
435	 * @param int $index
436	 * @return int
437	 */
438	function getImage($index) {
439		$images = $this->getImages();
440		if ($index >= 0 && $index < count($images)) {
441			return newImage($this, $this->images[$index]);
442		}
443		return false;
444	}
445
446	/**
447	 * Gets the album's set thumbnail image from the database if one exists,
448	 * otherwise, finds the first image in the album or sub-album and returns it
449	 * as an Image object.
450	 *
451	 * @return Image
452	 */
453	function getAlbumThumbImage() {
454		global $_zp_albumthumb_selector, $_zp_gallery;
455
456		if (!is_null($this->albumthumbnail)) {
457			return $this->albumthumbnail;
458		}
459
460		$albumdir = $this->localpath;
461		$thumb = $this->get('thumb');
462		if (is_null($thumb)) {
463			$this->set('thumb', $thumb = getOption('AlbumThumbSelect'));
464		}
465		$i = strpos($thumb, '/');
466		if ($root = ($i === 0)) {
467			$thumb = substr($thumb, 1); // strip off the slash
468			$albumdir = ALBUM_FOLDER_SERVERPATH;
469		}
470		if (!empty($thumb) && !is_numeric($thumb)) {
471			if (file_exists($albumdir . internalToFilesystem($thumb))) {
472				if ($i === false) {
473					return newImage($this, $thumb);
474				} else {
475					$pieces = explode('/', $thumb);
476					$i = count($pieces);
477					$thumb = $pieces[$i - 1];
478					unset($pieces[$i - 1]);
479					$albumdir = implode('/', $pieces);
480					if (!$root) {
481						$albumdir = $this->name . "/" . $albumdir;
482					} else {
483						$albumdir = $albumdir . "/";
484					}
485					$this->albumthumbnail = newImage(newAlbum($albumdir), $thumb);
486					return $this->albumthumbnail;
487				}
488			} else {
489				$this->set('thumb', $thumb = getOption('AlbumThumbSelect'));
490			}
491		}
492		if ($shuffle = empty($thumb)) {
493			$thumbs = $this->getImages(0, 0, NULL, NULL, false);
494		} else {
495			$thumbs = $this->getImages(0, 0, $_zp_albumthumb_selector[(int) $thumb]['field'], $_zp_albumthumb_selector[(int) $thumb]['direction']);
496		}
497		if (!is_null($thumbs)) {
498			if ($shuffle) {
499				shuffle($thumbs);
500			}
501			$mine = $this->isMyItem(LIST_RIGHTS);
502			$other = NULL;
503			while (count($thumbs) > 0) {
504				// first check for images
505				$thumb = array_shift($thumbs);
506				$thumb = newImage($this, $thumb);
507				if ($mine || $thumb->isPublished()) {
508					if (isImagePhoto($thumb)) {
509						// legitimate image
510						$this->albumthumbnail = $thumb;
511						return $this->albumthumbnail;
512					} else {
513						if (!is_null($thumb->objectsThumb)) {
514							//	"other" image with a thumb sidecar
515							$this->albumthumbnail = $thumb;
516							return $this->albumthumbnail;
517						} else {
518							if (is_null($other)) {
519								$other = $thumb;
520							}
521						}
522					}
523				}
524			}
525			if (!is_null($other)) {
526				//	"other" image, default thumb
527				$this->albumthumbnail = $other;
528				return $this->albumthumbnail;
529			}
530		}
531
532		// Otherwise, look in sub-albums.
533		$subalbums = $this->getAlbums();
534		if (!is_null($subalbums)) {
535			if ($shuffle) {
536				shuffle($subalbums);
537			}
538			while (count($subalbums) > 0) {
539				$folder = array_pop($subalbums);
540				$subalbum = newAlbum($folder);
541				$pwd = $subalbum->getPassword();
542				if (($subalbum->isPublished() && empty($pwd)) || $subalbum->isMyItem(LIST_RIGHTS)) {
543					$thumb = $subalbum->getAlbumThumbImage();
544					if (strtolower(get_class($thumb)) !== 'transientimage' && $thumb->exists) {
545						$this->albumthumbnail = $thumb;
546						return $thumb;
547					}
548				}
549			}
550		}
551
552		$nullimage = SERVERPATH . '/' . ZENFOLDER . '/images/imageDefault.png';
553		// check for theme imageDefault.png
554		$theme = '';
555		$uralbum = getUralbum($this);
556		$albumtheme = $uralbum->getAlbumTheme();
557		if (!empty($albumtheme)) {
558			$theme = $albumtheme;
559		} else {
560			$theme = $_zp_gallery->getCurrentTheme();
561		}
562		if (!empty($theme)) {
563			$themeimage = SERVERPATH . '/' . THEMEFOLDER . '/' . $theme . '/images/imageDefault.png';
564			if (file_exists(internalToFilesystem($themeimage))) {
565				$nullimage = $themeimage;
566			}
567		}
568
569		$this->albumthumbnail = new transientimage($this, $nullimage);
570		return $this->albumthumbnail;
571	}
572
573	/**
574	 * Gets the thumbnail URL for the album thumbnail image as returned by $this->getAlbumThumbImage();
575	 * @return string
576	 */
577	function getThumb() {
578		$image = $this->getAlbumThumbImage();
579		return $image->getThumb('album');
580	}
581
582	/**
583	 * Stores the thumbnail path for an album thumg
584	 *
585	 * @param string $filename thumbnail path
586	 */
587	function setThumb($filename) {
588		$this->set('thumb', $filename);
589	}
590
591	/**
592	 * Returns an URL to the album, including the current page number
593	 *
594	 * @param string $page if not null, apppend as page #
595	 * @return string
596	 */
597	function getLink($page = NULL) {
598		global $_zp_current_album;
599		global $_zp_page;
600		if (is_null($page) && $_zp_current_album && $_zp_current_album->name == $this->name) {
601			$page = $_zp_page;
602		}
603		$rewrite = pathurlencode($this->linkname) . '/';
604		$plain = '/index.php?album=' . pathurlencode($this->name);
605		if ($page > 1) {
606			$rewrite .=_PAGE_ . '/' . $page . '/';
607			$plain .= "&page=$page";
608		}
609		return zp_apply_filter('getLink', rewrite_path($rewrite, $plain), $this, $page);
610	}
611
612	/**
613	 * Delete the entire album PERMANENTLY. Be careful! This is unrecoverable.
614	 * Returns true if successful
615	 *
616	 * @return bool
617	 */
618	function remove() {
619		$rslt = false;
620		if (PersistentObject::remove()) {
621			query("DELETE FROM " . prefix('options') . "WHERE `ownerid`=" . $this->id);
622			query("DELETE FROM " . prefix('comments') . "WHERE `type`='albums' AND `ownerid`=" . $this->id);
623			query("DELETE FROM " . prefix('obj_to_tag') . "WHERE `type`='albums' AND `objectid`=" . $this->id);
624			$rslt = true;
625			$filestoremove = safe_glob(substr($this->localpath, 0, -1) . '.*');
626			foreach ($filestoremove as $file) {
627				if (in_array(strtolower(getSuffix($file)), $this->sidecars)) {
628					@chmod($file, 0777);
629					unlink($file);
630				}
631			}
632			$this->setUpdatedDateParents();
633		}
634		return $rslt;
635	}
636
637	/**
638	 * common album move code
639	 * @param type $newfolder
640	 * @return int
641	 */
642	protected function _move($newfolder) {
643		// First, ensure the new base directory exists.
644		$dest = ALBUM_FOLDER_SERVERPATH . internalToFilesystem($newfolder);
645		// Check to see if the destination already exists
646		if (file_exists($dest)) {
647			// Disallow moving an album over an existing one.
648			if (!(CASE_INSENSITIVE && strtolower($dest) == strtolower(rtrim($this->localpath, '/')))) {
649				return 3;
650			}
651		}
652		if(!$this->isValidMoveCopyDestination($newfolder)) {
653			// Disallow moving to a subfolder of the current folder.
654			return 4;
655		}
656		$filemask = substr($this->localpath, 0, -1) . '.*';
657		$perms = FOLDER_MOD;
658		@chmod($this->localpath, 0777);
659		$success = @rename(rtrim($this->localpath, '/'), $dest);
660		@chmod($dest, $perms);
661
662		if ($success) {
663			$this->localpath = $dest . "/";
664			$filestomove = safe_glob($filemask);
665			foreach ($filestomove as $file) {
666				if (in_array(strtolower(getSuffix($file)), $this->sidecars)) {
667					$d = stripslashes($dest) . '.' . getSuffix($file);
668					@chmod($file, 0777);
669					$success = $success && @rename($file, $d);
670					@chmod($d, FILE_MOD);
671				}
672			}
673			clearstatcache();
674			$success = self::move($newfolder);
675			if ($success) {
676				// Update old parent(s) that "lost" an album!
677				$this->setUpdatedDateParents();
678				$this->save();
679
680				$this->updateParent($newfolder);
681
682				//rename the cache folder
683				$cacherename = @rename(SERVERCACHE . '/' . $this->name, SERVERCACHE . '/' . $newfolder);
684				return 0;
685			}
686		}
687		return 1;
688	}
689
690
691
692	/**
693	 * Move this album to the location specified by $newfolder, copying all
694	 * metadata, subalbums, and subalbums' metadata with it.
695	 * @param $newfolder string the folder to move to, including the name of the current folder (possibly renamed).
696	 * @return int 0 on success and error indicator on failure.
697	 *
698	 */
699	function move($newfolder) {
700		return parent::move(array('folder' => $newfolder));
701	}
702
703	/**
704	 * Rename this album folder. Alias for move($newfoldername);
705	 * @param string $newfolder the new folder name of this album (including subalbum paths)
706	 * @return boolean true on success or false on failure.
707	 */
708	function rename($newfolder) {
709		return $this->move($newfolder);
710	}
711
712	protected function succeed($dest) {
713		return false;
714	}
715
716	/**
717	 * Copy this album to the location specified by $newfolder, copying all
718	 * metadata, subalbums, and subalbums' metadata with it.
719	 * @param $newfolder string the folder to copy to, including the name of the current folder (possibly renamed).
720	 * @return int 0 on success and error indicator on failure.
721	 *
722	 */
723	function copy($newfolder) {
724		// album name to destination folder
725		if (substr($newfolder, -1, 1) != '/') {
726			$newfolder .= '/';
727		}
728		$newfolder .= basename($this->localpath);
729		// First, ensure the new base directory exists.
730		$dest = ALBUM_FOLDER_SERVERPATH . internalToFilesystem($newfolder);
731		// Check to see if the destination directory already exists
732		if (file_exists($dest)) {
733			// Disallow moving an album over an existing one.
734			return 3;
735		}
736		if(!$this->isValidMoveCopyDestination($newfolder)) {
737			// Disallow copying to a subfolder of the current folder (infinite loop).
738			return 4;
739		}
740		$success = $this->succeed($dest);
741		$filemask = substr($this->localpath, 0, -1) . '.*';
742		if ($success) {
743			// replicate the album metadata and sub-files
744			$uniqueset = array('folder' => $newfolder);
745			$parentname = dirname($newfolder);
746			if (empty($parentname) || $parentname == '/' || $parentname == '.') {
747				$uniqueset['parentid'] = NULL;
748			} else {
749				$parent = newAlbum($parentname);
750				$uniqueset['parentid'] = $parent->getID();
751			}
752			$newID = parent::copy($uniqueset);
753			if ($newID) {
754				//replicate the tags
755				storeTags(readTags($this->getID(), 'albums'), $newID, 'albums');
756				//copy the sidecar files
757				$filestocopy = safe_glob($filemask);
758				foreach ($filestocopy as $file) {
759					if (in_array(strtolower(getSuffix($file)), $this->sidecars)) {
760						$success = $success && @copy($file, dirname($dest) . '/' . basename($file));
761					}
762				}
763
764			}
765		}
766		if ($success) {
767			$newalbum = newAlbum($newfolder);
768			$newalbum->setUpdatedDate();
769			$newalbum->setUpdatedDateParents();
770			return 0;
771		} else {
772			return 1;
773		}
774	}
775
776	/**
777	 * Checks is the destination is not a subfolder of the current folder itself
778	 *
779	 * @since Zenphoto 1.5.5
780	 *
781	 * @param string $destination album name to move or copy to
782	 * @return boolean
783	 */
784	function isValidMoveCopyDestination($destination) {
785		$oldfolders = explode('/', $this->name);
786		$newfolders = explode('/', $destination);
787		$sub = count($newfolders) > count($oldfolders);
788		if ($sub) {
789			for ($i = 0; $i < count($oldfolders); $i++) {
790				if ($newfolders[$i] != $oldfolders[$i]) {
791					$sub = false;
792					break;
793				}
794			}
795			if ($sub) {
796				// Disallow moving to a subfolder of the current folder.
797				return false;
798			}
799		}
800		return true;
801	}
802
803	/**
804	 * For every image in the album, look for its file. Delete from the database
805	 * if the file does not exist. Same for each sub-directory/album.
806	 *
807	 * @param bool $deep set to true for a thorough cleansing
808	 */
809	function garbageCollect($deep = false) {
810
811	}
812
813	/**
814	 * Load all of the filenames that are found in this Albums directory on disk.
815	 * Returns an array with all the names.
816	 *
817	 * @param  $dirs Whether or not to return directories ONLY with the file array.
818	 * @return array
819	 */
820	protected function loadFileNames($dirs = false) {
821
822	}
823
824	/**
825	 * Returns true if the album is "dynamic"
826	 *
827	 * @return bool
828	 */
829	function isDynamic() {
830		return false;
831	}
832
833	/**
834	 * Returns the search parameters for a dynamic album
835	 *
836	 * @return string
837	 */
838	function getSearchParams() {
839		return NULL;
840	}
841
842	/**
843	 * Sets the search parameters of a dynamic album
844	 *
845	 * @param string $params The search string to produce the dynamic album
846	 */
847	function setSearchParams($params) {
848
849	}
850
851	/**
852	 * Returns the search engine for a dynamic album
853	 *
854	 * @return object
855	 */
856	function getSearchEngine() {
857		return NULL;
858	}
859
860	/**
861	 * checks access to the album
862	 * @param bit $action What the requestor wants to do
863	 *
864	 * returns true of access is allowed
865	 */
866	function isMyItem($action) {
867		global $_zp_loggedin;
868		if ($parent = parent::isMyItem($action)) {
869			return $parent;
870		}
871		if (zp_loggedin($action)) {
872			$subRights = $this->albumSubRights();
873			if (is_null($subRights)) {
874// no direct rights, but if this is a private gallery and the album is published he should be allowed to see it
875				if (GALLERY_SECURITY != 'public' && $this->isPublished() && $action == LIST_RIGHTS) {
876					return LIST_RIGHTS;
877				}
878			} else {
879				$albumrights = LIST_RIGHTS;
880				if ($subRights & (MANAGED_OBJECT_RIGHTS_EDIT)) {
881					$albumrights = $albumrights | ALBUM_RIGHTS;
882				}
883				if ($subRights & MANAGED_OBJECT_RIGHTS_UPLOAD) {
884					$albumrights = $albumrights | UPLOAD_RIGHTS;
885				}
886				if ($action & $albumrights) {
887					return ($_zp_loggedin ^ (ALBUM_RIGHTS | UPLOAD_RIGHTS)) | $albumrights;
888				} else {
889					return false;
890				}
891			}
892		}
893		return false;
894	}
895
896	/**
897	 * Checks if guest is loggedin for the album
898	 * @param unknown_type $hint
899	 * @param unknown_type $show
900	 */
901	function checkforGuest(&$hint = NULL, &$show = NULL) {
902		if (!parent::checkForGuest()) {
903			return false;
904		}
905		return checkAlbumPassword($this, $hint);
906	}
907
908	/**
909	 *
910	 * returns true if there is any protection on the album
911	 */
912	function isProtected() {
913		return $this->checkforGuest() != 'zp_public_access';
914	}
915
916
917	/**
918	 * Returns true if this album is published and also all of its parents.
919	 *
920	 * @since Zenphoto 1.5.5
921	 *
922	 * @return bool
923	 */
924	function isPublic() {
925		if (is_null($this->is_public)) {
926			if (!$this->isPublished()) {
927				return $this->is_public = false;
928			}
929			$parent = $this->getParent();
930			if($parent && !$parent->isPublic()) {
931				return $this->is_public = false;
932			}
933			return $this->is_public = true;
934		} else {
935			return $this->is_public;
936		}
937	}
938
939	/**
940	 * Gets the owner of the album respectively of a parent album if not set specifically
941	 *
942	 * @global obj $_zp_authority
943	 * @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)
944	 * @return string
945	 */
946	function getOwner($fullname = false) {
947		global $_zp_authority;
948		$owner = $this->get('owner');
949		if (empty($owner)) {
950			$p = $this->getParent();
951			if (is_object($p)) {
952				$owner = $p->getOwner();
953			} else {
954				$admin = $_zp_authority->getMasterUser();
955				$owner = $admin->getUser();
956				if ($fullname && !empty($admin->getName())) {
957					return $admin->getName();
958				}
959			}
960		} else {
961			if ($fullname) {
962				return Zenphoto_Administrator::getNameByUser($owner);
963			}
964		}
965		return $owner;
966	}
967
968	function setOwner($owner) {
969		$this->set('owner', $owner);
970	}
971
972	/**
973	 *
974	 * Date at which the album last discovered an image
975	 */
976	function getUpdatedDate() {
977		return $this->get('updateddate');
978	}
979
980	function setUpdatedDate($date = null) {
981		if(is_null($date)) {
982			$date = date('Y-m-d H:i:s');
983		}
984		return $this->set('updateddate', $date);
985	}
986
987	/**
988	 * Sets the current date to all parent albums of this album recursively
989	 * @since Zenphoto 1.5.5
990	 */
991	function setUpdatedDateParents() {
992		$parent = $this->getParent();
993		if($parent) {
994			$parent->setUpdatedDate();
995			$parent->save();
996			$parent->setUpdatedDateParents();
997		}
998	}
999
1000	/**
1001	 * Returns the theme for the album
1002	 *
1003	 * @return string
1004	 */
1005	function getAlbumTheme() {
1006		global $_zp_gallery;
1007		if (in_context(ZP_SEARCH_LINKED)) {
1008			return $_zp_gallery->getCurrentTheme();
1009		} else {
1010			return $this->get('album_theme');
1011		}
1012	}
1013
1014	/**
1015	 * Sets the theme of the album
1016	 *
1017	 * @param string $theme
1018	 */
1019	function setAlbumTheme($theme) {
1020		$this->set('album_theme', $theme);
1021	}
1022
1023	/**
1024	 * returns the album watermark
1025	 * @return string
1026	 */
1027	function getWatermark() {
1028		return $this->get('watermark');
1029	}
1030
1031	/**
1032	 * Sets the album watermark
1033	 * @param string $wm
1034	 */
1035	function setWatermark($wm) {
1036		$this->set('watermark', $wm);
1037	}
1038
1039	/**
1040	 * Returns the album watermark thumb
1041	 *
1042	 * @return bool
1043	 */
1044	function getWatermarkThumb() {
1045		return $this->get('watermark_thumb');
1046	}
1047
1048	/**
1049	 * Sets the custom watermark usage
1050	 *
1051	 * @param $wm
1052	 */
1053	function setWatermarkThumb($wm) {
1054		$this->set('watermark_thumb', $wm);
1055	}
1056
1057	/**
1058	 * returns the mitigated album rights.
1059	 * returns NULL if not a managed album
1060	 */
1061	function albumSubRights() {
1062		if (!is_null($this->subrights)) {
1063			return $this->subrights;
1064		}
1065		global $_zp_admin_album_list;
1066		if (zp_loggedin(MANAGE_ALL_ALBUM_RIGHTS)) {
1067			$this->subrights = MANAGED_OBJECT_RIGHTS_EDIT | MANAGED_OBJECT_RIGHTS_UPLOAD | MANAGED_OBJECT_RIGHTS_VIEW;
1068			return $this->subrights;
1069		}
1070		if (zp_loggedin(VIEW_UNPUBLISHED_RIGHTS)) {
1071			$base = MANAGED_OBJECT_RIGHTS_VIEW;
1072		} else {
1073			$base = NULL;
1074		}
1075		getManagedAlbumList();
1076		if (count($_zp_admin_album_list) > 0) {
1077			$desired_folders = explode('/', $this->name);
1078			foreach ($_zp_admin_album_list as $adminalbum => $rights) {
1079// see if it is one of the managed folders or a subfolder there of
1080				$admin_folders = explode('/', $adminalbum);
1081				$level = 0;
1082				$ok = true;
1083				foreach ($admin_folders as $folder) {
1084					if ($level >= count($desired_folders) || $folder != $desired_folders[$level]) {
1085						$ok = false;
1086						break;
1087					}
1088					$level++;
1089				}
1090				if ($ok) {
1091					$this->subrights = $rights | $base;
1092					return $this->subrights;
1093				}
1094			}
1095		}
1096		$this->subrights = $base;
1097		return $this->subrights;
1098	}
1099
1100	/**
1101	 * sortImageArray will sort an array of Images based on the given key. The
1102	 * key must be one of (filename, title, sort_order) at the moment.
1103	 *
1104	 * @param array $images The array of filenames to be sorted.
1105	 * @param  string $sorttype optional sort type
1106	 * @param  string $sortdirection optional sort direction
1107	 * @param bool $mine set to true/false to override ownership clause
1108	 * @return array
1109	 */
1110	protected function sortImageArray($images, $sorttype, $sortdirection, $mine = NULL) {
1111		if (is_null($mine)) {
1112			$mine = $this->isMyItem(LIST_RIGHTS | MANAGE_ALL_ALBUM_RIGHTS);
1113		}
1114		if ($mine && !($mine & (MANAGE_ALL_ALBUM_RIGHTS))) {
1115			//check for managed album view unpublished image rights
1116			$mine = $this->albumSubRights() & (MANAGED_OBJECT_RIGHTS_EDIT | MANAGED_OBJECT_RIGHTS_VIEW);
1117		}
1118		$sortkey = $this->getImageSortKey($sorttype);
1119		if (($sortkey == '`sort_order`') || ($sortkey == 'RAND()')) {
1120			// manual sort is always ascending
1121			$order = false;
1122		} else {
1123			if (!is_null($sortdirection)) {
1124				$order = strtoupper($sortdirection) == 'DESC';
1125			} else {
1126				$order = $this->getSortDirection('image');
1127			}
1128		}
1129		$result = query($sql = "SELECT * FROM " . prefix("images") . " WHERE `albumid`= " . $this->getID() . ' ORDER BY ' . $sortkey . ' ' . $sortdirection);
1130		$results = array();
1131		while ($row = db_fetch_assoc($result)) {
1132			$filename = $row['filename'];
1133			if (($key = array_search($filename, $images)) !== false) {
1134				// the image exists in the filesystem
1135				$results[] = $row;
1136				unset($images[$key]);
1137			} else { // the image no longer exists
1138				$id = $row['id'];
1139				query("DELETE FROM " . prefix('images') . " WHERE `id`=$id"); // delete the record
1140				query("DELETE FROM " . prefix('comments') . " WHERE `type` ='images' AND `ownerid`= '$id'"); // remove image comments
1141			}
1142		}
1143		db_free_result($result);
1144		foreach ($images as $filename) {
1145			// these images are not in the database
1146			$imageobj = newImage($this, $filename);
1147			$results[] = $imageobj->getData();
1148		}
1149		// now put the results into the right order
1150		$results = sortByKey($results, str_replace('`', '', $sortkey), $order);
1151		// the results are now in the correct order
1152		$images_ordered = array();
1153		foreach ($results as $key => $row) {
1154			// check for visible
1155			switch (themeObject::checkScheduledPublishing($row)) {
1156				case 1:
1157					$imageobj = newImage($this, $row['filename']);
1158					$imageobj->setShow(0);
1159					$imageobj->save();
1160				case 2:
1161					$row['show'] = 0;
1162					break;
1163			}
1164			if ($row['show'] || $mine) {
1165				// don't display it
1166				$images_ordered[] = $row['filename'];
1167			}
1168		}
1169		return $images_ordered;
1170	}
1171
1172	/**
1173	 * changes the parent of an album for move/copy
1174	 *
1175	 * @param string $newfolder The folder name of the new parent
1176	 */
1177	protected function updateParent($newfolder) {
1178		$this->name = $newfolder;
1179		$parentname = dirname($newfolder);
1180		if ($parentname == '/' || $parentname == '.')
1181			$parentname = '';
1182		if (empty($parentname)) {
1183			$this->set('parentid', NULL);
1184		} else {
1185			$parent = newAlbum($parentname);
1186			$this->set('parentid', $parent->getID());
1187		}
1188		$this->setUpdatedDateParents();
1189		$this->save();
1190	}
1191
1192	/**
1193	 * Simply creates objects of all the images and sub-albums in this album to
1194	 * load accurate values into the database.
1195	 */
1196	function preLoad() {
1197		$images = $this->getImages(0);
1198		$subalbums = $this->getAlbums(0);
1199		foreach ($subalbums as $dir) {
1200			$album = newAlbum($dir);
1201			$album->preLoad();
1202		}
1203	}
1204
1205	/**
1206	 * Returns the album following the current album
1207	 *
1208	 * @return object
1209	 */
1210	function getNextAlbum() {
1211		global $_zp_gallery;
1212		if (is_null($parent = $this->getParent())) {
1213			$albums = $_zp_gallery->getAlbums(0);
1214		} else {
1215			$albums = $parent->getAlbums(0);
1216		}
1217		$inx = array_search($this->name, $albums) + 1;
1218		if ($inx >= 0 && $inx < count($albums)) {
1219			return newAlbum($albums[$inx]);
1220		}
1221		return null;
1222	}
1223
1224	/**
1225	 * Returns the album prior to the current album
1226	 *
1227	 * @return object
1228	 */
1229	function getPrevAlbum() {
1230		global $_zp_gallery;
1231		if (is_null($parent = $this->getParent())) {
1232			$albums = $_zp_gallery->getAlbums(0);
1233		} else {
1234			$albums = $parent->getAlbums(0);
1235		}
1236		$inx = array_search($this->name, $albums) - 1;
1237		if ($inx >= 0 && $inx < count($albums)) {
1238			return newAlbum($albums[$inx]);
1239		}
1240		return null;
1241	}
1242
1243	/**
1244	 * Returns the page number in the gallery or the parent album of this album
1245	 *
1246	 * @return int
1247	 */
1248	function getGalleryPage() {
1249		global $_zp_gallery;
1250		if ($this->index == null) {
1251			if (is_null($parent = $this->getParent())) {
1252				$albums = $_zp_gallery->getAlbums(0);
1253			} else {
1254				$albums = $parent->getAlbums(0);
1255			}
1256			$this->index = array_search($this->name, $albums);
1257		}
1258		return floor(($this->index / galleryAlbumsPerPage()) + 1);
1259	}
1260
1261}
1262
1263/**
1264 * Album Class
1265 * @package core
1266 * @subpackage classes\objects
1267 */
1268class Album extends AlbumBase {
1269
1270	/**
1271	 * Constructor for albums
1272	 *
1273	 * @param object $gallery The parent gallery: deprecated
1274	 * @param string $folder8 folder name (UTF8) of the album
1275	 * @param bool $cache load from cache if present
1276	 * @return Album
1277	 */
1278	function __construct($folder8, $cache = true, $quiet = false) {
1279		$folder8 = trim($folder8, '/');
1280		$folderFS = internalToFilesystem($folder8);
1281		$localpath = ALBUM_FOLDER_SERVERPATH . $folderFS . "/";
1282		$this->linkname = $this->name = $folder8;
1283		$this->localpath = $localpath;
1284		if (!$this->_albumCheck($folder8, $folderFS, $quiet))
1285			return;
1286
1287		$new = $this->instantiate('albums', array('folder' => $this->name), 'folder', $cache, empty($folder8));
1288
1289		if ($new) {
1290			$this->setUpdatedDateParents();
1291			$this->save();
1292			zp_apply_filter('new_album', $this);
1293		}
1294		zp_apply_filter('album_instantiate', $this);
1295	}
1296
1297	/**
1298	 * album validity check
1299	 * @return boolean
1300	 */
1301	protected function _albumCheck($folder8, $folderFS, $quiet) {
1302		$msg = false;
1303		if (empty($folder8)) {
1304			$msg = gettext('Invalid album instantiation: No album name');
1305		} else if (filesystemToInternal($folderFS) != $folder8) {
1306			// an attempt to spoof the album name.
1307			$msg = sprintf(gettext('Invalid album instantiation: %1$s!=%2$s'), html_encode(filesystemToInternal($folderFS)), html_encode($folder8));
1308		} else if (!file_exists($this->localpath) || !(is_dir($this->localpath)) || $folder8[0] == '.' || preg_match('~/\.*/~', $folder8)) {
1309			$msg = sprintf(gettext('Invalid album instantiation: %s does not exist.'), html_encode($folder8));
1310		}
1311		if ($msg) {
1312			$this->exists = false;
1313			if (!$quiet) {
1314				trigger_error($msg, E_USER_ERROR);
1315			}
1316			return false;
1317		}
1318		return true;
1319	}
1320
1321	/**
1322	 * Sets default values for a new album
1323	 *
1324	 * @return bool
1325	 */
1326	protected function setDefaults() {
1327		global $_zp_gallery;
1328		// Set default data for a new Album (title and parent_id)
1329		parent::setDefaults();
1330		$parentalbum = $this->getParent();
1331		$this->set('mtime', filemtime($this->localpath));
1332		if (!$_zp_gallery->getAlbumUseImagedate()) {
1333			$this->setDateTime(strftime('%Y-%m-%d %H:%M:%S', $this->get('mtime')));
1334		}
1335		$title = trim($this->name);
1336		if (!is_null($parentalbum)) {
1337			$this->set('parentid', $parentalbum->getID());
1338			$title = substr($title, strrpos($title, '/') + 1);
1339		}
1340		$this->set('title', sanitize($title, 2));
1341		return true;
1342	}
1343
1344	/**
1345	 * Guts of fetching the subalbums
1346	 * @return array
1347	 */
1348	protected function _getAlbums() {
1349		$dirs = $this->loadFileNames(true);
1350		$subalbums = array();
1351		foreach ($dirs as $dir) {
1352			$dir = $this->name . '/' . $dir;
1353			$subalbums[] = $dir;
1354		}
1355		return $subalbums;
1356	}
1357
1358	/**
1359	 * Returns all folder names for all the subdirectories.
1360	 *
1361	 * @param string $page  Which page of subalbums to display.
1362	 * @param string $sorttype The sort strategy
1363	 * @param string $sortdirection The direction of the sort
1364	 * @param bool $care set to false if the order does not matter
1365	 * @param bool $mine set true/false to override ownership
1366	 * @return array
1367	 */
1368	function getAlbums($page = 0, $sorttype = null, $sortdirection = null, $care = true, $mine = NULL) {
1369		global $_zp_gallery;
1370		if (!$this->exists)
1371			return array();
1372		if ($mine || is_null($this->subalbums) || $care && $sorttype . $sortdirection !== $this->lastsubalbumsort) {
1373			if (is_null($sorttype)) {
1374				$sorttype = $this->getSortType('album');
1375			}
1376			if (is_null($sortdirection)) {
1377				if ($this->getSortDirection('album')) {
1378					$sortdirection = 'DESC';
1379				}
1380			}
1381			$dirs = $this->loadFileNames(true);
1382			$subalbums = array();
1383			foreach ($dirs as $dir) {
1384				$dir = $this->name . '/' . $dir;
1385				$subalbums[] = $dir;
1386			}
1387			$key = $this->getAlbumSortKey($sorttype);
1388			$this->subalbums = $_zp_gallery->sortAlbumArray($this, $subalbums, $key, $sortdirection, $mine);
1389			$this->lastsubalbumsort = $sorttype . $sortdirection;
1390		}
1391		return parent::getAlbums($page);
1392	}
1393
1394	/**
1395	 * Returns a of a slice of the images for this album. They will
1396	 * also be sorted according to the sort type of this album, or by filename if none
1397	 * has been set.
1398	 *
1399	 * @param int $page  Which page of images should be returned. If zero, all images are returned.
1400	 * @param int $firstPageCount count of images that go on the album/image transition page
1401	 * @param string $sorttype optional sort type
1402	 * @param string $sortdirection optional sort direction
1403	 * @param bool $care set to false if the order of the images does not matter
1404	 * @param bool $mine set true/false to override ownership
1405	 *
1406	 * @return array
1407	 */
1408	function getImages($page = 0, $firstPageCount = 0, $sorttype = null, $sortdirection = null, $care = true, $mine = NULL) {
1409		if (!$this->exists)
1410			return array();
1411		if ($mine || is_null($this->images) || $care && $sorttype . $sortdirection !== $this->lastimagesort) {
1412			if (is_null($sorttype)) {
1413				$sorttype = $this->getSortType();
1414			}
1415			if (is_null($sortdirection)) {
1416				if ($this->getSortDirection('image')) {
1417					$sortdirection = 'DESC';
1418				}
1419			}
1420			$images = $this->loadFileNames();
1421			$this->images = $this->sortImageArray($images, $sorttype, $sortdirection, $mine);
1422			$this->lastimagesort = $sorttype . $sortdirection;
1423		}
1424		return parent::getImages($page, $firstPageCount);
1425	}
1426
1427	/**
1428	 * Delete the entire album PERMANENTLY. Be careful! This is unrecoverable.
1429	 * Returns true if successful
1430	 *
1431	 * @return bool
1432	 */
1433	function remove() {
1434		$rslt = false;
1435		if (PersistentObject::remove()) {
1436			foreach ($this->getImages() as $filename) {
1437				$image = newImage($this, $filename);
1438				$image->remove();
1439			}
1440			foreach ($this->getAlbums() as $folder) {
1441				$subalbum = newAlbum($folder);
1442				$subalbum->remove();
1443			}
1444			$curdir = getcwd();
1445			chdir($this->localpath);
1446			$filelist = safe_glob('*');
1447			foreach ($filelist as $file) {
1448				if (($file != '.') && ($file != '..')) {
1449					@chmod($file, 0777);
1450					unlink($this->localpath . $file); // clean out any other files in the folder
1451				}
1452			}
1453			chdir($curdir);
1454			clearstatcache();
1455			query("DELETE FROM " . prefix('options') . "WHERE `ownerid`=" . $this->id);
1456			query("DELETE FROM " . prefix('comments') . "WHERE `type`='albums' AND `ownerid`=" . $this->id);
1457			query("DELETE FROM " . prefix('obj_to_tag') . "WHERE `type`='albums' AND `objectid`=" . $this->id);
1458			$success = true;
1459			$filestoremove = safe_glob(substr($this->localpath, 0, strrpos($this->localpath, '.')) . '.*');
1460			foreach ($filestoremove as $file) {
1461				if (in_array(strtolower(getSuffix($file)), $this->sidecars)) {
1462					@chmod($file, 0777);
1463					$success = $success && unlink($file);
1464				}
1465			}
1466			@chmod($this->localpath, 0777);
1467			$rslt = @rmdir($this->localpath) && $success;
1468			$cachepath = SERVERCACHE . '/' . pathurlencode($this->name) . '/';
1469			@chmod($cachepath, 0777);
1470			@rmdir($cachepath);
1471			$this->setUpdatedDateParents();
1472		}
1473		clearstatcache();
1474		return $rslt;
1475	}
1476
1477	/**
1478	 * Move this album to the location specified by $newfolder, copying all
1479	 * metadata, subalbums, and subalbums' metadata with it.
1480	 * @param $newfolder string the folder to move to, including the name of the current folder (possibly renamed).
1481	 * @return int 0 on success and error indicator on failure.
1482	 *
1483	 */
1484	function move($newfolder) {
1485		$oldfolder = $this->name;
1486		$rslt = $this->_move($newfolder);
1487		if (!$rslt) {
1488			// Then: go through the db and change the album (and subalbum) paths. No ID changes are necessary for a move.
1489			// Get the subalbums.
1490			$sql = "SELECT id, folder FROM " . prefix('albums') . " WHERE folder LIKE " . db_quote(db_LIKE_escape($oldfolder) . '/%');
1491			$result = query($sql);
1492			if ($result) {
1493				while ($subrow = db_fetch_assoc($result)) {
1494					$newsubfolder = $subrow['folder'];
1495					$newsubfolder = $newfolder . substr($newsubfolder, strlen($oldfolder));
1496					$sql = "UPDATE " . prefix('albums') . " SET folder=" . db_quote($newsubfolder) . " WHERE id=" . $subrow['id'];
1497					query($sql);
1498				}
1499			}
1500			db_free_result($result);
1501			return 0;
1502		}
1503		return $rslt;
1504	}
1505
1506	protected function succeed($dest) {
1507		return mkdir_recursive($dest, FOLDER_MOD) === TRUE;
1508	}
1509
1510	/**
1511	 * Copy this album to the location specified by $newfolder, copying all
1512	 * metadata, subalbums, and subalbums' metadata with it.
1513	 * @param $newfolder string the folder to copy to, including the name of the current folder (possibly renamed).
1514	 * @return int 0 on success and error indicator on failure.
1515	 *
1516	 */
1517	function copy($newfolder) {
1518		$rslt = parent::copy($newfolder);
1519		if (!$rslt) {
1520			$newfolder .= '/' . basename($this->name);
1521			$success = true;
1522			//copy the images
1523			$images = $this->getImages(0);
1524			foreach ($images as $imagename) {
1525				$image = newImage($this, $imagename);
1526				if ($rslt = $image->copy($newfolder)) {
1527					$success = false;
1528				}
1529			}
1530			// copy the subalbums.
1531			$subalbums = $this->getAlbums(0);
1532			foreach ($subalbums as $subalbumname) {
1533				$subalbum = newAlbum($subalbumname);
1534				if ($rslt = $subalbum->copy($newfolder)) {
1535					$success = false;
1536				}
1537			}
1538			if ($success) {
1539				return 0;
1540			}
1541			return 1;
1542		}
1543		return $rslt;
1544	}
1545
1546	/**
1547	 * For every image in the album, look for its file. Delete from the database
1548	 * if the file does not exist. Same for each sub-directory/album.
1549	 *
1550	 * @param bool $deep set to true for a thorough cleansing
1551	 */
1552	function garbageCollect($deep = false) {
1553		$set_updateddate = false;
1554		if (is_null($this->images))
1555			$this->getImages();
1556		$result = query("SELECT `id`, `filename` FROM " . prefix('images') . " WHERE `albumid` = '" . $this->id . "'");
1557		$dead = array();
1558		$live = array();
1559
1560		$files = $this->loadFileNames();
1561
1562		// Does the filename from the db row match any in the files on disk?
1563		while ($row = db_fetch_assoc($result)) {
1564			if (!in_array($row['filename'], $files)) {
1565				// In the database but not on disk. Kill it.
1566				$dead[] = $row['id'];
1567			} else if (in_array($row['filename'], $live)) {
1568				// Duplicate in the database. Kill it.
1569				$dead[] = $row['id'];
1570				// Do something else here? Compare titles/descriptions/metadata/update dates to see which is the latest?
1571			} else {
1572				$live[] = $row['filename'];
1573			}
1574		}
1575		db_free_result($result);
1576
1577		if (count($dead) > 0) {
1578			$sql = "DELETE FROM " . prefix('images') . " WHERE `id` IN(" . implode(',', $dead) . ")";
1579			$sql2 = "DELETE FROM " . prefix('comments') . " WHERE `type`='albums' AND `ownerid` IN(" . implode(',', $dead) . ")";
1580			query($sql);
1581			query($sql2);
1582			$set_updateddate = true;
1583		}
1584
1585		// Get all sub-albums and make sure they exist.
1586		$result = query("SELECT `id`, `folder` FROM " . prefix('albums') . " WHERE `folder` LIKE " . db_quote(db_LIKE_escape($this->name) . '%'));
1587		$dead = array();
1588		$live = array();
1589		// Does the dirname from the db row exist on disk?
1590		while ($row = db_fetch_assoc($result)) {
1591			if (!is_dir(ALBUM_FOLDER_SERVERPATH . internalToFilesystem($row['folder'])) || in_array($row['folder'], $live) || substr($row['folder'], -1) == '/' || substr($row['folder'], 0, 1) == '/') {
1592				$dead[] = $row['id'];
1593			} else {
1594				$live[] = $row['folder'];
1595			}
1596		}
1597		db_free_result($result);
1598		if (count($dead) > 0) {
1599			$sql = "DELETE FROM " . prefix('albums') . " WHERE `id` IN(" . implode(',', $dead) . ")";
1600			$sql2 = "DELETE FROM " . prefix('comments') . " WHERE `type`='albums' AND `ownerid` IN(" . implode(',', $dead) . ")";
1601			query($sql);
1602			query($sql2);
1603			$set_updateddate = true;
1604		}
1605		if($set_updateddate) {
1606			$this->setUpdateddate();
1607			$this->save();
1608			$this->setUpdatedDateParents();
1609		}
1610
1611		if ($deep) {
1612			foreach ($this->getAlbums(0) as $dir) {
1613				$subalbum = newAlbum($dir);
1614				// Could have been deleted if it didn't exist above...
1615				if ($subalbum->exists)
1616					$subalbum->garbageCollect($deep);
1617			}
1618		}
1619	}
1620
1621	/**
1622	 * Load all of the filenames that are found in this Albums directory on disk.
1623	 * Returns an array with all the names.
1624	 *
1625	 * @param  $dirs Whether or not to return directories ONLY with the file array.
1626	 * @return array
1627	 */
1628	protected function loadFileNames($dirs = false) {
1629		clearstatcache();
1630		$albumdir = $this->localpath;
1631		$dir = @opendir($albumdir);
1632		if (!$dir) {
1633			if (is_dir($albumdir)) {
1634				$msg = sprintf(gettext("Error: The album %s is not readable."), html_encode($this->name));
1635			} else {
1636				$msg = sprintf(gettext("Error: The album named %s cannot be found."), html_encode($this->name));
1637			}
1638			trigger_error($msg, E_USER_NOTICE);
1639			return array();
1640		}
1641
1642		$files = array();
1643		$others = array();
1644
1645		while (false !== ($file = readdir($dir))) {
1646			$file8 = filesystemToInternal($file);
1647			if (@$file8[0] != '.') {
1648				if ($dirs && (is_dir($albumdir . $file) || hasDynamicAlbumSuffix($file))) {
1649					$files[] = $file8;
1650				} else if (!$dirs && is_file($albumdir . $file)) {
1651					if (Gallery::validImageAlt($file)) {
1652						$files[] = $file8;
1653						$others[] = $file8;
1654					} else if (Gallery::validImage($file)) {
1655						$files[] = $file8;
1656					}
1657				}
1658			}
1659		}
1660		closedir($dir);
1661		if (count($others) > 0) {
1662			$others_thumbs = array();
1663			foreach ($others as $other) {
1664				$others_root = substr($other, 0, strrpos($other, "."));
1665				foreach ($files as $image) {
1666					if ($image != $other) {
1667						$image_root = substr($image, 0, strrpos($image, "."));
1668						if ($image_root == $others_root && Gallery::validImage($image)) {
1669							$others_thumbs[] = $image;
1670						}
1671					}
1672				}
1673			}
1674			$files = array_diff($files, $others_thumbs);
1675		}
1676
1677		if ($dirs) {
1678			return zp_apply_filter('album_filter', $files);
1679		} else {
1680			return zp_apply_filter('image_filter', $files);
1681		}
1682	}
1683
1684
1685}
1686
1687/**
1688 * Dynamic Album Class for "saved searches"
1689 * @package core
1690 * @subpackage classes\objects
1691 */
1692class dynamicAlbum extends AlbumBase {
1693
1694	public $searchengine; // cache the search engine for dynamic albums
1695
1696	function __construct($folder8, $cache = true, $quiet = false) {
1697		$folder8 = trim($folder8, '/');
1698		$folderFS = internalToFilesystem($folder8);
1699		$localpath = ALBUM_FOLDER_SERVERPATH . $folderFS . "/";
1700		$this->linkname = $this->name = $folder8;
1701		$this->localpath = $localpath;
1702		if (!$this->_albumCheck($folder8, $folderFS, $quiet))
1703			return;
1704		$this->instantiate('albums', array('folder' => $this->name), 'folder', $cache, empty($folder8));
1705		$this->exists = true;
1706		if (!is_dir(stripSuffix($this->localpath))) {
1707			$this->linkname = stripSuffix($folder8);
1708		}
1709		$new = !$this->get('search_params');
1710		if ($new || (filemtime($this->localpath) > $this->get('mtime'))) {
1711			$constraints = '';
1712			$data = file_get_contents($this->localpath);
1713			while (!empty($data)) {
1714				$data1 = trim(substr($data, 0, $i = strpos($data, "\n")));
1715				if ($i === false) {
1716					$data1 = $data;
1717					$data = '';
1718				} else {
1719					$data = substr($data, $i + 1);
1720				}
1721				if (strpos($data1, 'WORDS=') !== false) {
1722					$words = "words=" . urlencode(substr($data1, 6));
1723				}
1724				if (strpos($data1, 'THUMB=') !== false) {
1725					$thumb = trim(substr($data1, 6));
1726					$this->set('thumb', $thumb);
1727				}
1728				if (strpos($data1, 'FIELDS=') !== false) {
1729					$fields = "&searchfields=" . trim(substr($data1, 7));
1730				}
1731				if (strpos($data1, 'CONSTRAINTS=') !== false) {
1732					$constraint = trim(substr($data1, 12));
1733					$constraints = '&' . $constraint;
1734				}
1735			}
1736			if (!empty($words)) {
1737				if (empty($fields)) {
1738					$fields = '&searchfields=tags';
1739				}
1740				$this->set('search_params', $words . $fields . $constraints);
1741			}
1742			$this->set('mtime', filemtime($this->localpath));
1743			if ($new) {
1744				$title = $this->get('title');
1745				$this->set('title', stripSuffix($title)); // Strip the suffix
1746				$this->save();
1747				zp_apply_filter('new_album', $this);
1748			}
1749		}
1750		zp_apply_filter('album_instantiate', $this);
1751	}
1752
1753	/**
1754	 * album validity check
1755	 * @param type $folder8
1756	 * @return boolean
1757	 */
1758	protected function _albumCheck($folder8, $folderFS, $quiet) {
1759		$this->localpath = rtrim($this->localpath, '/');
1760
1761		$msg = false;
1762		if (empty($folder8)) {
1763			$msg = gettext('Invalid album instantiation: No album name');
1764		} else if (filesystemToInternal($folderFS) != $folder8) {
1765			// an attempt to spoof the album name.
1766			$msg = sprintf(gettext('Invalid album instantiation: %1$s!=%2$s'), html_encode(filesystemToInternal($folderFS)), html_encode($folder8));
1767		} else if (!file_exists($this->localpath) || is_dir($this->localpath)) {
1768			$msg = sprintf(gettext('Invalid album instantiation: %s does not exist.'), html_encode($folder8));
1769		}
1770		if ($msg) {
1771			$this->exists = false;
1772			if (!$quiet) {
1773				trigger_error($msg, E_USER_ERROR);
1774			}
1775			return false;
1776		}
1777		return true;
1778	}
1779
1780	/**
1781	 * Returns all folder names for all the subdirectories.
1782	 *
1783	 * @param string $page  Which page of subalbums to display.
1784	 * @param string $sorttype The sort strategy
1785	 * @param string $sortdirection The direction of the sort
1786	 * @param bool $care set to false if the order does not matter
1787	 * @param bool $mine set true/false to override ownership
1788	 * @return array
1789	 */
1790	function getAlbums($page = 0, $sorttype = null, $sortdirection = null, $care = true, $mine = NULL) {
1791		global $_zp_gallery;
1792		if (!$this->exists)
1793			return array();
1794		if ($mine || is_null($this->subalbums) || $care && $sorttype . $sortdirection !== $this->lastsubalbumsort) {
1795			if (is_null($sorttype)) {
1796				$sorttype = $this->getSortType('album');
1797			}
1798			if (is_null($sortdirection)) {
1799				if ($this->getSortDirection('album')) {
1800					$sortdirection = 'DESC';
1801				} else {
1802					$sortdirection = '';
1803				}
1804			}
1805			$searchengine = $this->getSearchEngine();
1806			$subalbums = $searchengine->getAlbums(0, $sorttype, $sortdirection, $care, $mine);
1807			$key = $this->getAlbumSortKey($sorttype);
1808			$this->subalbums = $_zp_gallery->sortAlbumArray($this, $subalbums, $key, $sortdirection, $mine);
1809			$this->lastsubalbumsort = $sorttype . $sortdirection;
1810		}
1811		return parent::getAlbums($page);
1812	}
1813
1814	/**
1815	 * Returns the search parameters for a dynamic album
1816	 *
1817	 * @return string
1818	 */
1819	function getSearchParams() {
1820		return $this->get('search_params');
1821	}
1822
1823	/**
1824	 * Sets the search parameters of a dynamic album
1825	 *
1826	 * @param string $params The search string to produce the dynamic album
1827	 */
1828	function setSearchParams($params) {
1829		$this->set('search_params', $params);
1830	}
1831
1832	/**
1833	 * Returns the search engine for a dynamic album
1834	 *
1835	 * @return object
1836	 */
1837	function getSearchEngine() {
1838		if (!is_null($this->searchengine))
1839			return $this->searchengine;
1840		$this->searchengine = new SearchEngine(true);
1841		$params = $this->get('search_params');
1842		$params .= '&albumname=' . $this->name;
1843		$this->searchengine->setSearchParams($params);
1844		return $this->searchengine;
1845	}
1846
1847	/**
1848	 * Returns a of a slice of the images for this album. They will
1849	 * also be sorted according to the sort type of this album, or by filename if none
1850	 * has been set.
1851	 *
1852	 * @param int $page  Which page of images should be returned. If zero, all images are returned.
1853	 * @param int $firstPageCount count of images that go on the album/image transition page
1854	 * @param string $sorttype optional sort type
1855	 * @param string $sortdirection optional sort direction
1856	 * @param bool $care set to false if the order of the images does not matter
1857	 * @param bool $mine set true/false to override ownership
1858	 *
1859	 * @return array
1860	 */
1861	function getImages($page = 0, $firstPageCount = 0, $sorttype = null, $sortdirection = null, $care = true, $mine = NULL) {
1862		if (!$this->exists)
1863			return array();
1864		if ($mine || is_null($this->images) || $care && $sorttype . $sortdirection !== $this->lastimagesort) {
1865			if (is_null($sorttype)) {
1866				$sorttype = $this->getSortType();
1867			}
1868			if (is_null($sortdirection)) {
1869				if ($this->getSortDirection('image')) {
1870					$sortdirection = 'DESC';
1871				}
1872			}
1873			$searchengine = $this->getSearchEngine();
1874			$this->images = $searchengine->getImages(0, 0, $sorttype, $sortdirection, $care, $mine);
1875			$this->lastimagesort = $sorttype . $sortdirection;
1876		}
1877		return parent::getImages($page, $firstPageCount);
1878	}
1879
1880	/**
1881	 * Delete the entire album PERMANENTLY. Be careful! This is unrecoverable.
1882	 * Returns true if successful
1883	 *
1884	 * @return bool
1885	 */
1886	function remove() {
1887		if ($rslt = parent::remove()) {
1888			@chmod($this->localpath, 0777);
1889			$rslt = @unlink($this->localpath);
1890			clearstatcache();
1891		}
1892		return $rslt;
1893	}
1894
1895	/**
1896	 * Move this album to the location specified by $newfolder, copying all
1897	 * metadata, subalbums, and subalbums' metadata with it.
1898	 * @param $newfolder string the folder to move to, including the name of the current folder (possibly renamed).
1899	 * @return int 0 on success and error indicator on failure.
1900	 *
1901	 */
1902	function move($newfolder,$oldfolder="") {
1903		return $this->_move($newfolder);
1904	}
1905
1906	protected function succeed($dest) {
1907		return @copy($this->localpath, $dest);
1908	}
1909
1910	/**
1911	 * Copy this album to the location specified by $newfolder, copying all
1912	 * metadata, subalbums, and subalbums' metadata with it.
1913	 * @param $newfolder string the folder to copy to, including the name of the current folder (possibly renamed).
1914	 * @return int 0 on success and error indicator on failure.
1915	 *
1916	 */
1917	function copy($newfolder) {
1918		return parent::copy($newfolder);
1919	}
1920
1921	/**
1922	 * Simply creates objects of all the images and sub-albums in this album to
1923	 * load accurate values into the database.
1924	 */
1925	function preLoad() {
1926		return; // nothing to do
1927	}
1928
1929	protected function loadFileNames($dirs = false) {
1930		return array();
1931	}
1932
1933	function isDynamic() {
1934		return 'alb';
1935	}
1936
1937	/**
1938	 * Sets default values for a new album
1939	 *
1940	 * @return bool
1941	 */
1942	protected function setDefaults() {
1943		global $_zp_gallery;
1944		// Set default data for a new Album (title and parent_id)
1945		parent::setDefaults();
1946		$parentalbum = $this->getParent();
1947		$this->set('mtime', filemtime($this->localpath));
1948		if (!$_zp_gallery->getAlbumUseImagedate()) {
1949			$this->setDateTime(strftime('%Y-%m-%d %H:%M:%S', $this->get('mtime')));
1950		}
1951		$title = trim($this->name);
1952		if (!is_null($parentalbum)) {
1953			$this->set('parentid', $parentalbum->getID());
1954			$title = substr($title, strrpos($title, '/') + 1);
1955		}
1956		$this->set('title', sanitize($title, 2));
1957		return true;
1958	}
1959
1960}
1961
1962?>
1963