<?php
//
//	file: includes/class_attach_parent.php
//	author: ptirhiik
//	begin: 05/03/2006
//	version: 1.6.0 - 10/06/2006
//	license: http://opensource.org/licenses/gpl-license.php GNU General Public License (GPL)
//
//	based on : attachment_mod copyright (c) 2002 Meik Sievertsen (acydburn)
//

if ( !defined('IN_PHPBB') )
{
	die('Hacking attempt');
	exit;
}

// To re-add cryptic filenames (eg {user_id}_{filetime}.{ext}), change this to true
define('CRYPTIC_FILENAME', false);

//
// @package attachment_mod
// Base Class for Attaching
//
class attach_parent
{
	var $data; // attachments
	var $page; // On which page we are on ? This should be filled by child classes.
	var $is_auth; // also filled by childs
	var $is_active;
	var $processed;

	var $handler;

	// Constructor
	function attach_parent($page=0)
	{
		global $attach_config;

		// declare
		$this->data = array();
		$this->page = $page;
		$this->is_auth = array();
		$this->is_active = false;
		$this->processed = false;

		$this->handler = new file_handler($attach_config->data);
	}

	function action()
	{
		return _button('add_attachment') || _button('del_attachment') || _button('del_thumbnail') || _button('update_attachment') || _button('edit_comment');
	}

	function init()
	{
		global $attach_config;

		$this->get_auth();

		// we don't want to do any process if attachmod is not active or the user is not authed
		return $this->is_active = !intval($attach_config->data['disable_mod']) && ($this->is_auth['auth_attachments'] || $this->auth['auth_mod']);
	}

	// @private : define in childs
	function get_auth()
	{
	}
	function get_attachments()
	{
	}
	function get_rules_id()
	{
	}
	function check_extension_auth()
	{
	}
	function check_user_quota()
	{
	}

	function get_quota_limits($user_data, $page=0)
	{
		global $attach_config, $db;

		//
		// Define Filesize Limits (Prepare Quota Settings)
		// Priority: User, Group, Management
		//
		// This method is somewhat query intensive, but i think because this one is only executed while attaching a file,
		// it does not make much sense to come up with an new db-entry.
		// Maybe i will change this in a future version, where you are able to disable the User Quota Feature at all (using
		// Default Limits for all Users/Groups)
		//

		$user_id = intval($user_data['user_id']);

		// get quota parm def
		$quota_type = $limit_type = $default = $default_quota = '';
		if ( $page == PAGE_PRIVMSGS )
		{
			$quota_type = QUOTA_PM_LIMIT;
			$limit_type = 'pm_filesize_limit';
			$default = 'max_filesize_pm';
			$default_quota = 'default_pm_quota';
		}
		else
		{
			$quota_type = QUOTA_UPLOAD_LIMIT;
			$limit_type = 'upload_filesize_limit';
			$default = 'attachment_quota';
			$default_quota = 'default_upload_quota';
		}
		if ( $user_data['user_level'] == ADMIN )
		{
			$attach_config->data[$limit_type] = 0; // Unlimited
			return;
		}

		// Get Quota form the user group list
		$sql = 'SELECT l.quota_limit
					FROM ' . ATT_QUOTA_TABLE . ' q, ' . ATT_QUOTA_LIMITS_TABLE . ' l
					WHERE (q.group_id IN(' . $db->sql_subquery('group_id', '
						SELECT DISTINCT group_id
							FROM ' . USER_GROUP_TABLE . '
							WHERE user_id = ' . intval($user_id) . '
								AND user_pending <> 1
							', __LINE__, __FILE__) . ')' . (intval($user_id) == ANONYMOUS ? '' : ' OR q.group_id = ' . GROUP_REGISTERED) . ')
						AND q.quota_type = ' . $quota_type . '
						AND q.quota_limit_id = l.quota_limit_id
					ORDER BY l.quota_limit DESC 
					LIMIT 1';
		$result = $db->sql_query($sql, false, __LINE__, __FILE__);
		if ( $row = $db->sql_fetchrow($result) )
		{
			$attach_config->data[$limit_type] = intval($row['quota_limit']);
		}
		$db->sql_freeresult($result);

		// Never exceed the complete Attachment Upload Quota (specific to posting)
		if ( ($quota_type == QUOTA_UPLOAD_LIMIT) && intval($attach_config->data[$default]) )
		{
			$attach_config->data[$limit_type] = intval($attach_config->data[$limit_type]) ? min(intval($attach_config->data[$default]), intval($attach_config->data[$limit_type])) : intval($attach_config->data[$default]);
		}
	}

