<?php

require_once 'BeanFactory.class.php';
require_once 'Search.class.php';
require_once 'Request.class.php';
require_once 'Utils.class.php';

class Xoonips_File {

	private $file_name;
	private $file_type;
	private $file_size;
	private $mimetype_map = array(
    	'' => array(
			'dtd' => 'application/xml-dtd',
			'jnlp' => 'application/x-java-jnlp-file',
			'html' => 'text/html',
			'xhtml' => 'application/xhtml+xml',
			'xml' => 'application/xml',
			'xsl' => 'application/xml',
		),
		'application/msword' => array(
			'ppt' => 'application/vnd.ms-powerpoint',
			'xls' => 'application/vnd.ms-excel',
		),
		'application/x-zip' => array(
			'jar' => 'x-java-archive',
			'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
			'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
			'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    		'odc' => 'application/vnd.oasis.opendocument.chart',
    		'odb' => 'application/vnd.oasis.opendocument.database',
    		'odf' => 'application/vnd.oasis.opendocument.formula',
    		'odg' => 'application/vnd.oasis.opendocument.graphics',
    		'otg' => 'application/vnd.oasis.opendocument.graphics-template',
    		'odi' => 'application/vnd.oasis.opendocument.image',
    		'odp' => 'application/vnd.oasis.opendocument.presentation',
    		'otp' => 'application/vnd.oasis.opendocument.presentation-template',
    		'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
    		'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template',
    		'odt' => 'application/vnd.oasis.opendocument.text',
    		'odm' => 'application/vnd.oasis.opendocument.text-master',
    		'ott' => 'application/vnd.oasis.opendocument.text-template',
    		'oth' => 'application/vnd.oasis.opendocument.text-web',
    		'sxw' => 'application/vnd.sun.xml.writer',
    		'stw' => 'application/vnd.sun.xml.writer.template',
    		'sxc' => 'application/vnd.sun.xml.calc',
    		'stc' => 'application/vnd.sun.xml.calc.template',
    		'sxd' => 'application/vnd.sun.xml.draw',
    		'std' => 'application/vnd.sun.xml.draw.template',
    		'sxi' => 'application/vnd.sun.xml.impress sxi',
    		'sti' => 'application/vnd.sun.xml.impress.template',
    		'sxg' => 'application/vnd.sun.xml.writer.global',
    		'sxm' => 'application/vnd.sun.xml.math',
		),
		'application/octet-stream' => array(
			'wmv' => 'video/x-ms-wmv',
		),
		'text/html' => array(
			'css' => 'text/css',
			'dtd' => 'application/xml-dtd',
			'sgml' => 'text/sgml',
			'sgm' => 'text/sgml',
			'xml' => 'application/xml',
			'xsl' => 'application/xml',
		),
		'text/plain' => array(
			'c' => 'text/x-c',
			'cc' => 'text/x-c++',
			'cpp' => 'text/x-c++',
			'css' => 'text/css',
			'cxx' => 'text/x-c++',
			'dtd' => 'application/xml-dtd',
			'htm' => 'text/html',
			'html' => 'text/html',
			'js' => 'application/x-javascript',
			'php' => 'text/html',
			'sh' => 'application/x-shellscript',
			'sgml' => 'text/sgml',
			'sgm' => 'text/sgml',
			'tex' => 'application/x-tex',
			'xml' => 'application/xml',
			'xsl' => 'application/xml',
		),
		'text/x-c' => array(
			'cc' => 'text/x-c++',
			'cpp' => 'text/x-c++',
			'css' => 'text/css',
			'cxx' => 'text/x-c++',
			'dtd' => 'application/xml-dtd',
			'htm' => 'text/html',
			'html' => 'text/html',
			'js' => 'application/x-javascript',
			'php' => 'text/html',
			'sgml' => 'text/sgml',
			'sgm' => 'text/sgml',
			'xml' => 'application/xml',
			'xsl' => 'application/xml',
		),
		'text/x-c++' => array(
			'c' => 'text/x-c',
			'css' => 'text/css',
			'dtd' => 'application/xml-dtd',
			'htm' => 'text/html',
			'html' => 'text/html',
			'js' => 'application/x-javascript',
			'php' => 'text/html',
			'sgml' => 'text/sgml',
			'sgm' => 'text/sgml',
			'xml' => 'application/xml',
			'xsl' => 'application/xml',
		)
	);
	public $fsearch_plugins;

