/**
  * @param SymfonyStyle|null $io
  */
 public function updateCode(SymfonyStyle $io = null)
 {
     $io->title('CampaignChain Data Update');
     if (empty($this->versions)) {
         $io->warning('No code updater Service found, maybe you didn\'t enable a bundle?');
         return;
     }
     $io->comment('The following data versions will be updated');
     $migratedVersions = array_map(function (DataUpdateVersion $version) {
         return $version->getVersion();
     }, $this->em->getRepository('CampaignChainUpdateBundle:DataUpdateVersion')->findAll());
     $updated = false;
     foreach ($this->versions as $version => $class) {
         if (in_array($version, $migratedVersions)) {
             continue;
         }
         $io->section('Version ' . $class->getVersion());
         $io->listing($class->getDescription());
         $io->text('Begin data update');
         $result = $class->execute($io);
         if ($result) {
             $dbVersion = new DataUpdateVersion();
             $dbVersion->setVersion($version);
             $this->em->persist($dbVersion);
             $this->em->flush();
             $io->text('Data update finished');
         }
         $updated = true;
     }
     if (!$updated) {
         $io->success('All data is up to date.');
     } else {
         $io->success('Every data version has been updated.');
     }
 }
 public function execute(SymfonyStyle $io = null)
 {
     $currentProfiles = $this->em->getRepository('CampaignChainLocationGoogleAnalyticsBundle:Profile')->findAll();
     if (empty($currentProfiles)) {
         $io->text('There is no Profile entity to update');
         return true;
     }
     foreach ($currentProfiles as $profile) {
         if (substr($profile->getProfileId(), 0, 2) != 'UA') {
             continue;
         }
         $profile->setPropertyId($profile->getProfileId());
         $gaProfileUrl = $profile->getLocation()->getUrl();
         $google_base_url = 'https:\\/\\/www.google.com\\/analytics\\/web\\/#report\\/visitors-overview\\/a' . $profile->getAccountId() . 'w\\d+p';
         $pattern = '/' . $google_base_url . '(.*)/';
         preg_match($pattern, $gaProfileUrl, $matches);
         if (!empty($matches) && count($matches) == 2) {
             $profile->setProfileId($matches[1]);
             $profile->setIdentifier($profile->getProfileId());
         }
         $this->em->persist($profile);
     }
     $this->em->flush();
     return true;
 }
 /**
  * @param $id
  * @return \CampaignChain\Operation\LinkedInBundle\Entity\NewsItem
  * @throws \Exception
  * @deprecated Use getContent(Operation $operation) instead.
  */
 public function getNewsItemByOperation($id)
 {
     $newsitem = $this->em->getRepository('CampaignChainOperationLinkedInBundle:NewsItem')->findOneByOperation($id);
     if (!$newsitem) {
         throw new \Exception('No news item found by operation id ' . $id);
     }
     return $newsitem;
 }
 /**
  * @param string $operationId
  * @return string
  * @throws \Exception
  */
 public function execute($operationId)
 {
     /** @var NewsItem $newsItem */
     $newsItem = $this->em->getRepository('CampaignChainOperationLinkedInBundle:NewsItem')->findOneByOperation($operationId);
     if (!$newsItem) {
         throw new \Exception('No news item found for an operation with ID: ' . $operationId);
     }
     //have images?
     $images = $this->em->getRepository('CampaignChainHookImageBundle:Image')->getImagesForOperation($newsItem->getOperation());
     // if the message does not contain a url, we need to skip the content block
     if (is_null($newsItem->getLinkUrl())) {
         // LinkedIn accepts an image link only if we also provided a submitted URL.
         if ($images) {
             throw new \Exception('To include an image, you must also provide a URL in the text message.');
             return self::STATUS_WARNING;
         }
         $content = ['comment' => $newsItem->getMessage(), 'visibility' => ['code' => 'anyone']];
     } else {
         /*
          * process urls and add tracking
          * important: both the urls in the message and submitted url field must be identical
          */
         $newsItem->setLinkUrl($this->ctaService->processCTAs($newsItem->getLinkUrl(), $newsItem->getOperation())->getContent());
         $newsItem->setMessage($this->ctaService->processCTAs($newsItem->getMessage(), $newsItem->getOperation())->getContent());
         $content = ['comment' => $newsItem->getMessage(), 'content' => ['title' => $newsItem->getLinkTitle(), 'description' => $newsItem->getLinkDescription(), 'submitted-url' => $newsItem->getLinkUrl()], 'visibility' => ['code' => 'anyone']];
         if ($images) {
             //Linkedin can handle only 1 image
             $content['content']['submitted-image-url'] = $this->cacheManager->getBrowserPath($images[0]->getPath(), "campaignchain_linkedin_news_item");
         }
     }
     $activity = $newsItem->getOperation()->getActivity();
     $locationModuleIdentifier = $activity->getLocation()->getLocationModule()->getIdentifier();
     $isCompanyPageShare = 'campaignchain-linkedin-page' == $locationModuleIdentifier;
     $connection = $this->client->getConnectionByActivity($activity);
     if ($isCompanyPageShare) {
         $response = $connection->shareOnCompanyPage($activity, $content);
     } else {
         $response = $connection->shareOnUserPage($content);
     }
     $newsItem->setUrl($response['updateUrl']);
     $newsItem->setUpdateKey($response['updateKey']);
     // Set Operation to closed.
     $newsItem->getOperation()->setStatus(Action::STATUS_CLOSED);
     $location = $newsItem->getOperation()->getLocations()[0];
     $location->setIdentifier($response['updateKey']);
     $location->setUrl($response['updateUrl']);
     $location->setName($newsItem->getOperation()->getName());
     $location->setStatus(Medium::STATUS_ACTIVE);
     // Schedule data collection for report
     $this->reportShareNewsItem->schedule($newsItem->getOperation());
     $this->em->flush();
     $this->message = 'The message "' . $newsItem->getMessage() . '" with the ID "' . $newsItem->getUpdateKey() . '" has been posted on LinkedIn. See it on LinkedIn: <a href="' . $newsItem->getUrl() . '">' . $newsItem->getUrl() . '</a>';
     return self::STATUS_OK;
 }
 /**
  * Checks whether the Activities belonging to a Campaign and marked with
  * mustValidate are executable.
  *
  * @param Campaign $campaign
  * @return array
  */
 public function hasExecutableActivities(Campaign $campaign)
 {
     $activities = $this->em->getRepository('CampaignChainCoreBundle:Activity')->findBy(array('campaign' => $campaign, 'mustValidate' => true));
     if (count($activities)) {
         foreach ($activities as $activity) {
             $isExecutable = $this->activityService->isExecutableByCampaign($activity);
             if (!$isExecutable['status']) {
                 return $isExecutable;
             }
         }
     }
     return array('status' => true);
 }
 /**
  * @param Token $newToken
  * @param bool $sameToken
  * @return bool|string
  */
 public function setToken(Token $newToken, $sameToken = false)
 {
     if ($newToken->getLocation()) {
         $this->em->persist($newToken);
         $this->em->flush();
         return true;
     }
     // Check whether the token already exists in relation to a channel.
     $repository = $this->em->getRepository('CampaignChainSecurityAuthenticationClientOAuthBundle:Token');
     $query = $repository->createQueryBuilder('token')->where('token.application = :application')->andWhere('token.accessToken = :accessToken')->andWhere('token.location IS NOT NULL')->setParameter('application', $newToken->getApplication())->setParameter('accessToken', $newToken->getAccessToken())->setMaxResults(1)->getQuery();
     $oldToken = $query->getOneOrNullResult();
     if (!$oldToken) {
         // So, there's no token related to a specific channel, but perhaps there is one
         // that has been persisted at a previous attempt to connect to the channel?
         // TODO: Implement what to do if token was persisted previously without channel relationship?
         $this->token = $newToken;
         $this->em->persist($this->token);
         $this->em->flush();
         return self::STATUS_NEW_TOKEN;
     }
     // Has a scope been set?
     if (!$newToken->getScope() && !$sameToken) {
         return self::STATUS_NO_CHANGE;
     }
     //With LinkedIn if a user connects multiple times, he will get the same access token
     //And even though they are same, it has to be saved as a new token
     if ($sameToken) {
         $this->token = $newToken;
         $this->em->persist($this->token);
         $this->em->flush();
         return self::STATUS_NEW_TOKEN;
     }
     $newScope = $newToken->getScope();
     $newAccessToken = $newToken->getAccessToken();
     $existingScope = $oldToken->getScope();
     $existingAccessToken = $oldToken->getAccessToken();
     // If the channel has the same access token and the same scope,
     // or no scope has been defined, then we're done.
     if ($existingScope === $newScope) {
         return self::STATUS_SAME_SCOPE;
     }
     // Is the scope different for the same profile?
     if ($existingAccessToken !== $newAccessToken) {
         // If the channel has a different scope and access token,
         // then create a new token entry for the existing profile.
         // This takes care of how Google handles scopes for its APIs.
         $this->token = $newToken;
         $status = self::STATUS_NEW_TOKEN;
     } else {
         // If the channel has the same access token, but a different scope,
         // then just update the scope for the token.
         // This takes care of how Facebook deals with scope changes.
         $this->token = $oldToken;
         $this->token->setScope($newScope);
         $status = self::STATUS_NEW_SCOPE;
     }
     $this->em->persist($this->token);
     $this->em->flush();
     return $status;
 }
