/**
  * Execute the controller.
  *
  * @return  void  Redirects the application
  *
  * @since   2.0
  */
 public function execute()
 {
     // We don't want this request to be cached.
     $this->getApplication()->setHeader('Expires', 'Mon, 1 Jan 2001 00:00:00 GMT', true);
     $this->getApplication()->setHeader('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT', true);
     $this->getApplication()->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0', false);
     $this->getApplication()->setHeader('Pragma', 'no-cache');
     $this->getApplication()->setHeader('Content-Type', $this->getApplication()->mimeType . '; charset=' . $this->getApplication()->charSet);
     // Check for a valid token. If invalid, send a 403 with the error message.
     if (!\JSession::checkToken('request')) {
         $response = new \JResponseJson(new \Exception(\JText::_('JINVALID_TOKEN'), 403));
         $this->getApplication()->sendHeaders();
         echo json_encode($response);
         $this->getApplication()->close(1);
     }
     // Make sure we can fetch the data from GitHub - throw an error on < 10 available requests
     $github = Helper::initializeGithub();
     try {
         $rate = $github->authorization->getRateLimit();
     } catch (\Exception $e) {
         $response = new \JResponseJson(new \Exception(\JText::sprintf('COM_PATCHTESTER_COULD_NOT_CONNECT_TO_GITHUB', $e->getMessage()), $e->getCode(), $e));
         $this->getApplication()->sendHeaders();
         echo json_encode($response);
         $this->getApplication()->close(1);
     }
     // If over the API limit, we can't build this list
     if ($rate->resources->core->remaining < 10) {
         $response = new \JResponseJson(new \Exception(\JText::sprintf('COM_PATCHTESTER_API_LIMIT_LIST', \JFactory::getDate($rate->resources->core->reset)), 429));
         $this->getApplication()->sendHeaders();
         echo json_encode($response);
         $this->getApplication()->close(1);
     }
     // TODO - Decouple the model and context?
     $model = new PullsModel('com_patchtester.fetch', null, \JFactory::getDbo());
     // Initialize the state for the model
     $model->setState($this->initializeState($model));
     try {
         // Sanity check, ensure there aren't any applied patches
         if (count($model->getAppliedPatches()) >= 1) {
             $response = new \JResponseJson(new \Exception(\JText::_('COM_PATCHTESTER_ERROR_APPLIED_PATCHES'), 500));
             $this->getApplication()->sendHeaders();
             echo json_encode($response);
             $this->getApplication()->close(1);
         }
     } catch (\Exception $e) {
         $response = new \JResponseJson($e);
         $this->getApplication()->sendHeaders();
         echo json_encode($response);
         $this->getApplication()->close(1);
     }
     // We're able to successfully pull data, prepare our environment
     \JFactory::getSession()->set('com_patchtester_fetcher_page', 1);
     $response = new \JResponseJson(array('complete' => false, 'header' => \JText::_('COM_PATCHTESTER_FETCH_PROCESSING', true)), \JText::sprintf('COM_PATCHTESTER_FETCH_PAGE_NUMBER', 1), false, true);
     $this->getApplication()->sendHeaders();
     echo json_encode($response);
     $this->getApplication()->close();
 }
