CRM-14106 - Regex targeting other conditionals
[civicrm-core.git] / CRM / Core / BAO / File.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.4 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2013 |
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 *
30 * @package CRM
31 * @copyright CiviCRM LLC (c) 2004-2013
32 * $Id$
33 *
34 */
35
36 /**
37 * BAO object for crm_log table
38 */
39 class CRM_Core_BAO_File extends CRM_Core_DAO_File {
40
41 static $_signableFields = array('entityTable', 'entityID', 'fileID');
42
43 static function path($fileID, $entityID, $entityTable = NULL) {
44 $entityFileDAO = new CRM_Core_DAO_EntityFile();
45 if ($entityTable) {
46 $entityFileDAO->entity_table = $entityTable;
47 }
48 $entityFileDAO->entity_id = $entityID;
49 $entityFileDAO->file_id = $fileID;
50
51 if ($entityFileDAO->find(TRUE)) {
52 $fileDAO = new CRM_Core_DAO_File();
53 $fileDAO->id = $fileID;
54 if ($fileDAO->find(TRUE)) {
55 $config = CRM_Core_Config::singleton();
56 $path = $config->customFileUploadDir . $fileDAO->uri;
57
58 if (file_exists($path) && is_readable($path)) {
59 return array($path, $fileDAO->mime_type);
60 }
61 }
62 }
63
64 return array(NULL, NULL);
65 }
66
67
68 static function filePostProcess(
69 $data,
70 $fileTypeID,
71 $entityTable,
72 $entityID,
73 $entitySubtype,
74 $overwrite = TRUE,
75 $fileParams = NULL,
76 $uploadName = 'uploadFile',
77 $mimeType = null
78 ) {
79 if (!$mimeType) {
80 CRM_Core_Error::fatal(ts('Mime Type is now a required parameter'));
81 }
82
83 $config = CRM_Core_Config::singleton();
84
85 $path = explode('/', $data);
86 $filename = $path[count($path) - 1];
87
88 // rename this file to go into the secure directory
89 if ($entitySubtype) {
90 $directoryName = $config->customFileUploadDir . $entitySubtype . DIRECTORY_SEPARATOR . $entityID;
91 }
92 else {
93 $directoryName = $config->customFileUploadDir;
94 }
95
96 CRM_Utils_File::createDir($directoryName);
97
98 if (!rename($data, $directoryName . DIRECTORY_SEPARATOR . $filename)) {
99 CRM_Core_Error::fatal(ts('Could not move custom file to custom upload directory'));
100 break;
101 }
102
103 // to get id's
104 if ($overwrite && $fileTypeID) {
105 list($sql, $params) = self::sql($entityTable, $entityID, $fileTypeID);
106 }
107 else {
108 list($sql, $params) = self::sql($entityTable, $entityID, 0);
109 }
110
111 $dao = CRM_Core_DAO::executeQuery($sql, $params);
112 $dao->fetch();
113
114 $fileDAO = new CRM_Core_DAO_File();
115 $op = 'create';
116 if (isset($dao->cfID) && $dao->cfID) {
117 $op = 'edit';
118 $fileDAO->id = $dao->cfID;
119 unlink($directoryName . DIRECTORY_SEPARATOR . $dao->uri);
120 }
121
122 if (!empty($fileParams)) {
123 $fileDAO->copyValues($fileParams);
124 }
125
126 $fileDAO->uri = $filename;
127 $fileDAO->mime_type = $mimeType;
128 $fileDAO->file_type_id = $fileTypeID;
129 $fileDAO->upload_date = date('Ymdhis');
130 $fileDAO->save();
131
132 // need to add/update civicrm_entity_file
133 $entityFileDAO = new CRM_Core_DAO_EntityFile();
134 if (isset($dao->cefID) && $dao->cefID) {
135 $entityFileDAO->id = $dao->cefID;
136 }
137 $entityFileDAO->entity_table = $entityTable;
138 $entityFileDAO->entity_id = $entityID;
139 $entityFileDAO->file_id = $fileDAO->id;
140 $entityFileDAO->save();
141
142 //save static tags
143 if (!empty($fileParams['tag'])) {
144 CRM_Core_BAO_EntityTag::create($fileParams['tag'], 'civicrm_file', $entityFileDAO->id);
145 }
146
147 //save free tags
148 if (isset($fileParams['attachment_taglist']) && !empty($fileParams['attachment_taglist'])) {
149 CRM_Core_Form_Tag::postProcess($fileParams['attachment_taglist'], $entityFileDAO->id, 'civicrm_file', CRM_Core_DAO::$_nullObject);
150 }
151
152 // lets call the post hook here so attachments code can do the right stuff
153 CRM_Utils_Hook::post($op, 'File', $fileDAO->id, $fileDAO);
154 }
155
156 /**
157 * A static function wrapper that deletes the various objects that are
158 * connected to a file object (i.e. file, entityFile and customValue
159 */
160 public static function deleteFileReferences($fileID, $entityID, $fieldID) {
161 $fileDAO = new CRM_Core_DAO_File();
162 $fileDAO->id = $fileID;
163 if (!$fileDAO->find(TRUE)) {
164 CRM_Core_Error::fatal();
165 }
166
167 // lets call a pre hook before the delete, so attachments hooks can get the info before things
168 // disappear
169 CRM_Utils_Hook::pre('delete', 'File', $fileID, $fileDAO);
170
171 // get the table and column name
172 list($tableName, $columnName, $groupID) = CRM_Core_BAO_CustomField::getTableColumnGroup($fieldID);
173
174 $entityFileDAO = new CRM_Core_DAO_EntityFile();
175 $entityFileDAO->file_id = $fileID;
176 $entityFileDAO->entity_id = $entityID;
177 $entityFileDAO->entity_table = $tableName;
178
179 if (!$entityFileDAO->find(TRUE)) {
180 CRM_Core_Error::fatal();
181 }
182
183 $entityFileDAO->delete();
184 $fileDAO->delete();
185
186 // also set the value to null of the table and column
187 $query = "UPDATE $tableName SET $columnName = null WHERE $columnName = %1";
188 $params = array(1 => array($fileID, 'Integer'));
189 CRM_Core_DAO::executeQuery($query, $params);
190 }
191
192 /**
193 * The $useWhere is used so that the signature matches the parent class
194 */
195 public function delete($useWhere = false) {
196 list($fileID, $entityID, $fieldID) = func_get_args();
197
198 self::deleteFileReferences($fileID, $entityID, $fieldID);
199 }
200
201 /**
202 * delete all the files and associated object associated with this
203 * combination
204 */
205 static function deleteEntityFile($entityTable, $entityID, $fileTypeID = NULL, $fileID = NULL) {
206 if (empty($entityTable) || empty($entityID)) {
207 return;
208 }
209
210 $config = CRM_Core_Config::singleton();
211
212 list($sql, $params) = self::sql($entityTable, $entityID, $fileTypeID, $fileID);
213 $dao = CRM_Core_DAO::executeQuery($sql, $params);
214
215 $cfIDs = array();
216 $cefIDs = array();
217 while ($dao->fetch()) {
218 $cfIDs[$dao->cfID] = $dao->uri;
219 $cefIDs[] = $dao->cefID;
220 }
221
222 if (!empty($cefIDs)) {
223 $cefIDs = implode(',', $cefIDs);
224 $sql = "DELETE FROM civicrm_entity_file where id IN ( $cefIDs )";
225 CRM_Core_DAO::executeQuery($sql);
226 }
227
228 if (!empty($cfIDs)) {
229 // Delete file only if there no any entity using this file.
230 $deleteFiles = array();
231 foreach ($cfIDs as $fId => $fUri) {
232 //delete tags from entity tag table
233 $tagParams = array(
234 'entity_table' => 'civicrm_file',
235 'entity_id' => $fId
236 );
237
238 CRM_Core_BAO_EntityTag::del($tagParams);
239
240 if (!CRM_Core_DAO::getFieldValue('CRM_Core_DAO_EntityFile', $fId, 'id', 'file_id')) {
241 unlink($config->customFileUploadDir . DIRECTORY_SEPARATOR . $fUri);
242 $deleteFiles[$fId] = $fId;
243 }
244 }
245
246 if (!empty($deleteFiles)) {
247 $deleteFiles = implode(',', $deleteFiles);
248 $sql = "DELETE FROM civicrm_file where id IN ( $deleteFiles )";
249 CRM_Core_DAO::executeQuery($sql);
250 }
251 }
252 }
253
254 /**
255 * get all the files and associated object associated with this
256 * combination
257 */
258 static function getEntityFile($entityTable, $entityID, $addDeleteArgs = false) {
259 if (empty($entityTable) || !$entityID) {
260 $results = NULL;
261 return $results;
262 }
263
264 $config = CRM_Core_Config::singleton();
265
266 list($sql, $params) = self::sql($entityTable, $entityID, NULL);
267 $dao = CRM_Core_DAO::executeQuery($sql, $params);
268 $results = array();
269 while ($dao->fetch()) {
270 $result['fileID'] = $dao->cfID;
271 $result['entityID'] = $dao->cefID;
272 $result['mime_type'] = $dao->mime_type;
273 $result['fileName'] = $dao->uri;
274 $result['description'] = $dao->description;
275 $result['cleanName'] = CRM_Utils_File::cleanFileName($dao->uri);
276 $result['fullPath'] = $config->customFileUploadDir . DIRECTORY_SEPARATOR . $dao->uri;
277 $result['url'] = CRM_Utils_System::url('civicrm/file', "reset=1&id={$dao->cfID}&eid={$entityID}");
278 $result['href'] = "<a href=\"{$result['url']}\">{$result['cleanName']}</a>";
279 $result['tag'] = CRM_Core_BAO_EntityTag::getTag($dao->cfID, 'civicrm_file');
280 if ($addDeleteArgs) {
281 $result['deleteURLArgs'] = self::deleteURLArgs($entityTable, $entityID, $dao->cfID);
282 }
283 $results[$dao->cfID] = $result;
284 }
285
286 //fix tag names
287 $tags = CRM_Core_PseudoConstant::get('CRM_Core_DAO_EntityTag', 'tag_id', array('onlyActive' => FALSE));
288
289 foreach($results as &$values) {
290 if (!empty($values['tag'])) {
291 $tagNames = array();
292 foreach( $values['tag'] as $tid ) {
293 $tagNames[] = $tags[$tid];
294 }
295 $values['tag'] = implode(', ', $tagNames);
296 } else {
297 $values['tag'] = '';
298 }
299 }
300
301 $dao->free();
302 return $results;
303 }
304
305 static function sql($entityTable, $entityID, $fileTypeID = NULL, $fileID = NULL) {
306 $sql = "
307 SELECT CF.id as cfID,
308 CF.uri as uri,
309 CF.mime_type as mime_type,
310 CF.description as description,
311 CEF.id as cefID
312 FROM civicrm_file AS CF
313 LEFT JOIN civicrm_entity_file AS CEF ON ( CEF.file_id = CF.id )
314 WHERE CEF.entity_table = %1
315 AND CEF.entity_id = %2";
316
317 $params = array(
318 1 => array($entityTable, 'String'),
319 2 => array($entityID, 'Integer'),
320 );
321
322 if ($fileTypeID !== NULL) {
323 $sql .= " AND CF.file_type_id = %3";
324 $params[3] = array($fileTypeID, 'Integer');
325 }
326
327 if ($fileID !== NULL) {
328 $sql .= " AND CF.id = %4";
329 $params[4] = array($fileID, 'Integer');
330 }
331
332 return array($sql, $params);
333 }
334
335 static function buildAttachment(&$form, $entityTable, $entityID = NULL, $numAttachments = NULL, $ajaxDelete = FALSE) {
336
337 if (!$numAttachments) {
338 $numAttachments = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'max_attachments');
339 }
340 // Assign maxAttachments count to template for help message
341 $form->assign('maxAttachments', $numAttachments);
342
343 $config = CRM_Core_Config::singleton();
344 // set default max file size as 2MB
345 $maxFileSize = $config->maxFileSize ? $config->maxFileSize : 2;
346
347 $currentAttachmentInfo = self::getEntityFile($entityTable, $entityID, TRUE);
348 $totalAttachments = 0;
349 if ($currentAttachmentInfo) {
350 $totalAttachments = count($currentAttachmentInfo);
351 $form->add('checkbox', 'is_delete_attachment', ts('Delete All Attachment(s)'));
352 $form->assign('currentAttachmentInfo', $currentAttachmentInfo);
353 }
354 else {
355 $form->assign('currentAttachmentInfo', NULL);
356 }
357
358 if ( $totalAttachments ) {
359 if ($totalAttachments >= $numAttachments) {
360 $numAttachments = 0;
361 }
362 else {
363 $numAttachments -= $totalAttachments;
364 }
365 }
366
367 $form->assign('numAttachments', $numAttachments);
368
369 $tags = CRM_Core_BAO_Tag::getTags('civicrm_file');
370
371 // get tagset info
372 $parentNames = CRM_Core_BAO_Tag::getTagSet('civicrm_file');
373
374 // add attachments
375 for ($i = 1; $i <= $numAttachments; $i++) {
376 $form->addElement('file', "attachFile_$i", ts('Attach File'), 'size=30 maxlength=60');
377 $form->addUploadElement("attachFile_$i");
378 $form->setMaxFileSize($maxFileSize * 1024 * 1024);
379 $form->addRule("attachFile_$i",
380 ts('File size should be less than %1 MByte(s)',
381 array(1 => $maxFileSize)
382 ),
383 'maxfilesize',
384 $maxFileSize * 1024 * 1024
385 );
386 $form->addElement('text', "attachDesc_$i", ts('Description'), 'size=40 maxlength=255');
387
388 if (!empty($tags)) {
389 $form->add('select', "tag_$i", ts('Tags'), $tags, FALSE,
390 array('id' => "tags_$i", 'multiple' => 'multiple', 'title' => ts('- select -'))
391 );
392 }
393 }
394
395 // build tagset widget
396 CRM_Core_Form_Tag::buildQuickForm($form, $parentNames, 'civicrm_file', NULL, TRUE, TRUE, FALSE);
397 }
398
399 /**
400 * Function to return a clean url string and the number of attachment for a
401 * given entityTable, entityID
402 *
403 * @param $entityTable string The entityTable to which the file is attached
404 * @param $entityID int The id of the object in the above entityTable
405 * @param $separator string The string separator where to implode the urls
406 *
407 * @return array An array with 2 elements. The string and the number of attachments
408 * @static
409 */
410 static function attachmentInfo($entityTable, $entityID, $separator = '<br />') {
411 if (!$entityID) {
412 return NULL;
413 }
414
415 $currentAttachments = self::getEntityFile($entityTable, $entityID);
416 if (!empty($currentAttachments)) {
417 $currentAttachmentURL = array();
418 foreach ($currentAttachments as $fileID => $attach) {
419 $currentAttachmentURL[] = $attach['href'];
420 }
421 return implode($separator, $currentAttachmentURL);
422 }
423 return NULL;
424 }
425
426 static function formatAttachment(
427 &$formValues,
428 &$params,
429 $entityTable,
430 $entityID = NULL
431 ) {
432
433 // delete current attachments if applicable
434 if ($entityID && !empty($formValues['is_delete_attachment'])) {
435 CRM_Core_BAO_File::deleteEntityFile($entityTable, $entityID);
436 }
437
438 $numAttachments = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'max_attachments');
439
440 $now = date('Ymdhis');
441
442 // setup all attachments
443 for ($i = 1; $i <= $numAttachments; $i++) {
444 $attachName = "attachFile_$i";
445 $attachDesc = "attachDesc_$i";
446 $attachTags = "tag_$i";
447 $attachFreeTags = "attachment_taglist_$i";
448 if (isset($formValues[$attachName]) && !empty($formValues[$attachName])) {
449 // add static tags if selects
450 $tagParams = array();
451 if (!empty($formValues[$attachTags])) {
452 foreach ($formValues[$attachTags] as $tag) {
453 $tagParams[$tag] = 1;
454 }
455 }
456
457 // we dont care if the file is empty or not
458 // CRM-7448
459 $fileParams = array(
460 'uri' => $formValues[$attachName]['name'],
461 'type' => $formValues[$attachName]['type'],
462 'location' => $formValues[$attachName]['name'],
463 'description' => $formValues[$attachDesc],
464 'upload_date' => $now,
465 'tag' => $tagParams,
466 'attachment_taglist' => CRM_Utils_Array::value($attachFreeTags, $formValues, array())
467 );
468
469 $params[$attachName] = $fileParams;
470 }
471 }
472 }
473
474 static function processAttachment(&$params, $entityTable, $entityID) {
475 $numAttachments = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'max_attachments');
476
477 for ($i = 1; $i <= $numAttachments; $i++) {
478 if (
479 isset($params["attachFile_$i"]) &&
480 is_array($params["attachFile_$i"])
481 ) {
482 self::filePostProcess(
483 $params["attachFile_$i"]['location'],
484 NULL,
485 $entityTable,
486 $entityID,
487 NULL,
488 TRUE,
489 $params["attachFile_$i"],
490 "attachFile_$i",
491 $params["attachFile_$i"]['type']
492 );
493 }
494 }
495 }
496
497 static function uploadNames() {
498 $numAttachments = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'max_attachments');
499
500 $names = array();
501 for ($i = 1; $i <= $numAttachments; $i++) {
502 $names[] = "attachFile_{$i}";
503 }
504 $names[] = 'uploadFile';
505 return $names;
506 }
507
508 /*
509 * Function to copy/attach an existing file to a different entity
510 * table and id.
511 */
512 static function copyEntityFile($oldEntityTable, $oldEntityId, $newEntityTable, $newEntityId) {
513 $oldEntityFile = new CRM_Core_DAO_EntityFile();
514 $oldEntityFile->entity_id = $oldEntityId;
515 $oldEntityFile->entity_table = $oldEntityTable;
516 $oldEntityFile->find();
517
518 while ($oldEntityFile->fetch()) {
519 $newEntityFile = new CRM_Core_DAO_EntityFile();
520 $newEntityFile->entity_id = $newEntityId;
521 $newEntityFile->entity_table = $newEntityTable;
522 $newEntityFile->file_id = $oldEntityFile->file_id;
523 $newEntityFile->save();
524 }
525 }
526
527 static function deleteURLArgs($entityTable, $entityID, $fileID) {
528 $params['entityTable'] = $entityTable;
529 $params['entityID'] = $entityID;
530 $params['fileID'] = $fileID;
531
532 $signer = new CRM_Utils_Signer(CRM_Core_Key::privateKey(), self::$_signableFields);
533 $params['_sgn'] = $signer->sign($params);
534 return CRM_Utils_System::makeQueryString($params);
535 }
536
537 /**
538 * function to delete a file attachment from an entity table / entity ID
539 *
540 * @static
541 * @access public
542 */
543 static function deleteAttachment( ) {
544 $params = array( );
545 $params['entityTable'] = CRM_Utils_Request::retrieve( 'entityTable', 'String' , CRM_Core_DAO::$_nullObject, TRUE);
546 $params['entityID'] = CRM_Utils_Request::retrieve( 'entityID' , 'Positive', CRM_Core_DAO::$_nullObject, TRUE);
547 $params['fileID'] = CRM_Utils_Request::retrieve( 'fileID' , 'Positive', CRM_Core_DAO::$_nullObject, TRUE);
548
549 $signature = CRM_Utils_Request::retrieve( '_sgn', 'String', CRM_Core_DAO::$_nullObject, TRUE);
550
551 $signer = new CRM_Utils_Signer(CRM_Core_Key::privateKey(), self::$_signableFields);
552 if (! $signer->validate($signature, $params)) {
553 CRM_Core_Error::fatal('Request signature is invalid');
554 }
555
556 CRM_Core_BAO_File::deleteEntityFile($params['entityTable'], $params['entityID'], NULL, $params['fileID']);
557 }
558
559
560 /**
561 * function to display paper icon for a file attachment -- CRM-13624
562 *
563 * @param $entityTable string The entityTable to which the file is attached. eg "civicrm_contact", "civicrm_note", "civicrm_activity"
564 * @param $entityID int The id of the object in the above entityTable
565 *
566 * @return array|NULL list of HTML snippets; one HTML snippet for each attachment. If none found, then NULL
567 *
568 * @static
569 * @access public
570 */
571 static function paperIconAttachment( $entityTable, $entityID ) {
572 if (empty($entityTable) || !$entityID) {
573 $results = NULL;
574 return $results;
575 }
576 $currentAttachmentInfo = self::getEntityFile( $entityTable, $entityID );
577 foreach($currentAttachmentInfo as $fileKey => $fileValue) {
578 $fileID = $fileValue['fileID'];
579 $fileType = $fileValue['mime_type'];
580 $eid = $entityID;
581 if($fileID) {
582 if ($fileType == 'image/jpeg' ||
583 $fileType == 'image/pjpeg' ||
584 $fileType == 'image/gif' ||
585 $fileType == 'image/x-png' ||
586 $fileType == 'image/png'
587 ) {
588 $url = $fileValue['url'];
589 $alt = $fileValue['cleanName'];
590 $file_url[$fileID] = "
591 <a href=\"$url\" class='crm-image-popup'>
592 <div class='icon paper-icon' title=\"$alt\" alt=\"$alt\"></div>
593 </a>";
594 // for non image files
595 }
596 else {
597 $url = $fileValue['url'];
598 $alt = $fileValue['cleanName'];
599 $file_url[$fileID] = "<a href=\"$url\"><div class='icon paper-icon' title=\"$alt\" alt=\"$alt\"></div></a>";
600 }
601 }
602 }
603 if(empty($file_url)) {
604 $results = NULL;
605 }
606 else {
607 $results = $file_url;
608 }
609 return $results;
610 }
611
612 }