fix email processor dropping attachments
[civicrm-core.git] / CRM / Core / BAO / File.php
CommitLineData
6a488035 1<?php
0c56e4c8
TO
2/*
3 +--------------------------------------------------------------------+
fee14197 4 | CiviCRM version 5 |
0c56e4c8 5 +--------------------------------------------------------------------+
6b83d5bd 6 | Copyright CiviCRM LLC (c) 2004-2019 |
0c56e4c8
TO
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 +--------------------------------------------------------------------+
d25dd0ee 26 */
0c56e4c8
TO
27
28/**
29 *
30 * @package CRM
6b83d5bd 31 * @copyright CiviCRM LLC (c) 2004-2019
0c56e4c8
TO
32 * $Id$
33 *
6a488035
TO
34 */
35
0c56e4c8
TO
36/**
37 * BAO object for crm_log table
38 */
39class CRM_Core_BAO_File extends CRM_Core_DAO_File {
40
518fa0ee 41 public static $_signableFields = ['entityTable', 'entityID', 'fileID'];
0c56e4c8 42
8913e915
D
43 /**
44 * If there is no setting configured on the admin screens, maximum number
45 * of attachments to try to process when given a list of attachments to
46 * process.
47 */
48 const DEFAULT_MAX_ATTACHMENTS_BACKEND = 100;
49
ae2c7e00 50 /**
51 * Takes an associative array and creates a File object.
52 *
53 * @param array $params
54 * (reference ) an assoc array of name/value pairs.
55 *
56 * @return CRM_Core_BAO_File
57 */
58 public static function create($params) {
59 $fileDAO = new CRM_Core_DAO_File();
60
61 $op = empty($params['id']) ? 'create' : 'edit';
62
63 CRM_Utils_Hook::pre($op, 'File', CRM_Utils_Array::value('id', $params), $params);
64
65 $fileDAO->copyValues($params);
66
67 if (empty($params['id']) && empty($params['created_id'])) {
68 $fileDAO->created_id = CRM_Core_Session::getLoggedInContactID();
69 }
70
71 $fileDAO->save();
72
73 CRM_Utils_Hook::post($op, 'File', $fileDAO->id, $fileDAO);
74
75 return $fileDAO;
76 }
77
0c56e4c8 78 /**
100fef9d
CW
79 * @param int $fileID
80 * @param int $entityID
0c56e4c8
TO
81 *
82 * @return array
83 */
9bbc34f1 84 public static function path($fileID, $entityID) {
0c56e4c8 85 $entityFileDAO = new CRM_Core_DAO_EntityFile();
0c56e4c8
TO
86 $entityFileDAO->entity_id = $entityID;
87 $entityFileDAO->file_id = $fileID;
88
89 if ($entityFileDAO->find(TRUE)) {
90 $fileDAO = new CRM_Core_DAO_File();
91 $fileDAO->id = $fileID;
92 if ($fileDAO->find(TRUE)) {
93 $config = CRM_Core_Config::singleton();
94 $path = $config->customFileUploadDir . $fileDAO->uri;
95
96 if (file_exists($path) && is_readable($path)) {
be2fb01f 97 return [$path, $fileDAO->mime_type];
0c56e4c8
TO
98 }
99 }
100 }
101
be2fb01f 102 return [NULL, NULL];
0c56e4c8
TO
103 }
104
0c56e4c8
TO
105 /**
106 * @param $data
100fef9d 107 * @param int $fileTypeID
0c56e4c8 108 * @param $entityTable
100fef9d 109 * @param int $entityID
0c56e4c8
TO
110 * @param $entitySubtype
111 * @param bool $overwrite
75fd1335 112 * @param null|array $fileParams
0c56e4c8
TO
113 * @param string $uploadName
114 * @param null $mimeType
115 *
116 * @throws Exception
117 */
ae5ffbb7 118 public static function filePostProcess(
0c56e4c8
TO
119 $data,
120 $fileTypeID,
121 $entityTable,
122 $entityID,
123 $entitySubtype,
124 $overwrite = TRUE,
125 $fileParams = NULL,
126 $uploadName = 'uploadFile',
127 $mimeType = NULL
128 ) {
129 if (!$mimeType) {
b93376a8 130 CRM_Core_Error::statusBounce(ts('Mime Type is now a required parameter for file upload'));
0c56e4c8
TO
131 }
132
133 $config = CRM_Core_Config::singleton();
134
135 $path = explode('/', $data);
136 $filename = $path[count($path) - 1];
137
138 // rename this file to go into the secure directory
139 if ($entitySubtype) {
140 $directoryName = $config->customFileUploadDir . $entitySubtype . DIRECTORY_SEPARATOR . $entityID;
141 }
142 else {
143 $directoryName = $config->customFileUploadDir;
144 }
145
146 CRM_Utils_File::createDir($directoryName);
147
148 if (!rename($data, $directoryName . DIRECTORY_SEPARATOR . $filename)) {
b93376a8 149 CRM_Core_Error::statusBounce(ts('Could not move custom file to custom upload directory'));
0c56e4c8
TO
150 }
151
152 // to get id's
153 if ($overwrite && $fileTypeID) {
154 list($sql, $params) = self::sql($entityTable, $entityID, $fileTypeID);
155 }
156 else {
157 list($sql, $params) = self::sql($entityTable, $entityID, 0);
158 }
159
160 $dao = CRM_Core_DAO::executeQuery($sql, $params);
161 $dao->fetch();
162
163 $fileDAO = new CRM_Core_DAO_File();
164 $op = 'create';
165 if (isset($dao->cfID) && $dao->cfID) {
166 $op = 'edit';
167 $fileDAO->id = $dao->cfID;
168 unlink($directoryName . DIRECTORY_SEPARATOR . $dao->uri);
169 }
170
171 if (!empty($fileParams)) {
172 $fileDAO->copyValues($fileParams);
173 }
174
175 $fileDAO->uri = $filename;
176 $fileDAO->mime_type = $mimeType;
177 $fileDAO->file_type_id = $fileTypeID;
77ca21d6 178 $fileDAO->upload_date = date('YmdHis');
0c56e4c8
TO
179 $fileDAO->save();
180
181 // need to add/update civicrm_entity_file
182 $entityFileDAO = new CRM_Core_DAO_EntityFile();
183 if (isset($dao->cefID) && $dao->cefID) {
184 $entityFileDAO->id = $dao->cefID;
185 }
186 $entityFileDAO->entity_table = $entityTable;
187 $entityFileDAO->entity_id = $entityID;
188 $entityFileDAO->file_id = $fileDAO->id;
189 $entityFileDAO->save();
190
191 //save static tags
192 if (!empty($fileParams['tag'])) {
193 CRM_Core_BAO_EntityTag::create($fileParams['tag'], 'civicrm_file', $entityFileDAO->id);
194 }
195
196 //save free tags
197 if (isset($fileParams['attachment_taglist']) && !empty($fileParams['attachment_taglist'])) {
1273d77c 198 CRM_Core_Form_Tag::postProcess($fileParams['attachment_taglist'], $entityFileDAO->id, 'civicrm_file');
0c56e4c8
TO
199 }
200
201 // lets call the post hook here so attachments code can do the right stuff
202 CRM_Utils_Hook::post($op, 'File', $fileDAO->id, $fileDAO);
203 }
204
205 /**
ad37ac8e 206 * A static function wrapper that deletes the various objects.
207 *
208 * Objects are those hat are connected to a file object (i.e. file, entityFile and customValue.
209 *
210 * @param int $fileID
211 * @param int $entityID
212 * @param int $fieldID
213 *
214 * @throws \Exception
0c56e4c8
TO
215 */
216 public static function deleteFileReferences($fileID, $entityID, $fieldID) {
217 $fileDAO = new CRM_Core_DAO_File();
218 $fileDAO->id = $fileID;
219 if (!$fileDAO->find(TRUE)) {
eea8ec44 220 throw new CRM_Core_Exception(ts('File not found'));
0c56e4c8
TO
221 }
222
223 // lets call a pre hook before the delete, so attachments hooks can get the info before things
224 // disappear
225 CRM_Utils_Hook::pre('delete', 'File', $fileID, $fileDAO);
226
227 // get the table and column name
228 list($tableName, $columnName, $groupID) = CRM_Core_BAO_CustomField::getTableColumnGroup($fieldID);
229
230 $entityFileDAO = new CRM_Core_DAO_EntityFile();
231 $entityFileDAO->file_id = $fileID;
232 $entityFileDAO->entity_id = $entityID;
233 $entityFileDAO->entity_table = $tableName;
234
235 if (!$entityFileDAO->find(TRUE)) {
eea8ec44 236 throw new CRM_Core_Exception(sprintf('No record found for given file ID - %d and entity ID - %d', $fileID, $entityID));
0c56e4c8
TO
237 }
238
239 $entityFileDAO->delete();
240 $fileDAO->delete();
241
242 // also set the value to null of the table and column
243 $query = "UPDATE $tableName SET $columnName = null WHERE $columnName = %1";
be2fb01f 244 $params = [1 => [$fileID, 'Integer']];
0c56e4c8
TO
245 CRM_Core_DAO::executeQuery($query, $params);
246 }
247
248 /**
249 * The $useWhere is used so that the signature matches the parent class
0527cd81 250 *
353ffa53
TO
251 * public function delete($useWhere = FALSE) {
252 * list($fileID, $entityID, $fieldID) = func_get_args();
253 *
254 * self::deleteFileReferences($fileID, $entityID, $fieldID);
255 * } */
0c56e4c8
TO
256
257 /**
ad37ac8e 258 * Delete all the files and associated object associated with this combination.
259 *
260 * @param string $entityTable
261 * @param int $entityID
262 * @param int $fileTypeID
263 * @param int $fileID
264 *
7b8bc7a1
SB
265 * @return bool
266 * Was file deleted?
0c56e4c8 267 */
00be9182 268 public static function deleteEntityFile($entityTable, $entityID, $fileTypeID = NULL, $fileID = NULL) {
7b8bc7a1 269 $isDeleted = FALSE;
0c56e4c8 270 if (empty($entityTable) || empty($entityID)) {
7b8bc7a1 271 return $isDeleted;
0c56e4c8
TO
272 }
273
274 $config = CRM_Core_Config::singleton();
275
276 list($sql, $params) = self::sql($entityTable, $entityID, $fileTypeID, $fileID);
277 $dao = CRM_Core_DAO::executeQuery($sql, $params);
278
be2fb01f
CW
279 $cfIDs = [];
280 $cefIDs = [];
0c56e4c8
TO
281 while ($dao->fetch()) {
282 $cfIDs[$dao->cfID] = $dao->uri;
283 $cefIDs[] = $dao->cefID;
284 }
285
286 if (!empty($cefIDs)) {
287 $cefIDs = implode(',', $cefIDs);
288 $sql = "DELETE FROM civicrm_entity_file where id IN ( $cefIDs )";
289 CRM_Core_DAO::executeQuery($sql);
7b8bc7a1 290 $isDeleted = TRUE;
0c56e4c8
TO
291 }
292
293 if (!empty($cfIDs)) {
294 // Delete file only if there no any entity using this file.
be2fb01f 295 $deleteFiles = [];
0c56e4c8
TO
296 foreach ($cfIDs as $fId => $fUri) {
297 //delete tags from entity tag table
be2fb01f 298 $tagParams = [
0c56e4c8 299 'entity_table' => 'civicrm_file',
21dfd5f5 300 'entity_id' => $fId,
be2fb01f 301 ];
0c56e4c8
TO
302
303 CRM_Core_BAO_EntityTag::del($tagParams);
304
305 if (!CRM_Core_DAO::getFieldValue('CRM_Core_DAO_EntityFile', $fId, 'id', 'file_id')) {
306 unlink($config->customFileUploadDir . DIRECTORY_SEPARATOR . $fUri);
307 $deleteFiles[$fId] = $fId;
308 }
309 }
310
311 if (!empty($deleteFiles)) {
312 $deleteFiles = implode(',', $deleteFiles);
313 $sql = "DELETE FROM civicrm_file where id IN ( $deleteFiles )";
314 CRM_Core_DAO::executeQuery($sql);
315 }
7b8bc7a1 316 $isDeleted = TRUE;
0c56e4c8 317 }
7b8bc7a1 318 return $isDeleted;
0c56e4c8
TO
319 }
320
321 /**
ad37ac8e 322 * Get all the files and associated object associated with this combination.
323 *
324 * @param string $entityTable
325 * @param int $entityID
326 * @param bool $addDeleteArgs
327 *
328 * @return array|null
0c56e4c8 329 */
00be9182 330 public static function getEntityFile($entityTable, $entityID, $addDeleteArgs = FALSE) {
0c56e4c8
TO
331 if (empty($entityTable) || !$entityID) {
332 $results = NULL;
333 return $results;
334 }
335
336 $config = CRM_Core_Config::singleton();
337
338 list($sql, $params) = self::sql($entityTable, $entityID, NULL);
339 $dao = CRM_Core_DAO::executeQuery($sql, $params);
be2fb01f 340 $results = [];
0c56e4c8 341 while ($dao->fetch()) {
ff9aeadb 342 $fileHash = self::generateFileHash($dao->entity_id, $dao->cfID);
0c56e4c8
TO
343 $result['fileID'] = $dao->cfID;
344 $result['entityID'] = $dao->cefID;
345 $result['mime_type'] = $dao->mime_type;
346 $result['fileName'] = $dao->uri;
347 $result['description'] = $dao->description;
348 $result['cleanName'] = CRM_Utils_File::cleanFileName($dao->uri);
349 $result['fullPath'] = $config->customFileUploadDir . DIRECTORY_SEPARATOR . $dao->uri;
ff9aeadb 350 $result['url'] = CRM_Utils_System::url('civicrm/file', "reset=1&id={$dao->cfID}&eid={$dao->entity_id}&fcs={$fileHash}");
0c56e4c8
TO
351 $result['href'] = "<a href=\"{$result['url']}\">{$result['cleanName']}</a>";
352 $result['tag'] = CRM_Core_BAO_EntityTag::getTag($dao->cfID, 'civicrm_file');
30a0455f 353 $result['icon'] = CRM_Utils_File::getIconFromMimeType($dao->mime_type);
0c56e4c8 354 if ($addDeleteArgs) {
06faabfa 355 $result['deleteURLArgs'] = self::deleteURLArgs($dao->entity_table, $dao->entity_id, $dao->cfID);
0c56e4c8
TO
356 }
357 $results[$dao->cfID] = $result;
358 }
359
360 //fix tag names
be2fb01f 361 $tags = CRM_Core_PseudoConstant::get('CRM_Core_DAO_EntityTag', 'tag_id', ['onlyActive' => FALSE]);
0c56e4c8
TO
362
363 foreach ($results as &$values) {
364 if (!empty($values['tag'])) {
be2fb01f 365 $tagNames = [];
0c56e4c8
TO
366 foreach ($values['tag'] as $tid) {
367 $tagNames[] = $tags[$tid];
368 }
369 $values['tag'] = implode(', ', $tagNames);
370 }
371 else {
372 $values['tag'] = '';
373 }
374 }
375
0c56e4c8
TO
376 return $results;
377 }
378
379 /**
6a0b768e
TO
380 * @param string $entityTable
381 * Table-name or "*" (to reference files directly by file-id).
06faabfa 382 * @param int $entityID
100fef9d
CW
383 * @param int $fileTypeID
384 * @param int $fileID
0c56e4c8
TO
385 *
386 * @return array
387 */
00be9182 388 public static function sql($entityTable, $entityID, $fileTypeID = NULL, $fileID = NULL) {
06faabfa
TO
389 if ($entityTable == '*') {
390 // $entityID is the ID of a specific file
391 $sql = "
392SELECT CF.id as cfID,
393 CF.uri as uri,
394 CF.mime_type as mime_type,
395 CF.description as description,
396 CEF.id as cefID,
397 CEF.entity_table as entity_table,
398 CEF.entity_id as entity_id
399FROM civicrm_file AS CF
400LEFT JOIN civicrm_entity_file AS CEF ON ( CEF.file_id = CF.id )
401WHERE CF.id = %2";
402
0db6c3e1
TO
403 }
404 else {
06faabfa 405 $sql = "
0c56e4c8 406SELECT CF.id as cfID,
6a488035
TO
407 CF.uri as uri,
408 CF.mime_type as mime_type,
409 CF.description as description,
06faabfa
TO
410 CEF.id as cefID,
411 CEF.entity_table as entity_table,
412 CEF.entity_id as entity_id
0c56e4c8
TO
413FROM civicrm_file AS CF
414LEFT JOIN civicrm_entity_file AS CEF ON ( CEF.file_id = CF.id )
415WHERE CEF.entity_table = %1
416AND CEF.entity_id = %2";
06faabfa 417 }
0c56e4c8 418
be2fb01f
CW
419 $params = [
420 1 => [$entityTable, 'String'],
421 2 => [$entityID, 'Integer'],
422 ];
0c56e4c8
TO
423
424 if ($fileTypeID !== NULL) {
425 $sql .= " AND CF.file_type_id = %3";
be2fb01f 426 $params[3] = [$fileTypeID, 'Integer'];
0c56e4c8
TO
427 }
428
429 if ($fileID !== NULL) {
430 $sql .= " AND CF.id = %4";
be2fb01f 431 $params[4] = [$fileID, 'Integer'];
0c56e4c8
TO
432 }
433
be2fb01f 434 return [$sql, $params];
0c56e4c8
TO
435 }
436
437 /**
75fd1335
TO
438 * @param CRM_Core_Form $form
439 * @param string $entityTable
100fef9d 440 * @param int $entityID
0c56e4c8
TO
441 * @param null $numAttachments
442 * @param bool $ajaxDelete
443 */
00be9182 444 public static function buildAttachment(&$form, $entityTable, $entityID = NULL, $numAttachments = NULL, $ajaxDelete = FALSE) {
0c56e4c8
TO
445
446 if (!$numAttachments) {
aaffa79f 447 $numAttachments = Civi::settings()->get('max_attachments');
0c56e4c8
TO
448 }
449 // Assign maxAttachments count to template for help message
450 $form->assign('maxAttachments', $numAttachments);
451
452 $config = CRM_Core_Config::singleton();
453 // set default max file size as 2MB
454 $maxFileSize = $config->maxFileSize ? $config->maxFileSize : 2;
455
456 $currentAttachmentInfo = self::getEntityFile($entityTable, $entityID, TRUE);
457 $totalAttachments = 0;
458 if ($currentAttachmentInfo) {
459 $totalAttachments = count($currentAttachmentInfo);
460 $form->add('checkbox', 'is_delete_attachment', ts('Delete All Attachment(s)'));
461 $form->assign('currentAttachmentInfo', $currentAttachmentInfo);
462 }
463 else {
464 $form->assign('currentAttachmentInfo', NULL);
465 }
466
467 if ($totalAttachments) {
468 if ($totalAttachments >= $numAttachments) {
469 $numAttachments = 0;
470 }
471 else {
472 $numAttachments -= $totalAttachments;
473 }
474 }
475
476 $form->assign('numAttachments', $numAttachments);
477
e0f9d6a2 478 CRM_Core_BAO_Tag::getTags('civicrm_file', $tags, NULL,
479 '&nbsp;&nbsp;', TRUE);
0c56e4c8
TO
480
481 // get tagset info
482 $parentNames = CRM_Core_BAO_Tag::getTagSet('civicrm_file');
483
484 // add attachments
485 for ($i = 1; $i <= $numAttachments; $i++) {
d00e9ef0 486 $form->addElement('file', "attachFile_$i", ts('Attach File'), 'size=30 maxlength=221');
0c56e4c8
TO
487 $form->addUploadElement("attachFile_$i");
488 $form->setMaxFileSize($maxFileSize * 1024 * 1024);
489 $form->addRule("attachFile_$i",
490 ts('File size should be less than %1 MByte(s)',
be2fb01f 491 [1 => $maxFileSize]
0c56e4c8
TO
492 ),
493 'maxfilesize',
494 $maxFileSize * 1024 * 1024
495 );
be2fb01f 496 $form->addElement('text', "attachDesc_$i", NULL, [
0c56e4c8
TO
497 'size' => 40,
498 'maxlength' => 255,
21dfd5f5 499 'placeholder' => ts('Description'),
be2fb01f 500 ]);
0c56e4c8
TO
501
502 if (!empty($tags)) {
503 $form->add('select', "tag_$i", ts('Tags'), $tags, FALSE,
be2fb01f 504 [
0c56e4c8
TO
505 'id' => "tags_$i",
506 'multiple' => 'multiple',
507 'class' => 'huge crm-select2',
21dfd5f5 508 'placeholder' => ts('- none -'),
be2fb01f 509 ]
0c56e4c8
TO
510 );
511 }
512 CRM_Core_Form_Tag::buildQuickForm($form, $parentNames, 'civicrm_file', NULL, FALSE, TRUE, "file_taglist_$i");
513 }
514 }
515
516 /**
100fef9d 517 * Return a clean url string and the number of attachment for a
0c56e4c8
TO
518 * given entityTable, entityID
519 *
5a4f6742
CW
520 * @param string $entityTable
521 * The entityTable to which the file is attached.
522 * @param int $entityID
523 * The id of the object in the above entityTable.
524 * @param string $separator
525 * The string separator where to implode the urls.
0c56e4c8 526 *
a6c01b45
CW
527 * @return array
528 * An array with 2 elements. The string and the number of attachments
0c56e4c8 529 */
00be9182 530 public static function attachmentInfo($entityTable, $entityID, $separator = '<br />') {
0c56e4c8
TO
531 if (!$entityID) {
532 return NULL;
533 }
534
535 $currentAttachments = self::getEntityFile($entityTable, $entityID);
536 if (!empty($currentAttachments)) {
be2fb01f 537 $currentAttachmentURL = [];
0c56e4c8
TO
538 foreach ($currentAttachments as $fileID => $attach) {
539 $currentAttachmentURL[] = $attach['href'];
540 }
541 return implode($separator, $currentAttachmentURL);
542 }
543 return NULL;
544 }
545
546 /**
547 * @param $formValues
c490a46a 548 * @param array $params
0c56e4c8 549 * @param $entityTable
100fef9d 550 * @param int $entityID
0c56e4c8 551 */
ae5ffbb7 552 public static function formatAttachment(
0c56e4c8
TO
553 &$formValues,
554 &$params,
555 $entityTable,
556 $entityID = NULL
557 ) {
558
559 // delete current attachments if applicable
560 if ($entityID && !empty($formValues['is_delete_attachment'])) {
561 CRM_Core_BAO_File::deleteEntityFile($entityTable, $entityID);
562 }
563
aaffa79f 564 $numAttachments = Civi::settings()->get('max_attachments');
0c56e4c8 565
0c56e4c8
TO
566 // setup all attachments
567 for ($i = 1; $i <= $numAttachments; $i++) {
568 $attachName = "attachFile_$i";
569 $attachDesc = "attachDesc_$i";
570 $attachTags = "tag_$i";
571 $attachFreeTags = "file_taglist_$i";
572 if (isset($formValues[$attachName]) && !empty($formValues[$attachName])) {
573 // add static tags if selects
be2fb01f 574 $tagParams = [];
0c56e4c8
TO
575 if (!empty($formValues[$attachTags])) {
576 foreach ($formValues[$attachTags] as $tag) {
577 $tagParams[$tag] = 1;
578 }
579 }
580
581 // we dont care if the file is empty or not
582 // CRM-7448
be2fb01f 583 $extraParams = [
0c56e4c8 584 'description' => $formValues[$attachDesc],
0c56e4c8 585 'tag' => $tagParams,
be2fb01f
CW
586 'attachment_taglist' => CRM_Utils_Array::value($attachFreeTags, $formValues, []),
587 ];
0c56e4c8 588
90a73810 589 CRM_Utils_File::formatFile($formValues, $attachName, $extraParams);
cde03305 590
591 // set the formatted attachment attributes to $params, later used by
592 // CRM_Activity_BAO_Activity::sendEmail(...) to send mail with desired attachments
593 if (!empty($formValues[$attachName])) {
594 $params[$attachName] = $formValues[$attachName];
595 }
0c56e4c8
TO
596 }
597 }
598 }
599
600 /**
c490a46a 601 * @param array $params
0c56e4c8 602 * @param $entityTable
100fef9d 603 * @param int $entityID
0c56e4c8 604 */
00be9182 605 public static function processAttachment(&$params, $entityTable, $entityID) {
8913e915 606 $numAttachments = Civi::settings()->get('max_attachments_backend') ?? self::DEFAULT_MAX_ATTACHMENTS_BACKEND;
0c56e4c8
TO
607
608 for ($i = 1; $i <= $numAttachments; $i++) {
8913e915
D
609 if (isset($params["attachFile_$i"])) {
610 /**
611 * Moved the second condition into its own if block to avoid changing
612 * how it works if there happens to be an entry that is not an array,
613 * since we now might exit loop early via newly added break below.
614 */
615 if (is_array($params["attachFile_$i"])) {
616 self::filePostProcess(
617 $params["attachFile_$i"]['location'],
618 NULL,
619 $entityTable,
620 $entityID,
621 NULL,
622 TRUE,
623 $params["attachFile_$i"],
624 "attachFile_$i",
625 $params["attachFile_$i"]['type']
626 );
627 }
628 }
629 else {
630 /**
631 * No point looping 100 times if there aren't any more.
632 * This assumes the array is continuous and doesn't skip array keys,
633 * but (a) where would it be doing that, and (b) it would have caused
634 * problems before anyway if there were skipped keys.
635 */
636 break;
0c56e4c8
TO
637 }
638 }
639 }
640
641 /**
642 * @return array
643 */
00be9182 644 public static function uploadNames() {
aaffa79f 645 $numAttachments = Civi::settings()->get('max_attachments');
0c56e4c8 646
be2fb01f 647 $names = [];
0c56e4c8
TO
648 for ($i = 1; $i <= $numAttachments; $i++) {
649 $names[] = "attachFile_{$i}";
650 }
651 $names[] = 'uploadFile';
652 return $names;
653 }
654
d424ffde 655 /**
c490a46a 656 * copy/attach an existing file to a different entity
0c56e4c8 657 * table and id.
d424ffde 658 *
0c56e4c8 659 * @param $oldEntityTable
100fef9d 660 * @param int $oldEntityId
0c56e4c8 661 * @param $newEntityTable
100fef9d 662 * @param int $newEntityId
0c56e4c8 663 */
00be9182 664 public static function copyEntityFile($oldEntityTable, $oldEntityId, $newEntityTable, $newEntityId) {
6a488035
TO
665 $oldEntityFile = new CRM_Core_DAO_EntityFile();
666 $oldEntityFile->entity_id = $oldEntityId;
667 $oldEntityFile->entity_table = $oldEntityTable;
668 $oldEntityFile->find();
669
670 while ($oldEntityFile->fetch()) {
671 $newEntityFile = new CRM_Core_DAO_EntityFile();
672 $newEntityFile->entity_id = $newEntityId;
673 $newEntityFile->entity_table = $newEntityTable;
674 $newEntityFile->file_id = $oldEntityFile->file_id;
675 $newEntityFile->save();
676 }
677 }
678
0c56e4c8
TO
679 /**
680 * @param $entityTable
100fef9d
CW
681 * @param int $entityID
682 * @param int $fileID
0c56e4c8
TO
683 *
684 * @return string
685 */
00be9182 686 public static function deleteURLArgs($entityTable, $entityID, $fileID) {
6a488035 687 $params['entityTable'] = $entityTable;
0c56e4c8
TO
688 $params['entityID'] = $entityID;
689 $params['fileID'] = $fileID;
6a488035
TO
690
691 $signer = new CRM_Utils_Signer(CRM_Core_Key::privateKey(), self::$_signableFields);
692 $params['_sgn'] = $signer->sign($params);
693 return CRM_Utils_System::makeQueryString($params);
694 }
695
696 /**
100fef9d 697 * Delete a file attachment from an entity table / entity ID
6a488035 698 *
6a488035 699 */
00be9182 700 public static function deleteAttachment() {
be2fb01f 701 $params = [];
0c56e4c8
TO
702 $params['entityTable'] = CRM_Utils_Request::retrieve('entityTable', 'String', CRM_Core_DAO::$_nullObject, TRUE);
703 $params['entityID'] = CRM_Utils_Request::retrieve('entityID', 'Positive', CRM_Core_DAO::$_nullObject, TRUE);
704 $params['fileID'] = CRM_Utils_Request::retrieve('fileID', 'Positive', CRM_Core_DAO::$_nullObject, TRUE);
6a488035 705
0c56e4c8 706 $signature = CRM_Utils_Request::retrieve('_sgn', 'String', CRM_Core_DAO::$_nullObject, TRUE);
6a488035
TO
707
708 $signer = new CRM_Utils_Signer(CRM_Core_Key::privateKey(), self::$_signableFields);
0c56e4c8 709 if (!$signer->validate($signature, $params)) {
6a488035
TO
710 CRM_Core_Error::fatal('Request signature is invalid');
711 }
712
33d245c8 713 self::deleteEntityFile($params['entityTable'], $params['entityID'], NULL, $params['fileID']);
6a488035
TO
714 }
715
34f51a07 716 /**
100fef9d 717 * Display paper icon for a file attachment -- CRM-13624
34f51a07 718 *
5a4f6742
CW
719 * @param string $entityTable
720 * The entityTable to which the file is attached. eg "civicrm_contact", "civicrm_note", "civicrm_activity".
06faabfa 721 * If you have the ID of a specific row in civicrm_file, use $entityTable='*'
5a4f6742
CW
722 * @param int $entityID
723 * The id of the object in the above entityTable.
1a7ab71e 724 *
72b3a70c
CW
725 * @return array|NULL
726 * list of HTML snippets; one HTML snippet for each attachment. If none found, then NULL
1a7ab71e 727 *
34f51a07 728 */
00be9182 729 public static function paperIconAttachment($entityTable, $entityID) {
0c56e4c8
TO
730 if (empty($entityTable) || !$entityID) {
731 $results = NULL;
732 return $results;
733 }
734 $currentAttachmentInfo = self::getEntityFile($entityTable, $entityID);
735 foreach ($currentAttachmentInfo as $fileKey => $fileValue) {
34f51a07 736 $fileID = $fileValue['fileID'];
0c56e4c8 737 if ($fileID) {
b9773de0
CW
738 $fileType = $fileValue['mime_type'];
739 $url = $fileValue['url'];
740 $title = $fileValue['cleanName'];
34f51a07 741 if ($fileType == 'image/jpeg' ||
0c56e4c8
TO
742 $fileType == 'image/pjpeg' ||
743 $fileType == 'image/gif' ||
744 $fileType == 'image/x-png' ||
745 $fileType == 'image/png'
746 ) {
34f51a07 747 $file_url[$fileID] = "
b9773de0
CW
748 <a href='$url' class='crm-image-popup' title='$title'>
749 <i class='crm-i fa-file-image-o'></i>
34f51a07 750 </a>";
34f51a07 751 }
b9773de0 752 // for non image files
34f51a07 753 else {
b9773de0
CW
754 $file_url[$fileID] = "
755 <a href='$url' title='$title'>
756 <i class='crm-i fa-paperclip'></i>
757 </a>";
34f51a07
N
758 }
759 }
760 }
0c56e4c8
TO
761 if (empty($file_url)) {
762 $results = NULL;
34f51a07
N
763 }
764 else {
0c56e4c8 765 $results = $file_url;
34f51a07
N
766 }
767 return $results;
768 }
6cccc6d4
TO
769
770 /**
771 * Get a reference to the file-search service (if one is available).
772 *
773 * @return CRM_Core_FileSearchInterface|NULL
774 */
00be9182 775 public static function getSearchService() {
be2fb01f 776 $fileSearches = [];
6cccc6d4
TO
777 CRM_Utils_Hook::fileSearches($fileSearches);
778
779 // use the first available search
780 foreach ($fileSearches as $fileSearch) {
781 /** @var $fileSearch CRM_Core_FileSearchInterface */
782 return $fileSearch;
783 }
784 return NULL;
785 }
96025800 786
ff9aeadb 787 /**
7618eb9d
TO
788 * Generates an access-token for downloading a specific file.
789 *
efddd94f
TO
790 * @param int $entityId entity id the file is attached to
791 * @param int $fileId file ID
518fa0ee
SL
792 * @param int $genTs
793 * @param int $life
ff9aeadb
SL
794 * @return string
795 */
efddd94f 796 public static function generateFileHash($entityId = NULL, $fileId = NULL, $genTs = NULL, $life = NULL) {
ff9aeadb 797 // Use multiple (but stable) inputs for hash information.
65d060f5
TO
798 $siteKey = CRM_Utils_Constant::value('CIVICRM_SITE_KEY');
799 if (!$siteKey) {
800 throw new \CRM_Core_Exception("Cannot generate file access token. Please set CIVICRM_SITE_KEY.");
801 }
b68895ad
SL
802
803 if (!$genTs) {
804 $genTs = time();
805 }
806 if (!$life) {
54a043e4 807 $days = Civi::settings()->get('checksum_timeout');
efddd94f 808 $life = 24 * $days;
b68895ad 809 }
ff9aeadb
SL
810 // Trim 8 chars off the string, make it slightly easier to find
811 // but reveals less information from the hash.
efddd94f 812 $cs = hash_hmac('sha256', "entity={$entityId}&file={$fileId}&life={$life}", $siteKey);
b68895ad 813 return "{$cs}_{$genTs}_{$life}";
ff9aeadb
SL
814 }
815
c5c40df7 816 /**
7618eb9d
TO
817 * Validate a file access token.
818 *
c5c40df7 819 * @param string $hash
7618eb9d
TO
820 * @param int $entityId Entity Id the file is attached to
821 * @param int $fileId File Id
c5c40df7
TO
822 * @return bool
823 */
7618eb9d 824 public static function validateFileHash($hash, $entityId, $fileId) {
c5c40df7
TO
825 $input = CRM_Utils_System::explode('_', $hash, 3);
826 $inputTs = CRM_Utils_Array::value(1, $input);
827 $inputLF = CRM_Utils_Array::value(2, $input);
7618eb9d 828 $testHash = CRM_Core_BAO_File::generateFileHash($entityId, $fileId, $inputTs, $inputLF);
c5c40df7
TO
829 if (hash_equals($testHash, $hash)) {
830 $now = time();
831 if ($inputTs + ($inputLF * 60 * 60) >= $now) {
832 return TRUE;
833 }
834 else {
835 return FALSE;
836 }
837 }
838 return FALSE;
839 }
840
b2aaa85e 841}