	// process the form and send the display if required
	function process($mode, $post_id, $no_display=false)
	{
		global $config, $user, $template;
		global $error, $error_msg;

		if ( !$this->is_active )
		{
			return;
		}
		$this->post_id = intval($post_id);

		// take care of the mode (delete is handled by validate() method)
		$mode = $this->get_mode($mode);
		if ( !in_array($mode, array('editpost', 'newtopic', 'reply')) )
		{
			return;
		}
		$new_post = ($mode == 'newtopic') || ($mode == 'reply');
		if ( ($this->post_id && $new_post) || (!$this->post_id && !$new_post) )
		{
			message_die(GENERAL_MESSAGE, 'No_post_mode');
		}

		// When we reach this point, all auths relative to posting have been checked,
		// and the appropriate die messages have been sent.

		if ( !$this->processed )
		{
			$this->handle($this->post_id);
		}
		if ( !$no_display )
		{
			$this->display();
		}
	}

	// process the form @private
	function handle($post_id)
	{
		global $attach_config;
		global $db, $config, $user;
		global $HTTP_POST_VARS, $HTTP_POST_FILES, $error, $error_msg;

		// raz attachment list
		$this->data = array();

		// give admins green force power !
		$max_attachments = ($user->data['user_level'] == ADMIN) ? ADMIN_MAX_ATTACHMENTS : intval($attach_config->data['max_attachments']);

		//
		// read form
		//
		$form_file = (isset($HTTP_POST_FILES['fileupload']) && isset($HTTP_POST_FILES['fileupload']['name']) && $HTTP_POST_FILES['fileupload']['name'] != 'none') ? trim(stripslashes($HTTP_POST_FILES['fileupload']['name'])) : '';
		$form_comment = get_var('filecomment', '', true);
		$comment_list = get_var('comment_list', array(''), true);
		$attach_ids = get_var('attach_id_list', array(0));
		$attach_ids = empty($attach_ids) ? array() : array_flip($attach_ids);

		//
		// get attach_id list from database
		//

		// get all attachments not linked yet and stored at the form
		if ( !empty($attach_ids) )
		{
			$sql = 'SELECT d.*
						FROM ' . ATT_DESC_TABLE . ' d
							LEFT JOIN ' . ATT_LINKS_TABLE . ' a
								ON a.attach_id = d.attach_id
						WHERE a.attach_id IS NULL
							AND d.attach_id IN(' . implode(', ', array_map('intval', array_keys($attach_ids))) . ')
						ORDER BY d.filetime DESC';
			$result = $db->sql_query($sql, false, __LINE__, __FILE__);
			while ( $row = $db->sql_fetchrow($result) )
			{
				$this->data[ intval($row['attach_id']) ] = $row;
				$this->data[ intval($row['attach_id']) ]['pending'] = true;
			}
			$db->sql_freeresult($result);
		}

		// we have a post : get the existing attachments
		if ( $post_id )
		{
			// read existing attachments
			$attachments = $this->get_attachments($post_id);
			if ( !empty($attachments) )
			{
				$this->data += $attachments[ intval($post_id) ];
			}
			unset($attachments);
		}
		$count_data = count($this->data);


		//
		// handle actions
		//

		// get actions for attach_ids
		$attach_id_upd = $attach_id_del = $attach_id_del_thumbnail = $attach_id_upd_comment = 0;
		$new_comment = '';
		if ( $count_data )
		{
			foreach ( $this->data as $attach_id => $data )
			{
				if ( isset($attach_ids[$attach_id]) )
				{
					$idx = $attach_ids[$attach_id];
					if ( isset($HTTP_POST_VARS['del_attachment']) && isset($HTTP_POST_VARS['del_attachment'][$idx]) )
					{
						$attach_id_del = $attach_id;
					}
					if ( isset($HTTP_POST_VARS['del_thumbnail']) && isset($HTTP_POST_VARS['del_thumbnail'][$idx]) && ($attach_id_del != $attach_id) && $data['thumbnail'] )
					{
						$attach_id_del_thumbnail = $attach_id;
					}
					if ( isset($HTTP_POST_VARS['update_attachment']) && isset($HTTP_POST_VARS['update_attachment'][$idx]) )
					{
						$attach_id_upd = $attach_id;
					}
					if ( isset($HTTP_POST_VARS['edit_comment']) && isset($HTTP_POST_VARS['edit_comment'][$idx]) && ($attach_id_upd != $attach_id) )
					{
						$attach_id_upd_comment = $attach_id;
						$new_comment = $comment_list[$idx];
					}
				}
			}
		}

		// reclaim some memory
		unset($comment_list);
		unset($attach_ids);

		// deal with delete attachment
		if ( $attach_id_del )
		{
			$this->delete_one($post_id, $attach_id_del);
			unset($this->data[$attach_id_del]);
		}
		if ( $attach_id_del_thumbnail )
		{
			$this->handler->open_channel();
			$this->handler->unlink($this->data[$attach_id_del_thumbnail]['physical_filename'], true, true);
			$this->handler->close_channel();
			$sql = 'UPDATE ' . ATT_DESC_TABLE . '
						SET thumbnail = 0
						WHERE attach_id = ' . intval($attach_id_del_thumbnail);
			$db->sql_query($sql, false, __LINE__, __FILE__);
			$this->data[$attach_id_del_thumbnail]['thumbnail'] = 0;
		}

		// deal with comment update
		if ( $attach_id_upd_comment )
		{
			$sql = 'UPDATE ' . ATT_DESC_TABLE . '
						SET file_comment = \'' . $db->sql_escape_string($new_comment) . '\'
						WHERE attach_id = ' . intval($attach_id_upd_comment);
			$db->sql_query($sql, false, __LINE__, __FILE__);
			$this->data[$attach_id_upd_comment]['file_comment'] = $new_comment;
		}

		// deal with new/update attachment
		$count_data = count($this->data);
		if ( !empty($form_file ) )
		{
			if ( $count_data >= intval($max_attachments) )
			{
				_error(sprintf($user->lang('Too_many_attachments'), intval($max_attachments)));
			}
			else
			{
				if ( ($item = $this->upload_attachment($form_file)) && !$error )
				{
					$item['download_count'] = $attach_id_upd ? $this->data[$attach_id_upd]['download_count'] : 0;
					$item['file_comment'] = $attach_id_upd && empty($form_comment) ? $this->data[$attach_id_upd]['file_comment'] : $form_comment;

					// create or update the item in the database
					$fields = array(
						'physical_filename' => (string) basename($item['physical_filename']),
						'real_filename' => (string) basename($item['real_filename']),
						'download_count' => $attach_id_upd ? (int) $this->data[$attach_id_upd]['download_count'] : 0,
						'file_comment' => (string) $form_comment,
						'extension' => (string) strtolower($item['extension']),
						'mimetype' => (string) strtolower($item['mimetype']),
						'filesize' => (int) $item['filesize'],
						'filetime' => (int) $item['filetime'],
						'thumbnail' => (int) $item['thumbnail'],
					);
					$db->sql_statement($fields);

					// update an existing attachment
					if ( $attach_id_upd )
					{
						$attach_id = $attach_id_upd;
						$sql = 'UPDATE ' . ATT_DESC_TABLE . '
									SET ' . $db->sql_update . '
									WHERE attach_id = ' . intval($attach_id);
						$db->sql_query($sql, false, __LINE__, __FILE__);

						// delete the old file
						$this->handler->open_channel();
						$this->handler->unlink($this->data[$attach_id]['physical_filename'], $this->data[$attach_id]['thumbnail']);
						$this->handler->close_channel();

						// remove it from the buffer
						unset($this->data[$attach_id]);
					}
					// add a new attachment
					else
					{
						$sql = 'INSERT INTO ' . ATT_DESC_TABLE . '
									(' . $db->sql_fields . ') VALUES(' . $db->sql_values . ')';
						$db->sql_query($sql, false, __LINE__, __FILE__);
						$attach_id = $db->sql_nextid();
						$item['pending'] = true;
					}

					// store it to the buffer
					$this->data = array($attach_id => $item) + $this->data;
				}
			}
		}
		$this->processed = true;
	}

