/**
  * Sets a mock Mailchimp API that will pass the webhook is configured
  * correctly test.
  *
  * This code is used in many methods.
  *
  * @return Prophecy.
  */
 protected function prepMockForWebhookConfig()
 {
     // Make mock API that will return a webhook with the sources.API setting
     // set, which is wrong.
     $api_prophecy = $this->prophesize('CRM_Mailchimp_Api3');
     CRM_Mailchimp_Utils::setMailchimpApi($api_prophecy->reveal());
     $url = CRM_Mailchimp_Utils::getWebhookUrl();
     $api_prophecy->get("/lists/dummylistid/webhooks", Argument::any())->shouldBeCalled()->willReturn(json_decode('{"http_code":200,"data":{"webhooks":[{"url":"' . $url . '","sources":{"api":false}}]}}'));
     return $api_prophecy;
 }
 /**
  * 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];
 }
 /**
  * Configure webhook with Mailchimp.
  *
  * Returns a list of messages to display to the user.
  *
  * @param string $list_id Mailchimp List Id.
  * @param bool $dry_run   If set no changes are made.
  * @return array
  */
 public static function configureList($list_id, $dry_run = FALSE)
 {
     $api = CRM_Mailchimp_Utils::getMailchimpApi();
     $expected = ['url' => CRM_Mailchimp_Utils::getWebhookUrl(), 'events' => ['subscribe' => TRUE, 'unsubscribe' => TRUE, 'profile' => TRUE, 'cleaned' => TRUE, 'upemail' => TRUE, 'campaign' => FALSE], 'sources' => ['user' => TRUE, 'admin' => TRUE, 'api' => FALSE]];
     $verb = $dry_run ? 'Need to change ' : 'Changed ';
     try {
         $result = $api->get("/lists/{$list_id}/webhooks");
         $webhooks = empty($result->data->webhooks) ? NULL : $result->data->webhooks;
         //$webhooks = $api->get("/lists/$list_id/webhooks")->data->webhooks;
         if (empty($webhooks)) {
             $messages[] = ts(($dry_run ? 'Need to create' : 'Created') . ' a webhook at Mailchimp');
         } else {
             // Existing webhook(s) - check thoroughly.
             if (count($webhooks) > 1) {
                 // Unusual case, leave it alone.
                 $messages[] = "Mailchimp list {$list_id} has more than one webhook configured. This is unusual, and so CiviCRM has not made any changes. Please ensure the webhook is set up correctly.";
                 return $messages;
             }
             // Got a single webhook, check it looks right.
             $messages = [];
             // Correct URL?
             if ($webhooks[0]->url != $expected['url']) {
                 $messages[] = ts($verb . 'webhook URL from %1 to %2', [1 => $webhooks[0]->url, 2 => $expected['url']]);
             }
             // Correct sources?
             foreach ($expected['sources'] as $source => $expected_value) {
                 if ($webhooks[0]->sources->{$source} != $expected_value) {
                     $messages[] = ts($verb . 'webhook source %1 from %2 to %3', [1 => $source, 2 => (int) $webhooks[0]->sources->{$source}, 3 => (int) $expected_value]);
                 }
             }
             // Correct events?
             foreach ($expected['events'] as $event => $expected_value) {
                 if ($webhooks[0]->events->{$event} != $expected_value) {
                     $messages[] = ts($verb . 'webhook event %1 from %2 to %3', [1 => $event, 2 => (int) $webhooks[0]->events->{$event}, 3 => (int) $expected_value]);
                 }
             }
             if (empty($messages)) {
                 // All fine.
                 return;
             }
             if (!$dry_run) {
                 // As of May 2016, there doesn't seem to be an update method for
                 // webhooks, so we just delete this and add another.
                 $api->delete("/lists/{$list_id}/webhooks/" . $webhooks[0]->id);
             }
         }
         if (!$dry_run) {
             // Now create the proper one.
             $result = $api->post("/lists/{$list_id}/webhooks", $expected);
         }
     } catch (CRM_Mailchimp_RequestErrorException $e) {
         if ($e->request->method == 'GET' && $e->response->http_code == 404) {
             $messages[] = ts("The Mailchimp list that this once worked with has been deleted");
         } else {
             $messages[] = ts("Problems updating or fetching from Mailchimp. Please manually check the configuration. ") . $e->getMessage();
         }
     } catch (CRM_Mailchimp_NetworkErrorException $e) {
         $messages[] = ts("Problems (possibly temporary) talking to Mailchimp. ") . $e->getMessage();
     }
     return $messages;
 }