Example #7
0
 public function execute($id)
 {
     /** @var Campaign $campaign */
     $campaign = $this->em->getRepository('CampaignChainCoreBundle:Campaign')->find($id);
     if (!$campaign) {
         throw new \Exception('No Campaign found with id: ' . $id);
     }
     $now = new \DateTime('now');
     // The campaign ended.
     if ($campaign->getStartDate() < $now && $campaign->getEndDate() < $now) {
         $campaign->setStatus(Action::STATUS_CLOSED);
         $this->em->flush();
         $this->message = 'The campaign "' . $campaign->getName() . '" ' . 'with ID "' . $campaign->getId() . '" ended, ' . 'thus its status was set to "' . Action::STATUS_CLOSED . '"';
     }
     return self::STATUS_OK;
 }
 public function execute(SymfonyStyle $io = null)
 {
     $existingLocations = $this->em->getRepository('CampaignChainCoreBundle:Location')->findAll();
     if (empty($existingLocations)) {
         $io->text('There is no Location to update');
         return true;
     }
     $supportedLocationModuleIdentifiers = ['campaignchain-twitter-user' => $this->twitterUserMetrics, 'campaignchain-facebook-page' => $this->facebookPageMetrics];
     foreach ($existingLocations as $existingLocation) {
         if (!in_array($existingLocation->getLocationModule()->getIdentifier(), array_keys($supportedLocationModuleIdentifiers))) {
             continue;
         }
         //do we have already a scheduler?
         $existingScheduler = $this->em->getRepository('CampaignChainCoreBundle:SchedulerReportLocation')->findOneBy(['location' => $existingLocation]);
         if ($existingScheduler) {
             continue;
         }
         $service = $supportedLocationModuleIdentifiers[$existingLocation->getLocationModule()->getIdentifier()];
         $service->schedule($existingLocation);
     }
     $this->em->flush();
     return true;
 }
 public function execute()
 {
     $connection = $this->em->getConnection();
     $statement = $connection->prepare('SELECT * FROM campaignchain_campaign_repeating_instance');
     $statement->execute();
     $instances = $statement->fetchAll();
     try {
         $this->em->getConnection()->beginTransaction();
         foreach ($instances as $instance) {
             /** @var Campaign $campaignParent */
             $campaignParent = $this->em->getRepository('CampaignChainCoreBundle:Campaign')->find($instance['repeatingCampaign_id']);
             /** @var Campaign $campaignChild */
             $campaignChild = $this->em->getRepository('CampaignChainCoreBundle:Campaign')->find($instance['scheduledCampaign_id']);
             $campaignParent->addChild($campaignChild);
             $campaignChild->setParent($campaignParent);
             $this->em->flush();
         }
         $this->em->getConnection()->commit();
     } catch (\Exception $e) {
         $this->em->getConnection()->rollback();
         throw $e;
     }
 }
