/**
  * Start the actual execution of a job
  *
  * This method will continue executing until the job says it's completed
  *
  * @param int $jobId
  *			The ID of the job to start executing
  */
 public function runJob($jobId)
 {
     // first retrieve the descriptor
     $jobDescriptor = DataObject::get_by_id('QueuedJobDescriptor', (int) $jobId);
     if (!$jobDescriptor) {
         throw new Exception("{$jobId} is invalid");
     }
     // now lets see whether we have a current user to run as. Typically, if the job is executing via the CLI,
     // we want it to actually execute as the RunAs user - however, if running via the web (which is rare...), we
     // want to ensure that the current user has admin privileges before switching. Otherwise, we just run it
     // as the currently logged in user and hope for the best
     $originalUser = Member::currentUser();
     $runAsUser = null;
     if (Director::is_cli() || !Member::currentUser() || Member::currentUser()->isAdmin()) {
         $runAsUser = $jobDescriptor->RunAs();
         if ($runAsUser && $runAsUser->exists()) {
             // the job runner outputs content way early in the piece, meaning there'll be cooking errors
             // if we try and do a normal login, and we only want it temporarily...
             Session::set("loggedInAs", $runAsUser->ID);
         }
     }
     // set up a custom error handler for this processing
     $errorHandler = new JobErrorHandler();
     $job = null;
     try {
         $job = $this->initialiseJob($jobDescriptor);
         // get the job ready to begin.
         if (!$jobDescriptor->JobStarted) {
             $jobDescriptor->JobStarted = date('Y-m-d H:i:s');
         } else {
             $jobDescriptor->JobRestarted = date('Y-m-d H:i:s');
         }
         $jobDescriptor->JobStatus = QueuedJob::STATUS_RUN;
         $jobDescriptor->write();
         $lastStepProcessed = 0;
         // have we stalled at all?
         $stallCount = 0;
         $broken = false;
         // while not finished
         while (!$job->jobFinished() && !$broken) {
             // see that we haven't been set to 'paused' or otherwise by another process
             $jobDescriptor = DataObject::get_by_id('QueuedJobDescriptor', (int) $jobId);
             if ($jobDescriptor->JobStatus != QueuedJob::STATUS_RUN) {
                 // we've been paused by something, so we'll just exit
                 $job->addMessage(sprintf(_t('QueuedJobs.JOB_PAUSED', "Job paused at %s"), date('Y-m-d H:i:s')));
                 $broken = true;
             }
             if (!$broken) {
                 try {
                     $job->process();
                 } catch (Exception $e) {
                     // okay, we'll just catch this exception for now
                     $job->addMessage(sprintf(_t('QueuedJobs.JOB_EXCEPT', 'Job caused exception %s in %s at line %s'), $e->getMessage(), $e->getFile(), $e->getLine()), 'ERROR');
                     SS_Log::log($e, SS_Log::ERR);
                     $jobDescriptor->JobStatus = QueuedJob::STATUS_BROKEN;
                 }
                 // now check the job state
                 $data = $job->getJobData();
                 if ($data->currentStep == $lastStepProcessed) {
                     $stallCount++;
                 }
                 if ($stallCount > self::$stall_threshold) {
                     $broken = true;
                     $job->addMessage(sprintf(_t('QueuedJobs.JOB_STALLED', "Job stalled after %s attempts - please check"), $stallCount), 'ERROR');
                     $jobDescriptor->JobStatus = QueuedJob::STATUS_BROKEN;
                 }
                 // now we'll be good and check our memory usage. If it is too high, we'll set the job to
                 // a 'Waiting' state, and let the next processing run pick up the job.
                 if ($this->isMemoryTooHigh()) {
                     $job->addMessage(sprintf(_t('QueuedJobs.MEMORY_RELEASE', 'Job releasing memory and waiting (%s used)'), $this->humanReadable(memory_get_usage())));
                     $jobDescriptor->JobStatus = QueuedJob::STATUS_WAIT;
                     $broken = true;
                 }
             }
             $this->copyJobToDescriptor($job, $jobDescriptor);
             $jobDescriptor->write();
         }
         // a last final save
         $jobDescriptor->write();
     } catch (Exception $e) {
         // okay, we'll just catch this exception for now
         SS_Log::log($e, SS_Log::ERR);
         $jobDescriptor->JobStatus = QueuedJob::STATUS_BROKEN;
         $jobDescriptor->write();
     }
     $errorHandler->clear();
     // okay lets reset our user if we've got an original
     if ($runAsUser && $originalUser) {
         Session::clear("loggedInAs");
         if ($originalUser) {
             Session::set("loggedInAs", $originalUser->ID);
         }
     }
 }
 /**
  * Start the actual execution of a job.
  * The assumption is the jobID refers to a {@link QueuedJobDescriptor} that is status set as "Queued".
  *
  * This method will continue executing until the job says it's completed
  *
  * @param int $jobId
  *			The ID of the job to start executing
  * @return boolean
  */
 public function runJob($jobId)
 {
     // first retrieve the descriptor
     $jobDescriptor = DataObject::get_by_id('QueuedJobDescriptor', (int) $jobId);
     if (!$jobDescriptor) {
         throw new Exception("{$jobId} is invalid");
     }
     // now lets see whether we have a current user to run as. Typically, if the job is executing via the CLI,
     // we want it to actually execute as the RunAs user - however, if running via the web (which is rare...), we
     // want to ensure that the current user has admin privileges before switching. Otherwise, we just run it
     // as the currently logged in user and hope for the best
     // We need to use $_SESSION directly because SS ties the session to a controller that no longer exists at
     // this point of execution in some circumstances
     $originalUserID = isset($_SESSION['loggedInAs']) ? $_SESSION['loggedInAs'] : 0;
     $originalUser = $originalUserID ? DataObject::get_by_id('Member', $originalUserID) : null;
     $runAsUser = null;
     if (Director::is_cli() || !$originalUser || Permission::checkMember($originalUser, 'ADMIN')) {
         $runAsUser = $jobDescriptor->RunAs();
         if ($runAsUser && $runAsUser->exists()) {
             // the job runner outputs content way early in the piece, meaning there'll be cookie errors
             // if we try and do a normal login, and we only want it temporarily...
             if (Controller::has_curr()) {
                 Session::set('loggedInAs', $runAsUser->ID);
             } else {
                 $_SESSION['loggedInAs'] = $runAsUser->ID;
             }
             // this is an explicit coupling brought about by SS not having
             // a nice way of mocking a user, as it requires session
             // nastiness
             if (class_exists('SecurityContext')) {
                 singleton('SecurityContext')->setMember($runAsUser);
             }
         }
     }
     // set up a custom error handler for this processing
     $errorHandler = new JobErrorHandler();
     $job = null;
     $broken = false;
     // Push a config context onto the stack for the duration of this job run.
     Config::nest();
     if ($this->grabMutex($jobDescriptor)) {
         try {
             $job = $this->initialiseJob($jobDescriptor);
             // get the job ready to begin.
             if (!$jobDescriptor->JobStarted) {
                 $jobDescriptor->JobStarted = date('Y-m-d H:i:s');
             } else {
                 $jobDescriptor->JobRestarted = date('Y-m-d H:i:s');
             }
             $jobDescriptor->JobStatus = QueuedJob::STATUS_RUN;
             $jobDescriptor->write();
             $lastStepProcessed = 0;
             // have we stalled at all?
             $stallCount = 0;
             if ($job->SubsiteID && class_exists('Subsite')) {
                 Subsite::changeSubsite($job->SubsiteID);
                 // lets set the base URL as far as Director is concerned so that our URLs are correct
                 $subsite = DataObject::get_by_id('Subsite', $job->SubsiteID);
                 if ($subsite && $subsite->exists()) {
                     $domain = $subsite->domain();
                     $base = rtrim(Director::protocol() . $domain, '/') . '/';
                     Config::inst()->update('Director', 'alternate_base_url', $base);
                 }
             }
             // while not finished
             while (!$job->jobFinished() && !$broken) {
                 // see that we haven't been set to 'paused' or otherwise by another process
                 $jobDescriptor = DataObject::get_by_id('QueuedJobDescriptor', (int) $jobId);
                 if (!$jobDescriptor || !$jobDescriptor->exists()) {
                     $broken = true;
                     SS_Log::log(array('errno' => 0, 'errstr' => 'Job descriptor ' . $jobId . ' could not be found', 'errfile' => __FILE__, 'errline' => __LINE__, 'errcontext' => array()), SS_Log::ERR);
                     break;
                 }
                 if ($jobDescriptor->JobStatus != QueuedJob::STATUS_RUN) {
                     // we've been paused by something, so we'll just exit
                     $job->addMessage(sprintf(_t('QueuedJobs.JOB_PAUSED', "Job paused at %s"), date('Y-m-d H:i:s')));
                     $broken = true;
                 }
                 if (!$broken) {
                     try {
                         $job->process();
                     } catch (Exception $e) {
                         // okay, we'll just catch this exception for now
                         $job->addMessage(sprintf(_t('QueuedJobs.JOB_EXCEPT', 'Job caused exception %s in %s at line %s'), $e->getMessage(), $e->getFile(), $e->getLine()), 'ERROR');
                         SS_Log::log($e, SS_Log::ERR);
                         $jobDescriptor->JobStatus = QueuedJob::STATUS_BROKEN;
                     }
                     // now check the job state
                     $data = $job->getJobData();
                     if ($data->currentStep == $lastStepProcessed) {
                         $stallCount++;
                     }
                     if ($stallCount > Config::inst()->get(__CLASS__, 'stall_threshold')) {
                         $broken = true;
                         $job->addMessage(sprintf(_t('QueuedJobs.JOB_STALLED', "Job stalled after %s attempts - please check"), $stallCount), 'ERROR');
                         $jobDescriptor->JobStatus = QueuedJob::STATUS_BROKEN;
                     }
                     // now we'll be good and check our memory usage. If it is too high, we'll set the job to
                     // a 'Waiting' state, and let the next processing run pick up the job.
                     if ($this->isMemoryTooHigh()) {
                         $job->addMessage(sprintf(_t('QueuedJobs.MEMORY_RELEASE', 'Job releasing memory and waiting (%s used)'), $this->humanReadable($this->getMemoryUsage())));
                         $jobDescriptor->JobStatus = QueuedJob::STATUS_WAIT;
                         $broken = true;
                     }
                     // Also check if we are running too long
                     if ($this->hasPassedTimeLimit()) {
                         $job->addMessage(_t('QueuedJobs.TIME_LIMIT', 'Queue has passed time limit and will restart before continuing'));
                         $jobDescriptor->JobStatus = QueuedJob::STATUS_WAIT;
                         $broken = true;
                     }
                 }
                 if ($jobDescriptor) {
                     $this->copyJobToDescriptor($job, $jobDescriptor);
                     $jobDescriptor->write();
                 } else {
                     SS_Log::log(array('errno' => 0, 'errstr' => 'Job descriptor has been set to null', 'errfile' => __FILE__, 'errline' => __LINE__, 'errcontext' => array()), SS_Log::WARN);
                     $broken = true;
                 }
             }
             // a last final save. The job is complete by now
             if ($jobDescriptor) {
                 $jobDescriptor->write();
             }
             if (!$broken) {
                 $job->afterComplete();
                 $jobDescriptor->cleanupJob();
             }
         } catch (Exception $e) {
             // okay, we'll just catch this exception for now
             SS_Log::log($e, SS_Log::ERR);
             $jobDescriptor->JobStatus = QueuedJob::STATUS_BROKEN;
             $jobDescriptor->write();
             $broken = true;
         }
     }
     $errorHandler->clear();
     Config::unnest();
     // okay let's reset our user if we've got an original
     if ($runAsUser && $originalUser) {
         Session::clear("loggedInAs");
         if ($originalUser) {
             Session::set("loggedInAs", $originalUser->ID);
         }
     }
     return !$broken;
 }