Esempio n. 2
0
 /**
  * Patches the code with the supplied pull request
  *
  * @param   integer  $id  ID of the pull request to apply
  *
  * @return  boolean
  *
  * @since   2.0
  * @throws  \RuntimeException
  */
 public function apply($id)
 {
     // Get the Github object
     $github = Helper::initializeGithub();
     try {
         $rate = $github->authorization->getRateLimit();
     } catch (\Exception $e) {
         throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_COULD_NOT_CONNECT_TO_GITHUB', $e->getMessage()), $e->getCode(), $e);
     }
     // If over the API limit, we can't build this list
     if ($rate->resources->core->remaining == 0) {
         throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_API_LIMIT_LIST', \JFactory::getDate($rate->resources->core->reset)));
     }
     try {
         $pull = $github->pulls->get($this->getState()->get('github_user'), $this->getState()->get('github_repo'), $id);
     } catch (\Exception $e) {
         throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_COULD_NOT_CONNECT_TO_GITHUB', $e->getMessage()), $e->getCode(), $e);
     }
     if (is_null($pull->head->repo)) {
         throw new \RuntimeException(\JText::_('COM_PATCHTESTER_REPO_IS_GONE'));
     }
     // Set up the JHttp object
     $options = new Registry();
     $options->set('userAgent', 'JPatchTester/2.0');
     $options->set('timeout', 120);
     try {
         $transport = \JHttpFactory::getHttp($options);
         $patch = $transport->get($pull->diff_url)->body;
     } catch (\Exception $e) {
         throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_COULD_NOT_CONNECT_TO_GITHUB', $e->getMessage()), $e->getCode(), $e);
     }
     $files = $this->parsePatch($patch);
     if (!$files) {
         return false;
     }
     foreach ($files as $file) {
         if ($file->action == 'deleted' && !file_exists(JPATH_ROOT . '/' . $file->old)) {
             throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_FILE_DELETED_DOES_NOT_EXIST_S', $file->old));
         }
         if ($file->action == 'added' || $file->action == 'modified') {
             // If the backup file already exists, we can't apply the patch
             if (file_exists(JPATH_COMPONENT . '/backups/' . md5($file->new) . '.txt')) {
                 throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_CONFLICT_S', $file->new));
             }
             if ($file->action == 'modified' && !file_exists(JPATH_ROOT . '/' . $file->old)) {
                 throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_FILE_MODIFIED_DOES_NOT_EXIST_S', $file->old));
             }
             $url = 'https://raw.github.com/' . urlencode($pull->head->user->login) . '/' . urlencode($pull->head->repo->name) . '/' . urlencode($pull->head->ref) . '/' . $file->new;
             try {
                 $file->body = $transport->get($url)->body;
             } catch (\Exception $e) {
                 throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_COULD_NOT_CONNECT_TO_GITHUB', $e->getMessage()), $e->getCode(), $e);
             }
         }
     }
     jimport('joomla.filesystem.file');
     // At this point, we have ensured that we have all the new files and there are no conflicts
     foreach ($files as $file) {
         // We only create a backup if the file already exists
         if ($file->action == 'deleted' || file_exists(JPATH_ROOT . '/' . $file->new) && $file->action == 'modified') {
             if (!\JFile::copy(\JPath::clean(JPATH_ROOT . '/' . $file->old), JPATH_COMPONENT . '/backups/' . md5($file->old) . '.txt')) {
                 throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_ERROR_CANNOT_COPY_FILE', JPATH_ROOT . '/' . $file->old, JPATH_COMPONENT . '/backups/' . md5($file->old) . '.txt'));
             }
         }
         switch ($file->action) {
             case 'modified':
             case 'added':
                 if (!\JFile::write(\JPath::clean(JPATH_ROOT . '/' . $file->new), $file->body)) {
                     throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_ERROR_CANNOT_WRITE_FILE', JPATH_ROOT . '/' . $file->new));
                 }
                 break;
             case 'deleted':
                 if (!\JFile::delete(\JPath::clean(JPATH_ROOT . '/' . $file->old))) {
                     throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_ERROR_CANNOT_DELETE_FILE', JPATH_ROOT . '/' . $file->old));
                 }
                 break;
         }
     }
     /** @var \PatchTester\Table\TestsTable $table */
     $table = \JTable::getInstance('TestsTable', '\\PatchTester\\Table\\');
     $table->pull_id = $pull->number;
     $table->data = json_encode($files);
     $table->patched_by = \JFactory::getUser()->id;
     $table->applied = 1;
     $table->applied_version = JVERSION;
     if (!$table->store()) {
         throw new \RuntimeException($table->getError());
     }
     return true;
 }