Example #10
0
 /**
  * Search for open jobs and executes them
  * then show a report about the done job.
  */
 protected function executeJobs()
 {
     // Get the Jobs to be processed.
     $jobsInQueue = $this->em->getRepository('CampaignChainCoreBundle:Job')->getOpenJobsForScheduler($this->scheduler);
     if (empty($jobsInQueue)) {
         return;
     }
     $this->io->section('Executing jobs now:');
     $this->logger->info('Executing {counter} jobs now:', ['counter' => count($jobsInQueue)]);
     $this->io->progressStart(count($jobsInQueue));
     foreach ($jobsInQueue as $jobInQueue) {
         // Execute job.
         $this->executeJob($jobInQueue);
         $this->io->progressAdvance();
     }
     // ensure that the progress bar is at 100%
     $this->io->progressFinish();
     $this->em->clear();
     // Get the processed jobs.
     $jobsProcessed = $this->em->getRepository('CampaignChainCoreBundle:Job')->getProcessedJobsForScheduler($this->scheduler);
     if (empty($jobsProcessed)) {
         return;
     }
     // Display the results of the execution.
     $tableHeader = ['Job ID', 'Operation ID', 'Process ID', 'Job Name', 'Job Start Date', 'Job End Date', 'Duration', 'Status', 'Message'];
     $outputTableRows = [];
     foreach ($jobsProcessed as $jobProcessed) {
         $startDate = null;
         $endDate = null;
         if ($jobProcessed->getStartDate()) {
             $startDate = $jobProcessed->getStartDate()->format('Y-m-d H:i:s');
         }
         if ($jobProcessed->getEndDate()) {
             $endDate = $jobProcessed->getEndDate()->format('Y-m-d H:i:s');
         }
         $jobData = [$jobProcessed->getId(), $jobProcessed->getActionId(), $jobProcessed->getPid(), $jobProcessed->getName(), $startDate, $endDate, $jobProcessed->getDuration() . ' ms', $jobProcessed->getStatus(), $jobProcessed->getMessage()];
         $outputTableRows[] = $jobData;
         if (Job::STATUS_ERROR === $jobProcessed->getStatus()) {
             $context = array_combine($tableHeader, $jobData);
             $this->logger->error($jobProcessed->getMessage(), $context);
         }
     }
     $this->io->text('Results of executed actions:');
     $this->io->table($tableHeader, $outputTableRows);
 }
