iterator = $iterator; } public function __destruct() { $this->iterator->clean(); } public static function getRecentPhotos() { return new self(AssetIterator::getByOptions([ 'tag' => 'photo', 'order' => 'date_captured', 'direction' => 'desc', 'limit' => 15, // worst case: 3 rows * (portrait + 4 thumbs) ])); } private static function matchTypeMask(Image $image, $type_mask) { return ($type_mask & Image::TYPE_PANORAMA) && $image->isPanorama() || ($type_mask & Image::TYPE_LANDSCAPE) && $image->isLandscape() || ($type_mask & Image::TYPE_PORTRAIT) && $image->isPortrait(); } private function fetchImage($desired_type = Image::TYPE_PORTRAIT | Image::TYPE_LANDSCAPE | Image::TYPE_PANORAMA, Image $refDateImage = null) { // First, check if we have what we're looking for in the queue. foreach ($this->queue as $i => $image) { // Image has to match the desired type and be taken within a week of the reference image. if (self::matchTypeMask($image, $desired_type) && !(isset($refDateImage) && abs(self::daysApart($image, $refDateImage)) > self::NUM_DAYS_CUTOFF)) { unset($this->queue[$i]); return $image; } } // Check whatever's next up! while (($asset = $this->iterator->next()) && ($image = $asset->getImage())) { // Image has to match the desired type and be taken within a week of the reference image. if (self::matchTypeMask($image, $desired_type) && !(isset($refDateImage) && abs(self::daysApart($image, $refDateImage)) > self::NUM_DAYS_CUTOFF)) return $image; else $this->pushToQueue($image); } return false; } private function pushToQueue(Image $image) { $this->queue[] = $image; } private static function orderPhotos(Image $a, Image $b) { // Show images of highest priority first. $priority_diff = $a->getPriority() - $b->getPriority(); if ($priority_diff !== 0) return -$priority_diff; // In other cases, we'll just show the newest first. return $a->getDateCaptured() > $b->getDateCaptured() ? -1 : 1; } private static function daysApart(Image $a, Image $b) { return $a->getDateCaptured()->diff($b->getDateCaptured())->days; } public function getRow() { // Fetch the first image... $image = $this->fetchImage(); // No image at all? if (!$image) return false; // Is it a panorama? Then we've got our row! elseif ($image->isPanorama()) return [[$image], 'panorama']; // Alright, let's initalise a proper row, then. $photos = [$image]; $num_portrait = $image->isPortrait() ? 1 : 0; $num_landscape = $image->isLandscape() ? 1 : 0; // Get an initial batch of non-panorama images to work with. for ($i = 1; $i < 3 && ($image = $this->fetchImage(Image::TYPE_LANDSCAPE | Image::TYPE_PORTRAIT, $image)); $i++) { $num_portrait += $image->isPortrait() ? 1 : 0; $num_landscape += $image->isLandscape() ? 1 : 0; $photos[] = $image; } // Sort photos by priority and date captured. usort($photos, 'self::orderPhotos'); // At least one portrait? if ($num_portrait >= 1) { // Grab two more landscapes, so we can put a total of four tiles on the side. for ($i = 0; $image && $i < 2 && ($image = $this->fetchImage(Image::TYPE_LANDSCAPE, $image)); $i++) $photos[] = $image; // We prefer to have the portrait on the side, so prepare to process that first. usort($photos, function($a, $b) { if ($a->isPortrait() && !$b->isPortrait()) return -1; elseif ($b->isPortrait() && !$a->isPortrait()) return 1; else return self::orderPhotos($a, $b); }); // We might not have a full set of photos, but only bother if we have at least three. if (count($photos) > 3) return [$photos, 'portrait']; } // One landscape at least, hopefully? if ($num_landscape >= 1) { if (count($photos) === 3) { // We prefer to have the landscape on the side, so prepare to process that first. usort($photos, function($a, $b) { if ($a->isLandscape() && !$b->isLandscape()) return -1; elseif ($b->isLandscape() && !$a->isLandscape()) return 1; else return self::orderPhotos($a, $b); }); return [$photos, 'landscape']; } elseif (count($photos) === 2) return [$photos, 'duo']; else return [$photos, 'single']; } // A boring set it is, then. return [$photos, 'row']; } }