/** * Validate and process the request. * * This is separated from the run() method for testing purposes. * * This method serves as a router to other methods named after the type of * webhook we're called with. * * Methods may return data for mailchimp, or may throw RuntimeException * objects, the error code of which will be used for the response. * So you can throw a `RuntimeException("Invalid webhook configuration", 500);` * to tell mailchimp the webhook failed, but you can equally throw a * `RuntimeException("soft fail", 200)` which will not tell Mailchimp there * was any problem. Mailchimp retries if there was a problem. * * If an exception is thrown, it is logged. @todo where? * * @return array with two values: $response_code, $response_object. */ public function processRequest($expected_key, $key, $request_data) { // Check CMS's permission for (presumably) anonymous users. if (CRM_Core_Config::singleton()->userPermissionClass->isModulePermissionSupported() && !CRM_Mailchimp_Permission::check('allow webhook posts')) { throw new RuntimeException("Missing allow webhook posts permission.", 500); } // Check the 2 keys exist and match. if (!$key || !$expected_key || $key != $expected_key) { throw new RuntimeException("Invalid security key.", 500); } if (empty($request_data['data']['list_id']) || empty($request_data['type']) || !in_array($request_data['type'], ['subscribe', 'unsubscribe', 'profile', 'upemail', 'cleaned'])) { // We are not programmed to respond to this type of request. // But maybe Mailchimp introduced something new, so we'll just say OK. throw new RuntimeException("Missing or invalid data in request: " . json_encode($request_data), 200); } $method = $request_data['type']; // Check list config at Mailchimp. $list_id = $request_data['data']['list_id']; $api = CRM_Mailchimp_Utils::getMailchimpApi(); $result = $api->get("/lists/{$list_id}/webhooks")->data->webhooks; $url = CRM_Mailchimp_Utils::getWebhookUrl(); // Find our webhook and check for a particularly silly configuration. foreach ($result as $webhook) { if ($webhook->url == $url) { if ($webhook->sources->api) { // To continue could cause a nasty loop. throw new RuntimeException("The list '{$list_id}' is not configured correctly at Mailchimp. It has the 'API' source set so processing this using the API could cause a loop.", 500); } } } // Disable post hooks. We're updating *from* Mailchimp so we don't want // to fire anything *at* Mailchimp. CRM_Mailchimp_Utils::$post_hook_enabled = FALSE; // Pretty much all the request methods use these: $this->sync = new CRM_Mailchimp_Sync($request_data['data']['list_id']); $this->request_data = $request_data['data']; // Call the appropriate handler method. CRM_Mailchimp_Utils::checkDebug("Webhook: {$method} with request data: " . json_encode($request_data)); $this->{$method}(); // re-set the post hooks. CRM_Mailchimp_Utils::$post_hook_enabled = TRUE; // Return OK response. return [200, NULL]; }
function run() { $my_key = CRM_Core_BAO_Setting::getItem(self::MC_SETTING_GROUP, 'security_key', NULL, FALSE); CRM_Mailchimp_Utils::checkDebug('CRM_Mailchimp_Page_WebHook run $my_key= ', $my_key); if (CRM_Core_Config::singleton()->userPermissionClass->isModulePermissionSupported() && !CRM_Mailchimp_Permission::check('allow webhook posts')) { CRM_Core_Error::fatal(); } // Check the key // @todo is this a DOS attack vector? seems a lot of work for saying 403, go away, to a robot! if (!isset($_GET['key']) || $_GET['key'] != $my_key) { CRM_Core_Error::fatal(); } if (!empty($_POST['data']['list_id']) && !empty($_POST['type'])) { $requestType = $_POST['type']; $requestData = $_POST['data']; switch ($requestType) { case 'subscribe': case 'unsubscribe': case 'profile': // Create/Update contact details in CiviCRM $delay = $requestType == 'profile'; $contactID = CRM_Mailchimp_Utils::updateContactDetails($requestData['merges'], $delay); $contactArray = array($contactID); // Subscribe/Unsubscribe to related CiviCRM groups self::manageCiviCRMGroupSubcription($contactID, $requestData, $requestType); CRM_Mailchimp_Utils::checkDebug('Start - CRM_Mailchimp_Page_WebHook run $_POST= ', $_POST); CRM_Mailchimp_Utils::checkDebug('Start - CRM_Mailchimp_Page_WebHook run $contactID= ', $contactID); CRM_Mailchimp_Utils::checkDebug('Start - CRM_Mailchimp_Page_WebHook run $requestData= ', $requestData); CRM_Mailchimp_Utils::checkDebug('Start - CRM_Mailchimp_Page_WebHook run $requestType= ', $requestType); break; case 'upemail': // Mailchimp Email Update event // Try to find the email address $email = new CRM_Core_BAO_Email(); $email->get('email', $requestData['old_email']); CRM_Mailchimp_Utils::checkDebug('CRM_Mailchimp_Page_WebHook run- case upemail $requestData[old_email]= ', $requestData['old_email']); // If the Email was found. if (!empty($email->contact_id)) { $email->email = $requestData['new_email']; $email->save(); CRM_Mailchimp_Utils::checkDebug('CRM_Mailchimp_Page_WebHook run- case upemail inside condition $requestData[new_email]= ', $requestData['new_email']); } break; case 'cleaned': // Try to find the email address $email = new CRM_Core_BAO_Email(); $email->get('email', $requestData['email']); CRM_Mailchimp_Utils::checkDebug('CRM_Mailchimp_Page_WebHook run - case cleaned $requestData[new_email]= ', $requestData['email']); // If the Email was found. if (!empty($email->contact_id)) { $email->on_hold = 1; $email->holdEmail($email); $email->save(); CRM_Mailchimp_Utils::checkDebug('CRM_Mailchimp_Page_WebHook run - case cleaned inside condition $email= ', $email); CRM_Mailchimp_Utils::checkDebug('CRM_Mailchimp_Page_WebHook run - case cleaned inside condition $requestData[new_email]= ', $requestData['email']); } break; default: // unhandled webhook CRM_Mailchimp_Utils::checkDebug('End- CRM_Mailchimp_Page_WebHook run $contactID= ', $contactID); CRM_Mailchimp_Utils::checkDebug('End- CRM_Mailchimp_Page_WebHook run $requestData= ', $requestData); CRM_Mailchimp_Utils::checkDebug('End- CRM_Mailchimp_Page_WebHook run $requestType= ', $requestType); CRM_Mailchimp_Utils::checkDebug('End - CRM_Mailchimp_Page_WebHook run $email= ', $email); } } // Return the JSON output header('Content-type: application/json'); $data = NULL; // We should ideally throw some status print json_encode($data); CRM_Utils_System::civiExit(); }