Example #11
0
 /**
  * Register activity Channels.
  */
 private function registerChannelRelationships()
 {
     if (!count($this->channelRelationships)) {
         return;
     }
     foreach ($this->channelRelationships as $moduleEntity => $channelRelationships) {
         foreach ($channelRelationships as $bundleIdentifier => $modules) {
             $bundle = $this->em->getRepository('CampaignChainCoreBundle:Bundle')->findOneByName($bundleIdentifier);
             foreach ($modules as $moduleIdentifier => $moduleChannels) {
                 $module = $this->em->getRepository('CampaignChainCoreBundle:' . $moduleEntity)->findOneBy(['bundle' => $bundle, 'identifier' => $moduleIdentifier]);
                 foreach ($moduleChannels as $channelURI) {
                     $channelURISplit = explode('/', $channelURI);
                     $channelBundleIdentifier = $channelURISplit[0] . '/' . $channelURISplit[1];
                     $channelModuleIdentifier = $channelURISplit[2];
                     /** @var Bundle $channelBundle */
                     $channelBundle = $this->em->getRepository('CampaignChainCoreBundle:Bundle')->findOneByName($channelBundleIdentifier);
                     /** @var ChannelModule $channelModule */
                     $channelModule = $this->em->getRepository('CampaignChainCoreBundle:ChannelModule')->findOneBy(['bundle' => $channelBundle, 'identifier' => $channelModuleIdentifier]);
                     if (!$channelModule) {
                         throw new \Exception('The channel URI "' . $channelURI . '" provided in campaignchain.yml of bundle "' . $bundle->getName() . '" for its module "' . $moduleIdentifier . '" does not match an existing channel module.');
                     }
                     /*
                      * If an updated bundle, then do nothing for an existing
                      * Activity/Channel relationship.
                      *
                      * TODO: Check if existing relationship has been removed
                      * from campaignchain.yml and throw error.
                      */
                     if ($this->bundleConfigService->isRegisteredBundle($bundle) == self::STATUS_REGISTERED_OLDER) {
                         $method = 'findRegisteredModulesBy' . $moduleEntity;
                         $registeredModules = $this->em->getRepository('CampaignChainCoreBundle:ChannelModule')->{$method}($module);
                         if (count($registeredModules) && $registeredModules[0]->getIdentifier() == $channelModule->getIdentifier()) {
                             continue;
                         }
                     }
                     // Map activity and channel.
                     $module->addChannelModule($channelModule);
                     $this->em->persist($module);
                 }
             }
         }
     }
     $this->em->flush();
 }