	private $dirname;
	private $trustDirname;

	/**
	 * Config bean
	 *
	 * @var object
	 */
	private $configBean;
	/**
	 * File bean
	 *
	 * @var object
	 */
	private $fileBean;
	/**
	 * Search Text bean
	 *
	 * @var object
	 */
	private $searchTextBean;

	/**
	 * Constructor
	 *
	 * @param string $dirname
	 * @param string $trustDirname
	 * @param bool $execFileSearch
	 **/
	public function Xoonips_File($dirname, $trustDirname, $execFileSearch = false) {
		if (is_null($dirname)) {
			return;
		}
		$this->dirname = $dirname;
		$this->trustDirname = $trustDirname;
      	$this->configBean = Xoonips_BeanFactory::getBean('ConfigBean', $this->dirname, $this->trustDirname);
		$this->fileBean = Xoonips_BeanFactory::getBean('ItemFileBean', $this->dirname, $this->trustDirname);
		if ($execFileSearch) {
			$this->searchTextBean = Xoonips_BeanFactory::getBean('SearchTextBean', $this->dirname, $this->trustDirname);
			$this->loadFileSearchPlugins();
		}
	}

	// upload file
	public function uploadFile($fileName, $viewType, $itemId, $itemTypeDetailId, $group_id=0) {
	//global $xoopsDB;

      	$request = new Xoonips_Request();
      	$file = $request->getFile($fileName, $this->dirname, $this->trustDirname);
      	$itemtypeId = $request->getParameter('itemtype_id');
      	$xnpsid = session_id();

     	if (empty($file)) {
      		return 'none';
      	}

		if (_CHARSET == 'ISO-8859-1') {
			$file['name'] = Xoonips_Utils::convertToNumericEntities($file['name'], true);
		}

		global $xoopsUser;
      	$uid = $xoopsUser->getVar('uid');
      	$itemBean = Xoonips_BeanFactory::getBean('ItemVirtualBean', $this->dirname, $this->trustDirname);
		$privateItemLimit = $itemBean->getPrivateItemLimit($uid);
      	$filesizes = $itemBean->getFilesizePrivate($uid);
      	if ($filesizes > $privateItemLimit['itemStorage'] && $privateItemLimit['itemStorage'] > 0) {
      		return 'limit';
      	}

      	$this->file_name = $file['name'];
      	$this->file_type = $file['type'];
      	$this->file_size = $file['size'];
      	$file_tmp_name = $file['tmp_name'];
      	$thumbnail_file = '';
      	if ($viewType == 'preview') {
      		$thumbnail_file = $this->getThumbnail($file_tmp_name, $this->file_type);
      		if (empty($thumbnail_file)) {
      			return 'error';
      		}
      	}

      	$fs_name = $this->detectFileSearchPlugin($this->file_name, $this->file_type);
      	$fs_version = is_null($fs_name) ? null : $this->fsearch_plugins[$fs_name]['version'] * 100;

      	$fileInfo = array('item_id' => $itemId, 'item_field_detail_id' => $itemTypeDetailId,
      		'original_file_name' => $this->file_name, 'mime_type' => $this->file_type,
      		'file_size' => $this->file_size, 'sess_id' => $xnpsid,
      		'search_module_name' => $fs_name, 'search_module_version' => $fs_version, 'group_id' => $group_id);

      	$file_id = '';
      	$this->fileBean->insertUploadFile($fileInfo, $file_id);

      	$uploadDir = $this->configBean->getConfig('upload_dir');
      	$uploadfile = $uploadDir . '/' . (int) $file_id;
      	$thumbnailfile = $uploadDir . '/' . (int) $file_id . '_thumbnail';

      	if (move_uploaded_file($file['tmp_name'], $uploadfile)) {
      		if (!is_null($fs_name)) {
   			    // insert n-gram strings of full text into table
      			$this->insertSearchText($file_id, $fs_name, $uploadfile);
      		}
      	    //create thumbnail file
            if ($thumbnail_file != '') {
	            if (file_exists($thumbnail_file)) {
	                copy($thumbnail_file, $thumbnailfile);
	                unlink($thumbnail_file);
	            }
            }
      		return $file_id;
      	}
      	return 'none';
    }

