/** * _ProcessJob * This method does the "setup work" for a split test campaign. * * If a job is passed in that hasn't been started before, it will set everything up: * - create a "queue" of recipients * - clean the queue (remove banned/duplicate recipients etc) * - set up stats for each newsletter in the split test campaign * - save stats for the user sending the campaign to take off credits etc * * If a job is passed in that has been set up before, it just loads the data up. * * Once it has done either of those, it gives the details to the Splittest_Send_API class * and then calls _ActionJob. * Based on what that returns, it will either mark the job as complete or not. * * If the job is a percentage split test send (send to first X % of a list/segment), then * it sets the appropriate flags etc to make sure we only send to that first X%. * If the job hasn't been set up before, it works out the limit and sets the details in the jobdetails * which are then saved in the database in case the job dies or is paused etc. * * If the job is a percentage split test send and it's time to send to the rest of the list/segment, * then it works out the "best" one to send and sets the appropriate variables for that to happen. * * @param Int $jobid The specific job id we're going to process. * * @uses _jobid * @uses StartJob * @uses PauseJob * @uses LoadJob * @uses GetUser * @uses GetJobQueue * @uses CreateQueue * @uses JobQueue * @uses GetSubscribersFromSegment * @uses GetSubscribers * @uses RemoveDuplicatesInQueue * @uses RemoveBannedEmails * @uses RemoveUnsubscribedEmails * @uses QueueSize * @uses _CalculateBestNewsletter * @uses _FinishJob * @uses _ActionJob * @uses _CalculateBestNewsletter * * @return Boolean Returns whether the job was processed or not. If a job could not be processed, it returns false. Otherwise it returns true. */ private function _ProcessJob($jobid = 0) { if ($jobid <= 0) { return false; } $this->_jobid = $jobid; /** * Load the job, then start it. * We need to do this so when we call "StartJob" we can give it the splitid to "start" as well. */ $jobinfo = $this->LoadJob($jobid); $jobdetails = $jobinfo['jobdetails']; /** * Need to load the split campaign * before starting the job * so if we're in "t"imeout mode, * we can look at the stats * We also need the weighting's from the split campaign * to work it out. */ $this->splitcampaign_details = $this->_splittest_api->Load($jobdetails['splitid']); /** * If it's a "t"imeout, work out the best stat. */ if ($jobinfo['jobstatus'] == 't') { $jobdetails['newsletters'] = $this->_CalculateBestNewsletter($jobdetails['Stats']); /** * Also need to kill off the "percentage_send_maximum" element * as this would cause the job to go into "timeout" mode again. */ unset($jobdetails['percentage_send_maximum']); } if (!$this->StartJob($jobid, $jobdetails['splitid'])) { $this->PauseJob($jobid); return false; } // ----- "Login" to the system as the job's owner. $user = GetUser($jobinfo['ownerid']); IEM::userLogin($jobinfo['ownerid'], false); // ----- $queueid = false; // if there's no queue, start one up. if (!($queueid = $this->GetJobQueue($jobid))) { /** * Randomize the newsletters * It's probably already been done but can't hurt to do it again. */ shuffle($jobdetails['newsletters']); $sendqueue = $this->CreateQueue('splittest'); $queueok = $this->JobQueue($jobid, $sendqueue); $send_criteria = $jobdetails['SendCriteria']; $queueinfo = array('queueid' => $sendqueue, 'queuetype' => 'splittest', 'ownerid' => $jobinfo['ownerid']); if (isset($jobdetails['Segments']) && is_array($jobdetails['Segments'])) { $this->_subscribers_api->GetSubscribersFromSegment($jobdetails['Segments'], false, $queueinfo, 'nosort'); } else { $this->_subscribers_api->GetSubscribers($send_criteria, array(), false, $queueinfo, $sendqueue); } if (SENDSTUDIO_DATABASE_TYPE == 'pgsql') { $this->Db->OptimizeTable(SENDSTUDIO_TABLEPREFIX . "queues"); } $this->_subscribers_api->RemoveDuplicatesInQueue($sendqueue, 'splittest', $jobdetails['Lists']); $this->_subscribers_api->RemoveBannedEmails($jobdetails['Lists'], $sendqueue, 'splittest'); $this->_subscribers_api->RemoveUnsubscribedEmails($jobdetails['Lists'], $sendqueue, 'splittest'); if (SENDSTUDIO_DATABASE_TYPE == 'pgsql') { $this->Db->OptimizeTable(SENDSTUDIO_TABLEPREFIX . "queues"); } $jobdetails['SendSize'] = $this->_subscribers_api->QueueSize($sendqueue, 'splittest'); $jobdetails['Stats'] = array(); $jobdetails['SendQueue'] = $sendqueue; /** * Delete the old user stats allocations. * They were all allocated under one stat/job before so the user recorded their send info * so they couldn't set up split test sends and go over their send quota. * * Now, we need to re-allocate them per newsletter being sent. */ $this->_stats_api->DeleteUserStats($jobinfo['ownerid'], $this->_jobid); $statids = array(); foreach ($jobdetails['newsletters'] as $newsletterid) { $newsletterstats = $jobdetails; $newsletterstats['Job'] = $jobid; $newsletterstats['Queue'] = $sendqueue; $newsletterstats['SentBy'] = $queueinfo['ownerid']; $newsletterstats['SendType'] = 'splittest'; $newsletterstats['Newsletter'] = $newsletterid; $newsletterstats['Lists'] = $jobdetails['sendingto']['Lists']; $newsletterstats['SendCriteria'] = $jobdetails['SendCriteria']; $statid = $this->_stats_api->SaveNewsletterStats($newsletterstats); $statids[] = $statid; $jobdetails['Stats'][$newsletterid] = $statid; $this->_stats_api->RecordUserStats($jobinfo['ownerid'], $this->_jobid, $jobdetails['SendSize'], $jobdetails['SendStartTime'], $statid); } $this->SaveSplitStats($jobdetails['splitid'], $this->_jobid, $statids); /** * If it's a percentage send, * work out the number of emails to send for the first percentage * It gets stored in the jobdetails array so it can be saved in the database. */ if ($this->splitcampaign_details['splittype'] == 'percentage') { $max_to_email = ceil($this->splitcampaign_details['splitdetails']['percentage'] / 100 * $jobdetails['SendSize']); $jobdetails['percentage_send_maximum'] = $max_to_email; } /** * Save the job stat details. * Otherwise we could potentially end up with a 'start'ed queue but no stats. */ $this->Set('jobdetails', $jobdetails); $this->UpdateJobDetails(); /** * This is to process the 'queueid' later in the code. */ $queueid = $sendqueue; // This will make sure that the credit warning emails are also being send out from splittest API_USERS::creditEvaluateWarnings($user->GetNewAPI()); } $this->Db->OptimizeTable(SENDSTUDIO_TABLEPREFIX . "queues"); $queuesize = $this->_subscribers_api->QueueSize($queueid, 'splittest'); $this->_queueid = $queueid; /** * If there is a "percentage_send_maximum" variable in the jobdetails array, * we are sending to the first part of a 'percentage' split test. * * We have to send to the rest of the percentage maximum before we pause the job, * work out the "best" performing campaign and send to that. */ if (isset($jobdetails['percentage_send_maximum'])) { $this->_percentage_send_maximum = (int) $jobdetails['percentage_send_maximum']; } /** * If the _percentage_send_maximum is 0, then "timeout" the job. * We must have hit "pause" right at the end of the initial send process or something. */ if ($this->_percentage_send_maximum !== null && $this->_percentage_send_maximum <= 0) { $this->TimeoutJob($jobid, $this->splitcampaign_details['splitid'], $this->splitcampaign_details['splitdetails']['hoursafter']); IEM::userLogout(); return true; } $this->Set('statids', $jobdetails['Stats']); $this->Set('jobdetails', $jobdetails); $this->Set('jobowner', $jobinfo['ownerid']); /** * There's nothing left? Just mark it as done. */ if ($queuesize == 0) { $this->_FinishJob(); IEM::userLogout(); return true; } $finished = $this->_ActionJob($jobid, $queueid); if ($finished) { $this->_FinishJob(); } IEM::userLogout(); return true; }
/** * DeleteSchedules * If scheduled items are going to be deleted, this processes the jobs it needs to. * * The data passed in contains lots of data: * jobids - the job id's that need to be processed * message - the current success/failure message * success - the current success counter (how many jobs have successfully been deleted previous to getting here) * failure - the current failure counter (how many jobs have not successfully been deleted previous to getting here) * * Any non-"splittest" job types are skipped * Any "in progress" splittest jobs are skipped and the failure counter is incremented * Any jobs that can be deleted are - as well as figuring out whether a job needs to give back any email credits. * * Any appropriate messages are added to the "message" item in the passed in array. * * @param EventData_IEM_SCHEDULE_DELETEJOBS $data The data array containing the jobs to process, the current message, success and failure counts. * * @uses Jobs_API::LoadJob() * @uses Stats_API::DeleteUserStats() * @uses Stats_API::MarkNewsletterFinished() * @uses Splittest_Send_API::DeleteJob() * @uses User_API::ReduceEmails() * @uses EventData_IEM_SCHEDULE_DELETEJOBS */ public static function DeleteSchedules(EventData_IEM_SCHEDULE_DELETEJOBS $data) { $jobids =& $data->jobids; $message =& $data->Message; $success =& $data->success; $failure =& $data->failure; $user = GetUser(); require_once SENDSTUDIO_API_DIRECTORY . '/jobs.php'; require_once SENDSTUDIO_API_DIRECTORY . '/stats.php'; require_once dirname(__FILE__) . '/api/splittest_send.php'; $jobapi = new Jobs_API(); $stats_api = new Stats_API(); $send_api = new Splittest_Send_API(); foreach ($jobids as $p => $jobid) { $jobinfo = $jobapi->LoadJob($jobid); if (empty($jobinfo)) { continue; } if ($jobinfo['jobtype'] !== 'splittest') { continue; } if ($jobinfo['jobstatus'] == 'i') { $failure++; unset($jobids[$p]); continue; } $statids = array(); if (isset($jobinfo['jobdetails']['Stats'])) { $statids = array_values($jobinfo['jobdetails']['Stats']); } /** * If there are no stats, then the send hasn't started yet. * So just credit the user back with the number of emails they were trying to send. * Use 'ReduceEmails' to re-add the credits by using a double negative :) */ if (empty($statids) && $jobinfo['jobstatus'] == 'w') { $stats_api->DeleteUserStats($jobinfo['ownerid'], $jobid); $user->ReduceEmails(-(int) $jobinfo['jobdetails']['SendSize']); } /** * If a send is started (ie it has stats), * but is not completed, * We need to mark it as complete. * * This also credits a user back if they have any limits in place. * * This needs to happen before we delete the 'job' from the database * as deleting the job cleans up the queues/unsent items. */ if (!empty($statids) && $jobinfo['jobstatus'] != 'c') { $stats_api->MarkNewsletterFinished($statids, $jobinfo['jobdetails']['SendSize']); // Credits needs to be returned too whenever the job is canceled AFTER it has been scheduled, but before it was sent } elseif ($jobinfo['jobstatus'] != 'c') { $stats_api->RefundFixedCredit($jobid); } $result = $send_api->DeleteJob($jobid); if ($result) { $success++; } else { $failure++; } unset($jobids[$p]); } /** * Only failure messages get added to the message stack. * Successful deletes are handled in the calling code * in case: * - a non-addon deletes an item * - other addons delete their own items */ if ($failure > 0) { if ($failure == 1) { FlashMessage(GetLang('Addon_splittest_Schedule_JobDeleteFail'), SS_FLASH_MSG_ERROR); } else { FlashMessage(sprintf(GetLang('Addon_splittest_Schedule_JobsDeleteFail'), self::PrintNumber($failure)), SS_FLASH_MSG_SUCCESS); } } $message .= GetFlashMessages(); }