Example #12
0
 /**
  * @param Bundle $newBundle
  * @return string
  */
 public function isRegisteredBundle(Bundle $newBundle)
 {
     /** @var Bundle $registeredBundle */
     $registeredBundle = $this->em->getRepository('CampaignChainCoreBundle:Bundle')->findOneByName($newBundle->getName());
     if (!$registeredBundle) {
         // This case covers development of modules.
         return Installer::STATUS_REGISTERED_NO;
     }
     /*
      * Checking for dev-* ensures that the status is being registered
      * properly not just for dev-master, but also for branches (e.g.
      * dev-campaignchain-42).
      */
     if (substr($registeredBundle->getVersion(), 0, 4) === "dev-" && substr($newBundle->getVersion(), 0, 4) === "dev-") {
         return Installer::STATUS_REGISTERED_OLDER;
     }
     // Bundle with same version is already registered.
     if (version_compare($registeredBundle->getVersion(), $newBundle->getVersion(), '==')) {
         return Installer::STATUS_REGISTERED_SAME;
     }
     // Bundle with older version is already registered.
     return Installer::STATUS_REGISTERED_OLDER;
 }
 /**
  * @param string $operationId
  * @return string
  * @throws \Exception
  */
 public function execute($operationId)
 {
     $this->newsitem = $this->em->getRepository('CampaignChainOperationLinkedInBundle:NewsItem')->findOneByOperation($operationId);
     if (!$this->newsitem) {
         throw new \Exception('No Linkedin news item found for an operation with ID: ' . $operationId);
     }
     $activity = $this->newsitem->getOperation()->getActivity();
     $connection = $this->client->getConnectionByActivity($activity);
     $this->message = $connection->getCompanyUpdate($activity, $this->newsitem);
     $likes = 0;
     if (isset($response['numLikes'])) {
         $likes = $response['numLikes'];
     }
     $comments = 0;
     if (isset($response['updateComments']) && isset($response['updateComments']['_total'])) {
         $comments = $response['updateComments']['_total'];
     }
     // Add report data.
     $facts[self::METRIC_LIKES] = $likes;
     $facts[self::METRIC_COMMENTS] = $comments;
     $this->factService->addFacts('activity', self::BUNDLE_NAME, $this->newsitem->getOperation(), $facts);
     return self::STATUS_OK;
 }
