134 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			134 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
/*****************************************************************************
 | 
						|
 * Download.php
 | 
						|
 * Contains the code to download an album.
 | 
						|
 *
 | 
						|
 * Kabuki CMS (C) 2013-2019, Aaron van Geffen
 | 
						|
 *****************************************************************************/
 | 
						|
 | 
						|
class Download
 | 
						|
{
 | 
						|
	public function __construct()
 | 
						|
	{
 | 
						|
		// Ensure we're logged in at this point.
 | 
						|
		$user = Registry::get('user');
 | 
						|
		if (!$user->isLoggedIn())
 | 
						|
			throw new NotAllowedException();
 | 
						|
 | 
						|
		if (!isset($_GET['tag']))
 | 
						|
			throw new UserFacingException('No album or tag has been specified for download.');
 | 
						|
 | 
						|
		$tag = (int)$_GET['tag'];
 | 
						|
		$album = Tag::fromId($tag);
 | 
						|
 | 
						|
		if (isset($_GET['by']) && ($user = Member::fromSlug($_GET['by'])) !== false)
 | 
						|
			$id_user_uploaded = $user->getUserId();
 | 
						|
		else
 | 
						|
			$id_user_uploaded = null;
 | 
						|
 | 
						|
		if (isset($_SESSION['current_export']))
 | 
						|
			throw new UserFacingException('You can only export one album at the same time. Please wait until the other download finishes, or try again later.');
 | 
						|
 | 
						|
		// So far so good?
 | 
						|
		$this->exportAlbum($album, $id_user_uploaded);
 | 
						|
		exit;
 | 
						|
	}
 | 
						|
 | 
						|
	private function exportAlbum(Tag $album, $id_user_uploaded)
 | 
						|
	{
 | 
						|
		$files = [];
 | 
						|
 | 
						|
		$album_ids = array_merge([$album->id_tag], $this->getChildAlbumIds($album->id_tag));
 | 
						|
		foreach ($album_ids as $album_id)
 | 
						|
		{
 | 
						|
			$iterator = AssetIterator::getByOptions([
 | 
						|
				'id_tag' => $album_id,
 | 
						|
				'id_user_uploaded' => $id_user_uploaded,
 | 
						|
			]);
 | 
						|
			while ($asset = $iterator->next())
 | 
						|
				$files[] = join(DIRECTORY_SEPARATOR, [$asset->getSubdir(), $asset->getFilename()]);
 | 
						|
		}
 | 
						|
 | 
						|
		$descriptorspec = [
 | 
						|
			0 => ['pipe', 'r'], // STDIN
 | 
						|
			1 => ['pipe', 'w'], // STDOUT
 | 
						|
		];
 | 
						|
 | 
						|
		// Prevent simultaneous exports.
 | 
						|
		$_SESSION['current_export'] = $album->id_tag;
 | 
						|
 | 
						|
		// Allow new exports if the connection is terminated unexpectedly (e.g. when a user aborts a download).
 | 
						|
		register_shutdown_function(function() {
 | 
						|
			if (isset($_SESSION['current_export']))
 | 
						|
				unset($_SESSION['current_export']);
 | 
						|
		});
 | 
						|
 | 
						|
		$command = 'tar -cf - -C ' . escapeshellarg(ASSETSDIR) . ' --null -T -';
 | 
						|
 | 
						|
		$proc = proc_open($command, $descriptorspec, $pipes, ASSETSDIR);
 | 
						|
 | 
						|
		if(!$proc)
 | 
						|
			throw new UnexpectedValueException('Could not execute TAR command');
 | 
						|
 | 
						|
		if(!$pipes[0])
 | 
						|
			throw new UnexpectedValueException('Could not open pipe for STDIN');
 | 
						|
 | 
						|
		if(!$pipes[1])
 | 
						|
			throw new UnexpectedValueException('Could not open pipe for STDOUT');
 | 
						|
 | 
						|
		// STDOUT should not block.
 | 
						|
		stream_set_blocking($pipes[1], 0);
 | 
						|
 | 
						|
		// Allow this the download to take its time...
 | 
						|
		set_time_limit(0);
 | 
						|
 | 
						|
		header('Pragma: no-cache');
 | 
						|
		header('Content-Description: File Download');
 | 
						|
		header('Content-disposition: attachment; filename="' . $album->tag . '.tar"');
 | 
						|
		header('Content-Type: application/octet-stream');
 | 
						|
		header('Content-Transfer-Encoding: binary');
 | 
						|
 | 
						|
		// Write filenames to include to STDIN, separated by null bytes.
 | 
						|
		foreach ($files as $file)
 | 
						|
			fwrite($pipes[0], $file . "\0");
 | 
						|
 | 
						|
		// Close STDIN pipe to start archiving.
 | 
						|
		fclose($pipes[0]);
 | 
						|
 | 
						|
		// At this point, end output buffering so we can enjoy more than ~62MB of photos.
 | 
						|
		ob_end_flush();
 | 
						|
 | 
						|
		do
 | 
						|
		{
 | 
						|
			// Read STDOUT as `tar` is doing its work.
 | 
						|
			echo stream_get_contents($pipes[1], 4096);
 | 
						|
 | 
						|
			// Are we still running?
 | 
						|
			$status = proc_get_status($proc);
 | 
						|
		}
 | 
						|
		while (!empty($status) && $status['running']);
 | 
						|
 | 
						|
		// Close STDOUT pipe and clean up process.
 | 
						|
		fclose($pipes[1]);
 | 
						|
 | 
						|
		proc_close($proc);
 | 
						|
 | 
						|
		// Allow new exports from this point onward.
 | 
						|
		unset($_SESSION['current_export']);
 | 
						|
	}
 | 
						|
 | 
						|
	private function getChildAlbumIds($parent_id)
 | 
						|
	{
 | 
						|
		$ids = [];
 | 
						|
 | 
						|
		$albums = Tag::getAlbums($parent_id, 0, PHP_INT_MAX);
 | 
						|
		foreach ($albums as $album)
 | 
						|
		{
 | 
						|
			$ids[] = $album['id_tag'];
 | 
						|
			$ids = array_merge($ids, $this->getChildAlbumIds($album['id_tag']));
 | 
						|
		}
 | 
						|
 | 
						|
		return $ids;
 | 
						|
	}
 | 
						|
}
 |