    // insert n-gram strings of full text into table
    private function insertSearchText($file_id, $fs_name, $file_path) {
 		set_time_limit(0);
      	// fetch plain text string using file search plugins
      	$indexer = $this->fsearch_plugins[$fs_name]['instance'];
      	$indexer->open($file_path);
      	$text = $indexer->fetch();
      	$indexer->close();

		// get n-gram strings
		$search = new Xoonips_Search();
		$text = $search->getFulltextData($text);

      	// open temporary file
      	$tmpfile = tempnam('/temp', 'XooNIpsSearch');
      	$fp = fopen($tmpfile, 'w');
      	if ($fp === false) {
      		return false;
      	}

      	// register callback function to remove temporary file
      	register_shutdown_function(array($this, 'unlinkFileOnshutdown'), $tmpfile);

      	// write first field 'file_id'
      	fwrite($fp, $file_id . "\t");
      	// dump hashed search text to temporary file
      	fwrite($fp, Xoonips_Utils::decodeUtf8($text, Xoonips_Utils::getServerCharset(), 'h'));
      	fclose($fp);

      	// insert search text
      	$esc_tmpfile = addslashes($tmpfile);
      	$this->searchTextBean->insert($esc_tmpfile);
    }

	// detect file search plugin
	private function detectFileSearchPlugin($file_name, $file_mimetype) {
      	$file_pathinfo = pathinfo($file_name);
      	$file_ext = isset($file_pathinfo['extension']) ? $file_pathinfo['extension'] : '';
      	$fs_name = null;
      	foreach ($this->fsearch_plugins as $module) {
      		if (in_array($file_ext, $module['extensions']) && in_array($file_mimetype, $module['mime_type'])) {
      			$fs_name = $module['name'];
      			break;
      		}
      	}
      	return $fs_name;
	}

	// load file search plugins
	private function loadFileSearchPlugins() {
      	if ($this->fsearch_plugins) {
      		return true;
      	}
      	$this->fsearch_plugins = array();
      	require_once XOOPS_TRUST_PATH . '/modules/' . $this->trustDirname . '/class/core/FileSearchBase.class.php';

	    $fsearch_dir = XOOPS_TRUST_PATH . '/modules/' . $this->trustDirname . '/class/filesearch';
		if ($fsearch_handle = opendir($fsearch_dir)) {
    		while ($file = readdir($fsearch_handle)) {
				if (preg_match('/^FileSearch.+\\.class.php$/', $file)) {
					require_once $fsearch_dir . '/' . $file;
					$className = 'Xoonips_' . substr($file, 0, strlen($file) - 10);
					$fileSearch = new $className();
					$searchInstance = $fileSearch->getSearchInstance();
					$searchInstance['instance'] = $fileSearch;
					$this->fsearch_plugins[$searchInstance['name']] = $searchInstance;
				}
    		}
			closedir($fsearch_handle);
		}

      	uasort($this->fsearch_plugins, array(&$this, 'sortFileSearchPlugins'));
      	return true;
	}