Example #14
0
 /**
  * @param array $files
  * @param bool $doDrop
  */
 public function load(array $files, $doDrop = true)
 {
     try {
         $this->em->getConnection()->beginTransaction();
         $userProcessor = new UserProcessor(realpath(SystemUtil::getRootDir() . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR), $this->userService, $this->mimeTypeGuesser, $this->extensionGuesser);
         // Create Alice manager and fixture set
         $this->fixtureManager->addProcessor($userProcessor);
         $set = $this->fixtureManager->createFixtureSet();
         // Add the fixture files
         foreach ($files as $file) {
             $set->addFile($file, 'yaml');
         }
         $set->setDoDrop($doDrop);
         $set->setDoPersist(true);
         $set->setSeed(1337 + 42);
         // TODO Keep Module data intact
         $bundles = $this->em->getRepository("CampaignChain\\CoreBundle\\Entity\\Bundle")->findAll();
         $modules = $this->em->getRepository("CampaignChain\\CoreBundle\\Entity\\Module")->findAll();
         if ($this->fixtureManager->load($set)) {
             // TODO: Restore modules data
             foreach ($bundles as $bundle) {
                 $this->em->persist($bundle);
             }
             foreach ($modules as $module) {
                 $this->em->persist($module);
             }
             $this->em->flush();
             $this->em->getConnection()->commit();
             return true;
         }
         return false;
     } catch (\Exception $e) {
         $this->em->getConnection()->rollback();
         $this->setException($e);
         return false;
     }
 }
 public function execute($operationId)
 {
     /** @var Status $status */
     $status = $this->em->getRepository('CampaignChainOperationTwitterBundle:Status')->findOneByOperation($operationId);
     if (!$status) {
         throw new \Exception('No status message found for an operation with ID: ' . $operationId);
     }
     // Check whether the message can be posted in the Location.
     $isExecutable = $this->validator->isExecutableByLocation($status, $status->getOperation()->getStartDate());
     if ($isExecutable['status'] == false) {
         throw new JobException($isExecutable['message'], ErrorCode::OPERATION_NOT_EXECUTABLE_IN_LOCATION);
     }
     /*
      * If it is a campaign or parent campaign with an interval (e.g.
      * repeating campaign), we make sure that every URL will be shortened to
      * avoid a duplicate status message error.
      */
     $options = array();
     if ($status->getOperation()->getActivity()->getCampaign()->getInterval() || $status->getOperation()->getActivity()->getCampaign()->getParent() && $status->getOperation()->getActivity()->getCampaign()->getParent()->getInterval()) {
         $options['shorten_all_unique'] = true;
     }
     /*
      * Process URLs in message and save the new message text, now including
      * the replaced URLs with the Tracking ID attached for call to action tracking.
      */
     $status->setMessage($this->cta->processCTAs($status->getMessage(), $status->getOperation(), $options)->getContent());
     /** @var Client $connection */
     $connection = $this->client->connectByActivity($status->getOperation()->getActivity());
     $params['status'] = $status->getMessage();
     //have images?
     $images = $this->em->getRepository('CampaignChainHookImageBundle:Image')->getImagesForOperation($status->getOperation());
     $mediaIds = [];
     if ($images) {
         foreach ($images as $image) {
             $streamPath = 'gaufrette://images/' . $image->getPath();
             $imageRequest = $connection->post('https://upload.twitter.com/1.1/media/upload.json', null, ['media_data' => base64_encode(file_get_contents($streamPath))]);
             try {
                 $response = $imageRequest->send()->json();
                 $mediaIds[] = $response['media_id'];
             } catch (\Exception $e) {
             }
         }
         if ($mediaIds) {
             $params['media_ids'] = implode(',', $mediaIds);
         }
     }
     /*
      * @TODO
      *
      * If there are URLs in the tweet, they have been shortened. Thus, we'll
      * pass the expanded URLs as entities in the API call, so that Twitter
      * can display them when hovering the mouse on a short URL.
      */
     $request = $connection->post('statuses/update.json', null, $params);
     $response = $request->send()->json();
     // TODO
     // If status code is 403, this means that the same tweet with identical content already exists
     // This should be checked upon creation of tweet (same with FB!)
     // Set URL to published status message on Facebook
     $statusURL = 'https://twitter.com/' . $response['user']['screen_name'] . '/status/' . $response['id_str'];
     $status->setUrl($statusURL);
     $status->setIdStr($response['id_str']);
     // Set Operation to closed.
     $status->getOperation()->setStatus(Action::STATUS_CLOSED);
     $location = $status->getOperation()->getLocations()[0];
     $location->setIdentifier($response['id_str']);
     $location->setUrl($statusURL);
     $location->setName($status->getOperation()->getName());
     $location->setStatus(Medium::STATUS_ACTIVE);
     // Schedule data collection for report
     $this->report->schedule($status->getOperation());
     $this->em->flush();
     $this->message = 'The message "' . $response['text'] . '" with the ID "' . $response['id_str'] . '" has been posted on Twitter. See it on Twitter: <a href="' . $statusURL . '">' . $statusURL . '</a>';
     return self::STATUS_OK;
     //            }
     //            else {
     //                // Handle errors, if authentication did not work.
     //                // 1) Check if App is installed.
     //                // 2) check if access token is valid and retrieve new access token if necessary.
     //                // Log error, send email, prompt user, ask to check App Key and Secret or to authenticate again
     //            }
 }
