3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2015 |
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 +--------------------------------------------------------------------+
29 * CRM_Core_Page_AJAX_Attachment defines an end-point for AJAX operations which upload file-attachments.
31 * To upload a new file, submit a POST (multi-part encoded) to "civicrm/ajax/attachment". Inputs:
32 * - POST['entity_table']: string
33 * - POST['entity_id']: int
34 * - POST['attachment_token']: string
35 * - FILES[*]: all of the files to attach to the entity
37 * The response is a JSON document. Foreach item in FILES, there's a corresponding record in the response which
38 * describes the success or failure.
40 * Note: The permission requirements are determined by the underlying Attachment API.
42 class CRM_Core_Page_AJAX_Attachment
{
44 const ATTACHMENT_TOKEN_TTL
= 10800; // 3hr; 3*60*60
49 public static function attachFile() {
50 $result = self
::_attachFile($_POST, $_FILES, $_SERVER);
51 self
::sendResponse($result);
58 * Like global $_FILES.
59 * @param array $server
60 * Like global $_SERVER.
63 public static function _attachFile($post, $files, $server) {
64 $config = CRM_Core_Config
::singleton();
67 foreach ($files as $key => $file) {
68 if (!$config->debug
&& !self
::checkToken($post['crm_attachment_token'])) {
69 require_once 'api/v3/utils.php';
70 $results[$key] = civicrm_api3_create_error("SECURITY ALERT: Attaching files via AJAX requires a recent, valid token.",
72 'IP' => $server['REMOTE_ADDR'],
73 'level' => 'security',
74 'referer' => $server['HTTP_REFERER'],
75 'reason' => 'CSRF suspected',
79 elseif ($file['error']) {
80 $results[$key] = civicrm_api3_create_error("Upload failed (code=" . $file['error'] . ")");
83 CRM_Core_Transaction
::create(TRUE)
84 ->run(function (CRM_Core_Transaction
$tx) use ($key, $file, $post, &$results) {
85 // We want check_permissions=1 while creating the DB record and check_permissions=0 while moving upload,
86 // so split the work across two api calls.
89 if (isset($file['name'])) {
90 $params['name'] = $file['name'];
92 if (isset($file['type'])) {
93 $params['mime_type'] = $file['type'];
95 foreach (array('entity_table', 'entity_id', 'description') as $field) {
96 if (isset($post[$field])) {
97 $params[$field] = $post[$field];
100 $params['version'] = 3;
101 $params['check_permissions'] = 1;
102 $params['content'] = '';
103 $results[$key] = civicrm_api('Attachment', 'create', $params);
105 if (!$results[$key]['is_error']) {
107 'id' => $results[$key]['id'],
109 'options.move-file' => $file['tmp_name'],
110 // note: in this second call, check_permissions==false
112 $moveResult = civicrm_api('Attachment', 'create', $moveParams);
113 if ($moveResult['is_error']) {
114 $results[$key] = $moveResult;
126 * @param array $result
127 * List of API responses, keyed by file.
129 public static function sendResponse($result) {
131 foreach ($result as $item) {
132 $isError = $isError ||
$item['is_error'];
136 $sapi_type = php_sapi_name();
137 if (substr($sapi_type, 0, 3) == 'cgi') {
138 CRM_Utils_System
::setHttpHeader("Status", "500 Internal Server Error");
141 header("HTTP/1.1 500 Internal Server Error");
145 CRM_Utils_JSON
::output(array_merge($result));
151 public static function createToken() {
152 $signer = new CRM_Utils_Signer(CRM_Core_Key
::privateKey(), array('for', 'ts'));
153 $ts = CRM_Utils_Time
::getTimeRaw();
154 return $signer->sign(array(
155 'for' => 'crmAttachment',
161 * @param string $token
162 * A token supplied by the user.
164 * TRUE if the token is valid for submitting attachments
167 public static function checkToken($token) {
168 list ($signature, $ts) = explode(';;;', $token);
169 $signer = new CRM_Utils_Signer(CRM_Core_Key
::privateKey(), array('for', 'ts'));
170 if (!is_numeric($ts) || CRM_Utils_Time
::getTimeRaw() > $ts + self
::ATTACHMENT_TOKEN_TTL
) {
173 return $signer->validate($signature, array(
174 'for' => 'crmAttachment',