<?php
/**
* @version	$Id: file_helper.php 16513 2017-01-20 14:10:53Z alex $
* @package	In-Portal
* @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license      GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/

	defined('FULL_PATH') or die('restricted access!');

	class FileHelper extends kHelper {

		/**
		 * Puts existing item images (from sub-item) to virtual fields (in main item)
		 *
		 * @param kCatDBItem $object
		 * @return void
		 * @access public
		 */
		public function LoadItemFiles(&$object)
		{
			$max_file_count = $this->Application->ConfigValue($object->Prefix.'_MaxImageCount'); // file count equals to image count (temporary measure)

			$sql = 'SELECT *
					FROM '.TABLE_PREFIX.'CatalogFiles
					WHERE ResourceId = '.$object->GetDBField('ResourceId').'
					ORDER BY FileId ASC
					LIMIT 0, '.(int)$max_file_count;
			$item_files = $this->Conn->Query($sql);

			$file_counter = 1;
			foreach ($item_files as $item_file) {
				$file_path = $item_file['FilePath'];
				$object->SetDBField('File'.$file_counter, $file_path);
				$object->SetOriginalField('File'.$file_counter, $file_path);
				$object->SetFieldOption('File'.$file_counter, 'original_field', $item_file['FileName']);
				$file_counter++;
			}
		}

		/**
		 * Saves newly uploaded images to external image table
		 *
		 * @param kCatDBItem $object
		 * @return void
		 * @access public
		 */
		public function SaveItemFiles(&$object)
		{
			$table_name = $this->Application->getUnitOption('#file', 'TableName');
			$max_file_count = $this->Application->getUnitOption($object->Prefix, 'FileCount'); // $this->Application->ConfigValue($object->Prefix.'_MaxImageCount');

			$this->CheckFolder(FULL_PATH . ITEM_FILES_PATH);

			$i = 0;
			while ($i < $max_file_count) {
				$field = 'File'.($i + 1);
				$field_options = $object->GetFieldOptions($field);

				$file_path = $object->GetDBField($field);
				if ($file_path) {
					if (isset($field_options['original_field'])) {
						$key_clause = 'FileName = '.$this->Conn->qstr($field_options['original_field']).' AND ResourceId = '.$object->GetDBField('ResourceId');

						if ($object->GetDBField('Delete'.$field)) {
							// if item was cloned, then new filename is in db (not in $image_src)
							$sql = 'SELECT FilePath
									FROM '.$table_name.'
									WHERE '.$key_clause;
							$file_path = $this->Conn->GetOne($sql);
							if (@unlink(FULL_PATH.ITEM_FILES_PATH.$file_path)) {
								$sql = 'DELETE FROM '.$table_name.'
										WHERE '.$key_clause;
								$this->Conn->Query($sql);
							}
						}
						else {
							// image record found -> update
							$fields_hash = Array (
								'FilePath' => $file_path,
							);

							$this->Conn->doUpdate($fields_hash, $table_name, $key_clause);
						}
					}
					else {
						// record not found -> create
						$fields_hash = Array (
							'ResourceId' => $object->GetDBField('ResourceId'),
							'FileName' => $field,
							'Status' => STATUS_ACTIVE,
							'FilePath' => $file_path,
						);

						$this->Conn->doInsert($fields_hash, $table_name);
						$field_options['original_field'] = $field;
						$object->SetFieldOptions($field, $field_options);
					}
				}
				$i++;
			}
		}

		/**
		 * Preserves cloned item images/files to be rewritten with original item images/files
		 *
		 * @param Array $field_values
		 * @return void
		 * @access public
		 */
		public function PreserveItemFiles(&$field_values)
		{
			foreach ($field_values as $field_name => $field_value) {
				if ( !is_array($field_value) ) {
					continue;
				}

				if ( isset($field_value['upload']) && ($field_value['error'] == UPLOAD_ERR_NO_FILE) ) {
					// this is upload field, but nothing was uploaded this time
					unset($field_values[$field_name]);
				}
			}
		}

		/**
		 * Determines what image/file fields should be created (from post or just dummy fields for 1st upload)
		 *
		 * @param string $prefix
		 * @param bool $is_image
		 * @return void
		 * @access public
		 */
		public function createItemFiles($prefix, $is_image = false)
		{
			$items_info = $this->Application->GetVar($prefix);
			if ($items_info) {
				list (, $fields_values) = each($items_info);
				$this->createUploadFields($prefix, $fields_values, $is_image);
			}
			else {
				$this->createUploadFields($prefix, Array(), $is_image);
			}
		}

		/**
		 * Dynamically creates virtual fields for item for each image/file field in submit
		 *
		 * @param string $prefix
		 * @param Array $fields_values
		 * @param bool $is_image
		 * @return void
		 * @access public
		 */
		public function createUploadFields($prefix, $fields_values, $is_image = false)
		{
			$field_options = Array (
				'type'			=>	'string',
				'max_len'		=>	240,
				'default'		=>	'',
			);

			if ($is_image) {
				$field_options['formatter'] = 'kPictureFormatter';
				$field_options['include_path'] = 1;
				$field_options['allowed_types'] = Array ('image/jpeg', 'image/pjpeg', 'image/png', 'image/x-png', 'image/gif', 'image/bmp');
				$field_prefix = 'Image';
			}
			else {
				$field_options['formatter'] = 'kUploadFormatter';
				$field_options['upload_dir'] = ITEM_FILES_PATH;
				$field_options['allowed_types'] = Array ('application/pdf', 'application/msexcel', 'application/msword', 'application/mspowerpoint');
				$field_prefix = 'File';
			}

			$fields = $this->Application->getUnitOption($prefix, 'Fields');
			$virtual_fields = $this->Application->getUnitOption($prefix, 'VirtualFields');

			$image_count = 0;
			foreach ($fields_values as $field_name => $field_value) {
				if (preg_match('/^('.$field_prefix.'[\d]+|Primary'.$field_prefix.')$/', $field_name)) {
					$fields[$field_name] = $field_options;
					$virtual_fields[$field_name] = $field_options;
					$this->_createCustomFields($prefix, $field_name, $virtual_fields, $is_image);

					$image_count++;
				}
			}

			if (!$image_count) {
				// no images found in POST -> create default image fields
				$image_count = $this->Application->ConfigValue($prefix.'_MaxImageCount');

				if ($is_image) {
					$created_count = 1;
					$image_names = Array ('Primary' . $field_prefix => '');

					while ($created_count < $image_count) {
						$image_names[$field_prefix . $created_count] = '';
						$created_count++;
					}
				}
				else {
					$created_count = 0;
					$image_names = Array ();

					while ($created_count < $image_count) {
						$image_names[$field_prefix . ($created_count + 1)] = '';
						$created_count++;
					}
				}

				if ($created_count) {
					$this->createUploadFields($prefix, $image_names, $is_image);
				}

				return ;
			}

			$this->Application->setUnitOption($prefix, $field_prefix.'Count', $image_count);
			$this->Application->setUnitOption($prefix, 'Fields', $fields);
			$this->Application->setUnitOption($prefix, 'VirtualFields', $virtual_fields);
		}

		/**
		 * Adds ability to create more virtual fields associated with main image/file
		 *
		 * @param string $prefix
		 * @param string $field_name
		 * @param Array $virtual_fields
		 * @param bool $is_image
		 * @return void
		 * @access protected
		 */
		protected function _createCustomFields($prefix, $field_name, &$virtual_fields, $is_image = false)
		{
			$virtual_fields['Delete' . $field_name] = Array ('type' => 'int', 'default' => 0);

			if ( $is_image ) {
				$virtual_fields[$field_name . 'Alt'] = Array ('type' => 'string', 'default' => '');
			}
		}

		/**
		 * Downloads file to user
		 *
		 * @param string $filename
		 * @return void
		 * @access public
		 */
		public function DownloadFile($filename)
		{
			$this->Application->setContentType(kUtil::mimeContentType($filename), false);
			header('Content-Disposition: attachment; filename="' . basename($filename) . '"');
			header('Content-Length: ' . filesize($filename));
			readfile($filename);
			flush();
		}

		/**
		 * Creates folder with given $path
		 *
		 * @param string $path
		 * @return bool
		 * @access public
		 */
		public function CheckFolder($path)
		{
			$result = true;

			if (!file_exists($path) || !is_dir($path)) {
				$parent_path = preg_replace('#(/|\\\)[^/\\\]+(/|\\\)?$#', '', rtrim($path , '/\\'));
				$result = $this->CheckFolder($parent_path);

				if ($result) {
					$result = mkdir($path);

					if ($result) {
						chmod($path, 0777);

						// don't commit any files from created folder
						if (file_exists(FULL_PATH . '/CVS')) {
							$cvsignore = fopen($path . '/.cvsignore', 'w');
							fwrite($cvsignore, '*.*');
							fclose($cvsignore);
							chmod($path . '/.cvsignore', 0777);
						}
					}
					else {
						trigger_error('Cannot create directory "<strong>' . $path . '</strong>"', E_USER_WARNING);
						return false;
					}
				}
			}

			return $result;
		}

		/**
		 * Copies all files and directories from $source to $destination directory. Create destination directory, when missing.
		 *
		 * @param string $source
		 * @param string $destination
		 * @return bool
		 * @access public
		 */
		public function copyFolderRecursive($source, $destination)
		{
			if ( substr($source, -1) == DIRECTORY_SEPARATOR ) {
				$source = substr($source, 0, -1);
				$destination .= DIRECTORY_SEPARATOR . basename($source);
			}

			$iterator = new DirectoryIterator($source);
			/** @var DirectoryIterator $file_info */

			$result = $this->CheckFolder($destination);

			foreach ($iterator as $file_info) {
				if ( $file_info->isDot() ) {
					continue;
				}

				$file = $file_info->getFilename();

				if ( $file_info->isDir() ) {
					$result = $this->copyFolderRecursive($file_info->getPathname(), $destination . DIRECTORY_SEPARATOR . $file);
				}
				else {
					$result = copy($file_info->getPathname(), $destination . DIRECTORY_SEPARATOR . $file);
				}

				if (!$result) {
					trigger_error('Cannot create file/directory "<strong>' . $destination . DIRECTORY_SEPARATOR . $file . '</strong>"', E_USER_WARNING);
					break;
				}
			}

			return $result;
		}

		/**
		 * Copies all files from $source to $destination directory. Create destination directory, when missing.
		 *
		 * @param string $source
		 * @param string $destination
		 * @return bool
		 * @access public
		 */
		public function copyFolder($source, $destination)
		{
			if ( substr($source, -1) == DIRECTORY_SEPARATOR ) {
				$source = substr($source, 0, -1);
				$destination .= DIRECTORY_SEPARATOR . basename($source);
			}

			$iterator = new DirectoryIterator($source);
			/** @var DirectoryIterator $file_info */

			$result = $this->CheckFolder($destination);

			foreach ($iterator as $file_info) {
				if ( $file_info->isDot() || !$file_info->isFile() ) {
					continue;
				}

				$file = $file_info->getFilename();

				$result = copy($file_info->getPathname(), $destination . DIRECTORY_SEPARATOR . $file);

				if ( !$result ) {
					trigger_error('Cannot create file "<strong>' . $destination . DIRECTORY_SEPARATOR . $file . '</strong>"', E_USER_WARNING);
					break;
				}
			}

			return $result;
		}

		/**
		 * Transforms given path to file into it's url, where each each component is encoded (excluding domain and protocol)
		 *
		 * @param string $url
		 * @return string
		 * @access public
		 */
		public function pathToUrl($url)
		{
			$url = str_replace(DIRECTORY_SEPARATOR, '/', preg_replace('/^' . preg_quote(FULL_PATH, '/') . '(.*)/', '\\1', $url, 1));

			// TODO: why?
			$url = implode('/', array_map('rawurlencode', explode('/', $url)));

			return rtrim($this->Application->BaseURL(), '/') . $url;
		}

		/**
		 * Transforms given url to path to it
		 *
		 * @param string $url
		 * @return string
		 * @access public
		 */
		public function urlToPath($url)
		{
			$base_url = rtrim($this->Application->BaseURL(), '/');

			// escape replacement patterns, like "\<number>"
			$full_path = preg_replace('/(\\\[\d]+)/', '\\\\\1', FULL_PATH);
			$path = preg_replace('/^' . preg_quote($base_url, '/') . '(.*)/', $full_path . '\\1', $url, 1);

			return str_replace('/', DIRECTORY_SEPARATOR, kUtil::unescape($path, kUtil::ESCAPE_URL));
		}

		/**
		 * Makes given paths DocumentRoot agnostic.
		 *
		 * @param array $paths List of file paths.
		 *
		 * @return array
		 */
		public function makeRelative(array $paths)
		{
			foreach ( $paths as $index => $path ) {
				$replaced_count = 0;
				$relative_path = preg_replace('/^' . preg_quote(FULL_PATH, '/') . '/', '', $path, 1, $replaced_count);

				if ( $replaced_count === 1 ) {
					$paths[$index] = $relative_path;
				}
			}

			return $paths;
		}

		/**
		 * Ensures, that new file will not overwrite any of previously created files with same name
		 *
		 * @param string $path
		 * @param string $name
		 * @param Array $forbidden_names
		 * @return string
		 */
		public function ensureUniqueFilename($path, $name, $forbidden_names = Array ())
		{
			$parts = pathinfo($name);
			$ext = '.' . $parts['extension'];
			$filename = $parts['filename'];
			$path = rtrim($path, '/');

			$original_checked = false;
			$new_name = $filename . $ext;

			if ( $parts['dirname'] != '.' ) {
				$path .= '/' . ltrim($parts['dirname'], '/');
			}

			// make sure target folder always exists, especially for cases,
			// when storage engine folder is supplied as a part of $name
			$this->CheckFolder($path);

			while (file_exists($path . '/' . $new_name) || in_array($path . '/' . $new_name, $forbidden_names)) {
				if ( preg_match('/(.*)_([0-9]*)(' . preg_quote($ext, '/') . ')/', $new_name, $regs) ) {
					$new_name = $regs[1] . '_' . ((int)$regs[2] + 1) . $regs[3];
				}
				elseif ( $original_checked ) {
					$new_name = $filename . '_1' . $ext;
				}

				$original_checked = true;
			}

			if ( $parts['dirname'] != '.' ) {
				$new_name = $parts['dirname'] . '/' . $new_name;
			}

			return $new_name;
		}
	}