	private function sortFileSearchPlugins($a, $b) {
      	return strcmp($a['display_name'], $b['display_name']);
	}

	private function unlinkFileOnshutdown($file_path) {
      	@unlink($file_path);
	}

      // get file path
	public function getFilePath($opTp, $file_id) {
      	$uploadDir = $this->configBean->getConfig('upload_dir');
      	$fileInfo = $this->fileBean->getFile($file_id);
      	if (isset($fileInfo['item_id']) && empty($fileInfo['item_id'])) {
      		$file_path = $uploadDir . '/' .(int) $file_id;
      	} else {
      		$file_path = $uploadDir . '/'. $opTp. '/' .$fileInfo['item_id'] . '/' . (int) $file_id;
      	}
      	return $file_path;
	}

    // update file information
    public function updateFileInfo($file_id) {
      	$file_path = $this->getFilePath('item', $file_id);
      	$fileInfo = $this->fileBean->getFile($file_id);
      	if (!file_exists($file_path) || !$fileInfo) {
      		// file or object not found
      		return false;
      	}
      	$file_name = $fileInfo['original_file_name'];
      	$mimetype = $this->getMimetype($file_path, $file_name);
    	if ($mimetype != '') {
			$fileInfo['mime_type'] = $mimetype;
		}
      	if ($this->fileBean->canPreview($file_id)) {
      		$thumbnail = $this->getThumbnail($file_path, $fileInfo['mime_type']);
      	} else {
      		$thumbnail = null;
      	}
      	if (is_null($thumbnail)) {
      		$fileInfo['thumbnail_file'] = null;
      	} else {
      		$fileInfo['thumbnail_file'] = addslashes($thumbnail);
      	}
      	return $this->fileBean->updateFile($file_id,$fileInfo);
	}

	// update file search text
	public function updateFileSearchText($file_id, $force) {
      	$fileInfo = $this->fileBean->getFile($file_id);
      	if (!$fileInfo) {
      		return false;
      	}
      	$file_name = $fileInfo['original_file_name'];
      	$file_mimetype = $fileInfo['mime_type'];
      	$file_path = $this->getFilePath('item', $file_id);
      	$fs_name = $this->detectFileSearchPlugin($file_name, $file_mimetype);
      	$fs_version = is_null($fs_name) ? null : $this->fsearch_plugins[$fs_name]['version'];
      	if (!$force) {
      		// plugin version check
      		$old_fs_name = $fileInfo['search_module_name'];
      		$old_fs_version = $fileInfo['search_module_version'];
      		if ($fs_name == $old_fs_name) {
      			if (is_null($fs_name)) {
      				// file search is not supported
      				return true;
      			}
      			if (floatVal($fs_version) <= floatVal($old_fs_version)) {
      				// no need to update search text
      				return true;
      			}
      		}
      	}

      	// delete search text at once
      	$this->searchTextBean->delete($file_id);

      	if (!is_readable($file_path) || is_null($fs_name)) {
      		// clear search plugin informations
      		$fileInfo['search_module_name'] = null;
      		$fileInfo['search_module_version'] = null;
      		return $this->fileBean->updateFile($file_id,$fileInfo);
      	}

   	    // insert n-gram strings of full text into table
      	$this->insertSearchText($file_id, $fs_name, $file_path);

      	// update file search plugin information
      	$fileInfo['search_module_name'] = $fs_name;
      	$fileInfo['search_module_version'] = $fs_version;
      	return $this->fileBean->updateFile($file_id, $fileInfo);
    }

