1<?php
2# MantisBT - A PHP based bugtracking system
3
4# MantisBT is free software: you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation, either version 2 of the License, or
7# (at your option) any later version.
8#
9# MantisBT is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with MantisBT.  If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * A webservice interface to Mantis Bug Tracker
19 *
20 * @package MantisBT
21 * @copyright Copyright 2004  Victor Boctor - vboctor@users.sourceforge.net
22 * @copyright Copyright 2005  MantisBT Team - mantisbt-dev@lists.sourceforge.net
23 * @link http://www.mantisbt.org
24 */
25
26/**
27 * Check if the current user can download attachments for the specified bug.
28 * @param integer $p_bug_id  A bug identifier.
29 * @param integer $p_user_id A user identifier.
30 * @return boolean
31 */
32function mci_file_can_download_bug_attachments( $p_bug_id, $p_user_id ) {
33	$t_can_download = access_has_bug_level( config_get( 'download_attachments_threshold' ), $p_bug_id );
34	if( $t_can_download ) {
35		return true;
36	}
37
38	$t_reported_by_me = bug_is_user_reporter( $p_bug_id, $p_user_id );
39	return( $t_reported_by_me && config_get( 'allow_download_own_attachments' ) );
40}
41
42/**
43 * Read a local file and return its content.
44 * @param string $p_diskfile Name of file on disk.
45 * @return string
46 */
47function mci_file_read_local( $p_diskfile ) {
48	$t_handle = fopen( $p_diskfile, 'r' );
49	$t_content = fread( $t_handle, filesize( $p_diskfile ) );
50	fclose( $t_handle );
51	return $t_content;
52}
53
54/**
55 * Write a local file.
56 * @param string $p_diskfile Name of file on disk.
57 * @param string $p_content  File content to write.
58 * @return void
59 */
60function mci_file_write_local( $p_diskfile, $p_content ) {
61	$t_handle = fopen( $p_diskfile, 'w' );
62	fwrite( $t_handle, $p_content );
63	fclose( $t_handle );
64}
65
66/**
67 * Add a file
68 * @param integer $p_id        File id.
69 * @param string  $p_name      File name.
70 * @param string  $p_content   File content to write.
71 * @param string  $p_file_type File type.
72 * @param string  $p_table     Database table name.
73 * @param string  $p_title     Title.
74 * @param string  $p_desc      Description.
75 * @param string  $p_user_id   User id.
76 * @return mixed
77 */
78function mci_file_add( $p_id, $p_name, $p_content, $p_file_type, $p_table, $p_title = '', $p_desc = '', $p_user_id = null ) {
79	if( !file_type_check( $p_name ) ) {
80		return ApiObjectFactory::faultForbidden( 'File type not allowed.' );
81	}
82
83	if( !file_is_name_unique( $p_name, $p_id ) ) {
84		return ApiObjectFactory::faultConflict( 'Duplicate filename.' );
85	}
86
87	$t_file_size = strlen( $p_content );
88	$t_max_file_size = file_get_max_file_size();
89	if( $t_file_size > $t_max_file_size ) {
90		return ApiObjectFactory::faultBadRequest( 'File is too big. Max size is "' . $t_max_file_size . '" bytes.' );
91	}
92
93	if( 'bug' == $p_table ) {
94		$t_project_id = bug_get_field( $p_id, 'project_id' );
95		$t_id = (int)$p_id;
96		$t_issue_id = bug_format_id( $p_id );
97	} else {
98		$t_project_id = $p_id;
99		$t_id = $t_project_id;
100		$t_issue_id = 0;
101	}
102
103	if( $p_user_id === null ) {
104		$p_user_id = auth_get_current_user_id();
105	}
106
107	if( $t_project_id == ALL_PROJECTS ) {
108		$t_file_path = config_get_global( 'absolute_path_default_upload_folder' );
109	} else {
110		$t_file_path = project_get_field( $t_project_id, 'file_path' );
111		if( is_blank( $t_file_path ) ) {
112			$t_file_path = config_get_global( 'absolute_path_default_upload_folder' );
113		}
114	}
115
116	$t_unique_name = file_generate_unique_name( $t_file_path );
117	$t_disk_file_name = $t_file_path . $t_unique_name;
118
119	$t_method = config_get( 'file_upload_method' );
120
121	switch( $t_method ) {
122		case DISK:
123			if( !file_exists( $t_file_path ) || !is_dir( $t_file_path ) || !is_writable( $t_file_path ) || !is_readable( $t_file_path ) ) {
124				return ApiObjectFactory::faultServerError( "Upload folder doesn't exist." );
125			}
126
127			file_ensure_valid_upload_path( $t_file_path );
128
129			if( !file_exists( $t_disk_file_name ) ) {
130				mci_file_write_local( $t_disk_file_name, $p_content );
131				chmod( $t_disk_file_name, config_get( 'attachments_file_permissions' ) );
132				$c_content = "''";
133			}
134			break;
135		case DATABASE:
136			$c_content = db_prepare_binary_string( $p_content );
137			$t_file_path = '';
138			break;
139	}
140
141	$t_file_table = db_get_table( $p_table . '_file' );
142	$t_id_col = $p_table . '_id';
143
144	$t_param = array(
145		$t_id_col     => $t_id,
146		'title'       => $p_title,
147		'description' => $p_desc,
148		'diskfile'    => $t_unique_name,
149		'filename'    => $p_name,
150		'folder'      => $t_file_path,
151		'filesize'    => $t_file_size,
152		'file_type'   => $p_file_type,
153		'date_added'  => db_now(),
154		'user_id'     => (int)$p_user_id,
155	);
156	# Oracle has to update BLOBs separately
157	if( !db_is_oracle() ) {
158		$t_param['content'] = $c_content;
159	}
160	$t_query_param = db_param();
161	for( $i = 1; $i < count( $t_param ); $i++ ) {
162		$t_query_param .= ', ' . db_param();
163	}
164
165	$t_query = 'INSERT INTO ' . $t_file_table . '
166		( ' . implode( ', ', array_keys( $t_param ) ) . ' )
167	VALUES
168		( ' . $t_query_param . ' )';
169	db_query( $t_query, array_values( $t_param ) );
170
171	# get attachment id
172	$t_attachment_id = db_insert_id( $t_file_table );
173
174	if( db_is_oracle() ) {
175		db_update_blob( $t_file_table, 'content', $c_content, "diskfile='$t_unique_name'" );
176	}
177
178	if( 'bug' == $p_table ) {
179		# bump the last_updated date
180		bug_update_date( $t_issue_id );
181
182		# add history entry
183		history_log_event_special( $t_issue_id, FILE_ADDED, $p_name );
184	}
185
186	return $t_attachment_id;
187}
188
189/**
190 * Returns the attachment contents
191 *
192 * @param integer $p_file_id File identifier.
193 * @param string  $p_type    The file type, bug or doc.
194 * @param integer $p_user_id A valid user identifier.
195 * @return string|soap_fault the string contents, or a soap_fault
196 */
197function mci_file_get( $p_file_id, $p_type, $p_user_id ) {
198	# we handle the case where the file is attached to a bug
199	# or attached to a project as a project doc.
200	$t_query = '';
201	switch( $p_type ) {
202		case 'bug':
203			$t_query = 'SELECT * FROM {bug_file} WHERE id=' . db_param();
204			break;
205		case 'doc':
206			$t_query = 'SELECT * FROM {project_file} WHERE id=' . db_param();
207			break;
208		default:
209			return ApiObjectFactory::faultServerError( 'Invalid file type '. $p_type . ' .' );
210	}
211
212	$t_result = db_query( $t_query, array( $p_file_id ) );
213
214	if( $t_result->EOF ) {
215		return ApiObjectFactory::faultNotFound( 'Unable to find an attachment with type ' . $p_type . ' and id ' .
216			$p_file_id . '.' );
217	}
218
219	$t_row = db_fetch_array( $t_result );
220
221	if( $p_type == 'doc' ) {
222		$t_project_id = $t_row['project_id'];
223	} else if( $p_type == 'bug' ) {
224		$t_bug_id = $t_row['bug_id'];
225		$t_project_id = bug_get_field( $t_bug_id, 'project_id' );
226	}
227
228	$t_diskfile = file_normalize_attachment_path( $t_row['diskfile'], $t_project_id );
229	$t_content = $t_row['content'];
230
231	# Check access rights
232	switch( $p_type ) {
233		case 'bug':
234			if( !mci_file_can_download_bug_attachments( $t_bug_id, $p_user_id ) ) {
235				return mci_fault_access_denied( $p_user_id );
236			}
237			break;
238		case 'doc':
239			# Check if project documentation feature is enabled.
240			if( OFF == config_get( 'enable_project_documentation' ) ) {
241				return mci_fault_access_denied( $p_user_id );
242			}
243			if( !access_has_project_level( config_get( 'view_proj_doc_threshold' ), $t_project_id, $p_user_id ) ) {
244				return mci_fault_access_denied( $p_user_id );
245			}
246			break;
247	}
248
249	# dump file content to the connection.
250	switch( config_get( 'file_upload_method' ) ) {
251		case DISK:
252			if( file_exists( $t_diskfile ) ) {
253				return mci_file_read_local( $t_diskfile ) ;
254			} else {
255				return ApiObjectFactory::faultNotFound( 'Unable to find an attachment with type ' . $p_type .
256					' and id ' . $p_file_id . '.' );
257			}
258		case DATABASE:
259			return $t_content;
260		default:
261			trigger_error( ERROR_GENERIC, ERROR );
262	}
263}
264