diff --git a/administrator/components/com_patchtester/forms/filter_pulls.xml b/administrator/components/com_patchtester/forms/filter_pulls.xml index 3fb1b1d..1b83eb2 100644 --- a/administrator/components/com_patchtester/forms/filter_pulls.xml +++ b/administrator/components/com_patchtester/forms/filter_pulls.xml @@ -1,5 +1,5 @@ -
+ - - - - + + + + app = $app; -// Set the context for the controller - $this->context = 'com_patchtester.' . $this->getInput()->getCmd('view', $this->defaultView); - } - - /** - * Get the application object. - * - * @return CMSApplication - * - * @since 4.0.0 - */ - public function getApplication() - { - return $this->app; - } - - /** - * Get the input object. - * - * @return Input - * - * @since 4.0.0 - */ - public function getInput() - { - return $this->app->input; - } - - /** - * Sets the state for the model object - * - * @param AbstractModel $model Model object - * - * @return Registry - * - * @since 2.0 - */ - protected function initializeState($model) - { - $state = new Registry(); -// Load the parameters. - $params = ComponentHelper::getParams('com_patchtester'); - $state->set('github_user', $params->get('org', 'joomla')); - $state->set('github_repo', $params->get('repo', 'joomla-cms')); - return $state; - } -} diff --git a/administrator/components/com_patchtester/src/Controller/ApplyController.php b/administrator/components/com_patchtester/src/Controller/ApplyController.php index af8b89d..7e267dd 100644 --- a/administrator/components/com_patchtester/src/Controller/ApplyController.php +++ b/administrator/components/com_patchtester/src/Controller/ApplyController.php @@ -11,6 +11,7 @@ namespace Joomla\Component\Patchtester\Administrator\Controller; use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; +use Joomla\CMS\MVC\Controller\BaseController; use Joomla\CMS\Router\Route; use Joomla\Component\Patchtester\Administrator\Model\PullModel; @@ -24,7 +25,7 @@ use Joomla\Component\Patchtester\Administrator\Model\PullModel; * * @since 2.0 */ -class ApplyController extends AbstractController +class ApplyController extends BaseController { /** * Execute the controller. @@ -33,16 +34,15 @@ class ApplyController extends AbstractController * * @since 2.0 */ - public function execute() + public function execute($task): void { try { - $model = new PullModel(null, Factory::getDbo()); -// Initialize the state for the model - $model->setState($this->initializeState($model)); - if ($model->apply($this->getInput()->getUint('pull_id'))) { + /** @var PullModel $model */ + $model = Factory::getApplication()->bootComponent('com_patchtester')->getMVCFactory()->createModel('Pull', 'Administrator', ['ignore_request' => true]); + $msg = Text::_('COM_PATCHTESTER_NO_FILES_TO_PATCH'); + + if ($model->apply($this->input->getUint('pull_id'))) { $msg = Text::_('COM_PATCHTESTER_APPLY_OK'); - } else { - $msg = Text::_('COM_PATCHTESTER_NO_FILES_TO_PATCH'); } $type = 'message'; @@ -51,7 +51,7 @@ class ApplyController extends AbstractController $type = 'error'; } - $this->getApplication()->enqueueMessage($msg, $type); - $this->getApplication()->redirect(Route::_('index.php?option=com_patchtester', false)); + $this->app->enqueueMessage($msg, $type); + $this->app->redirect(Route::_('index.php?option=com_patchtester', false)); } } diff --git a/administrator/components/com_patchtester/src/Controller/DisplayController.php b/administrator/components/com_patchtester/src/Controller/DisplayController.php index ddd35f2..36f2125 100644 --- a/administrator/components/com_patchtester/src/Controller/DisplayController.php +++ b/administrator/components/com_patchtester/src/Controller/DisplayController.php @@ -9,10 +9,7 @@ namespace Joomla\Component\Patchtester\Administrator\Controller; -use Joomla\CMS\Factory; -use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\Controller\BaseController; -use Joomla\Registry\Registry; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; @@ -33,111 +30,4 @@ class DisplayController extends BaseController * @since 1.6 */ protected $default_view = 'pulls'; - - /** - * Default ordering value - * - * @var string - * @since 4.0.0 - */ - protected $defaultFullOrdering = 'a.pull_id DESC'; -/** - * Execute the controller. - * - * @return boolean True on success - * - * @since 2.0 - * @throws \RuntimeException - */ - public function executeOld() - { - // Set up variables to build our classes - $view = $this->getInput()->getCmd('view', $this->defaultView); - $format = $this->getInput()->getCmd('format', 'html'); -// Register the layout paths for the view - $paths = new \SplPriorityQueue(); -// Add the path for template overrides - $paths->insert(JPATH_THEMES . '/' . $this->getApplication()->getTemplate() . '/html/com_patchtester/' . $view, 2); -// Add the path for the default layouts - $paths->insert(dirname(__DIR__) . '/View/' . ucfirst($view) . '/tmpl', 1); -// Build the class names for the model and view - $viewClass = '\\PatchTester\\View\\' . ucfirst($view) . '\\' . ucfirst($view) . ucfirst($format) . 'View'; - $modelClass = '\\PatchTester\\Model\\' . ucfirst($view) . 'Model'; -// Sanity check - Ensure our classes exist - if (!class_exists($viewClass)) { -// Try to use a default view - $viewClass = '\\PatchTester\\View\\Default' . ucfirst($format) . 'View'; - if (!class_exists($viewClass)) { - throw new \RuntimeException(Text::sprintf('COM_PATCHTESTER_ERROR_VIEW_NOT_FOUND', $view, $format), 500); - } - } - - if (!class_exists($modelClass)) { - throw new \RuntimeException(Text::sprintf('COM_PATCHTESTER_ERROR_MODEL_NOT_FOUND', $modelClass), 500); - } - - // Initialize the model class now; need to do it before setting the state to get required data from it - $model = new $modelClass($this->context, null, Factory::getDbo()); -// Initialize the state for the model - $state = $this->initializeState($model); - foreach ($state as $key => $value) { - $model->setState($key, $value); - } - - // Initialize the view class now - $view = new $viewClass($model, $paths); -// Echo the rendered view for the application - echo $view->render(); -// Finished! - return true; - } - - /** - * Sets the state for the model object - * - * @param AbstractModel $model Model object - * - * @return Registry - * - * @since 2.0 - */ - protected function initializeState($model) - { - $state = parent::initializeState($model); - $app = $this->getApplication(); -// Load the filter state. - $state->set('filter.search', $app->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '')); - $state->set('filter.applied', $app->getUserStateFromRequest($this->context . '.filter.applied', 'filter_applied', '')); - $state->set('filter.branch', $app->getUserStateFromRequest($this->context . '.filter.branch', 'filter_branch', '')); - $state->set('filter.rtc', $app->getUserStateFromRequest($this->context . '.filter.rtc', 'filter_rtc', '')); - $state->set('filter.npm', $app->getUserStateFromRequest($this->context . '.filter.npm', 'filter_npm', '')); - $state->set('filter.label', $app->getUserStateFromRequest($this->context . '.filter.label', 'filter_label', '')); -// Pre-fill the limits. - $limit = $app->getUserStateFromRequest('global.list.limit', 'limit', $app->input->get('list_limit', 20), 'uint'); - $state->set('list.limit', $limit); - $fullOrdering = $app->getUserStateFromRequest($this->context . '.fullorder', 'list_fullordering', $this->defaultFullOrdering); - $orderingParts = explode(' ', $fullOrdering); - if (count($orderingParts) !== 2) { - $fullOrdering = $this->defaultFullOrdering; - $orderingParts = explode(' ', $fullOrdering); - } - - $state->set('list.fullordering', $fullOrdering); -// The 2nd part will be considered the direction - $direction = $orderingParts[array_key_last($orderingParts)]; - if (in_array(strtoupper($direction), ['ASC', 'DESC', ''])) { - $state->set('list.direction', $direction); - } - - // The 1st part will be the ordering - $ordering = $orderingParts[array_key_first($orderingParts)]; - if (in_array($ordering, $model->getSortFields())) { - $state->set('list.ordering', $ordering); - } - - $value = $app->getUserStateFromRequest($this->context . '.limitstart', 'limitstart', 0); - $limitstart = ($limit != 0 ? (floor($value / $limit) * $limit) : 0); - $state->set('list.start', $limitstart); - return $state; - } } diff --git a/administrator/components/com_patchtester/src/Controller/FetchController.php b/administrator/components/com_patchtester/src/Controller/FetchController.php index a04a1fc..c422638 100644 --- a/administrator/components/com_patchtester/src/Controller/FetchController.php +++ b/administrator/components/com_patchtester/src/Controller/FetchController.php @@ -9,8 +9,10 @@ namespace Joomla\Component\Patchtester\Administrator\Controller; +use Exception; use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; +use Joomla\CMS\MVC\Controller\BaseController; use Joomla\CMS\Response\JsonResponse; use Joomla\Component\Patchtester\Administrator\Model\PullsModel; @@ -23,48 +25,42 @@ use Joomla\Component\Patchtester\Administrator\Model\PullsModel; * * @since 2.0 */ -class FetchController extends AbstractController +class FetchController extends BaseController { /** * Execute the controller. * * @return void Redirects the application * + * @throws Exception * @since 2.0 */ - public function execute() + public function execute($task) { - // 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); - $session = Factory::getSession(); + $this->app->setHeader('Expires', 'Mon, 1 Jan 2001 00:00:00 GMT', true); + $this->app->setHeader('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT', true); + $this->app->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0', false); + $this->app->setHeader('Pragma', 'no-cache'); + $this->app->setHeader('Content-Type', $this->app->mimeType . '; charset=' . $this->app->charSet); + $session = Factory::getApplication()->getSession(); + try { - // Fetch our page from the session $page = $session->get('com_patchtester_fetcher_page', 1); - $model = new PullsModel(); - // Initialize the state for the model - $state = $this->initializeState($model); - foreach ($state as $key => $value) { - $model->setState($key, $value); - } + /** @var PullsModel $model */ + $model = $this->app->bootComponent('com_patchtester')->getMVCFactory()->createModel('Pulls', 'Administrator', ['ignore_request' => true]); $status = $model->requestFromGithub($page); - } catch (\Exception $e) { + } catch (Exception $e) { $response = new JsonResponse($e); - $this->getApplication()->sendHeaders(); + $this->app->sendHeaders(); echo json_encode($response); - $this->getApplication()->close(1); + $this->app->close(1); } - // Store the last page to the session if given one if (isset($status['lastPage']) && $status['lastPage'] !== false) { $session->set('com_patchtester_fetcher_last_page', $status['lastPage']); } - // Update the UI and session now if ($status['complete'] || $page === $session->get('com_patchtester_fetcher_last_page', false)) { $status['complete'] = true; $status['header'] = Text::_('COM_PATCHTESTER_FETCH_SUCCESSFUL', true); @@ -83,8 +79,8 @@ class FetchController extends AbstractController } $response = new JsonResponse($status, $message, false, true); - $this->getApplication()->sendHeaders(); + $this->app->sendHeaders(); echo json_encode($response); - $this->getApplication()->close(); + $this->app->close(); } } diff --git a/administrator/components/com_patchtester/src/Controller/ResetController.php b/administrator/components/com_patchtester/src/Controller/ResetController.php index 7019e49..517055d 100644 --- a/administrator/components/com_patchtester/src/Controller/ResetController.php +++ b/administrator/components/com_patchtester/src/Controller/ResetController.php @@ -13,6 +13,7 @@ use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Filesystem\Folder; use Joomla\CMS\Language\Text; +use Joomla\CMS\MVC\Controller\BaseController; use Joomla\CMS\Router\Route; use Joomla\Component\Patchtester\Administrator\Model\PullModel; use Joomla\Component\Patchtester\Administrator\Model\PullsModel; @@ -28,7 +29,7 @@ use Joomla\Filesystem\File; * * @since 2.0 */ -class ResetController extends AbstractController +class ResetController extends BaseController { /** * Execute the controller. @@ -37,20 +38,25 @@ class ResetController extends AbstractController * * @since 2.0 */ - public function execute(): void + public function execute($task): void { try { $hasErrors = false; $revertErrored = false; - $pullModel = new PullModel(null, Factory::getDbo()); - $pullsModel = new PullsModel($this->context, null, Factory::getDbo()); - $testsModel = new TestsModel(null, Factory::getDbo()); -// Check the applied patches in the database first + $mvcFactory = Factory::getApplication()->bootComponent('com_patchtester')->getMVCFactory(); + /** @var PullModel $pullModel */ + $pullModel = $mvcFactory->createModel('Pull', 'Administrator', ['ignore_request' => true]); + /** @var PullsModel $pullModel */ + $pullsModel = $mvcFactory->createModel('Pulls', 'Administrator', ['ignore_request' => true]); + /** @var TestsModel $pullModel */ + $testsModel = $mvcFactory->createModel('Tests', 'Administrator', ['ignore_request' => true]); + + // Check the applied patches in the database first $appliedPatches = $testsModel->getAppliedPatches(); $params = ComponentHelper::getParams('com_patchtester'); -// Decide based on repository settings whether patch will be applied through Github or CIServer + // Decide based on repository settings whether patch will be applied through Github or CIServer if ((bool) $params->get('ci_switch', 1)) { -// Let's try to cleanly revert all applied patches with ci + // Let's try to cleanly revert all applied patches with ci foreach ($appliedPatches as $patch) { try { $pullModel->revertWithCIServer($patch->id); @@ -76,7 +82,7 @@ class ResetController extends AbstractController } catch (\RuntimeException $e) { $hasErrors = true; - $this->getApplication()->enqueueMessage( + $this->app->enqueueMessage( Text::sprintf('COM_PATCHTESTER_ERROR_TRUNCATING_PULLS_TABLE', $e->getMessage()), 'error' ); @@ -89,7 +95,7 @@ class ResetController extends AbstractController } catch (\RuntimeException $e) { $hasErrors = true; - $this->getApplication()->enqueueMessage( + $this->app->enqueueMessage( Text::sprintf('COM_PATCHTESTER_ERROR_TRUNCATING_TESTS_TABLE', $e->getMessage()), 'error' ); @@ -101,7 +107,7 @@ class ResetController extends AbstractController if (count($backups)) { foreach ($backups as $file) { if (!File::delete(JPATH_COMPONENT . '/backups/' . $file)) { - $this->getApplication()->enqueueMessage( + $this->app->enqueueMessage( Text::sprintf('COM_PATCHTESTER_ERROR_CANNOT_DELETE_FILE', JPATH_COMPONENT . '/backups/' . $file), 'error' ); @@ -115,7 +121,7 @@ class ResetController extends AbstractController $msg = Text::sprintf( 'COM_PATCHTESTER_RESET_HAS_ERRORS', JPATH_COMPONENT . '/backups', - Factory::getDbo()->replacePrefix('#__patchtester_tests') + Factory::getApplication()->get('DatabaseDriver')->replacePrefix('#__patchtester_tests') ); $type = 'warning'; } else { @@ -127,7 +133,7 @@ class ResetController extends AbstractController $type = 'error'; } - $this->getApplication()->enqueueMessage($msg, $type); - $this->getApplication()->redirect(Route::_('index.php?option=com_patchtester', false)); + $this->app->enqueueMessage($msg, $type); + $this->app->redirect(Route::_('index.php?option=com_patchtester', false)); } } diff --git a/administrator/components/com_patchtester/src/Controller/RevertController.php b/administrator/components/com_patchtester/src/Controller/RevertController.php index 9d6bf62..f6c1809 100644 --- a/administrator/components/com_patchtester/src/Controller/RevertController.php +++ b/administrator/components/com_patchtester/src/Controller/RevertController.php @@ -9,8 +9,8 @@ namespace Joomla\Component\Patchtester\Administrator\Controller; -use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; +use Joomla\CMS\MVC\Controller\BaseController; use Joomla\CMS\Router\Route; use Joomla\Component\Patchtester\Administrator\Model\PullModel; @@ -23,7 +23,7 @@ use Joomla\Component\Patchtester\Administrator\Model\PullModel; * * @since 2.0 */ -class RevertController extends AbstractController +class RevertController extends BaseController { /** * Execute the controller. @@ -32,13 +32,12 @@ class RevertController extends AbstractController * * @since 2.0 */ - public function execute() + public function execute($task) { try { - $model = new PullModel(null, Factory::getDbo()); -// Initialize the state for the model - $model->setState($this->initializeState($model)); - $model->revert($this->getInput()->getUint('pull_id')); + /** @var PullModel $model */ + $model = $this->app->bootComponent('com_patchtester')->getMVCFactory()->createModel('Pull', 'Administrator', ['ignore_request' => true]); + $model->revert($this->input->getUint('pull_id')); $msg = Text::_('COM_PATCHTESTER_REVERT_OK'); $type = 'message'; } catch (\Exception $e) { @@ -46,7 +45,7 @@ class RevertController extends AbstractController $type = 'error'; } - $this->getApplication()->enqueueMessage($msg, $type); - $this->getApplication()->redirect(Route::_('index.php?option=com_patchtester', false)); + $this->app->enqueueMessage($msg, $type); + $this->app->redirect(Route::_('index.php?option=com_patchtester', false)); } } diff --git a/administrator/components/com_patchtester/src/Controller/StartfetchController.php b/administrator/components/com_patchtester/src/Controller/StartfetchController.php index b7db63a..bd8d79e 100644 --- a/administrator/components/com_patchtester/src/Controller/StartfetchController.php +++ b/administrator/components/com_patchtester/src/Controller/StartfetchController.php @@ -11,10 +11,10 @@ namespace Joomla\Component\Patchtester\Administrator\Controller; use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; +use Joomla\CMS\MVC\Controller\BaseController; use Joomla\CMS\Response\JsonResponse; use Joomla\CMS\Session\Session; use Joomla\Component\Patchtester\Administrator\Helper\Helper; -use Joomla\Component\Patchtester\Administrator\Model\TestsModel; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; @@ -25,7 +25,7 @@ use Joomla\Component\Patchtester\Administrator\Model\TestsModel; * * @since 2.0 */ -class StartfetchController extends AbstractController +class StartfetchController extends BaseController { /** * Execute the controller. @@ -34,20 +34,19 @@ class StartfetchController extends AbstractController * * @since 2.0 */ - public function execute() + public function execute($task): void { - // 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. + $this->app->setHeader('Expires', 'Mon, 1 Jan 2001 00:00:00 GMT', true); + $this->app->setHeader('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT', true); + $this->app->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0', false); + $this->app->setHeader('Pragma', 'no-cache'); + $this->app->setHeader('Content-Type', $this->app->mimeType . '; charset=' . $this->app->charSet); + if (!Session::checkToken('request')) { $response = new JsonResponse(new \Exception(Text::_('JINVALID_TOKEN'), 403)); - $this->getApplication()->sendHeaders(); + $this->app->sendHeaders(); echo json_encode($response); - $this->getApplication()->close(1); + $this->app->close(1); } // Make sure we can fetch the data from GitHub - throw an error on < 10 available requests @@ -56,40 +55,48 @@ class StartfetchController extends AbstractController $rate = json_decode($rateResponse->body); } catch (\Exception $e) { $response = new JsonResponse(new \Exception(Text::sprintf('COM_PATCHTESTER_COULD_NOT_CONNECT_TO_GITHUB', $e->getMessage()), $e->getCode(), $e)); - $this->getApplication()->sendHeaders(); + $this->app->sendHeaders(); echo json_encode($response); - $this->getApplication()->close(1); + $this->app->close(1); } // If over the API limit, we can't build this list if ($rate->resources->core->remaining < 10) { $response = new JsonResponse(new \Exception(Text::sprintf('COM_PATCHTESTER_API_LIMIT_LIST', Factory::getDate($rate->resources->core->reset)), 429)); - $this->getApplication()->sendHeaders(); + $this->app->sendHeaders(); echo json_encode($response); - $this->getApplication()->close(1); + $this->app->close(1); } - $testsModel = new TestsModel(null, Factory::getDbo()); + $testsModel = Factory::getApplication()->bootComponent('com_patchtester')->getMVCFactory()->createModel('Tests', 'Administrator', ['ignore_request' => true]); try { // Sanity check, ensure there aren't any applied patches if (count($testsModel->getAppliedPatches()) >= 1) { $response = new JsonResponse(new \Exception(Text::_('COM_PATCHTESTER_ERROR_APPLIED_PATCHES'), 500)); - $this->getApplication()->sendHeaders(); + $this->app->sendHeaders(); echo json_encode($response); - $this->getApplication()->close(1); + $this->app->close(1); } } catch (\Exception $e) { $response = new JsonResponse($e); - $this->getApplication()->sendHeaders(); + $this->app->sendHeaders(); echo json_encode($response); - $this->getApplication()->close(1); + $this->app->close(1); } // We're able to successfully pull data, prepare our environment - Factory::getSession()->set('com_patchtester_fetcher_page', 1); - $response = new JsonResponse(array('complete' => false, 'header' => Text::_('COM_PATCHTESTER_FETCH_PROCESSING', true)), Text::sprintf('COM_PATCHTESTER_FETCH_PAGE_NUMBER', 1), false, true); - $this->getApplication()->sendHeaders(); + Factory::getApplication()->getSession()->set('com_patchtester_fetcher_page', 1); + $response = new JsonResponse( + [ + 'complete' => false, + 'header' => Text::_('COM_PATCHTESTER_FETCH_PROCESSING', true) + ], + Text::sprintf('COM_PATCHTESTER_FETCH_PAGE_NUMBER', 1), + false, + true + ); + $this->app->sendHeaders(); echo json_encode($response); - $this->getApplication()->close(); + $this->app->close(); } } diff --git a/administrator/components/com_patchtester/src/GitHub/Exception/UnexpectedResponse.php b/administrator/components/com_patchtester/src/Exception/UnexpectedResponse.php similarity index 53% rename from administrator/components/com_patchtester/src/GitHub/Exception/UnexpectedResponse.php rename to administrator/components/com_patchtester/src/Exception/UnexpectedResponse.php index d5c262e..7a78fce 100644 --- a/administrator/components/com_patchtester/src/GitHub/Exception/UnexpectedResponse.php +++ b/administrator/components/com_patchtester/src/Exception/UnexpectedResponse.php @@ -7,16 +7,18 @@ * @license GNU General Public License version 2 or later */ -namespace Joomla\Component\Patchtester\Administrator\Github\Exception; +namespace Joomla\Component\Patchtester\Administrator\Exception; -use Joomla\CMS\Http\Response; +use DomainException; +use Exception; +use Joomla\Http\Response; /** * Exception representing an unexpected response * * @since 3.0.0 */ -class UnexpectedResponse extends \DomainException +class UnexpectedResponse extends DomainException { /** * The Response object. @@ -25,18 +27,23 @@ class UnexpectedResponse extends \DomainException * @since 3.0.0 */ private $response; -/** + + /** * Constructor * - * @param Response $response The Response object. - * @param string $message The Exception message to throw. - * @param integer $code The Exception code. - * @param \Exception $previous The previous exception used for the exception chaining. + * @param Response $response The Response object. + * @param string $message The Exception message to throw. + * @param int $code The Exception code. + * @param Exception|null $previous The previous exception used for the exception chaining. * * @since 3.0.0 */ - public function __construct(Response $response, $message = '', $code = 0, \Exception $previous = null) - { + public function __construct( + Response $response, + $message = '', + $code = 0, + Exception $previous = null + ) { parent::__construct($message, $code, $previous); $this->response = $response; } @@ -48,7 +55,7 @@ class UnexpectedResponse extends \DomainException * * @since 3.0.0 */ - public function getResponse() + public function getResponse(): Response { return $this->response; } diff --git a/administrator/components/com_patchtester/src/Field/BranchField.php b/administrator/components/com_patchtester/src/Field/BranchField.php index 30ea768..5fde5ea 100644 --- a/administrator/components/com_patchtester/src/Field/BranchField.php +++ b/administrator/components/com_patchtester/src/Field/BranchField.php @@ -31,7 +31,8 @@ class BranchField extends ListField * @since 4.1.0 */ protected $type = 'Branch'; -/** + + /** * Build a list of available branches. * * @return array List of options @@ -40,14 +41,19 @@ class BranchField extends ListField */ public function getOptions(): array { - $db = Factory::getContainer()->get('DatabaseDriver'); + $db = Factory::getContainer()->get('DatabaseDriver'); $query = $db->getQuery(true); - $query->select('DISTINCT(' . $db->quoteName('branch') . ') AS ' . $db->quoteName('text')) + $query->select( + 'DISTINCT(' . $db->quoteName('branch') . ') AS ' . $db->quoteName( + 'text' + ) + ) ->select($db->quoteName('branch', 'value')) ->from('#__patchtester_pulls') ->where($db->quoteName('branch') . ' != ' . $db->quote('')) ->order($db->quoteName('branch') . ' ASC'); $options = $db->setQuery($query)->loadAssocList(); + return array_merge(parent::getOptions(), $options); } } diff --git a/administrator/components/com_patchtester/src/Field/LabelField.php b/administrator/components/com_patchtester/src/Field/LabelField.php index 099e816..7b33f9d 100644 --- a/administrator/components/com_patchtester/src/Field/LabelField.php +++ b/administrator/components/com_patchtester/src/Field/LabelField.php @@ -31,7 +31,8 @@ class LabelField extends ListField * @since 4.1.0 */ protected $type = 'Label'; -/** + + /** * Build a list of available fields. * * @return array List of options @@ -40,13 +41,18 @@ class LabelField extends ListField */ public function getOptions(): array { - $db = Factory::getContainer()->get('DatabaseDriver'); + $db = Factory::getContainer()->get('DatabaseDriver'); $query = $db->getQuery(true); - $query->select('DISTINCT(' . $db->quoteName('name') . ') AS ' . $db->quoteName('text')) + $query->select( + 'DISTINCT(' . $db->quoteName('name') . ') AS ' . $db->quoteName( + 'text' + ) + ) ->select($db->quoteName('name', 'value')) ->from($db->quoteName('#__patchtester_pulls_labels')) ->order($db->quoteName('name') . ' ASC'); $options = $db->setQuery($query)->loadAssocList(); + return array_merge(parent::getOptions(), $options); } } diff --git a/administrator/components/com_patchtester/src/GitHub/GitHub.php b/administrator/components/com_patchtester/src/GitHub/GitHub.php index f575921..87a3880 100644 --- a/administrator/components/com_patchtester/src/GitHub/GitHub.php +++ b/administrator/components/com_patchtester/src/GitHub/GitHub.php @@ -11,9 +11,9 @@ namespace Joomla\Component\Patchtester\Administrator\GitHub; use Joomla\CMS\Http\Http; use Joomla\CMS\Http\HttpFactory; -use Joomla\CMS\Http\Response; use Joomla\CMS\Uri\Uri; -use Joomla\Component\Patchtester\Administrator\Github\Exception\UnexpectedResponse; +use Joomla\Component\Patchtester\Administrator\Exception\UnexpectedResponse; +use Joomla\Http\Response; use Joomla\Registry\Registry; /** @@ -30,18 +30,19 @@ class GitHub * @since 3.0.0 */ protected $options; -/** + /** * The HTTP client object to use in sending HTTP requests. * * @var Http * @since 3.0.0 */ protected $client; -/** + + /** * Constructor. * - * @param Registry $options Connector options. - * @param Http $client The HTTP client object. + * @param Registry|null $options Connector options. + * @param Http|null $client The HTTP client object. * * @since 3.0.0 */ @@ -58,7 +59,7 @@ class GitHub * * @since 3.0.0 */ - public function getClient() + public function getClient(): Http { return $this->client; } @@ -66,22 +67,27 @@ class GitHub /** * Get the diff for a pull request. * - * @param string $user The name of the owner of the GitHub repository. - * @param string $repo The name of the GitHub repository. - * @param integer $pullId The pull request number. + * @param string $user The name of the owner of the GitHub repository. + * @param string $repo The name of the GitHub repository. + * @param int $pullId The pull request number. * * @return Response * * @since 3.0.0 */ - public function getDiffForPullRequest($user, $repo, $pullId) - { - // Build the request path. - $path = "/repos/$user/$repo/pulls/" . (int) $pullId; -// Build the request headers. - $headers = array('Accept' => 'application/vnd.github.diff'); + public function getDiffForPullRequest( + string $user, + string $repo, + int $pullId + ): Response { + $path = "/repos/$user/$repo/pulls/" . $pullId; + + $headers = ['Accept' => 'application/vnd.github.diff']; $prepared = $this->prepareRequest($path, 0, 0, $headers); - return $this->processResponse($this->client->get($prepared['url'], $prepared['headers'])); + + return $this->processResponse( + $this->client->get($prepared['url'], $prepared['headers']) + ); } /** @@ -89,27 +95,28 @@ class GitHub * * This method will add appropriate pagination details if necessary and also prepend the API url to have a complete URL for the request. * - * @param string $path Path to process - * @param integer $page Page to request - * @param integer $limit Number of results to return per page - * @param array $headers The headers to send with the request + * @param string $path Path to process + * @param int $page Page to request + * @param int $limit Number of results to return per page + * @param array $headers The headers to send with the request * * @return array Associative array containing the prepared URL and request headers * * @since 3.0.0 */ protected function prepareRequest( - $path, - $page = 0, - $limit = 0, - array $headers = array() - ) { + string $path, + int $page = 0, + int $limit = 0, + array $headers = [] + ): array { $url = $this->fetchUrl($path, $page, $limit); + if ($token = $this->options->get('gh.token', false)) { $headers['Authorization'] = "token $token"; } - return array('url' => $url, 'headers' => $headers); + return ['url' => $url, 'headers' => $headers]; } /** @@ -118,9 +125,9 @@ class GitHub * This method will add appropriate pagination details and basic authentication credentials if necessary * and also prepend the API url to have a complete URL for the request. * - * @param string $path URL to inflect - * @param integer $page Page to request - * @param integer $limit Number of results to return per page + * @param string $path URL to inflect + * @param int $page Page to request + * @param int $limit Number of results to return per page * * @return string The request URL. * @@ -130,38 +137,39 @@ class GitHub { // Get a new Uri object using the API URL and given path. $uri = new Uri($this->options->get('api.url') . $path); -// If we have a defined page number add it to the JUri object. + // If we have a defined page number add it to the JUri object. if ($page > 0) { - $uri->setVar('page', (int) $page); + $uri->setVar('page', (int)$page); } // If we have a defined items per page add it to the JUri object. if ($limit > 0) { - $uri->setVar('per_page', (int) $limit); + $uri->setVar('per_page', (int)$limit); } - return (string) $uri; + return (string)$uri; } /** * Process the response and return it. * * @param Response $response The response. - * @param integer $expectedCode The expected response code. + * @param int $expectedCode The expected response code. * * @return Response * * @throws UnexpectedResponse - *@since 3.0.0 + * @since 3.0.0 */ - protected function processResponse(Response $response, $expectedCode = 200) - { + protected function processResponse( + Response $response, + int $expectedCode = 200 + ): Response { // Validate the response code. if ($response->code != $expectedCode) { -// Decode the error response and throw an exception. + // Decode the error response and throw an exception. $body = json_decode($response->body); - $error = isset($body->error) ? $body->error - : (isset($body->message) ? $body->message : 'Unknown Error'); + $error = $body->error ?? ($body->message ?? 'Unknown Error'); throw new UnexpectedResponse( $response, @@ -187,15 +195,17 @@ class GitHub */ public function getFileContents($user, $repo, $path, $ref = null) { - $path = "/repos/$user/$repo/contents/$path"; + $path = "/repos/$user/$repo/contents/$path"; $prepared = $this->prepareRequest($path); if ($ref) { $url = new Uri($prepared['url']); $url->setVar('ref', $ref); - $prepared['url'] = (string) $url; + $prepared['url'] = (string)$url; } - return $this->processResponse($this->client->get($prepared['url'], $prepared['headers'])); + return $this->processResponse( + $this->client->get($prepared['url'], $prepared['headers']) + ); } /** @@ -212,18 +222,22 @@ class GitHub public function getFilesForPullRequest($user, $repo, $pullId, $page = 1) { // Build the request path. - $path = "/repos/$user/$repo/pulls/" . (int) $pullId . '/files?page=' . $page; + $path = "/repos/$user/$repo/pulls/" . (int)$pullId . '/files?page=' + . $page; $prepared = $this->prepareRequest($path); - return $this->processResponse($this->client->get($prepared['url'], $prepared['headers'])); + + return $this->processResponse( + $this->client->get($prepared['url'], $prepared['headers']) + ); } /** * Get a list of the open issues for a repository. * - * @param string $user The name of the owner of the GitHub repository. - * @param string $repo The name of the GitHub repository. - * @param integer $page The page number from which to get items. - * @param integer $limit The number of items on a page. + * @param string $user The name of the owner of the GitHub repository. + * @param string $repo The name of the GitHub repository. + * @param int $page The page number from which to get items. + * @param int $limit The number of items on a page. * * @return Response * @@ -236,16 +250,19 @@ class GitHub $page, $limit ); - return $this->processResponse($this->client->get($prepared['url'], $prepared['headers'])); + + return $this->processResponse( + $this->client->get($prepared['url'], $prepared['headers']) + ); } /** * Get a list of the open pull requests for a repository. * - * @param string $user The name of the owner of the GitHub repository. - * @param string $repo The name of the GitHub repository. - * @param integer $page The page number from which to get items. - * @param integer $limit The number of items on a page. + * @param string $user The name of the owner of the GitHub repository. + * @param string $repo The name of the GitHub repository. + * @param int $page The page number from which to get items. + * @param int $limit The number of items on a page. * * @return Response * @@ -258,7 +275,10 @@ class GitHub $page, $limit ); - return $this->processResponse($this->client->get($prepared['url'], $prepared['headers'])); + + return $this->processResponse( + $this->client->get($prepared['url'], $prepared['headers']) + ); } /** @@ -279,9 +299,9 @@ class GitHub /** * Get a single pull request. * - * @param string $user The name of the owner of the GitHub repository. - * @param string $repo The name of the GitHub repository. - * @param integer $pullId The pull request number. + * @param string $user The name of the owner of the GitHub repository. + * @param string $repo The name of the GitHub repository. + * @param int $pullId The pull request number. * * @return Response * @@ -290,9 +310,12 @@ class GitHub public function getPullRequest($user, $repo, $pullId) { // Build the request path. - $path = "/repos/$user/$repo/pulls/" . (int) $pullId; + $path = "/repos/$user/$repo/pulls/" . (int)$pullId; $prepared = $this->prepareRequest($path); - return $this->processResponse($this->client->get($prepared['url'], $prepared['headers'])); + + return $this->processResponse( + $this->client->get($prepared['url'], $prepared['headers']) + ); } /** @@ -305,7 +328,10 @@ class GitHub public function getRateLimit() { $prepared = $this->prepareRequest('/rate_limit'); - return $this->processResponse($this->client->get($prepared['url'], $prepared['headers'])); + + return $this->processResponse( + $this->client->get($prepared['url'], $prepared['headers']) + ); } /** @@ -321,6 +347,7 @@ class GitHub public function setOption($key, $value) { $this->options->set($key, $value); + return $this; } } diff --git a/administrator/components/com_patchtester/src/GithubCredentialsTrait.php b/administrator/components/com_patchtester/src/GithubCredentialsTrait.php new file mode 100644 index 0000000..746ba1a --- /dev/null +++ b/administrator/components/com_patchtester/src/GithubCredentialsTrait.php @@ -0,0 +1,25 @@ +set('github_user', $params->get('org', 'joomla')); + $state->set('github_repo', $params->get('repo', 'joomla-cms')); + + return $state; + } +} diff --git a/administrator/components/com_patchtester/src/Helper/Helper.php b/administrator/components/com_patchtester/src/Helper/Helper.php index c07ae56..c11e9f8 100644 --- a/administrator/components/com_patchtester/src/Helper/Helper.php +++ b/administrator/components/com_patchtester/src/Helper/Helper.php @@ -12,8 +12,8 @@ namespace Joomla\Component\Patchtester\Administrator\Helper; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; +use Joomla\Component\Patchtester\Administrator\GitHub\GitHub; use Joomla\Registry\Registry; -use src\GitHub\GitHub; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; diff --git a/administrator/components/com_patchtester/src/Model/FetchModel.php b/administrator/components/com_patchtester/src/Model/FetchModel.php index 1fa0387..2a863d0 100644 --- a/administrator/components/com_patchtester/src/Model/FetchModel.php +++ b/administrator/components/com_patchtester/src/Model/FetchModel.php @@ -10,7 +10,6 @@ namespace Joomla\Component\Patchtester\Administrator\Model; // phpcs:disable PSR1.Files.SideEffects - \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects diff --git a/administrator/components/com_patchtester/src/Model/PullModel.php b/administrator/components/com_patchtester/src/Model/PullModel.php index f194e5e..9905699 100644 --- a/administrator/components/com_patchtester/src/Model/PullModel.php +++ b/administrator/components/com_patchtester/src/Model/PullModel.php @@ -15,15 +15,17 @@ use Joomla\CMS\Factory; use Joomla\CMS\Filesystem\Path; use Joomla\CMS\Http\HttpFactory; use Joomla\CMS\Language\Text; +use Joomla\CMS\MVC\Factory\MVCFactoryInterface; +use Joomla\CMS\MVC\Model\BaseDatabaseModel; use Joomla\CMS\Version; +use Joomla\Component\Patchtester\Administrator\Github\Exception\UnexpectedResponse; +use Joomla\Component\Patchtester\Administrator\GitHub\GitHub; +use Joomla\Component\Patchtester\Administrator\GithubCredentialsTrait; use Joomla\Component\Patchtester\Administrator\Helper\Helper; -use Joomla\Database\DatabaseDriver; use Joomla\Filesystem\File; use Joomla\Filesystem\Folder; use Joomla\Registry\Registry; use RuntimeException; -use src\GitHub\Exception\UnexpectedResponse; -use src\GitHub\GitHub; use stdClass; // phpcs:disable PSR1.Files.SideEffects @@ -35,8 +37,9 @@ use stdClass; * * @since 2.0 */ -class PullModel extends AbstractModel +class PullModel extends BaseDatabaseModel { + use GithubCredentialsTrait; /** * Array containing top level non-production folders * @@ -44,21 +47,21 @@ class PullModel extends AbstractModel * @since 2.0 */ protected $nonProductionFolders - = array( + = [ 'build', 'docs', 'installation', 'tests', '.github', - ); -/** + ]; + /** * Array containing non-production files * * @var array * @since 2.0 */ protected $nonProductionFiles - = array( + = [ '.drone.yml', '.gitignore', '.php_cs', @@ -77,25 +80,27 @@ class PullModel extends AbstractModel 'jorobo.dist.ini', 'manifest.xml', 'crowdin.yaml', - ); -/** + ]; + + /** * The namespace mapper * * @var \JNamespacePsr4Map * @since 4.0.0 */ protected $namespaceMapper; -/** - * Instantiate the model. - * - * @param Registry $state The model state. - * @param DatabaseDriver $db The database adpater. - * - * @since 4.0.0 + + /** + * @inheritDoc */ - public function __construct(Registry $state = null, DatabaseDriver $db = null) - { - parent::__construct($state, $db); + public function __construct( + $config = [], + MVCFactoryInterface $factory = null + ) { + parent::__construct($config, $factory); + + $this->state = $this->getCredentials(); + $this->namespaceMapper = new \JNamespacePsr4Map(); } @@ -103,19 +108,19 @@ class PullModel extends AbstractModel * Patches the code with the supplied pull request * However uses different methods for different repositories. * - * @param integer $id ID of the pull request to apply + * @param int $id ID of the pull request to apply * - * @return boolean - * - * @since 3.0 + * @return bool * * @throws RuntimeException + * @since 3.0 + * */ public function apply(int $id): bool { $params = ComponentHelper::getParams('com_patchtester'); // Decide based on repository settings whether patch will be applied through Github or CIServer - if ((bool) $params->get('ci_switch', 0)) { + if ((bool)$params->get('ci_switch', 0)) { return $this->applyWithCIServer($id); } @@ -129,9 +134,9 @@ class PullModel extends AbstractModel * * @return boolean * + * @throws RuntimeException * @since 3.0 * - * @throws RuntimeException */ private function applyWithCIServer(int $id): bool { @@ -151,10 +156,10 @@ class PullModel extends AbstractModel Folder::create($ciSettings->get('folder.temp')); } - $tempPath = $ciSettings->get('folder.temp') . '/' . $id; - $backupsPath = $ciSettings->get('folder.backups') . '/' . $id; - $delLogPath = $tempPath . '/' . $ciSettings->get('zip.log.name'); - $zipPath = $tempPath . '/' . $ciSettings->get('zip.name'); + $tempPath = $ciSettings->get('folder.temp') . '/' . $id; + $backupsPath = $ciSettings->get('folder.backups') . '/' . $id; + $delLogPath = $tempPath . '/' . $ciSettings->get('zip.log.name'); + $zipPath = $tempPath . '/' . $ciSettings->get('zip.name'); $serverZipPath = sprintf($ciSettings->get('zip.url'), $id); // Patch has already been applied if (file_exists($backupsPath)) { @@ -163,7 +168,10 @@ class PullModel extends AbstractModel $version = new Version(); $httpOption = new Registry(); - $httpOption->set('userAgent', $version->getUserAgent('Joomla', true, false)); + $httpOption->set( + 'userAgent', + $version->getUserAgent('Joomla', true, false) + ); // Try to download the zip file try { $http = HttpFactory::getHttp($httpOption); @@ -172,24 +180,33 @@ class PullModel extends AbstractModel $result = null; } - if ($result === null || ($result->getStatusCode() !== 200 && $result->getStatusCode() !== 310)) { - throw new RuntimeException(Text::_('COM_PATCHTESTER_SERVER_RESPONDED_NOT_200')); + if ($result === null + || ($result->getStatusCode() !== 200 + && $result->getStatusCode() !== 310) + ) { + throw new RuntimeException( + Text::_('COM_PATCHTESTER_SERVER_RESPONDED_NOT_200') + ); } // Assign to variable to avlod PHP notice "Indirect modification of overloaded property" - $content = (string) $result->getBody(); + $content = (string)$result->getBody(); // Write the file to disk File::write($zipPath, $content); // Check if zip folder could have been downloaded if (!file_exists($zipPath)) { - throw new RuntimeException(Text::_('COM_PATCHTESTER_ZIP_DOES_NOT_EXIST')); + throw new RuntimeException( + Text::_('COM_PATCHTESTER_ZIP_DOES_NOT_EXIST') + ); } Folder::create($tempPath); $zip = new Zip(); if (!$zip->extract($zipPath, $tempPath)) { Folder::delete($tempPath); - throw new RuntimeException(Text::_('COM_PATCHTESTER_ZIP_EXTRACT_FAILED')); + throw new RuntimeException( + Text::_('COM_PATCHTESTER_ZIP_EXTRACT_FAILED') + ); } // Remove zip to avoid get listing afterwards @@ -198,14 +215,16 @@ class PullModel extends AbstractModel if ($this->verifyAutoloader($tempPath) === false) { // There is something broken in the autoloader, clean up and go back Folder::delete($tempPath); - throw new RuntimeException(Text::_('COM_PATCHTESTER_PATCH_BREAKS_SITE')); + throw new RuntimeException( + Text::_('COM_PATCHTESTER_PATCH_BREAKS_SITE') + ); } // Get files from deleted_logs $deletedFiles = (file_exists($delLogPath) ? file($delLogPath) : []); $deletedFiles = array_map('trim', $deletedFiles); if (file_exists($delLogPath)) { - // Remove deleted_logs to avoid get listing afterwards + // Remove deleted_logs to avoid get listing afterwards File::delete($delLogPath); } @@ -220,7 +239,7 @@ class PullModel extends AbstractModel $filePath = explode(DIRECTORY_SEPARATOR, Path::clean($file)); array_pop($filePath); $filePath = implode(DIRECTORY_SEPARATOR, $filePath); - // Deleted_logs returns files as well as folder, if value is folder, unset and skip + // Deleted_logs returns files as well as folder, if value is folder, unset and skip if (is_dir(JPATH_ROOT . '/' . $file)) { unset($files[$key]); continue; @@ -232,21 +251,37 @@ class PullModel extends AbstractModel Folder::create($backupsPath . '/' . $filePath); } - File::move(JPATH_ROOT . '/' . $file, $backupsPath . '/' . $file); + File::move( + JPATH_ROOT . '/' . $file, + $backupsPath . '/' . $file + ); } if (file_exists($tempPath . '/' . $file)) { // Create directories if they don't exist until file - if (!file_exists(JPATH_ROOT . '/' . $filePath) || !is_dir(JPATH_ROOT . '/' . $filePath)) { + if (!file_exists(JPATH_ROOT . '/' . $filePath) + || !is_dir( + JPATH_ROOT . '/' . $filePath + ) + ) { Folder::create(JPATH_ROOT . '/' . $filePath); } - File::copy($tempPath . '/' . $file, JPATH_ROOT . '/' . $file); + File::copy( + $tempPath . '/' . $file, + JPATH_ROOT . '/' . $file + ); } } catch (RuntimeException $exception) { Folder::delete($tempPath); Folder::move($backupsPath, $backupsPath . '_failed'); - throw new RuntimeException(Text::sprintf('COM_PATCHTESTER_FAILED_APPLYING_PATCH', $file, $exception->getMessage())); + throw new RuntimeException( + Text::sprintf( + 'COM_PATCHTESTER_FAILED_APPLYING_PATCH', + $file, + $exception->getMessage() + ) + ); } } @@ -258,6 +293,7 @@ class PullModel extends AbstractModel // Change the media version $version = new Version(); $version->refreshMediaVersion(); + return true; } @@ -269,9 +305,9 @@ class PullModel extends AbstractModel * * @return stdClass The pull request data * + * @throws RuntimeException * @since 2.0 * - * @throws RuntimeException */ private function retrieveGitHubData(GitHub $github, int $id): stdClass { @@ -279,19 +315,38 @@ class PullModel extends AbstractModel $rateResponse = $github->getRateLimit(); $rate = json_decode($rateResponse->body, false); } catch (UnexpectedResponse $exception) { - throw new RuntimeException(Text::sprintf('COM_PATCHTESTER_COULD_NOT_CONNECT_TO_GITHUB', $exception->getMessage()), $exception->getCode(), $exception); + throw new RuntimeException( + Text::sprintf( + 'COM_PATCHTESTER_COULD_NOT_CONNECT_TO_GITHUB', + $exception->getMessage() + ), $exception->getCode(), $exception + ); } // If over the API limit, we can't build this list - if ((int) $rate->resources->core->remaining === 0) { - throw new RuntimeException(Text::sprintf('COM_PATCHTESTER_API_LIMIT_LIST', Factory::getDate($rate->resources->core->reset))); + if ((int)$rate->resources->core->remaining === 0) { + throw new RuntimeException( + Text::sprintf( + 'COM_PATCHTESTER_API_LIMIT_LIST', + Factory::getDate($rate->resources->core->reset) + ) + ); } try { - $pullResponse = $github->getPullRequest($this->getState()->get('github_user'), $this->getState()->get('github_repo'), $id); + $pullResponse = $github->getPullRequest( + $this->getState()->get('github_user'), + $this->getState()->get('github_repo'), + $id + ); $pull = json_decode($pullResponse->body, false); } catch (UnexpectedResponse $exception) { - throw new RuntimeException(Text::sprintf('COM_PATCHTESTER_COULD_NOT_CONNECT_TO_GITHUB', $exception->getMessage()), $exception->getCode(), $exception); + throw new RuntimeException( + Text::sprintf( + 'COM_PATCHTESTER_COULD_NOT_CONNECT_TO_GITHUB', + $exception->getMessage() + ), $exception->getCode(), $exception + ); } return $pull; @@ -309,7 +364,7 @@ class PullModel extends AbstractModel private function verifyAutoloader(string $path): bool { $result = false; -// Check if we have an autoload file + // Check if we have an autoload file if (!file_exists($path . '/libraries/vendor/autoload.php')) { return $result; } @@ -322,35 +377,50 @@ class PullModel extends AbstractModel 'autoload_psr4.php', ]; $filesToCheck = []; - array_walk($composerFiles, static function ($composerFile) use (&$filesToCheck, $path) { - - if (file_exists($path . '/libraries/vendor/composer/' . $composerFile) === false) { - return; - } - - if ($composerFile === 'autoload_static.php') { -// Get the generated token - $autoload = file_get_contents($path . '/libraries/vendor/autoload.php'); - $resultMatch = preg_match('/ComposerAutoloaderInit(.*)::/', $autoload, $match); - if (!$resultMatch) { + array_walk( + $composerFiles, + static function ($composerFile) use (&$filesToCheck, $path) { + if (file_exists( + $path . '/libraries/vendor/composer/' . $composerFile + ) === false + ) { return; } - // Check if the class is already defined - $autoloadClass = '\Composer\Autoload\ComposerStaticInit' . $match[1]; - if (class_exists($autoloadClass)) { - return; - } + if ($composerFile === 'autoload_static.php') { + // Get the generated token + $autoload = file_get_contents( + $path . '/libraries/vendor/autoload.php' + ); + $resultMatch = preg_match( + '/ComposerAutoloaderInit(.*)::/', + $autoload, + $match + ); + if (!$resultMatch) { + return; + } - require_once $path . '/libraries/vendor/composer/autoload_static.php'; -// Get all the files - $files = $autoloadClass::$files; - $filesToCheck = array_merge($filesToCheck, $files); - } else { - $files = require $path . '/libraries/vendor/composer/' . $composerFile; - $filesToCheck = array_merge($filesToCheck, $files); + // Check if the class is already defined + $autoloadClass = '\Composer\Autoload\ComposerStaticInit' + . $match[1]; + if (class_exists($autoloadClass)) { + return; + } + + require_once $path + . '/libraries/vendor/composer/autoload_static.php'; + // Get all the files + $files = $autoloadClass::$files; + $filesToCheck = array_merge($filesToCheck, $files); + } else { + $files = require $path + . '/libraries/vendor/composer/' . $composerFile; + $filesToCheck = array_merge($filesToCheck, $files); + } } - }); + ); + return $this->checkFilesExist($filesToCheck, $path); } @@ -393,24 +463,29 @@ class PullModel extends AbstractModel * * @since 3.0 */ - private function saveAppliedPatch(int $id, array $fileList, string $sha = null): int - { - $record = (object) array( + private function saveAppliedPatch( + int $id, + array $fileList, + string $sha = null + ): int { + $record = (object)[ 'pull_id' => $id, 'data' => json_encode($fileList), 'patched_by' => Factory::getUser()->id, 'applied' => 1, 'applied_version' => JVERSION, - ); - $db = $this->getDb(); + ]; + $db = $this->getDatabase(); $db->insertObject('#__patchtester_tests', $record); $insertId = $db->insertid(); if ($sha !== null) { - // Insert the retrieved commit SHA into the pulls table for this item - $db->setQuery($db->getQuery(true) + // Insert the retrieved commit SHA into the pulls table for this item + $db->setQuery( + $db->getQuery(true) ->update('#__patchtester_pulls') ->set('sha = ' . $db->quote($sha)) - ->where($db->quoteName('pull_id') . ' = ' . $id))->execute(); + ->where($db->quoteName('pull_id') . ' = ' . $id) + )->execute(); } return $insertId; @@ -423,14 +498,14 @@ class PullModel extends AbstractModel * * @return boolean * + * @throws RuntimeException * @since 2.0 * - * @throws RuntimeException */ private function applyWithGitHub(int $id): bool { $github = Helper::initializeGithub(); - $pull = $this->retrieveGitHubData($github, $id); + $pull = $this->retrieveGitHubData($github, $id); if ($pull->head->repo === null) { throw new RuntimeException(Text::_('COM_PATCHTESTER_REPO_IS_GONE')); } @@ -438,7 +513,12 @@ class PullModel extends AbstractModel try { $files = $this->getFiles($id, 1); } catch (UnexpectedResponse $exception) { - throw new RuntimeException(Text::sprintf('COM_PATCHTESTER_COULD_NOT_CONNECT_TO_GITHUB', $exception->getMessage()), $exception->getCode(), $exception); + throw new RuntimeException( + Text::sprintf( + 'COM_PATCHTESTER_COULD_NOT_CONNECT_TO_GITHUB', + $exception->getMessage() + ), $exception->getCode(), $exception + ); } if (!count($files)) { @@ -454,7 +534,12 @@ class PullModel extends AbstractModel switch ($file->action) { case 'deleted': if (!file_exists(JPATH_ROOT . '/' . $file->filename)) { - throw new RuntimeException(Text::sprintf('COM_PATCHTESTER_FILE_DELETED_DOES_NOT_EXIST_S', $file->filename)); + throw new RuntimeException( + Text::sprintf( + 'COM_PATCHTESTER_FILE_DELETED_DOES_NOT_EXIST_S', + $file->filename + ) + ); } @@ -463,17 +548,43 @@ class PullModel extends AbstractModel 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(Text::sprintf('COM_PATCHTESTER_CONFLICT_S', $file->filename)); + if (file_exists( + JPATH_COMPONENT . '/backups/' . md5($file->filename) + . '.txt' + ) + ) { + throw new RuntimeException( + Text::sprintf( + 'COM_PATCHTESTER_CONFLICT_S', + $file->filename + ) + ); } - if ($file->action === 'modified' && !file_exists(JPATH_ROOT . '/' . $file->filename)) { - throw new RuntimeException(Text::sprintf('COM_PATCHTESTER_FILE_MODIFIED_DOES_NOT_EXIST_S', $file->filename)); + if ($file->action === 'modified' + && !file_exists( + JPATH_ROOT . '/' . $file->filename + ) + ) { + throw new RuntimeException( + Text::sprintf( + 'COM_PATCHTESTER_FILE_MODIFIED_DOES_NOT_EXIST_S', + $file->filename + ) + ); } try { - $contentsResponse = $github->getFileContents($pull->head->user->login, $pull->head->repo->name, $file->repofilename, urlencode($pull->head->ref)); - $contents = json_decode($contentsResponse->body, false); + $contentsResponse = $github->getFileContents( + $pull->head->user->login, + $pull->head->repo->name, + $file->repofilename, + urlencode($pull->head->ref) + ); + $contents = json_decode( + $contentsResponse->body, + false + ); // In case encoding type ever changes switch ($contents->encoding) { case 'base64': @@ -481,10 +592,19 @@ class PullModel extends AbstractModel break; default: - throw new RuntimeException(Text::_('COM_PATCHTESTER_ERROR_UNSUPPORTED_ENCODING')); + throw new RuntimeException( + Text::_( + 'COM_PATCHTESTER_ERROR_UNSUPPORTED_ENCODING' + ) + ); } } catch (UnexpectedResponse $exception) { - throw new RuntimeException(Text::sprintf('COM_PATCHTESTER_COULD_NOT_CONNECT_TO_GITHUB', $exception->getMessage()), $exception->getCode(), $exception); + throw new RuntimeException( + Text::sprintf( + 'COM_PATCHTESTER_COULD_NOT_CONNECT_TO_GITHUB', + $exception->getMessage() + ), $exception->getCode(), $exception + ); } break; @@ -496,40 +616,84 @@ class PullModel extends AbstractModel // 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') + || (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; + $filename = $file->action === 'renamed' ? $file->originalFile + : $file->filename; $src = JPATH_ROOT . '/' . $filename; - $dest = JPATH_COMPONENT . '/backups/' . md5($filename) . '.txt'; + $dest = JPATH_COMPONENT . '/backups/' . md5($filename) + . '.txt'; if (!File::copy(Path::clean($src), $dest)) { - throw new RuntimeException(Text::sprintf('COM_PATCHTESTER_ERROR_CANNOT_COPY_FILE', $src, $dest)); + throw new RuntimeException( + Text::sprintf( + 'COM_PATCHTESTER_ERROR_CANNOT_COPY_FILE', + $src, + $dest + ) + ); } } switch ($file->action) { case 'modified': case 'added': - if (!File::write(Path::clean(JPATH_ROOT . '/' . $file->filename), $file->body)) { - throw new RuntimeException(Text::sprintf('COM_PATCHTESTER_ERROR_CANNOT_WRITE_FILE', JPATH_ROOT . '/' . $file->filename)); + if (!File::write( + Path::clean(JPATH_ROOT . '/' . $file->filename), + $file->body + ) + ) { + throw new RuntimeException( + Text::sprintf( + 'COM_PATCHTESTER_ERROR_CANNOT_WRITE_FILE', + JPATH_ROOT . '/' . $file->filename + ) + ); } break; case 'deleted': - if (!File::delete(Path::clean(JPATH_ROOT . '/' . $file->filename))) { - throw new RuntimeException(Text::sprintf('COM_PATCHTESTER_ERROR_CANNOT_DELETE_FILE', JPATH_ROOT . '/' . $file->filename)); + if (!File::delete( + Path::clean(JPATH_ROOT . '/' . $file->filename) + ) + ) { + throw new RuntimeException( + Text::sprintf( + 'COM_PATCHTESTER_ERROR_CANNOT_DELETE_FILE', + JPATH_ROOT . '/' . $file->filename + ) + ); } break; case 'renamed': - if (!File::delete(Path::clean(JPATH_ROOT . '/' . $file->originalFile))) { - throw new RuntimeException(Text::sprintf('COM_PATCHTESTER_ERROR_CANNOT_DELETE_FILE', JPATH_ROOT . '/' . $file->originalFile)); + if (!File::delete( + Path::clean(JPATH_ROOT . '/' . $file->originalFile) + ) + ) { + throw new RuntimeException( + Text::sprintf( + 'COM_PATCHTESTER_ERROR_CANNOT_DELETE_FILE', + JPATH_ROOT . '/' . $file->originalFile + ) + ); } - if (!File::write(Path::clean(JPATH_ROOT . '/' . $file->filename), $file->body)) { - throw new RuntimeException(Text::sprintf('COM_PATCHTESTER_ERROR_CANNOT_WRITE_FILE', JPATH_ROOT . '/' . $file->filename)); + if (!File::write( + Path::clean(JPATH_ROOT . '/' . $file->filename), + $file->body + ) + ) { + throw new RuntimeException( + Text::sprintf( + 'COM_PATCHTESTER_ERROR_CANNOT_WRITE_FILE', + JPATH_ROOT . '/' . $file->filename + ) + ); } break; @@ -545,6 +709,7 @@ class PullModel extends AbstractModel // Change the media version $version = new Version(); $version->refreshMediaVersion(); + return true; } @@ -561,11 +726,19 @@ class PullModel extends AbstractModel */ private function getFiles(int $id, int $page, array $files = []): array { - $github = Helper::initializeGithub(); - $filesResponse = $github->getFilesForPullRequest($this->getState()->get('github_user'), $this->getState()->get('github_repo'), $id, $page); - $files = array_merge($files, json_decode($filesResponse->getBody(), false)); - $lastPage = 1; - $headers = $filesResponse->getHeaders(); + $github = Helper::initializeGithub(); + $filesResponse = $github->getFilesForPullRequest( + $this->getState()->get('github_user'), + $this->getState()->get('github_repo'), + $id, + $page + ); + $files = array_merge( + $files, + json_decode($filesResponse->getBody(), false) + ); + $lastPage = 1; + $headers = $filesResponse->getHeaders(); if (!isset($headers['link'])) { return $files; @@ -578,7 +751,7 @@ class PullModel extends AbstractModel ); if ($matches && isset($matches[0])) { preg_match('/\d+/', $matches[0], $pages); - $lastPage = (int) $pages[0]; + $lastPage = (int)$pages[0]; } if ($page <= $lastPage) { @@ -600,10 +773,10 @@ class PullModel extends AbstractModel private function parseFileList(array $files): array { $parsedFiles = array(); -/* - * Check if the patch tester is running in a development environment - * If we are not in development, we'll need to check the exclusion lists - */ + /* + * Check if the patch tester is running in a development environment + * If we are not in development, we'll need to check the exclusion lists + */ $isDev = file_exists(JPATH_INSTALLATION . '/index.php'); foreach ($files as $file) { if (!$isDev) { @@ -621,20 +794,24 @@ class PullModel extends AbstractModel $prodFileName = $file->filename; $prodRenamedFileName = $file->previous_filename ?? false; $filePath = explode('/', $prodFileName); - // Remove the `src` here to match the CMS paths if needed + // Remove the `src` here to match the CMS paths if needed if ($filePath[0] === 'src') { $prodFileName = str_replace('src/', '', $prodFileName); } if ($prodRenamedFileName) { $filePath = explode('/', $prodRenamedFileName); - // Remove the `src` here to match the CMS paths if needed + // Remove the `src` here to match the CMS paths if needed if ($filePath[0] === 'src') { - $prodRenamedFileName = str_replace('src/', '', $prodRenamedFileName); + $prodRenamedFileName = str_replace( + 'src/', + '', + $prodRenamedFileName + ); } } - $parsedFiles[] = (object) array( + $parsedFiles[] = (object)array( 'action' => $file->status, 'filename' => $prodFileName, 'repofilename' => $file->filename, @@ -654,14 +831,14 @@ class PullModel extends AbstractModel * * @return boolean * - * @since 3.0 * @throws RuntimeException + * @since 3.0 */ public function revert(int $id): bool { $params = ComponentHelper::getParams('com_patchtester'); // Decide based on repository settings whether patch will be applied through Github or CIServer - if ((bool) $params->get('ci_switch', 0)) { + if ((bool)$params->get('ci_switch', 0)) { return $this->revertWithCIServer($id); } @@ -675,8 +852,8 @@ class PullModel extends AbstractModel * * @return boolean * - * @since 3.0 * @throws RuntimeException + * @since 3.0 */ public function revertWithCIServer(int $id): bool { @@ -690,10 +867,17 @@ class PullModel extends AbstractModel $files = json_decode($testRecord->data, false); if (!$files) { - throw new RuntimeException(Text::sprintf('COM_PATCHTESTER_ERROR_READING_DATABASE_TABLE', __METHOD__, htmlentities($testRecord->data))); + throw new RuntimeException( + Text::sprintf( + 'COM_PATCHTESTER_ERROR_READING_DATABASE_TABLE', + __METHOD__, + htmlentities($testRecord->data) + ) + ); } - $backupsPath = $ciSettings->get('folder.backups') . "/$testRecord->pull_id"; + $backupsPath = $ciSettings->get('folder.backups') + . "/$testRecord->pull_id"; foreach ($files as $file) { try { $filePath = explode("\\", $file); @@ -704,11 +888,16 @@ class PullModel extends AbstractModel File::delete(JPATH_ROOT . "/$file"); // Move from backup, if it exists there if (file_exists($backupsPath . '/' . $file)) { - File::move($backupsPath . '/' . $file, JPATH_ROOT . '/' . $file); + File::move( + $backupsPath . '/' . $file, + JPATH_ROOT . '/' . $file + ); } // If folder is empty, remove it as well - if (count(glob(JPATH_ROOT . '/' . $filePath . '/*')) === 0) { + if (count(glob(JPATH_ROOT . '/' . $filePath . '/*')) + === 0 + ) { Folder::delete(JPATH_ROOT . '/' . $filePath); } } elseif (file_exists($backupsPath . '/' . $file)) { @@ -717,10 +906,19 @@ class PullModel extends AbstractModel Folder::create(JPATH_ROOT . '/' . $filePath); } - File::move($backupsPath . '/' . $file, JPATH_ROOT . '/' . $file); + File::move( + $backupsPath . '/' . $file, + JPATH_ROOT . '/' . $file + ); } } catch (RuntimeException $exception) { - throw new RuntimeException(Text::sprintf('COM_PATCHTESTER_FAILED_REVERT_PATCH', $file, $exception->getMessage())); + throw new RuntimeException( + Text::sprintf( + 'COM_PATCHTESTER_FAILED_REVERT_PATCH', + $file, + $exception->getMessage() + ) + ); } } @@ -730,6 +928,7 @@ class PullModel extends AbstractModel // Change the media version $version = new Version(); $version->refreshMediaVersion(); + return $this->removeTest($testRecord); } @@ -744,11 +943,14 @@ class PullModel extends AbstractModel */ private function getTestRecord(int $id): stdClass { - $db = $this->getDb(); - return $db->setQuery($db->getQuery(true) + $db = $this->getDatabase(); + + return $db->setQuery( + $db->getQuery(true) ->select('*') ->from('#__patchtester_tests') - ->where('id = ' . (int) $id))->loadObject(); + ->where('id = ' . (int)$id) + )->loadObject(); } /** @@ -762,16 +964,21 @@ class PullModel extends AbstractModel */ private function removeTest(stdClass $testRecord): bool { - $db = $this->getDb(); -// Remove the retrieved commit SHA from the pulls table for this item - $db->setQuery($db->getQuery(true) + $db = $this->getDatabase(); + // Remove the retrieved commit SHA from the pulls table for this item + $db->setQuery( + $db->getQuery(true) ->update('#__patchtester_pulls') ->set('sha = ' . $db->quote('')) - ->where($db->quoteName('id') . ' = ' . (int) $testRecord->id))->execute(); -// And delete the record from the tests table - $db->setQuery($db->getQuery(true) + ->where($db->quoteName('id') . ' = ' . (int)$testRecord->id) + )->execute(); + // And delete the record from the tests table + $db->setQuery( + $db->getQuery(true) ->delete('#__patchtester_tests') - ->where('id = ' . (int) $testRecord->id))->execute(); + ->where('id = ' . (int)$testRecord->id) + )->execute(); + return true; } @@ -782,8 +989,8 @@ class PullModel extends AbstractModel * * @return boolean * - * @since 2.0 * @throws RuntimeException + * @since 2.0 */ public function revertWithGitHub(int $id): bool { @@ -795,22 +1002,40 @@ class PullModel extends AbstractModel $files = json_decode($testRecord->data, false); if (!$files) { - throw new RuntimeException(Text::sprintf('COM_PATCHTESTER_ERROR_READING_DATABASE_TABLE', __METHOD__, htmlentities($testRecord->data))); + throw new RuntimeException( + Text::sprintf( + 'COM_PATCHTESTER_ERROR_READING_DATABASE_TABLE', + __METHOD__, + htmlentities($testRecord->data) + ) + ); } foreach ($files as $file) { switch ($file->action) { case 'deleted': case 'modified': - $src = JPATH_COMPONENT . '/backups/' . md5($file->filename) . '.txt'; + $src = JPATH_COMPONENT . '/backups/' . md5($file->filename) + . '.txt'; $dest = JPATH_ROOT . '/' . $file->filename; if (!File::copy($src, $dest)) { - throw new RuntimeException(Text::sprintf('COM_PATCHTESTER_ERROR_CANNOT_COPY_FILE', $src, $dest)); + throw new RuntimeException( + Text::sprintf( + 'COM_PATCHTESTER_ERROR_CANNOT_COPY_FILE', + $src, + $dest + ) + ); } if (file_exists($src)) { if (!File::delete($src)) { - throw new RuntimeException(Text::sprintf('COM_PATCHTESTER_ERROR_CANNOT_DELETE_FILE', $src)); + throw new RuntimeException( + Text::sprintf( + 'COM_PATCHTESTER_ERROR_CANNOT_DELETE_FILE', + $src + ) + ); } } @@ -820,28 +1045,51 @@ class PullModel extends AbstractModel $src = JPATH_ROOT . '/' . $file->filename; if (file_exists($src)) { if (!File::delete($src)) { - throw new RuntimeException(Text::sprintf('COM_PATCHTESTER_ERROR_CANNOT_DELETE_FILE', $src)); + throw new RuntimeException( + Text::sprintf( + 'COM_PATCHTESTER_ERROR_CANNOT_DELETE_FILE', + $src + ) + ); } } break; case 'renamed': - $originalSrc = JPATH_COMPONENT . '/backups/' . md5($file->originalFile) . '.txt'; + $originalSrc = JPATH_COMPONENT . '/backups/' . md5( + $file->originalFile + ) . '.txt'; $newSrc = JPATH_ROOT . '/' . $file->filename; $dest = JPATH_ROOT . '/' . $file->originalFile; if (!File::copy($originalSrc, $dest)) { - throw new RuntimeException(Text::sprintf('COM_PATCHTESTER_ERROR_CANNOT_COPY_FILE', $originalSrc, $dest)); + throw new RuntimeException( + Text::sprintf( + 'COM_PATCHTESTER_ERROR_CANNOT_COPY_FILE', + $originalSrc, + $dest + ) + ); } if (file_exists($originalSrc)) { if (!File::delete($originalSrc)) { - throw new RuntimeException(Text::sprintf('COM_PATCHTESTER_ERROR_CANNOT_DELETE_FILE', $originalSrc)); + throw new RuntimeException( + Text::sprintf( + 'COM_PATCHTESTER_ERROR_CANNOT_DELETE_FILE', + $originalSrc + ) + ); } } if (file_exists($newSrc)) { if (!File::delete($newSrc)) { - throw new RuntimeException(Text::sprintf('COM_PATCHTESTER_ERROR_CANNOT_DELETE_FILE', $newSrc)); + throw new RuntimeException( + Text::sprintf( + 'COM_PATCHTESTER_ERROR_CANNOT_DELETE_FILE', + $newSrc + ) + ); } } @@ -855,6 +1103,7 @@ class PullModel extends AbstractModel // Change the media version $version = new Version(); $version->refreshMediaVersion(); + return $this->removeTest($testRecord); } } diff --git a/administrator/components/com_patchtester/src/Model/PullsModel.php b/administrator/components/com_patchtester/src/Model/PullsModel.php index d2c229b..e4664da 100644 --- a/administrator/components/com_patchtester/src/Model/PullsModel.php +++ b/administrator/components/com_patchtester/src/Model/PullsModel.php @@ -14,6 +14,7 @@ use Joomla\CMS\HTML\HTMLHelper; use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\Model\ListModel; use Joomla\Component\Patchtester\Administrator\Github\Exception\UnexpectedResponse; +use Joomla\Component\Patchtester\Administrator\GithubCredentialsTrait; use Joomla\Component\Patchtester\Administrator\Helper\Helper; use Joomla\Database\DatabaseQuery; use RuntimeException; @@ -29,6 +30,8 @@ use RuntimeException; */ class PullsModel extends ListModel { + use GithubCredentialsTrait; + /** * The object context * @@ -43,18 +46,18 @@ class PullsModel extends ListModel * @since 2.0 */ protected $sortFields = ['pulls.pull_id', 'pulls.title']; + /** * Constructor. * * @param array $config An optional associative array of configuration settings. * - * @since 4.0.0 * @throws Exception * + * @since 4.0.0 */ public function __construct($config = []) { - $config = []; if (empty($config['filter_fields'])) { $config['filter_fields'] = [ 'applied', @@ -63,12 +66,27 @@ class PullsModel extends ListModel 'draft', 'label', 'branch', + + 'pulls.title', + 'pulls.pull_id', ]; } parent::__construct($config); } + protected function populateState($ordering = 'pulls.pull_id', $direction = 'DESC') + { + parent::populateState( + $ordering, + $direction + ); + + foreach ($this->getCredentials() as $name => $value) { + $this->state->set($name, $value); + } + } + /** * Method to get an array of data items. * @@ -93,13 +111,13 @@ class PullsModel extends ListModel ->select($db->quoteName(['name', 'color'])) ->from($db->quoteName('#__patchtester_pulls_labels')); array_walk($items, static function ($item) use ($db, $query) { - - $query->clear('where'); + $query->clear('where'); $query->where($db->quoteName('pull_id') . ' = ' . $item->pull_id); $db->setQuery($query); $item->labels = $db->loadObjectList(); }); $this->cache[$store] = $items; + return $this->cache[$store]; } @@ -118,11 +136,11 @@ class PullsModel extends ListModel */ protected function getStoreId($id = '') { - // Add the list state to the store id. $id .= ':' . $this->getState()->get('list.start'); $id .= ':' . $this->getState()->get('list.limit'); $id .= ':' . $this->getState()->get('list.ordering'); $id .= ':' . $this->getState()->get('list.direction'); + return md5($this->context . ':' . $id); } @@ -135,11 +153,14 @@ class PullsModel extends ListModel * * @return array An array of results. * - * @since 2.0 * @throws RuntimeException + * @since 2.0 */ - protected function getList($query, int $limitstart = 0, int $limit = 0): array - { + protected function getList( + $query, + int $limitstart = 0, + int $limit = 0 + ): array { return $this->getDatabase()->setQuery($query, $limitstart, $limit) ->loadObjectList(); } @@ -183,53 +204,68 @@ class PullsModel extends ListModel $query->select('pulls.*') ->select($db->quoteName('tests.id', 'applied')) ->from($db->quoteName('#__patchtester_pulls', 'pulls')) - ->leftJoin($db->quoteName('#__patchtester_tests', 'tests') + ->leftJoin( + $db->quoteName('#__patchtester_tests', 'tests') . ' ON ' . $db->quoteName('tests.pull_id') . ' = ' - . $db->quoteName('pulls.pull_id')); + . $db->quoteName('pulls.pull_id') + ); $search = $this->getState()->get('filter.search'); if (!empty($search)) { if (stripos($search, 'id:') === 0) { - $query->where($db->quoteName('pulls.pull_id') . ' = ' . (int) substr( - $search, - 3 - )); + $query->where( + $db->quoteName('pulls.pull_id') . ' = ' . (int)substr( + $search, + 3 + ) + ); } elseif (is_numeric($search)) { - $query->where($db->quoteName('pulls.pull_id') . ' = ' . (int) $search); + $query->where( + $db->quoteName('pulls.pull_id') . ' = ' . (int)$search + ); } else { - $query->where('(' . $db->quoteName('pulls.title') . ' LIKE ' . $db->quote('%' . $db->escape($search, true) . '%') . ')'); + $query->where( + '(' . $db->quoteName('pulls.title') . ' LIKE ' . $db->quote( + '%' . $db->escape($search, true) . '%' + ) . ')' + ); } } $applied = $this->getState()->get('filter.applied'); if (!empty($applied)) { - // Not applied patches have a NULL value, so build our value part of the query based on this + // Not applied patches have a NULL value, so build our value part of the query based on this $value = $applied === 'no' ? ' IS NULL' : ' = 1'; $query->where($db->quoteName('applied') . $value); } $branch = $this->getState()->get('filter.branch'); if (!empty($branch)) { - $query->where($db->quoteName('pulls.branch') . ' IN (' . implode(',', $db->quote($branch)) . ')'); + $query->where( + $db->quoteName('pulls.branch') . ' IN (' . implode( + ',', + $db->quote($branch) + ) . ')' + ); } $applied = $this->getState()->get('filter.rtc'); if (!empty($applied)) { - // Not applied patches have a NULL value, so build our value part of the query based on this + // Not applied patches have a NULL value, so build our value part of the query based on this $value = $applied === 'no' ? '0' : '1'; $query->where($db->quoteName('pulls.is_rtc') . ' = ' . $value); } $npm = $this->getState()->get('filter.npm'); if (!empty($npm)) { - // Not applied patches have a NULL value, so build our value part of the query based on this + // Not applied patches have a NULL value, so build our value part of the query based on this $value = $npm === 'no' ? '0' : '1'; $query->where($db->quoteName('pulls.is_npm') . ' = ' . $value); } $draft = $this->getState()->get('filter.draft'); if (!empty($draft)) { - // Not applied patches have a NULL value, so build our value part of the query based on this + // Not applied patches have a NULL value, so build our value part of the query based on this $value = $draft === 'no' ? '0' : '1'; $query->where($db->quoteName('pulls.is_draft') . ' = ' . $value); } @@ -239,27 +275,43 @@ class PullsModel extends ListModel if (!empty($labels) && $labels[0] !== '') { $labelQuery ->select($db->quoteName('pulls_labels.pull_id')) - ->select('COUNT(' . $db->quoteName('pulls_labels.name') . ') AS ' - . $db->quoteName('labelCount')) - ->from($db->quoteName( - '#__patchtester_pulls_labels', - 'pulls_labels' - )) - ->where($db->quoteName('pulls_labels.name') . ' IN (' . implode( - ',', - $db->quote($labels) - ) . ')') + ->select( + 'COUNT(' . $db->quoteName('pulls_labels.name') . ') AS ' + . $db->quoteName('labelCount') + ) + ->from( + $db->quoteName( + '#__patchtester_pulls_labels', + 'pulls_labels' + ) + ) + ->where( + $db->quoteName('pulls_labels.name') . ' IN (' . implode( + ',', + $db->quote($labels) + ) . ')' + ) ->group($db->quoteName('pulls_labels.pull_id')); - $query->leftJoin('(' . $labelQuery->__toString() . ') AS ' . $db->quoteName('pulls_labels') + $query->leftJoin( + '(' . $labelQuery->__toString() . ') AS ' . $db->quoteName( + 'pulls_labels' + ) . ' ON ' . $db->quoteName('pulls_labels.pull_id') . ' = ' - . $db->quoteName('pulls.pull_id')) - ->where($db->quoteName('pulls_labels.labelCount') . ' = ' . count($labels)); + . $db->quoteName('pulls.pull_id') + ) + ->where( + $db->quoteName('pulls_labels.labelCount') . ' = ' . count( + $labels + ) + ); } $ordering = $this->getState()->get('list.ordering', 'pulls.pull_id'); $direction = $this->getState()->get('list.direction', 'DESC'); if (!empty($ordering)) { - $query->order($db->escape($ordering) . ' ' . $db->escape($direction)); + $query->order( + $db->escape($ordering) . ' ' . $db->escape($direction) + ); } return $query; @@ -296,12 +348,20 @@ class PullsModel extends ListModel try { // TODO - Option to configure the batch size - $batchSize = 100; - $pullsResponse = Helper::initializeGithub()->getOpenPulls($this->getState()->get('github_user'), $this->getState()->get('github_repo'), $page, $batchSize); - $pulls = json_decode($pullsResponse->body); + $batchSize = 100; + $pullsResponse = Helper::initializeGithub()->getOpenPulls( + $this->getState()->get('github_user'), + $this->getState()->get('github_repo'), + $page, + $batchSize + ); + $pulls = json_decode($pullsResponse->body); } catch (UnexpectedResponse $exception) { throw new RuntimeException( - Text::sprintf('COM_PATCHTESTER_ERROR_GITHUB_FETCH', $exception->getMessage()), + Text::sprintf( + 'COM_PATCHTESTER_ERROR_GITHUB_FETCH', + $exception->getMessage() + ), $exception->getCode(), $exception ); @@ -332,7 +392,7 @@ class PullsModel extends ListModel $matches[0] ); preg_match('/\d+/', $pageSegment, $pages); - $lastPage = (int) $pages[0]; + $lastPage = (int)$pages[0]; } } } @@ -353,30 +413,38 @@ class PullsModel extends ListModel if (strtolower($label->name) === 'rtc') { $isRTC = true; } elseif ( - in_array(strtolower($label->name), ['npm resource changed', 'composer dependency changed'], true) + in_array( + strtolower($label->name), + ['npm resource changed', 'composer dependency changed'], + true + ) ) { $isNPM = true; } $labels[] = implode(',', [ - (int) $pull->number, - $this->getDatabase()->quote($label->name), - $this->getDatabase()->quote($label->color), - ]); + (int)$pull->number, + $this->getDatabase()->quote($label->name), + $this->getDatabase()->quote($label->color), + ]); } // Build the data object to store in the database $pullData = [ - (int) $pull->number, - $this->getDatabase()->quote(HTMLHelper::_('string.truncate', ($pull->title ?? ''), 150)), - $this->getDatabase()->quote(HTMLHelper::_('string.truncate', ($pull->body ?? ''), 100)), + (int)$pull->number, + $this->getDatabase()->quote( + HTMLHelper::_('string.truncate', ($pull->title ?? ''), 150) + ), + $this->getDatabase()->quote( + HTMLHelper::_('string.truncate', ($pull->body ?? ''), 100) + ), $this->getDatabase()->quote($pull->html_url), - (int) $isRTC, - (int) $isNPM, + (int)$isRTC, + (int)$isNPM, $this->getDatabase()->quote($branch), ($pull->draft ? 1 : 0) ]; - $data[] = implode(',', $pullData); + $data[] = implode(',', $pullData); } // If there are no pulls to insert then bail, assume we're finished @@ -385,15 +453,28 @@ class PullsModel extends ListModel } try { - $this->getDatabase()->setQuery($this->getDatabase()->getQuery(true) + $this->getDatabase()->setQuery( + $this->getDatabase()->getQuery(true) ->insert('#__patchtester_pulls') - ->columns(['pull_id', 'title', 'description', 'pull_url', - 'is_rtc', 'is_npm', 'branch', 'is_draft']) - ->values($data)); + ->columns([ + 'pull_id', + 'title', + 'description', + 'pull_url', + 'is_rtc', + 'is_npm', + 'branch', + 'is_draft' + ]) + ->values($data) + ); $this->getDatabase()->execute(); } catch (RuntimeException $exception) { throw new RuntimeException( - Text::sprintf('COM_PATCHTESTER_ERROR_INSERT_DATABASE', $exception->getMessage()), + Text::sprintf( + 'COM_PATCHTESTER_ERROR_INSERT_DATABASE', + $exception->getMessage() + ), $exception->getCode(), $exception ); @@ -401,10 +482,12 @@ class PullsModel extends ListModel if ($labels) { try { - $this->getDatabase()->setQuery($this->getDatabase()->getQuery(true) + $this->getDatabase()->setQuery( + $this->getDatabase()->getQuery(true) ->insert('#__patchtester_pulls_labels') ->columns(['pull_id', 'name', 'color']) - ->values($labels)); + ->values($labels) + ); $this->getDatabase()->execute(); } catch (RuntimeException $exception) { throw new RuntimeException( diff --git a/administrator/components/com_patchtester/src/Model/TestsModel.php b/administrator/components/com_patchtester/src/Model/TestsModel.php index 0df2425..60a1ccd 100644 --- a/administrator/components/com_patchtester/src/Model/TestsModel.php +++ b/administrator/components/com_patchtester/src/Model/TestsModel.php @@ -11,6 +11,8 @@ namespace Joomla\Component\Patchtester\Administrator\Model; // phpcs:disable PSR1.Files.SideEffects +use Joomla\CMS\MVC\Model\BaseDatabaseModel; + \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects @@ -19,7 +21,7 @@ namespace Joomla\Component\Patchtester\Administrator\Model; * * @since 2.0 */ -class TestsModel extends AbstractModel +class TestsModel extends BaseDatabaseModel { /** * Retrieves a list of applied patches @@ -30,7 +32,7 @@ class TestsModel extends AbstractModel */ public function getAppliedPatches(): array { - $db = $this->getDb(); + $db = $this->getDatabase(); $db->setQuery($db->getQuery(true) ->select('*') ->from($db->quoteName('#__patchtester_tests')) @@ -47,6 +49,6 @@ class TestsModel extends AbstractModel */ public function truncateTable(): void { - $this->getDb()->truncateTable('#__patchtester_tests'); + $this->getDatabase()->truncateTable('#__patchtester_tests'); } } diff --git a/administrator/components/com_patchtester/src/View/Fetch/HtmlView.php b/administrator/components/com_patchtester/src/View/Fetch/HtmlView.php new file mode 100644 index 0000000..25ee20d --- /dev/null +++ b/administrator/components/com_patchtester/src/View/Fetch/HtmlView.php @@ -0,0 +1,25 @@ +envErrors)) { $toolbar = Toolbar::getInstance('toolbar'); - $toolbar->appendButton('Popup', 'sync', 'COM_PATCHTESTER_TOOLBAR_FETCH_DATA', 'index.php?option=com_patchtester&view=fetch&tmpl=component', 500, 210, 0, 0, 'window.parent.location.reload()', Text::_('COM_PATCHTESTER_HEADING_FETCH_DATA')); - // Add a reset button. - $toolbar->appendButton('Standard', 'expired', 'COM_PATCHTESTER_TOOLBAR_RESET', 'reset', false); + $toolbar->appendButton('Popup', 'sync', 'COM_PATCHTESTER_TOOLBAR_FETCH_DATA', 'index.php?option=com_patchtester&view=fetch&task=fetch&tmpl=component', 500, 210, 0, 0, 'window.parent.location.reload()', Text::_('COM_PATCHTESTER_HEADING_FETCH_DATA')); + $toolbar->appendButton('Standard', 'expired', 'COM_PATCHTESTER_TOOLBAR_RESET', 'reset.reset', false); } ToolbarHelper::preferences('com_patchtester'); diff --git a/administrator/components/com_patchtester/src/View/Fetch/tmpl/default.php b/administrator/components/com_patchtester/tmpl/fetch/default.php similarity index 89% rename from administrator/components/com_patchtester/src/View/Fetch/tmpl/default.php rename to administrator/components/com_patchtester/tmpl/fetch/default.php index ae2b2a4..7a26b00 100644 --- a/administrator/components/com_patchtester/src/View/Fetch/tmpl/default.php +++ b/administrator/components/com_patchtester/tmpl/fetch/default.php @@ -15,8 +15,6 @@ use Joomla\CMS\Language\Text; \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects -/** @var \Joomla\Component\Patchtester\Administrator\View\DefaultHtmlView $this */ - HTMLHelper::_('jquery.framework'); HTMLHelper::_('behavior.core'); HTMLHelper::_('script', 'com_patchtester/fetcher.js', ['version' => 'auto', 'relative' => true]); @@ -30,5 +28,5 @@ Text::script('COM_PATCHTESTER_FETCH_AN_ERROR_HAS_OCCURRED');
- + diff --git a/media/com_patchtester/js/fetcher.js b/media/com_patchtester/js/fetcher.js index 21484e3..77a25ea 100644 --- a/media/com_patchtester/js/fetcher.js +++ b/media/com_patchtester/js/fetcher.js @@ -40,7 +40,7 @@ if (typeof Joomla === 'undefined') { jQuery.ajax({ type: 'GET', url: path, - data: 'task=' + task, + data: `task=${task}.${task}`, dataType: 'json', success: function (response, textStatus, xhr) { try { diff --git a/media/com_patchtester/js/patchtester.js b/media/com_patchtester/js/patchtester.js index a2f3c29..a040914 100644 --- a/media/com_patchtester/js/patchtester.js +++ b/media/com_patchtester/js/patchtester.js @@ -10,7 +10,7 @@ if (typeof Joomla === 'undefined') { } document.addEventListener("DOMContentLoaded", function (event) { - var submitPatch = document.querySelectorAll(".submitPatch"); + const submitPatch = document.querySelectorAll(".submitPatch"); /** * EventListener which listens on submitPatch Button, @@ -21,9 +21,9 @@ document.addEventListener("DOMContentLoaded", function (event) { */ submitPatch.forEach(function (element) { element.addEventListener("click", function (event) { - var currentTarget = event.currentTarget; - var task = currentTarget.dataset.task - var id = currentTarget.dataset.id + const currentTarget = event.currentTarget; + const task = `${currentTarget.dataset.task}.${currentTarget.dataset.task}` + const id = parseInt(currentTarget.dataset.id) PatchTester.submitpatch(task, id); });