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