Merge pull request #12774 from colemanw/pledgePicker
[civicrm-core.git] / CRM / Core / Page / AJAX / Attachment.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2018 |
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 * CRM_Core_Page_AJAX_Attachment defines an end-point for AJAX operations which upload file-attachments.
30 *
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
36 *
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.
39 *
40 * Note: The permission requirements are determined by the underlying Attachment API.
41 */
42 class CRM_Core_Page_AJAX_Attachment {
43
44 const ATTACHMENT_TOKEN_TTL = 10800; // 3hr; 3*60*60
45
46 /**
47 * (Page Callback)
48 */
49 public static function attachFile() {
50 $result = self::_attachFile($_POST, $_FILES, $_SERVER);
51 self::sendResponse($result);
52 }
53
54 /**
55 * @param array $post
56 * Like global $_POST.
57 * @param array $files
58 * Like global $_FILES.
59 * @param array $server
60 * Like global $_SERVER.
61 * @return array
62 */
63 public static function _attachFile($post, $files, $server) {
64 $config = CRM_Core_Config::singleton();
65 $results = array();
66
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.",
71 array(
72 'IP' => $server['REMOTE_ADDR'],
73 'level' => 'security',
74 'referer' => $server['HTTP_REFERER'],
75 'reason' => 'CSRF suspected',
76 )
77 );
78 }
79 elseif ($file['error']) {
80 $results[$key] = civicrm_api3_create_error("Upload failed (code=" . $file['error'] . ")");
81 }
82 else {
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.
87
88 $params = array();
89 if (isset($file['name'])) {
90 $params['name'] = $file['name'];
91 }
92 if (isset($file['type'])) {
93 $params['mime_type'] = $file['type'];
94 }
95 foreach (array('entity_table', 'entity_id', 'description') as $field) {
96 if (isset($post[$field])) {
97 $params[$field] = $post[$field];
98 }
99 }
100 $params['version'] = 3;
101 $params['check_permissions'] = 1;
102 $params['content'] = '';
103 $results[$key] = civicrm_api('Attachment', 'create', $params);
104
105 if (!$results[$key]['is_error']) {
106 $moveParams = array(
107 'id' => $results[$key]['id'],
108 'version' => 3,
109 'options.move-file' => $file['tmp_name'],
110 // note: in this second call, check_permissions==false
111 );
112 $moveResult = civicrm_api('Attachment', 'create', $moveParams);
113 if ($moveResult['is_error']) {
114 $results[$key] = $moveResult;
115 $tx->rollback();
116 }
117 }
118 });
119 }
120 }
121
122 return $results;
123 }
124
125 /**
126 * @param array $result
127 * List of API responses, keyed by file.
128 */
129 public static function sendResponse($result) {
130 $isError = FALSE;
131 foreach ($result as $item) {
132 $isError = $isError || $item['is_error'];
133 }
134
135 if ($isError) {
136 $sapi_type = php_sapi_name();
137 if (substr($sapi_type, 0, 3) == 'cgi') {
138 CRM_Utils_System::setHttpHeader("Status", "500 Internal Server Error");
139 }
140 else {
141 header("HTTP/1.1 500 Internal Server Error");
142 }
143 }
144
145 CRM_Utils_JSON::output(array_merge($result));
146 }
147
148 /**
149 * @return string
150 */
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',
156 'ts' => $ts,
157 )) . ';;;' . $ts;
158 }
159
160 /**
161 * @param string $token
162 * A token supplied by the user.
163 * @return bool
164 * TRUE if the token is valid for submitting attachments
165 * @throws Exception
166 */
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) {
171 return FALSE;
172 }
173 return $signer->validate($signature, array(
174 'for' => 'crmAttachment',
175 'ts' => $ts,
176 ));
177 }
178
179 }