Example #16
0
 public function getActiveSystem()
 {
     return $this->em->getRepository('CampaignChainCoreBundle:System')->findOneBy([], ['id' => 'ASC']);
 }
 /**
  * @param  string $operationId
  * @return string
  * @throws \Exception
  */
 public function execute($operationId)
 {
     /** @var StatusBase $status */
     $status = $this->em->getRepository('CampaignChainOperationFacebookBundle:StatusBase')->findOneByOperation($operationId);
     if (!$status) {
         throw new \Exception('No Facebook status found for an operation with ID: ' . $operationId);
     }
     // Check whether the message can be posted in the Location.
     $isExecutable = $this->validator->isExecutableByLocation($status, $status->getOperation()->getStartDate());
     if ($isExecutable['status'] == false) {
         throw new JobException($isExecutable['message'], ErrorCode::OPERATION_NOT_EXECUTABLE_IN_LOCATION);
     }
     /*
      * If it is a campaign or parent campaign with an interval (e.g.
      * repeating campaign), we make sure that every URL will be shortened to
      * avoid a duplicate status message error.
      */
     $options = array();
     if ($status->getOperation()->getActivity()->getCampaign()->getInterval() || $status->getOperation()->getActivity()->getCampaign()->getParent() && $status->getOperation()->getActivity()->getCampaign()->getParent()->getInterval()) {
         $options['shorten_all_unique'] = true;
     }
     /*
      * Process URLs in message and save the new message text, now including
      * the replaced URLs with the Tracking ID attached for call to action
      * tracking.
      */
     $status->setMessage($this->cta->processCTAs($status->getMessage(), $status->getOperation(), $options)->getContent());
     /** @var \Facebook $connection */
     $connection = $this->client->connectByActivity($status->getOperation()->getActivity());
     if (!$connection) {
         throw new JobException('Cannot connect to Facebook REST API for Location "' . $status->getOperation()->getActivity()->getLocation()->getUrl() . '"', ErrorCode::CONNECTION_TO_REST_API_FAILED);
     }
     $paramsMsg = array();
     /*
      * If an image was attached, we'll first upload the photo to Facebook
      * and then use the Facebook object ID of the picture in the message.
      */
     $images = $this->em->getRepository('CampaignChainHookImageBundle:Image')->getImagesForOperation($status->getOperation());
     if ($images) {
         $paramsImg = array();
         $paramsImg['caption'] = $status->getMessage();
         // Avoid that feed shows "... added a new photo" entry automtically.
         $paramsImg['no_story'] = 1;
         //Facebook handles only 1 image
         $paramsImg['url'] = $this->cacheManager->getBrowserPath($images[0]->getPath(), "campaignchain_facebook_photo");
         try {
             $responseImg = $connection->api('/' . $status->getFacebookLocation()->getIdentifier() . '/photos', 'POST', $paramsImg);
             $paramsMsg['object_attachment'] = $responseImg['id'];
         } catch (\Exception $e) {
             throw new ExternalApiException($e->getMessage() . '. Parameters of REST API call: ' . json_encode($paramsImg), $e->getCode(), $e);
         }
     }
     if ($status instanceof UserStatus) {
         $privacy = array('value' => $status->getPrivacy());
         $paramsMsg['privacy'] = json_encode($privacy);
     }
     $paramsMsg['message'] = $status->getMessage();
     try {
         $responseMsg = $connection->api('/' . $status->getFacebookLocation()->getIdentifier() . '/feed', 'POST', $paramsMsg);
     } catch (\Exception $e) {
         throw new ExternalApiException($e->getMessage() . '. Parameters of REST API call: ' . json_encode($paramsMsg), $e->getCode(), $e);
     }
     $connection->destroySession();
     // Set URL to published status message on Facebook
     $statusURL = 'https://www.facebook.com/' . str_replace('_', '/posts/', $responseMsg['id']);
     $status->setUrl($statusURL);
     $status->setPostId($responseMsg['id']);
     // Set Operation to closed.
     $status->getOperation()->setStatus(Action::STATUS_CLOSED);
     $location = $status->getOperation()->getLocations()[0];
     $location->setIdentifier($responseMsg['id']);
     $location->setUrl($statusURL);
     $location->setName($status->getOperation()->getName());
     $location->setStatus(Medium::STATUS_ACTIVE);
     // Schedule data collection for report
     $this->report->schedule($status->getOperation());
     $this->em->flush();
     $this->message = 'The message "' . $paramsMsg['message'] . '" with the ID "' . $responseMsg['id'] . '" has been posted on Facebook';
     if ($status instanceof UserStatus) {
         $this->message .= ' with privacy setting "' . $privacy['value'] . '"';
     }
     $this->message .= '. See it on Facebook: <a href="' . $statusURL . '">' . $statusURL . '</a>';
     return self::STATUS_OK;
 }