	// link attachments to post or pm
	function submit_all($link)
	{
		global $db;

		$found = false;
		if ( !empty($this->data) )
		{
			$db->sql_stack_reset();
			foreach ( $this->data as $attach_id => $data )
			{
				if ( $data['pending'] )
				{
					$found = true;
					$fields = $link + array('attach_id' => $attach_id);
					$db->sql_stack_statement($fields);
				}
			}
			$db->sql_stack_insert(ATT_LINKS_TABLE, false, __LINE__, __FILE__);
		}
		return $found;
	}

	function display()
	{
		global $attach_config;
		global $config, $user, $template;

		if ( $count_data = count($this->data) )
		{
			$row_id = 0;
			$template->set_switch('attach');
			foreach ( $this->data as $attach_id => $data )
			{
				$template->assign_block_vars('attach.row', array(
					'ROW_ID' => $row_id,
					'ATTACH_ID' => $attach_id,
					'U_FILE_NAME' => $config->url('viewtopic', array('mode' => 'attach', 'id' => $attach_id), true),
					'FILE_NAME' => $data['real_filename'],
					'FILE_COMMENT' => $data['file_comment'],
				));
				$row_id++;
				$template->set_switch('attach.row.thumbnail', $data['thumbnail'] && $this->is_auth['auth_mod']);
			}
			$template->assign_vars(array(
				'L_POSTED_ATTACHMENTS' => $user->lang('Posted_attachments'),
				'L_UPDATE_COMMENT' => $user->lang('Update_comment'),
				'L_UPLOAD_NEW_VERSION' => $user->lang('Upload_new_version'),
				'L_DELETE_ATTACHMENT' => $user->lang('Delete_attachment'),
				'L_DELETE_THUMBNAIL' => $user->lang('Delete_thumbnail'),
			));
		}
		$template->assign_vars(array(
			'L_ADD_ATTACH_TITLE' => $user->lang('Add_attachment_title'),
			'L_ADD_ATTACH_EXPLAIN' => $user->lang('Add_attachment_explain'),
			'U_RULES' => $config->url('faq', array('mode' => 'attach', POST_FORUM_URL => $this->get_rules_id()), true),
			'L_RULES' => $user->lang('Allowed_extensions_and_sizes'),
			'L_FILE_NAME' => $user->lang('File_name'),
			'L_FILE_COMMENT' => $user->lang('File_comment'),
			'L_ADD_ATTACHMENT' => $user->lang('Add_attachment'),

			'FILE_SIZE' => intval($attach_config->data['max_filesize']),
			'S_FORM_ENCTYPE' => 'enctype="multipart/form-data"',
		));
		$template->assign_vars(array('ATTACHBOX' => $template->include_file('attach_posting_box.tpl', 'attach')));
	}

