| 1 | <?php |
| 2 | |
| 3 | /* |
| 4 | +--------------------------------------------------------------------+ |
| 5 | | Copyright CiviCRM LLC. All rights reserved. | |
| 6 | | | |
| 7 | | This work is published under the GNU AGPLv3 license with some | |
| 8 | | permitted exceptions and without any warranty. For full license | |
| 9 | | and copyright information, see https://civicrm.org/licensing | |
| 10 | +--------------------------------------------------------------------+ |
| 11 | */ |
| 12 | |
| 13 | /** |
| 14 | * |
| 15 | * @package CRM |
| 16 | * @copyright CiviCRM LLC https://civicrm.org/licensing |
| 17 | */ |
| 18 | class CRM_Api4_Page_AJAX extends CRM_Core_Page { |
| 19 | |
| 20 | /** |
| 21 | * Handler for api4 ajax requests |
| 22 | */ |
| 23 | public function run() { |
| 24 | $config = CRM_Core_Config::singleton(); |
| 25 | if (!$config->debug && (!array_key_exists('HTTP_X_REQUESTED_WITH', $_SERVER) || |
| 26 | $_SERVER['HTTP_X_REQUESTED_WITH'] != "XMLHttpRequest" |
| 27 | ) |
| 28 | ) { |
| 29 | $response = [ |
| 30 | 'error_code' => 401, |
| 31 | 'error_message' => "SECURITY ALERT: Ajax requests can only be issued by javascript clients, eg. CRM.api4().", |
| 32 | ]; |
| 33 | Civi::log()->debug("SECURITY ALERT: Ajax requests can only be issued by javascript clients, eg. CRM.api4().", |
| 34 | [ |
| 35 | 'IP' => $_SERVER['REMOTE_ADDR'], |
| 36 | 'level' => 'security', |
| 37 | 'referer' => $_SERVER['HTTP_REFERER'], |
| 38 | 'reason' => 'CSRF suspected', |
| 39 | ] |
| 40 | ); |
| 41 | CRM_Utils_System::setHttpHeader('Content-Type', 'application/json'); |
| 42 | echo json_encode($response); |
| 43 | CRM_Utils_System::civiExit(); |
| 44 | } |
| 45 | if ($_SERVER['REQUEST_METHOD'] == 'GET' && |
| 46 | strtolower(substr($this->urlPath[4], 0, 3)) != 'get') { |
| 47 | $response = [ |
| 48 | 'error_code' => 400, |
| 49 | 'error_message' => "SECURITY: All requests that modify the database must be http POST, not GET.", |
| 50 | ]; |
| 51 | Civi::log()->debug("SECURITY: All requests that modify the database must be http POST, not GET.", |
| 52 | [ |
| 53 | 'IP' => $_SERVER['REMOTE_ADDR'], |
| 54 | 'level' => 'security', |
| 55 | 'referer' => $_SERVER['HTTP_REFERER'], |
| 56 | 'reason' => 'Destructive HTTP GET', |
| 57 | ] |
| 58 | ); |
| 59 | CRM_Utils_System::setHttpHeader('Content-Type', 'application/json'); |
| 60 | echo json_encode($response); |
| 61 | CRM_Utils_System::civiExit(); |
| 62 | } |
| 63 | try { |
| 64 | // Call multiple |
| 65 | if (empty($this->urlPath[3])) { |
| 66 | $calls = CRM_Utils_Request::retrieve('calls', 'String', CRM_Core_DAO::$_nullObject, TRUE, NULL, 'POST'); |
| 67 | $calls = json_decode($calls, TRUE); |
| 68 | $response = []; |
| 69 | foreach ($calls as $index => $call) { |
| 70 | $response[$index] = call_user_func_array([$this, 'execute'], $call); |
| 71 | } |
| 72 | } |
| 73 | // Call single |
| 74 | else { |
| 75 | $entity = $this->urlPath[3]; |
| 76 | $action = $this->urlPath[4]; |
| 77 | $params = CRM_Utils_Request::retrieve('params', 'String'); |
| 78 | $params = $params ? json_decode($params, TRUE) : []; |
| 79 | $index = CRM_Utils_Request::retrieve('index', 'String'); |
| 80 | $response = $this->execute($entity, $action, $params, $index); |
| 81 | } |
| 82 | } |
| 83 | catch (Exception $e) { |
| 84 | http_response_code(500); |
| 85 | $response = []; |
| 86 | if (CRM_Core_Permission::check('view debug output')) { |
| 87 | $response['error_code'] = $e->getCode(); |
| 88 | $response['error_message'] = $e->getMessage(); |
| 89 | if (!empty($params['debug'])) { |
| 90 | if (method_exists($e, 'getUserInfo')) { |
| 91 | $response['debug']['info'] = $e->getUserInfo(); |
| 92 | } |
| 93 | $cause = method_exists($e, 'getCause') ? $e->getCause() : $e; |
| 94 | if ($cause instanceof \DB_Error) { |
| 95 | $response['debug']['db_error'] = \DB::errorMessage($cause->getCode()); |
| 96 | $response['debug']['sql'][] = $cause->getDebugInfo(); |
| 97 | } |
| 98 | if (\Civi::settings()->get('backtrace')) { |
| 99 | // Would prefer getTrace() but that causes json_encode to bomb |
| 100 | $response['debug']['backtrace'] = $e->getTraceAsString(); |
| 101 | } |
| 102 | } |
| 103 | } |
| 104 | } |
| 105 | CRM_Utils_System::setHttpHeader('Content-Type', 'application/json'); |
| 106 | echo json_encode($response); |
| 107 | CRM_Utils_System::civiExit(); |
| 108 | } |
| 109 | |
| 110 | /** |
| 111 | * Run api call & prepare result for json encoding |
| 112 | * |
| 113 | * @param string $entity |
| 114 | * @param string $action |
| 115 | * @param array $params |
| 116 | * @param string $index |
| 117 | * @return array |
| 118 | */ |
| 119 | protected function execute($entity, $action, $params = [], $index = NULL) { |
| 120 | $params['checkPermissions'] = TRUE; |
| 121 | |
| 122 | // Handle numeric indexes later so we can get the count |
| 123 | $itemAt = CRM_Utils_Type::validate($index, 'Integer', FALSE); |
| 124 | |
| 125 | $result = civicrm_api4($entity, $action, $params, isset($itemAt) ? NULL : $index); |
| 126 | |
| 127 | // Convert arrayObject into something more suitable for json |
| 128 | $vals = ['values' => isset($itemAt) ? $result->itemAt($itemAt) : (array) $result]; |
| 129 | foreach (get_class_vars(get_class($result)) as $key => $val) { |
| 130 | $vals[$key] = $result->$key; |
| 131 | } |
| 132 | $vals['count'] = $result->count(); |
| 133 | return $vals; |
| 134 | } |
| 135 | |
| 136 | } |