Esempio n. 3
0
 /**
  * Method to request new data from GitHub
  *
  * @param   integer  $page  The page of the request
  *
  * @return  array
  *
  * @since   2.0
  * @throws  \RuntimeException
  */
 public function requestFromGithub($page)
 {
     // Get the Github object
     $github = Helper::initializeGithub();
     // If on page 1, dump the old data
     if ($page === 1) {
         $this->getDb()->truncateTable('#__patchtester_pulls');
     }
     try {
         // TODO - Option to configure the batch size
         $pulls = $github->pulls->getList($this->getState()->get('github_user'), $this->getState()->get('github_repo'), 'open', $page, 100);
     } catch (\DomainException $e) {
         throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_ERROR_GITHUB_FETCH', $e->getMessage()), $e->getCode(), $e);
     }
     $count = is_array($pulls) ? count($pulls) : 0;
     // If there are no pulls to insert then bail, assume we're finished
     if ($count === 0 || empty($pulls)) {
         return array('complete' => true);
     }
     $data = array();
     foreach ($pulls as $pull) {
         // Build the data object to store in the database
         $pullData = array($pull->number, $this->getDb()->quote(\JHtml::_('string.truncate', $pull->title, 150)), $this->getDb()->quote(\JHtml::_('string.truncate', $pull->body, 100)), $this->getDb()->quote($pull->html_url), $this->getDb()->quote($pull->head->sha));
         $data[] = implode($pullData, ',');
     }
     $this->getDb()->setQuery($this->getDb()->getQuery(true)->insert($this->getDb()->quoteName('#__patchtester_pulls'))->columns(array($this->getDb()->quoteName('pull_id'), $this->getDb()->quoteName('title'), $this->getDb()->quoteName('description'), $this->getDb()->quoteName('pull_url'), $this->getDb()->quoteName('sha')))->values($data));
     try {
         $this->getDb()->execute();
     } catch (\RuntimeException $e) {
         throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_ERROR_INSERT_DATABASE', $e->getMessage()), $e->getCode(), $e);
     }
     // Need to make another request
     return array('complete' => false, 'page' => $page + 1);
 }
 /**
  * Patches the code with the supplied pull request
  *
  * @param   integer  $id  ID of the pull request to apply
  *
  * @return  boolean
  *
  * @since   2.0
  * @throws  \RuntimeException
  */
 public function apply($id)
 {
     // Get the Github object
     $github = Helper::initializeGithub();
     try {
         $rateResponse = $github->getRateLimit();
         $rate = json_decode($rateResponse->body);
     } catch (UnexpectedResponse $e) {
         throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_COULD_NOT_CONNECT_TO_GITHUB', $e->getMessage()), $e->getCode(), $e);
     }
     // If over the API limit, we can't build this list
     if ($rate->resources->core->remaining == 0) {
         throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_API_LIMIT_LIST', \JFactory::getDate($rate->resources->core->reset)));
     }
     try {
         $pullResponse = $github->getPullRequest($this->getState()->get('github_user'), $this->getState()->get('github_repo'), $id);
         $pull = json_decode($pullResponse->body);
     } catch (UnexpectedResponse $e) {
         throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_COULD_NOT_CONNECT_TO_GITHUB', $e->getMessage()), $e->getCode(), $e);
     }
     if (is_null($pull->head->repo)) {
         throw new \RuntimeException(\JText::_('COM_PATCHTESTER_REPO_IS_GONE'));
     }
     try {
         $filesResponse = $github->getFilesForPullRequest($this->getState()->get('github_user'), $this->getState()->get('github_repo'), $id);
         $files = json_decode($filesResponse->body);
     } catch (UnexpectedResponse $e) {
         throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_COULD_NOT_CONNECT_TO_GITHUB', $e->getMessage()), $e->getCode(), $e);
     }
     if (!count($files)) {
         return false;
     }
     $parsedFiles = $this->parseFileList($files);
     foreach ($parsedFiles as $file) {
         switch ($file->action) {
             case 'deleted':
                 if (!file_exists(JPATH_ROOT . '/' . $file->filename)) {
                     throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_FILE_DELETED_DOES_NOT_EXIST_S', $file->filename));
                 }
                 break;
             case 'added':
             case 'modified':
             case 'renamed':
                 // If the backup file already exists, we can't apply the patch
                 if (file_exists(JPATH_COMPONENT . '/backups/' . md5($file->filename) . '.txt')) {
                     throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_CONFLICT_S', $file->filename));
                 }
                 if ($file->action == 'modified' && !file_exists(JPATH_ROOT . '/' . $file->filename)) {
                     throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_FILE_MODIFIED_DOES_NOT_EXIST_S', $file->filename));
                 }
                 try {
                     $contentsResponse = $github->getFileContents($pull->head->user->login, $this->getState()->get('github_repo'), $file->repofilename, urlencode($pull->head->ref));
                     $contents = json_decode($contentsResponse->body);
                     // In case encoding type ever changes
                     switch ($contents->encoding) {
                         case 'base64':
                             $file->body = base64_decode($contents->content);
                             break;
                         default:
                             throw new \RuntimeException(\JText::_('COM_PATCHTESTER_ERROR_UNSUPPORTED_ENCODING'));
                     }
                 } catch (UnexpectedResponse $e) {
                     throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_COULD_NOT_CONNECT_TO_GITHUB', $e->getMessage()), $e->getCode(), $e);
                 }
                 break;
         }
     }
     jimport('joomla.filesystem.file');
     jimport('joomla.filesystem.path');
     // At this point, we have ensured that we have all the new files and there are no conflicts
     foreach ($parsedFiles as $file) {
         // We only create a backup if the file already exists
         if ($file->action == 'deleted' || file_exists(JPATH_ROOT . '/' . $file->filename) && $file->action == 'modified' || file_exists(JPATH_ROOT . '/' . $file->originalFile) && $file->action == 'renamed') {
             $filename = $file->action == 'renamed' ? $file->originalFile : $file->filename;
             $src = JPATH_ROOT . '/' . $filename;
             $dest = JPATH_COMPONENT . '/backups/' . md5($filename) . '.txt';
             if (!\JFile::copy(\JPath::clean($src), $dest)) {
                 throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_ERROR_CANNOT_COPY_FILE', $src, $dest));
             }
         }
         switch ($file->action) {
             case 'modified':
             case 'added':
                 if (!\JFile::write(\JPath::clean(JPATH_ROOT . '/' . $file->filename), $file->body)) {
                     throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_ERROR_CANNOT_WRITE_FILE', JPATH_ROOT . '/' . $file->filename));
                 }
                 break;
             case 'deleted':
                 if (!\JFile::delete(\JPath::clean(JPATH_ROOT . '/' . $file->filename))) {
                     throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_ERROR_CANNOT_DELETE_FILE', JPATH_ROOT . '/' . $file->filename));
                 }
                 break;
             case 'renamed':
                 if (!\JFile::delete(\JPath::clean(JPATH_ROOT . '/' . $file->originalFile))) {
                     throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_ERROR_CANNOT_DELETE_FILE', JPATH_ROOT . '/' . $file->originalFile));
                 }
                 if (!\JFile::write(\JPath::clean(JPATH_ROOT . '/' . $file->filename), $file->body)) {
                     throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_ERROR_CANNOT_WRITE_FILE', JPATH_ROOT . '/' . $file->filename));
                 }
                 break;
         }
         // We don't need the file's body any longer (and it causes issues with binary data when json_encode() is run), so remove it
         unset($file->body);
     }
     $record = (object) array('pull_id' => $pull->number, 'data' => json_encode($parsedFiles), 'patched_by' => \JFactory::getUser()->id, 'applied' => 1, 'applied_version' => JVERSION);
     $db = $this->getDb();
     $db->insertObject('#__patchtester_tests', $record);
     // Insert the retrieved commit SHA into the pulls table for this item
     $db->setQuery($db->getQuery(true)->update('#__patchtester_pulls')->set('sha = ' . $db->quote($pull->head->sha))->where($db->quoteName('pull_id') . ' = ' . (int) $id))->execute();
     return true;
 }
 /**
  * Method to request new data from GitHub
  *
  * @param   integer  $page  The page of the request
  *
  * @return  array
  *
  * @since   2.0
  * @throws  \RuntimeException
  */
 public function requestFromGithub($page)
 {
     // If on page 1, dump the old data
     if ($page === 1) {
         $this->getDb()->truncateTable('#__patchtester_pulls');
     }
     try {
         // TODO - Option to configure the batch size
         $batchSize = 100;
         $pullsResponse = Helper::initializeGithub()->getOpenIssues($this->getState()->get('github_user'), $this->getState()->get('github_repo'), $page, $batchSize);
         $pulls = json_decode($pullsResponse->body);
     } catch (UnexpectedResponse $e) {
         throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_ERROR_GITHUB_FETCH', $e->getMessage()), $e->getCode(), $e);
     }
     // If this is page 1, let's check to see if we need to paginate
     if ($page === 1) {
         // Default this to being a single page of results
         $lastPage = 1;
         if (isset($pullsResponse->headers['Link'])) {
             preg_match('/(\\?page=[0-9]&per_page=' . $batchSize . '+>; rel=\\"last\\")/', $pullsResponse->headers['Link'], $matches);
             if ($matches && isset($matches[0])) {
                 $pageSegment = str_replace('&per_page=' . $batchSize, '', $matches[0]);
                 preg_match('/\\d+/', $pageSegment, $pages);
                 $lastPage = (int) $pages[0];
             }
         }
     }
     // If there are no pulls to insert then bail, assume we're finished
     if (count($pulls) === 0) {
         return array('complete' => true);
     }
     $data = array();
     foreach ($pulls as $pull) {
         if (isset($pull->pull_request)) {
             // Check if this PR is RTC
             $isRTC = false;
             foreach ($pull->labels as $label) {
                 if ($label->name === 'RTC') {
                     $isRTC = true;
                     break;
                 }
             }
             // Build the data object to store in the database
             $pullData = array((int) $pull->number, $this->getDb()->quote(\JHtml::_('string.truncate', $pull->title, 150)), $this->getDb()->quote(\JHtml::_('string.truncate', $pull->body, 100)), $this->getDb()->quote($pull->pull_request->html_url), (int) $isRTC);
             $data[] = implode($pullData, ',');
         }
     }
     // If there are no pulls to insert then bail, assume we're finished
     if (count($data) === 0) {
         return array('complete' => true);
     }
     $this->getDb()->setQuery($this->getDb()->getQuery(true)->insert('#__patchtester_pulls')->columns(array('pull_id', 'title', 'description', 'pull_url', 'is_rtc'))->values($data));
     try {
         $this->getDb()->execute();
     } catch (\RuntimeException $e) {
         throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_ERROR_INSERT_DATABASE', $e->getMessage()), $e->getCode(), $e);
     }
     // Need to make another request
     return array('complete' => false, 'page' => $page + 1, 'lastPage' => isset($lastPage) ? $lastPage : false);
 }