	// Upload an Attachment to Filespace (intern) @private
	function upload_attachment($filename)
	{
		global $HTTP_POST_FILES, $db;
		global $attach_config, $attach_extensions;
		global $error, $error_msg;
		global $config, $user, $db;

		$filename = trim($filename);
		if ( empty($filename) )
		{
			return false;
		}

		$file = $HTTP_POST_FILES['fileupload']['tmp_name'];
		$mimetype = $HTTP_POST_FILES['fileupload']['type'];
		if ( isset($HTTP_POST_FILES['fileupload']['size']) && ($HTTP_POST_FILES['fileupload']['size'] == 0) )
		{
			message_die(GENERAL_ERROR, 'Tried to upload empty file');
		}

		// Opera add the name to the mime type
		$mimetype = strtolower(strstr($mimetype, '; name') ? str_replace(strstr($mimetype, '; name'), '', $mimetype) : $mimetype);

		// check extension settings
		$extension = substr(strrchr(strtolower(trim($filename)), '.'), 1);
		$attach_extensions->read();
		if ( !isset($attach_extensions->data[$extension]) )
		{
			_error(sprintf($user->lang('Disallowed_extension'), $extension));
		}

		// Check Forum Permissions
		if ( !$error && !$this->check_extension_auth(trim($attach_extensions->data[$extension]['forum_permissions'])) )
		{
			_error(sprintf($user->lang('Disallowed_extension_within_forum'), $extension));
		}

		// check Filename
		if ( !$error && preg_match("#[\\/:*?\"<>|]#i", $filename) )
		{
			_error(sprintf($user->lang('Invalid_filename'), htmlspecialchars($filename)));
		}

		// check php upload-size
		if ( !$error && ($file == 'none') )
		{
			$ini_val = phpversion() >= '4.0.0' ? 'ini_get' : 'get_cfg_var';
			$max_size = @$ini_val('upload_max_filesize');
			_error(empty($max_size) ? $user->lang('Attachment_php_size_na') : sprintf($user->lang('Attachment_php_size_overrun'), $max_size));
		}
		if ( $error )
		{
			return false;
		}

		// get group extension parms
		$allowed_filesize = intval($attach_extensions->data[$extension]['max_filesize']) ? intval($attach_extensions->data[$extension]['max_filesize']) : intval($attach_config->data['max_filesize']);
		$cat_id = intval($attach_extensions->data[$extension]['cat_id']);

		//
		// Upload attachment
		//

		// Prepare Values
		$real_filename = trim(basename(htmlspecialchars($filename)));
		$physical_filename = strtolower($real_filename);
		$filetime = time();
		$filesize = intval(@filesize($file));
		$thumbnail = 0;

		// Do we have to create a thumbnail ?
		if ( ($cat_id == IMAGE_CAT) && intval($attach_config->data['img_create_thumbnail']) )
		{
			$thumbnail = 1;
		}

		$u_id = intval($user->data['user_id']) == ANONYMOUS ? 0 : intval($user->data['user_id']);
		if ( CRYPTIC_FILENAME )
		{
			$physical_filename = $u_id . '_' . $filetime . '.' . $extension;
		}
		else
		{
			$physical_filename = html_entity_decode(trim(stripslashes($physical_filename)));
			$physical_filename = substr($physical_filename, 0, strrpos(strtolower(trim($physical_filename)), '.'));
			$physical_filename = str_replace(array(' ','-'), array('_','_'), $physical_filename);
			$physical_filename = str_replace('__', '_', $physical_filename);
			$physical_filename = str_replace(array(',', '.', '!', '?', 'ü', 'Ü', 'ö', 'Ö', 'ä', 'Ä', ';', ':', '@', "'", '"', '&'), array('', '', '', '', 'ue', 'ue', 'oe', 'oe', 'ae', 'ae', '', '', '', '', '', 'and'), $physical_filename);
			$physical_filename = str_replace(array('$', 'ß', '>','<','§','%','=','/','(',')','#','*','+',"\\",'{','}','[',']'), array('dollar', 'ss','greater','lower','paragraph','percent','equal','','','','','','','','','','',''), $physical_filename);

			// Remove non-latin characters
			$physical_filename = preg_replace("/([\xC2\xC3])([\x80-\xBF])/e", "chr(ord('\\1')<<6&0xC0|ord('\\2')&0x3F)", $physical_filename);
			$physical_filename = rawurlencode($physical_filename);
			$physical_filename = preg_replace("/(%[0-9A-F]{1,2})/i", '', $physical_filename);
			$physical_filename = trim($physical_filename);
			if ( empty($physical_filename) )
			{
				$physical_filename = $u_id . '_' . $filetime;
			}
			$physical_filename .= ($this->filename_exists($physical_filename . '.' . $extension) ? '_' . $filetime : '') . '.' . $extension;
			$physical_filename = basename($physical_filename);
		}

		// attempt to upload the file
		if ( !$error )
		{
			$this->handler->open_channel();
			$dst_file = $this->handler->path($physical_filename);
			if ( !$this->handler->upload($file, $mimetype, $dst_file) )
			{
				_error($this->handler->error());
			}
			$this->handler->close_channel();
		}

		// attempt to create thumbnail if required
		if ( !$error && $thumbnail )
		{
			$src_file = ($this->handler->method == 'ftp') ? $file : $this->handler->path($physical_filename);
			$dst_file = $this->handler->path(THUMB_DIR . '/t_' . $physical_filename);
			if ( !empty($src_file) )
			{
				$pic_thumbnail = new thumbnail($this->handler, intval($attach_config->data['img_min_thumb_filesize']), trim($attach_config->data['img_imagick']), intval($attach_config->data['use_gd2']));
				$thumbnail = $pic_thumbnail->create($src_file, $dst_file);
				unset($pic_thumbnail);
			}
		}

		// End there if error
		if ( $error )
		{
			return false;
		}

		//
		// from now, the file has been uploaded into the file directory
		//

		// Now, check filesize parameters
		if ( !$filesize )
		{
			$this->open_channel();
			$dst_file = $this->handler->path($physical_filename);
			$filesize = $this->handler->get_filesize($dst_file);
			$this->close_channel();
		}

		// check Filesize
		if ( !$error && ($user->data['user_level'] != ADMIN) && ($allowed_filesize != 0) && ($filesize > $allowed_filesize) )
		{
			$units = array();
			$unit = size_get_units($allowed_filesize, $units);
			$allowed_filesize = round($allowed_filesize / $unit, 2);
			$size_lang = $units[$unit];
			_error(sprintf($user->lang('Attachment_too_big'), $allowed_filesize, $user->lang($size_lang)));
		}

		// Check Image Size, if it's an image
		if ( ($user->data['user_level'] != ADMIN) && ($cat_id == IMAGE_CAT) )
		{
			$dst_file = ($this->handler->method == 'ftp') ? $file : $this->handler->path($physical_filename);

			$image_standard = new image_standard($this->handler);
			list($width, $height) = $image_standard->image_size($dst_file);
			unset($image_standard);
			if ( ($width != 0) && ($height != 0) && (intval($attach_config->data['img_max_width']) != 0) && (intval($attach_config->data['img_max_height']) != 0) )
			{
				if ( ($width > intval($attach_config->data['img_max_width'])) || ($height > intval($attach_config->data['img_max_height'])) )
				{
					_error(sprintf($user->lang('Error_imagesize'), intval($attach_config->data['img_max_width']), intval($attach_config->data['img_max_height'])));
				}
			}
		}

		// Check our complete quota
		if ( !$error && $attach_config->data['attachment_quota'] )
		{
			$sql = 'SELECT SUM(filesize) AS sum_filesize
						FROM ' . ATT_DESC_TABLE;
			$result = $db->sql_query($sql, false, __LINE__, __FILE__);
			$total_filesize = ($row = $db->sql_fetchrow($result)) ? intval($row['sum_filesize']) : 0;
			$db->sql_freeresult($result);
			if ( ($total_filesize + $filesize) > intval($attach_config->data['attachment_quota']) )
			{
				_error('Attach_quota_reached');
			}
		}

		// Check our user(s) quota
		if ( !$error )
		{
			$this->check_user_quota($filesize);
		}

		// error occurs, break
		if ( $error )
		{
			$this->handler->open_channel();
			$this->handler->unlink($physical_filename, $thumbnail);
			$this->handler->close_channel();
			return false;
		}

		// all is done, no error, fill the result
		return array(
			'physical_filename' => $physical_filename,
			'real_filename' => $real_filename,
			'extension' => $extension,
			'mimetype' => $mimetype,
			'filesize' => $filesize,
			'filetime' => $filetime,
			'thumbnail' => $thumbnail,
		);
	}

	function filename_exists($filename)
	{
		global $db;

		if ( empty($filename) )
		{
			return false;
		}

		$filename = basename($filename);
		$sql = 'SELECT attach_id 
					FROM ' . ATT_DESC_TABLE . '
					WHERE physical_filename = \'' . $db->sql_escape_string($filename) . '\'
					LIMIT 1';
		$result = $db->sql_query($sql, false, __LINE__, __FILE__);
		$found = $db->sql_numrows($result);
		$db->sql_freeresult($result);

		return $found;
	}
}

?>