Commit | Line | Data |
---|---|---|
56154d36 | 1 | <?php |
56154d36 TO |
2 | /* |
3 | +--------------------------------------------------------------------+ | |
39de6fd5 | 4 | | CiviCRM version 4.6 | |
56154d36 TO |
5 | +--------------------------------------------------------------------+ |
6 | | Copyright CiviCRM LLC (c) 2004-2014 | | |
7 | +--------------------------------------------------------------------+ | |
8 | | This file is a part of CiviCRM. | | |
9 | | | | |
10 | | CiviCRM is free software; you can copy, modify, and distribute it | | |
11 | | under the terms of the GNU Affero General Public License | | |
12 | | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. | | |
13 | | | | |
14 | | CiviCRM is distributed in the hope that it will be useful, but | | |
15 | | WITHOUT ANY WARRANTY; without even the implied warranty of | | |
16 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | | |
17 | | See the GNU Affero General Public License for more details. | | |
18 | | | | |
19 | | You should have received a copy of the GNU Affero General Public | | |
20 | | License and the CiviCRM Licensing Exception along | | |
21 | | with this program; if not, contact CiviCRM LLC | | |
22 | | at info[AT]civicrm[DOT]org. If you have questions about the | | |
23 | | GNU Affero General Public License or the licensing of CiviCRM, | | |
24 | | see the CiviCRM license FAQ at http://civicrm.org/licensing | | |
25 | +--------------------------------------------------------------------+ | |
26 | */ | |
27 | ||
28 | /** | |
29 | * "Attachment" is a pseudo-entity which represents a record in civicrm_file | |
30 | * combined with a record in civicrm_entity_file as well as the underlying | |
31 | * file content. | |
32 | * | |
33 | * @code | |
34 | * $result = civicrm_api3('Attachment', 'create', array( | |
35 | * 'entity_table' => 'civicrm_activity', | |
36 | * 'entity_id' => 123, | |
37 | * 'name' => 'README.txt', | |
38 | * 'mime_type' => 'text/plain', | |
39 | * 'content' => 'Please to read the README', | |
40 | * )); | |
41 | * $attachment = $result['values'][$result['id']]; | |
42 | * echo sprintf("<a href='%s'>View %s</a>", $attachment['url'], $attachment['name']); | |
43 | * @endcode | |
44 | * | |
45 | * @code | |
46 | * $result = civicrm_api3('Attachment', 'create', array( | |
47 | * 'entity_table' => 'civicrm_activity', | |
48 | * 'entity_id' => 123, | |
49 | * 'name' => 'README.txt', | |
50 | * 'mime_type' => 'text/plain', | |
51 | * 'options' => array( | |
52 | * 'move-file' => '/tmp/upload1a2b3c4d', | |
53 | * ), | |
54 | * )); | |
55 | * $attachment = $result['values'][$result['id']]; | |
56 | * echo sprintf("<a href='%s'>View %s</a>", $attachment['url'], $attachment['name']); | |
57 | * @endcode | |
58 | * | |
59 | * Notes: | |
60 | * - File content is not returned by default. One must specify 'return => content'. | |
61 | * - Features which deal with local file system (e.g. passing "options.move-file" | |
62 | * or returning a "path") are only valid when executed as a local API (ie | |
63 | * "check_permissions"==false) | |
64 | * | |
65 | * @package CiviCRM_APIv3 | |
66 | * @subpackage API_Attachment | |
67 | * @copyright CiviCRM LLC (c) 2004-2014 | |
68 | * $Id: $ | |
69 | * | |
70 | */ | |
71 | ||
72 | /** | |
73 | * Adjust metadata for "create" action | |
74 | * | |
cf470720 TO |
75 | * @param array $spec |
76 | * List of fields. | |
56154d36 TO |
77 | */ |
78 | function _civicrm_api3_attachment_create_spec(&$spec) { | |
79 | $spec = array_merge($spec, _civicrm_api3_attachment_getfields()); | |
80 | $spec['name']['api.required'] = 1; | |
81 | $spec['mime_type']['api.required'] = 1; | |
82 | $spec['entity_table']['api.required'] = 1; | |
83 | $spec['entity_id']['api.required'] = 1; | |
84 | $spec['upload_date']['api.default'] = 'now'; | |
85 | } | |
86 | ||
87 | /** | |
88 | * Create an attachment | |
89 | * | |
cf470720 | 90 | * @param array $params |
a6c01b45 | 91 | * @return array |
16b10e64 | 92 | * Array of newly created file property values. |
56154d36 TO |
93 | * @throws API_Exception validation errors |
94 | */ | |
95 | function civicrm_api3_attachment_create($params) { | |
96 | $config = CRM_Core_Config::singleton(); | |
97 | list($id, $file, $entityFile, $name, $content, $moveFile, $isTrusted, $returnContent) = _civicrm_api3_attachment_parse_params($params); | |
98 | ||
99 | $fileDao = new CRM_Core_BAO_File(); | |
100 | $entityFileDao = new CRM_Core_DAO_EntityFile(); | |
101 | ||
102 | if ($id) { | |
103 | $fileDao->id = $id; | |
104 | if (!$fileDao->find(TRUE)) { | |
105 | throw new API_Exception("Invalid ID"); | |
106 | } | |
107 | ||
108 | $entityFileDao->file_id = $id; | |
109 | if (!$entityFileDao->find(TRUE)) { | |
110 | throw new API_Exception("Cannot modify orphaned file"); | |
111 | } | |
112 | } | |
113 | ||
114 | if (!$id && !is_string($content) && !is_string($moveFile)) { | |
115 | throw new API_Exception("Mandatory key(s) missing from params array: 'id' or 'content' or 'options.move-file'"); | |
116 | } | |
117 | if (!$isTrusted && $moveFile) { | |
118 | throw new API_Exception("options.move-file is only supported on secure calls"); | |
119 | } | |
120 | if (is_string($content) && is_string($moveFile)) { | |
121 | throw new API_Exception("'content' and 'options.move-file' are mutually exclusive"); | |
122 | } | |
123 | if ($id && !$isTrusted && isset($file['upload_date']) && $file['upload_date'] != CRM_Utils_Date::isoToMysql($fileDao->upload_date)) { | |
124 | throw new API_Exception("Cannot modify upload_date" . var_export(array($file['upload_date'], $fileDao->upload_date, CRM_Utils_Date::isoToMysql($fileDao->upload_date)), TRUE)); | |
125 | } | |
126 | if ($id && $name && $name != CRM_Utils_File::cleanFileName($fileDao->uri)) { | |
127 | throw new API_Exception("Cannot modify name"); | |
128 | } | |
129 | ||
130 | $fileDao->copyValues($file); | |
131 | if (!$id) { | |
132 | $fileDao->uri = CRM_Utils_File::makeFileName($name); | |
133 | } | |
134 | $fileDao->save(); | |
135 | ||
136 | $entityFileDao->copyValues($entityFile); | |
137 | $entityFileDao->file_id = $fileDao->id; | |
138 | $entityFileDao->save(); | |
139 | ||
140 | $path = $config->customFileUploadDir . DIRECTORY_SEPARATOR . $fileDao->uri; | |
141 | if (is_string($content)) { | |
142 | file_put_contents($path, $content); | |
143 | } | |
144 | elseif (is_string($moveFile)) { | |
145 | rename($moveFile, $path); | |
146 | } | |
147 | ||
148 | $result = array( | |
21dfd5f5 | 149 | $fileDao->id => _civicrm_api3_attachment_format_result($fileDao, $entityFileDao, $returnContent, $isTrusted), |
56154d36 TO |
150 | ); |
151 | return civicrm_api3_create_success($result, $params, 'Attachment', 'create'); | |
152 | } | |
153 | ||
154 | /** | |
155 | * Adjust metadata for "create" action | |
156 | * | |
cf470720 TO |
157 | * @param array $spec |
158 | * List of fields. | |
56154d36 TO |
159 | */ |
160 | function _civicrm_api3_attachment_get_spec(&$spec) { | |
161 | $spec = array_merge($spec, _civicrm_api3_attachment_getfields()); | |
162 | } | |
163 | ||
164 | /** | |
165 | * @param array $params | |
a6c01b45 | 166 | * @return array |
72b3a70c | 167 | * per APIv3 |
56154d36 TO |
168 | * @throws API_Exception validation errors |
169 | */ | |
170 | function civicrm_api3_attachment_get($params) { | |
171 | list($id, $file, $entityFile, $name, $content, $moveFile, $isTrusted, $returnContent) = _civicrm_api3_attachment_parse_params($params); | |
172 | ||
173 | $dao = __civicrm_api3_attachment_find($params, $id, $file, $entityFile, $isTrusted); | |
174 | $result = array(); | |
175 | while ($dao->fetch()) { | |
176 | $result[$dao->id] = _civicrm_api3_attachment_format_result($dao, $dao, $returnContent, $isTrusted); | |
177 | } | |
178 | return civicrm_api3_create_success($result, $params, 'Attachment', 'create'); | |
179 | } | |
180 | ||
24a70b66 EM |
181 | /** |
182 | * @param $spec | |
183 | */ | |
56154d36 TO |
184 | function _civicrm_api3_attachment_delete_spec(&$spec) { |
185 | unset($spec['id']['api.required']); | |
186 | $entityFileFields = CRM_Core_DAO_EntityFile::fields(); | |
187 | $spec['entity_table'] = $entityFileFields['entity_table']; | |
188 | $spec['entity_table']['title'] = CRM_Utils_Array::value('title', $spec['entity_table'], 'Entity Table') . ' (write-once)'; | |
189 | $spec['entity_id'] = $entityFileFields['entity_id']; | |
190 | $spec['entity_id']['title'] = CRM_Utils_Array::value('title', $spec['entity_id'], 'Entity ID') . ' (write-once)'; | |
191 | } | |
192 | ||
193 | /** | |
16b10e64 | 194 | * @param array $params |
56154d36 TO |
195 | * @return array |
196 | * @throws API_Exception | |
197 | */ | |
198 | function civicrm_api3_attachment_delete($params) { | |
199 | if (!empty($params['id'])) { | |
200 | // ok | |
201 | } | |
202 | elseif (!empty($params['entity_table']) && !empty($params['entity_id'])) { | |
203 | // ok | |
204 | } | |
205 | else { | |
206 | throw new API_Exception("Mandatory key(s) missing from params array: id or entity_table+entity_table"); | |
207 | } | |
208 | ||
209 | $config = CRM_Core_Config::singleton(); | |
210 | list($id, $file, $entityFile, $name, $content, $moveFile, $isTrusted, $returnContent) = _civicrm_api3_attachment_parse_params($params); | |
211 | $dao = __civicrm_api3_attachment_find($params, $id, $file, $entityFile, $isTrusted); | |
212 | ||
213 | $filePaths = array(); | |
214 | $fileIds = array(); | |
215 | while ($dao->fetch()) { | |
35671d00 | 216 | $filePaths[] = $config->customFileUploadDir . DIRECTORY_SEPARATOR . $dao->uri; |
56154d36 TO |
217 | $fileIds[] = $dao->id; |
218 | } | |
219 | ||
220 | if (!empty($fileIds)) { | |
221 | $idString = implode(',', array_filter($fileIds, 'is_numeric')); | |
222 | CRM_Core_DAO::executeQuery("DELETE FROM civicrm_entity_file WHERE file_id in ($idString)"); | |
223 | CRM_Core_DAO::executeQuery("DELETE FROM civicrm_file WHERE id in ($idString)"); | |
224 | } | |
225 | ||
226 | // unlink is non-transactional, so we do this as the last step -- just in case the other steps produce errors | |
227 | if (!empty($filePaths)) { | |
228 | foreach ($filePaths as $filePath) { | |
229 | unlink($filePath); | |
230 | } | |
231 | } | |
232 | ||
233 | $result = array(); | |
234 | return civicrm_api3_create_success($result, $params, 'Attachment', 'create'); | |
235 | } | |
236 | ||
237 | /** | |
238 | * @param array $params | |
239 | * @param int|null $id the user-supplied ID of the attachment record | |
cf470720 TO |
240 | * @param array $file |
241 | * The user-supplied vales for the file (mime_type, description, upload_date). | |
242 | * @param array $entityFile | |
906e6120 | 243 | * The user-supplied values of the entity-file (entity_table, entity_id). |
56154d36 TO |
244 | * @param bool $isTrusted |
245 | * @return CRM_Core_DAO | |
246 | * @throws API_Exception | |
247 | */ | |
248 | function __civicrm_api3_attachment_find($params, $id, $file, $entityFile, $isTrusted) { | |
249 | foreach (array('name', 'content', 'path', 'url') as $unsupportedFilter) { | |
250 | if (!empty($params[$unsupportedFilter])) { | |
251 | throw new API_Exception("Get by $unsupportedFilter is not currently supported"); | |
252 | } | |
253 | } | |
254 | ||
255 | $select = CRM_Utils_SQL_Select::from('civicrm_file cf') | |
256 | ->join('cef', 'INNER JOIN civicrm_entity_file cef ON cf.id = cef.file_id') | |
257 | ->select(array( | |
258 | 'cf.id', | |
259 | 'cf.uri', | |
260 | 'cf.mime_type', | |
261 | 'cf.description', | |
262 | 'cf.upload_date', | |
263 | 'cef.entity_table', | |
264 | 'cef.entity_id', | |
265 | )); | |
266 | ||
267 | if ($id) { | |
268 | $select->where('cf.id = #id', array('#id' => $id)); | |
269 | } | |
270 | // recall: $file is filtered by parse_params | |
271 | foreach ($file as $key => $value) { | |
272 | $select->where('cf.!field = @value', array( | |
273 | '!field' => $key, | |
274 | '@value' => $value, | |
275 | )); | |
276 | } | |
277 | // recall: $entityFile is filtered by parse_params | |
278 | foreach ($entityFile as $key => $value) { | |
279 | $select->where('cef.!field = @value', array( | |
280 | '!field' => $key, | |
281 | '@value' => $value, | |
282 | )); | |
283 | } | |
284 | if (!$isTrusted) { | |
285 | // FIXME ACLs: Add any JOIN or WHERE clauses needed to enforce access-controls for the target entity. | |
286 | // | |
287 | // The target entity is identified by "cef.entity_table" (aka $entityFile['entity_table']) and "cef.entity_id". | |
288 | // | |
289 | // As a simplification, we *require* the "get" actions to filter on a single "entity_table" which should | |
290 | // avoid the complexity of matching ACL's against multiple entity types. | |
291 | } | |
292 | ||
293 | $dao = CRM_Core_DAO::executeQuery($select->toSQL()); | |
294 | return $dao; | |
295 | } | |
296 | ||
297 | /** | |
298 | * @param array $params | |
bed98343 | 299 | * @return array (0 => int $id, 1 => array $file, 2 => array $entityFile, 3 => string $name, 4 => string $content, |
300 | * 5 => string $moveFile, 6 => $isTrusted, 7 => bool $returnContent) | |
301 | * - array $file: whitelisted fields that can pass through directly to civicrm_file | |
302 | * - array $entityFile: whitelisted fields that can pass through directly to civicrm_entity_file | |
303 | * - string $name: the printable name | |
304 | * - string $moveFile: the full path to a local file whose content should be loaded | |
305 | * - bool $isTrusted: whether we trust the requester to do sketchy things (like moving files or reassigning entities) | |
306 | * - bool $returnContent: whether we are expected to return the full content of the file | |
56154d36 TO |
307 | * @throws API_Exception validation errors |
308 | */ | |
309 | function _civicrm_api3_attachment_parse_params($params) { | |
310 | $id = CRM_Utils_Array::value('id', $params, NULL); | |
311 | if ($id && !is_numeric($id)) { | |
312 | throw new API_Exception("Malformed id"); | |
313 | } | |
314 | ||
315 | $file = array(); | |
316 | foreach (array('mime_type', 'description', 'upload_date') as $field) { | |
317 | if (array_key_exists($field, $params)) { | |
318 | $file[$field] = $params[$field]; | |
319 | } | |
320 | } | |
321 | ||
322 | $entityFile = array(); | |
323 | foreach (array('entity_table', 'entity_id') as $field) { | |
324 | if (array_key_exists($field, $params)) { | |
325 | $entityFile[$field] = $params[$field]; | |
326 | } | |
327 | } | |
328 | ||
329 | $name = NULL; | |
330 | if (array_key_exists('name', $params)) { | |
331 | if ($params['name'] != basename($params['name']) || preg_match(':[/\\\\]:', $params['name'])) { | |
332 | throw new API_Exception('Malformed name'); | |
333 | } | |
334 | $name = $params['name']; | |
335 | } | |
336 | ||
337 | $content = NULL; | |
338 | if (isset($params['content'])) { | |
339 | $content = $params['content']; | |
340 | } | |
341 | ||
342 | $moveFile = NULL; | |
343 | if (isset($params['options']['move-file'])) { | |
344 | $moveFile = $params['options']['move-file']; | |
345 | } | |
346 | elseif (isset($params['options.move-file'])) { | |
347 | $moveFile = $params['options.move-file']; | |
348 | } | |
349 | ||
350 | $isTrusted = empty($params['check_permissions']); | |
351 | ||
352 | $returns = isset($params['return']) ? $params['return'] : array(); | |
353 | $returns = is_array($returns) ? $returns : array($returns); | |
354 | $returnContent = in_array('content', $returns); | |
355 | ||
356 | return array($id, $file, $entityFile, $name, $content, $moveFile, $isTrusted, $returnContent); | |
357 | } | |
358 | ||
359 | /** | |
cf470720 TO |
360 | * @param CRM_Core_DAO_File $fileDao |
361 | * Maybe "File" or "File JOIN EntityFile". | |
362 | * @param CRM_Core_DAO_EntityFile $entityFileDao | |
363 | * Maybe "EntityFile" or "File JOIN EntityFile". | |
364 | * @param bool $returnContent | |
365 | * Whether to return the full content of the file. | |
366 | * @param bool $isTrusted | |
367 | * Whether the current request is trusted to perform file-specific operations. | |
56154d36 TO |
368 | * @return array |
369 | */ | |
370 | function _civicrm_api3_attachment_format_result($fileDao, $entityFileDao, $returnContent, $isTrusted) { | |
371 | $config = CRM_Core_Config::singleton(); | |
372 | $path = $config->customFileUploadDir . DIRECTORY_SEPARATOR . $fileDao->uri; | |
373 | ||
374 | $result = array( | |
375 | 'id' => $fileDao->id, | |
376 | 'name' => CRM_Utils_File::cleanFileName($fileDao->uri), | |
377 | 'mime_type' => $fileDao->mime_type, | |
378 | 'description' => $fileDao->description, | |
379 | 'upload_date' => is_numeric($fileDao->upload_date) ? CRM_Utils_Date::mysqlToIso($fileDao->upload_date) : $fileDao->upload_date, | |
380 | 'entity_table' => $entityFileDao->entity_table, | |
381 | 'entity_id' => $entityFileDao->entity_id, | |
382 | ); | |
383 | $result['url'] = CRM_Utils_System::url( | |
384 | 'civicrm/file', 'reset=1&id=' . $result['id'] . '&eid=' . $result['entity_id'], | |
385 | TRUE, | |
386 | NULL, | |
387 | FALSE, | |
388 | TRUE | |
389 | ); | |
390 | if ($isTrusted) { | |
391 | $result['path'] = $path; | |
392 | } | |
393 | if ($returnContent) { | |
394 | $result['content'] = file_get_contents($path); | |
395 | } | |
396 | return $result; | |
397 | } | |
398 | ||
399 | /** | |
a6c01b45 | 400 | * @return array |
72b3a70c | 401 | * list of fields (indexed by name) |
56154d36 TO |
402 | */ |
403 | function _civicrm_api3_attachment_getfields() { | |
404 | $fileFields = CRM_Core_DAO_File::fields(); | |
405 | $entityFileFields = CRM_Core_DAO_EntityFile::fields(); | |
406 | ||
407 | $spec = array(); | |
408 | $spec['id'] = $fileFields['id']; | |
409 | $spec['name'] = array( | |
410 | 'title' => 'Name (write-once)', | |
411 | 'description' => 'The logical file name (not searchable)', | |
412 | 'type' => CRM_Utils_Type::T_STRING, | |
413 | ); | |
414 | $spec['mime_type'] = $fileFields['mime_type']; | |
415 | $spec['description'] = $fileFields['description']; | |
416 | $spec['upload_date'] = $fileFields['upload_date']; | |
417 | $spec['entity_table'] = $entityFileFields['entity_table']; | |
418 | $spec['entity_table']['title'] = CRM_Utils_Array::value('title', $spec['entity_table'], 'Entity Table') . ' (write-once)'; // would be hard to securely handle changes | |
419 | $spec['entity_id'] = $entityFileFields['entity_id']; | |
420 | $spec['entity_id']['title'] = CRM_Utils_Array::value('title', $spec['entity_id'], 'Entity ID') . ' (write-once)'; // would be hard to securely handle changes | |
421 | $spec['url'] = array( | |
422 | 'title' => 'URL (read-only)', | |
423 | 'description' => 'URL for downloading the file (not searchable, expire-able)', | |
424 | 'type' => CRM_Utils_Type::T_STRING, | |
425 | ); | |
426 | $spec['path'] = array( | |
427 | 'title' => 'Path (read-only)', | |
428 | 'description' => 'Local file path (not searchable, local-only)', | |
429 | 'type' => CRM_Utils_Type::T_STRING, | |
430 | ); | |
431 | $spec['content'] = array( | |
432 | 'title' => 'Content', | |
433 | 'description' => 'File content (not searchable, not returned by default)', | |
434 | 'type' => CRM_Utils_Type::T_STRING, | |
435 | ); | |
436 | ||
437 | return $spec; | |
438 | } |