3 +--------------------------------------------------------------------+
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
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. |
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. |
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 +--------------------------------------------------------------------+
31 * @copyright CiviCRM LLC (c) 2004-2019
37 * BAO object for crm_log table
39 class CRM_Core_BAO_File
extends CRM_Core_DAO_File
{
41 static $_signableFields = array('entityTable', 'entityID', 'fileID');
44 * Takes an associative array and creates a File object.
46 * @param array $params
47 * (reference ) an assoc array of name/value pairs.
49 * @return CRM_Core_BAO_File
51 public static function create($params) {
52 $fileDAO = new CRM_Core_DAO_File();
54 $op = empty($params['id']) ?
'create' : 'edit';
56 CRM_Utils_Hook
::pre($op, 'File', CRM_Utils_Array
::value('id', $params), $params);
58 $fileDAO->copyValues($params);
60 if (empty($params['id']) && empty($params['created_id'])) {
61 $fileDAO->created_id
= CRM_Core_Session
::getLoggedInContactID();
66 CRM_Utils_Hook
::post($op, 'File', $fileDAO->id
, $fileDAO);
73 * @param int $entityID
77 public static function path($fileID, $entityID) {
78 $entityFileDAO = new CRM_Core_DAO_EntityFile();
79 $entityFileDAO->entity_id
= $entityID;
80 $entityFileDAO->file_id
= $fileID;
82 if ($entityFileDAO->find(TRUE)) {
83 $fileDAO = new CRM_Core_DAO_File();
84 $fileDAO->id
= $fileID;
85 if ($fileDAO->find(TRUE)) {
86 $config = CRM_Core_Config
::singleton();
87 $path = $config->customFileUploadDir
. $fileDAO->uri
;
89 if (file_exists($path) && is_readable($path)) {
90 return array($path, $fileDAO->mime_type
);
95 return array(NULL, NULL);
101 * @param int $fileTypeID
102 * @param $entityTable
103 * @param int $entityID
104 * @param $entitySubtype
105 * @param bool $overwrite
106 * @param null|array $fileParams
107 * @param string $uploadName
108 * @param null $mimeType
112 public static function filePostProcess(
120 $uploadName = 'uploadFile',
124 CRM_Core_Error
::statusBounce(ts('Mime Type is now a required parameter for file upload'));
127 $config = CRM_Core_Config
::singleton();
129 $path = explode('/', $data);
130 $filename = $path[count($path) - 1];
132 // rename this file to go into the secure directory
133 if ($entitySubtype) {
134 $directoryName = $config->customFileUploadDir
. $entitySubtype . DIRECTORY_SEPARATOR
. $entityID;
137 $directoryName = $config->customFileUploadDir
;
140 CRM_Utils_File
::createDir($directoryName);
142 if (!rename($data, $directoryName . DIRECTORY_SEPARATOR
. $filename)) {
143 CRM_Core_Error
::statusBounce(ts('Could not move custom file to custom upload directory'));
147 if ($overwrite && $fileTypeID) {
148 list($sql, $params) = self
::sql($entityTable, $entityID, $fileTypeID);
151 list($sql, $params) = self
::sql($entityTable, $entityID, 0);
154 $dao = CRM_Core_DAO
::executeQuery($sql, $params);
157 $fileDAO = new CRM_Core_DAO_File();
159 if (isset($dao->cfID
) && $dao->cfID
) {
161 $fileDAO->id
= $dao->cfID
;
162 unlink($directoryName . DIRECTORY_SEPARATOR
. $dao->uri
);
165 if (!empty($fileParams)) {
166 $fileDAO->copyValues($fileParams);
169 $fileDAO->uri
= $filename;
170 $fileDAO->mime_type
= $mimeType;
171 $fileDAO->file_type_id
= $fileTypeID;
172 $fileDAO->upload_date
= date('YmdHis');
175 // need to add/update civicrm_entity_file
176 $entityFileDAO = new CRM_Core_DAO_EntityFile();
177 if (isset($dao->cefID
) && $dao->cefID
) {
178 $entityFileDAO->id
= $dao->cefID
;
180 $entityFileDAO->entity_table
= $entityTable;
181 $entityFileDAO->entity_id
= $entityID;
182 $entityFileDAO->file_id
= $fileDAO->id
;
183 $entityFileDAO->save();
186 if (!empty($fileParams['tag'])) {
187 CRM_Core_BAO_EntityTag
::create($fileParams['tag'], 'civicrm_file', $entityFileDAO->id
);
191 if (isset($fileParams['attachment_taglist']) && !empty($fileParams['attachment_taglist'])) {
192 CRM_Core_Form_Tag
::postProcess($fileParams['attachment_taglist'], $entityFileDAO->id
, 'civicrm_file');
195 // lets call the post hook here so attachments code can do the right stuff
196 CRM_Utils_Hook
::post($op, 'File', $fileDAO->id
, $fileDAO);
200 * A static function wrapper that deletes the various objects.
202 * Objects are those hat are connected to a file object (i.e. file, entityFile and customValue.
205 * @param int $entityID
206 * @param int $fieldID
210 public static function deleteFileReferences($fileID, $entityID, $fieldID) {
211 $fileDAO = new CRM_Core_DAO_File();
212 $fileDAO->id
= $fileID;
213 if (!$fileDAO->find(TRUE)) {
214 CRM_Core_Error
::fatal();
217 // lets call a pre hook before the delete, so attachments hooks can get the info before things
219 CRM_Utils_Hook
::pre('delete', 'File', $fileID, $fileDAO);
221 // get the table and column name
222 list($tableName, $columnName, $groupID) = CRM_Core_BAO_CustomField
::getTableColumnGroup($fieldID);
224 $entityFileDAO = new CRM_Core_DAO_EntityFile();
225 $entityFileDAO->file_id
= $fileID;
226 $entityFileDAO->entity_id
= $entityID;
227 $entityFileDAO->entity_table
= $tableName;
229 if (!$entityFileDAO->find(TRUE)) {
230 CRM_Core_Error
::fatal(sprintf('No record found for given file ID - %d and entity ID - %d', $fileID, $entityID));
233 $entityFileDAO->delete();
236 // also set the value to null of the table and column
237 $query = "UPDATE $tableName SET $columnName = null WHERE $columnName = %1";
238 $params = array(1 => array($fileID, 'Integer'));
239 CRM_Core_DAO
::executeQuery($query, $params);
243 * The $useWhere is used so that the signature matches the parent class
245 * public function delete($useWhere = FALSE) {
246 * list($fileID, $entityID, $fieldID) = func_get_args();
248 * self::deleteFileReferences($fileID, $entityID, $fieldID);
252 * Delete all the files and associated object associated with this combination.
254 * @param string $entityTable
255 * @param int $entityID
256 * @param int $fileTypeID
262 public static function deleteEntityFile($entityTable, $entityID, $fileTypeID = NULL, $fileID = NULL) {
264 if (empty($entityTable) ||
empty($entityID)) {
268 $config = CRM_Core_Config
::singleton();
270 list($sql, $params) = self
::sql($entityTable, $entityID, $fileTypeID, $fileID);
271 $dao = CRM_Core_DAO
::executeQuery($sql, $params);
275 while ($dao->fetch()) {
276 $cfIDs[$dao->cfID
] = $dao->uri
;
277 $cefIDs[] = $dao->cefID
;
280 if (!empty($cefIDs)) {
281 $cefIDs = implode(',', $cefIDs);
282 $sql = "DELETE FROM civicrm_entity_file where id IN ( $cefIDs )";
283 CRM_Core_DAO
::executeQuery($sql);
287 if (!empty($cfIDs)) {
288 // Delete file only if there no any entity using this file.
289 $deleteFiles = array();
290 foreach ($cfIDs as $fId => $fUri) {
291 //delete tags from entity tag table
293 'entity_table' => 'civicrm_file',
297 CRM_Core_BAO_EntityTag
::del($tagParams);
299 if (!CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_EntityFile', $fId, 'id', 'file_id')) {
300 unlink($config->customFileUploadDir
. DIRECTORY_SEPARATOR
. $fUri);
301 $deleteFiles[$fId] = $fId;
305 if (!empty($deleteFiles)) {
306 $deleteFiles = implode(',', $deleteFiles);
307 $sql = "DELETE FROM civicrm_file where id IN ( $deleteFiles )";
308 CRM_Core_DAO
::executeQuery($sql);
316 * Get all the files and associated object associated with this combination.
318 * @param string $entityTable
319 * @param int $entityID
320 * @param bool $addDeleteArgs
324 public static function getEntityFile($entityTable, $entityID, $addDeleteArgs = FALSE) {
325 if (empty($entityTable) ||
!$entityID) {
330 $config = CRM_Core_Config
::singleton();
332 list($sql, $params) = self
::sql($entityTable, $entityID, NULL);
333 $dao = CRM_Core_DAO
::executeQuery($sql, $params);
335 while ($dao->fetch()) {
336 $result['fileID'] = $dao->cfID
;
337 $result['entityID'] = $dao->cefID
;
338 $result['mime_type'] = $dao->mime_type
;
339 $result['fileName'] = $dao->uri
;
340 $result['description'] = $dao->description
;
341 $result['cleanName'] = CRM_Utils_File
::cleanFileName($dao->uri
);
342 $result['fullPath'] = $config->customFileUploadDir
. DIRECTORY_SEPARATOR
. $dao->uri
;
343 $result['url'] = CRM_Utils_System
::url('civicrm/file', "reset=1&id={$dao->cfID}&eid={$dao->entity_id}");
344 $result['href'] = "<a href=\"{$result['url']}\">{$result['cleanName']}</a>";
345 $result['tag'] = CRM_Core_BAO_EntityTag
::getTag($dao->cfID
, 'civicrm_file');
346 $result['icon'] = CRM_Utils_File
::getIconFromMimeType($dao->mime_type
);
347 if ($addDeleteArgs) {
348 $result['deleteURLArgs'] = self
::deleteURLArgs($dao->entity_table
, $dao->entity_id
, $dao->cfID
);
350 $results[$dao->cfID
] = $result;
354 $tags = CRM_Core_PseudoConstant
::get('CRM_Core_DAO_EntityTag', 'tag_id', array('onlyActive' => FALSE));
356 foreach ($results as &$values) {
357 if (!empty($values['tag'])) {
359 foreach ($values['tag'] as $tid) {
360 $tagNames[] = $tags[$tid];
362 $values['tag'] = implode(', ', $tagNames);
374 * @param string $entityTable
375 * Table-name or "*" (to reference files directly by file-id).
376 * @param int $entityID
377 * @param int $fileTypeID
382 public static function sql($entityTable, $entityID, $fileTypeID = NULL, $fileID = NULL) {
383 if ($entityTable == '*') {
384 // $entityID is the ID of a specific file
386 SELECT CF.id as cfID,
388 CF.mime_type as mime_type,
389 CF.description as description,
391 CEF.entity_table as entity_table,
392 CEF.entity_id as entity_id
393 FROM civicrm_file AS CF
394 LEFT JOIN civicrm_entity_file AS CEF ON ( CEF.file_id = CF.id )
400 SELECT CF.id as cfID,
402 CF.mime_type as mime_type,
403 CF.description as description,
405 CEF.entity_table as entity_table,
406 CEF.entity_id as entity_id
407 FROM civicrm_file AS CF
408 LEFT JOIN civicrm_entity_file AS CEF ON ( CEF.file_id = CF.id )
409 WHERE CEF.entity_table = %1
410 AND CEF.entity_id = %2";
414 1 => array($entityTable, 'String'),
415 2 => array($entityID, 'Integer'),
418 if ($fileTypeID !== NULL) {
419 $sql .= " AND CF.file_type_id = %3";
420 $params[3] = array($fileTypeID, 'Integer');
423 if ($fileID !== NULL) {
424 $sql .= " AND CF.id = %4";
425 $params[4] = array($fileID, 'Integer');
428 return array($sql, $params);
432 * @param CRM_Core_Form $form
433 * @param string $entityTable
434 * @param int $entityID
435 * @param null $numAttachments
436 * @param bool $ajaxDelete
438 public static function buildAttachment(&$form, $entityTable, $entityID = NULL, $numAttachments = NULL, $ajaxDelete = FALSE) {
440 if (!$numAttachments) {
441 $numAttachments = Civi
::settings()->get('max_attachments');
443 // Assign maxAttachments count to template for help message
444 $form->assign('maxAttachments', $numAttachments);
446 $config = CRM_Core_Config
::singleton();
447 // set default max file size as 2MB
448 $maxFileSize = $config->maxFileSize ?
$config->maxFileSize
: 2;
450 $currentAttachmentInfo = self
::getEntityFile($entityTable, $entityID, TRUE);
451 $totalAttachments = 0;
452 if ($currentAttachmentInfo) {
453 $totalAttachments = count($currentAttachmentInfo);
454 $form->add('checkbox', 'is_delete_attachment', ts('Delete All Attachment(s)'));
455 $form->assign('currentAttachmentInfo', $currentAttachmentInfo);
458 $form->assign('currentAttachmentInfo', NULL);
461 if ($totalAttachments) {
462 if ($totalAttachments >= $numAttachments) {
466 $numAttachments -= $totalAttachments;
470 $form->assign('numAttachments', $numAttachments);
472 CRM_Core_BAO_Tag
::getTags('civicrm_file', $tags, NULL,
473 ' ', TRUE);
476 $parentNames = CRM_Core_BAO_Tag
::getTagSet('civicrm_file');
479 for ($i = 1; $i <= $numAttachments; $i++
) {
480 $form->addElement('file', "attachFile_$i", ts('Attach File'), 'size=30 maxlength=221');
481 $form->addUploadElement("attachFile_$i");
482 $form->setMaxFileSize($maxFileSize * 1024 * 1024);
483 $form->addRule("attachFile_$i",
484 ts('File size should be less than %1 MByte(s)',
485 array(1 => $maxFileSize)
488 $maxFileSize * 1024 * 1024
490 $form->addElement('text', "attachDesc_$i", NULL, array(
493 'placeholder' => ts('Description'),
497 $form->add('select', "tag_$i", ts('Tags'), $tags, FALSE,
500 'multiple' => 'multiple',
501 'class' => 'huge crm-select2',
502 'placeholder' => ts('- none -'),
506 CRM_Core_Form_Tag
::buildQuickForm($form, $parentNames, 'civicrm_file', NULL, FALSE, TRUE, "file_taglist_$i");
511 * Return a clean url string and the number of attachment for a
512 * given entityTable, entityID
514 * @param string $entityTable
515 * The entityTable to which the file is attached.
516 * @param int $entityID
517 * The id of the object in the above entityTable.
518 * @param string $separator
519 * The string separator where to implode the urls.
522 * An array with 2 elements. The string and the number of attachments
524 public static function attachmentInfo($entityTable, $entityID, $separator = '<br />') {
529 $currentAttachments = self
::getEntityFile($entityTable, $entityID);
530 if (!empty($currentAttachments)) {
531 $currentAttachmentURL = array();
532 foreach ($currentAttachments as $fileID => $attach) {
533 $currentAttachmentURL[] = $attach['href'];
535 return implode($separator, $currentAttachmentURL);
542 * @param array $params
543 * @param $entityTable
544 * @param int $entityID
546 public static function formatAttachment(
553 // delete current attachments if applicable
554 if ($entityID && !empty($formValues['is_delete_attachment'])) {
555 CRM_Core_BAO_File
::deleteEntityFile($entityTable, $entityID);
558 $numAttachments = Civi
::settings()->get('max_attachments');
560 // setup all attachments
561 for ($i = 1; $i <= $numAttachments; $i++
) {
562 $attachName = "attachFile_$i";
563 $attachDesc = "attachDesc_$i";
564 $attachTags = "tag_$i";
565 $attachFreeTags = "file_taglist_$i";
566 if (isset($formValues[$attachName]) && !empty($formValues[$attachName])) {
567 // add static tags if selects
568 $tagParams = array();
569 if (!empty($formValues[$attachTags])) {
570 foreach ($formValues[$attachTags] as $tag) {
571 $tagParams[$tag] = 1;
575 // we dont care if the file is empty or not
577 $extraParams = array(
578 'description' => $formValues[$attachDesc],
580 'attachment_taglist' => CRM_Utils_Array
::value($attachFreeTags, $formValues, array()),
583 CRM_Utils_File
::formatFile($formValues, $attachName, $extraParams);
585 // set the formatted attachment attributes to $params, later used by
586 // CRM_Activity_BAO_Activity::sendEmail(...) to send mail with desired attachments
587 if (!empty($formValues[$attachName])) {
588 $params[$attachName] = $formValues[$attachName];
595 * @param array $params
596 * @param $entityTable
597 * @param int $entityID
599 public static function processAttachment(&$params, $entityTable, $entityID) {
600 $numAttachments = Civi
::settings()->get('max_attachments');
602 for ($i = 1; $i <= $numAttachments; $i++
) {
604 isset($params["attachFile_$i"]) &&
605 is_array($params["attachFile_$i"])
607 self
::filePostProcess(
608 $params["attachFile_$i"]['location'],
614 $params["attachFile_$i"],
616 $params["attachFile_$i"]['type']
625 public static function uploadNames() {
626 $numAttachments = Civi
::settings()->get('max_attachments');
629 for ($i = 1; $i <= $numAttachments; $i++
) {
630 $names[] = "attachFile_{$i}";
632 $names[] = 'uploadFile';
637 * copy/attach an existing file to a different entity
640 * @param $oldEntityTable
641 * @param int $oldEntityId
642 * @param $newEntityTable
643 * @param int $newEntityId
645 public static function copyEntityFile($oldEntityTable, $oldEntityId, $newEntityTable, $newEntityId) {
646 $oldEntityFile = new CRM_Core_DAO_EntityFile();
647 $oldEntityFile->entity_id
= $oldEntityId;
648 $oldEntityFile->entity_table
= $oldEntityTable;
649 $oldEntityFile->find();
651 while ($oldEntityFile->fetch()) {
652 $newEntityFile = new CRM_Core_DAO_EntityFile();
653 $newEntityFile->entity_id
= $newEntityId;
654 $newEntityFile->entity_table
= $newEntityTable;
655 $newEntityFile->file_id
= $oldEntityFile->file_id
;
656 $newEntityFile->save();
661 * @param $entityTable
662 * @param int $entityID
667 public static function deleteURLArgs($entityTable, $entityID, $fileID) {
668 $params['entityTable'] = $entityTable;
669 $params['entityID'] = $entityID;
670 $params['fileID'] = $fileID;
672 $signer = new CRM_Utils_Signer(CRM_Core_Key
::privateKey(), self
::$_signableFields);
673 $params['_sgn'] = $signer->sign($params);
674 return CRM_Utils_System
::makeQueryString($params);
678 * Delete a file attachment from an entity table / entity ID
681 public static function deleteAttachment() {
683 $params['entityTable'] = CRM_Utils_Request
::retrieve('entityTable', 'String', CRM_Core_DAO
::$_nullObject, TRUE);
684 $params['entityID'] = CRM_Utils_Request
::retrieve('entityID', 'Positive', CRM_Core_DAO
::$_nullObject, TRUE);
685 $params['fileID'] = CRM_Utils_Request
::retrieve('fileID', 'Positive', CRM_Core_DAO
::$_nullObject, TRUE);
687 $signature = CRM_Utils_Request
::retrieve('_sgn', 'String', CRM_Core_DAO
::$_nullObject, TRUE);
689 $signer = new CRM_Utils_Signer(CRM_Core_Key
::privateKey(), self
::$_signableFields);
690 if (!$signer->validate($signature, $params)) {
691 CRM_Core_Error
::fatal('Request signature is invalid');
694 self
::deleteEntityFile($params['entityTable'], $params['entityID'], NULL, $params['fileID']);
699 * Display paper icon for a file attachment -- CRM-13624
701 * @param string $entityTable
702 * The entityTable to which the file is attached. eg "civicrm_contact", "civicrm_note", "civicrm_activity".
703 * If you have the ID of a specific row in civicrm_file, use $entityTable='*'
704 * @param int $entityID
705 * The id of the object in the above entityTable.
708 * list of HTML snippets; one HTML snippet for each attachment. If none found, then NULL
711 public static function paperIconAttachment($entityTable, $entityID) {
712 if (empty($entityTable) ||
!$entityID) {
716 $currentAttachmentInfo = self
::getEntityFile($entityTable, $entityID);
717 foreach ($currentAttachmentInfo as $fileKey => $fileValue) {
718 $fileID = $fileValue['fileID'];
720 $fileType = $fileValue['mime_type'];
721 $url = $fileValue['url'];
722 $title = $fileValue['cleanName'];
723 if ($fileType == 'image/jpeg' ||
724 $fileType == 'image/pjpeg' ||
725 $fileType == 'image/gif' ||
726 $fileType == 'image/x-png' ||
727 $fileType == 'image/png'
729 $file_url[$fileID] = "
730 <a href='$url' class='crm-image-popup' title='$title'>
731 <i class='crm-i fa-file-image-o'></i>
734 // for non image files
736 $file_url[$fileID] = "
737 <a href='$url' title='$title'>
738 <i class='crm-i fa-paperclip'></i>
743 if (empty($file_url)) {
747 $results = $file_url;
753 * Get a reference to the file-search service (if one is available).
755 * @return CRM_Core_FileSearchInterface|NULL
757 public static function getSearchService() {
758 $fileSearches = array();
759 CRM_Utils_Hook
::fileSearches($fileSearches);
761 // use the first available search
762 foreach ($fileSearches as $fileSearch) {
763 /** @var $fileSearch CRM_Core_FileSearchInterface */