    /**
     * download file
     *
     * @access public
     * @param string $filePath downloading local file path
     * @param string $fileName file name on server environment
     * @param string $mimeType mime type of downloading file
     */
    public function downloadFile($filePath, $fileName, $mimeType) {
      	// check file exists
      	if (!file_exists($filePath) || !is_readable($filePath)) {
      		die('Fatal Error : file not found');
      	}

      	// get file inforamation
      	$file_size = filesize($filePath);

      	// remove ob fileters
      	$handlers = ob_list_handlers();
      	while (!empty($handlers)) {
      		ob_end_clean();
      		$handlers = ob_list_handlers();
      	}

      	// unlimit time out
      	set_time_limit(0);

      	// output header
      	header('Expires: Sat, 01 Jan 2000 00:00:00 GMT');
      	header('Last-Modified: ' . gmdate( 'D, d M Y H:i:s' ) . ' GMT');
      	// Cache-Control: avoid IE bug - see http://support.microsoft/kb/436605/ja
      	header('Cache-Control: none');
      	header('Content-Disposition: attachment; filename="' . $fileName . '"');
      	if (!empty($file_size)) {
      		header('Content-Length: ' . $file_size);
      	}
      	header('Content-Type: "' . $mimeType . '"');

      	// output content body
      	// readfile( $filePath );
      	$chunksize = 1024 * 1024;
      	$fh = fopen($filePath, 'rb');
      	while (!feof($fh)) {
      		echo fread($fh, $chunksize);
      		flush();
      	}
      	fclose($fh);
    }

    /**
     * get thumbnail
     *
     * @access public
     * @param string $filePath downloading local file path
     * @param string $mimeType mime type of downloading file
     */
    public function getThumbnail($filePath, $mimeType) {
      	$image_id = '';
      	list($width, $height, $mimeType, $attr) = getimagesize($filePath);
      	switch ($mimeType) {
      		case IMAGETYPE_PNG:
      			$image_id = @imagecreatefrompng($filePath);
      			break;
      		case IMAGETYPE_GIF:
      			$image_id = @imagecreatefromgif($filePath);
      			break;
      		case IMAGETYPE_JPEG:
      			$image_id = @imagecreatefromjpeg($filePath);
      			break;
      	}
      	if ($image_id === '') {
      		return null;
      	}
      	$width = imagesx($image_id);
      	$height = imagesy($image_id);
      	// maximum file size in thumbnail
      	$max_width = 100;
      	$max_height = 100;
      	if ($max_width < $width || $max_height < $height) {
      		// If size of image file is too large, need to reduce it.
      		$scale = min($max_width / $width, $max_height / $height);
      		$new_width = round($width * $scale);
      		$new_height = round($height * $scale);
      		// resize
      		$new_image_id = imagecreatetruecolor($new_width, $new_height);
      		imagecopyresampled($new_image_id, $image_id, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
      		imagedestroy($image_id);
      		$image_id = $new_image_id;
      	}
      	$tmpfile = tempnam('/tmp', 'XooNIpsThumbnail');
      	if ($tmpfile === false) {
      		return null;
      	}
      	$result = imagepng($image_id, $tmpfile);
      	imagedestroy($image_id);
      	if ($result === false) {
      		return null;
      	}
      	return $tmpfile;
	}

    /**
     * get file mime type
     *
     * @access public
     * @param string $file_path local file path
     * @param string $file_name original file name
     * @return string mime type
     */
    public function getMimetype($file_path, $file_name) {
      	// get mimetype
      	$magic_file_path = $this->configBean->getConfig('magic_file_path');
      	if ($magic_file_path == '') {
      		return '';
      	}
      	$finfo = new finfo(FILEINFO_MIME, $magic_file_path);
      	if (!$finfo) {
      		return false;
      	}
      	$mimetype = preg_replace('/[,; ].*$/', '', $finfo->file($file_path));

      	$path_parts = pathinfo($file_name);
      	$extension = isset($path_parts['extension']) ? $path_parts['extension'] : '';

      	if ($extension != '' && isset($this->mimetype_map[$mimetype][$extension])) {
      		$mimetype = $this->mimetype_map[$mimetype][$extension];
      	}

      	if ($mimetype == '') {
      		$mimetype = 'application/octet-stream';
      	}
      	return $mimetype;
    }
}

