31
0
mirror of https://github.com/joomla-extensions/patchtester.git synced 2024-06-06 15:30:48 +00:00

Refactor to be namespaced and not use legacy MVC

This commit is contained in:
Michael Babker 2014-05-03 18:48:08 -05:00
parent 881d5fa9ae
commit 9171c4b234
21 changed files with 1086 additions and 628 deletions

View File

@ -0,0 +1,46 @@
<?php
/**
* Patch testing component for the Joomla! CMS
*
* @copyright Copyright (C) 2011 - 2012 Ian MacLennan, Copyright (C) 2013 - 2014 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later
*/
namespace PatchTester\Controller;
use PatchTester\Model\PullModel;
/**
* Controller class to apply patches
*
* @since 2.0
*/
class ApplyController extends \JControllerBase
{
/**
* Execute the controller.
*
* @return void Redirects the application
*
* @since 2.0
*/
public function execute()
{
try
{
$model = new PullModel;
$model->apply($this->getInput()->getUint('pull_id'));
$msg = \JText::_('COM_PATCHTESTER_APPLY_OK');
$type = 'message';
}
catch (\Exception $e)
{
$msg = $e->getMessage();
$type = 'error';
}
$this->getApplication()->enqueueMessage($msg, $type);
$this->getApplication()->redirect(\JRoute::_('index.php?option=com_patchtester', false));
}
}

View File

@ -0,0 +1,179 @@
<?php
/**
* Patch testing component for the Joomla! CMS
*
* @copyright Copyright (C) 2011 - 2012 Ian MacLennan, Copyright (C) 2013 - 2014 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later
*/
namespace PatchTester\Controller;
use Joomla\Registry\Registry;
/**
* Default display controller
*
* @since 2.0
*/
class DisplayController extends \JControllerBase
{
/**
* The object context
*
* @var string
* @since 2.0
*/
protected $context;
/**
* Default ordering column
*
* @var string
* @since 2.0
*/
protected $defaultOrderColumn = 'a.pull_id';
/**
* Default sort direction
*
* @var string
* @since 2.0
*/
protected $defaultDirection = 'DESC';
/**
* The default view to display
*
* @var string
* @since 2.0
*/
protected $defaultView = 'pulls';
/**
* Instantiate the controller
*
* @param \JInput $input The input object.
* @param \JApplicationBase $app The application object.
*
* @since 2.0
*/
public function __construct(\JInput $input = null, \JApplicationBase $app = null)
{
parent::__construct($input, $app);
// Set the context for the controller
$this->context = \JApplicationHelper::getComponentName() . '.' . $this->getInput()->getCmd('view', $this->defaultView);
}
/**
* Execute the controller.
*
* @return boolean True on success
*
* @since 2.0
* @throws \RuntimeException
*/
public function execute()
{
// 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/' . \JApplicationHelper::getComponentName() . '/' . $view, 2);
// Add the path for the default layouts
$paths->insert(JPATH_BASE . '/components/' . \JApplicationHelper::getComponentName() . '/PatchTester/View/' . $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))
{
throw new \RuntimeException(sprintf('The view class for the %1%s view in the %2%s was not found.', $view, $format), 500);
}
if (!class_exists($modelClass))
{
throw new \RuntimeException(sprintf('The model class for the %s view was not found.', $view), 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, \JFactory::getDbo());
// Initialize the state for the model
$model->setState($this->initializeState($model));
// 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 \JModel $model Model object
*
* @return Registry
*
* @since 2.0
* @todo This should really be more generic for a default controller
*/
protected function initializeState(\JModel $model)
{
$state = new Registry;
// Load the filter state.
$search = $this->getApplication()->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '');
$state->set('filter.search', $search);
// Load the parameters.
$params = \JComponentHelper::getParams('com_patchtester');
$state->set('params', $params);
$state->set('github_user', $params->get('org', 'joomla'));
$state->set('github_repo', $params->get('repo', 'joomla-cms'));
// Pre-fill the limits
$limit = $this->getApplication()->getUserStateFromRequest('global.list.limit', 'limit', $this->getApplication()->get('list_limit'), 'uint');
$state->set('list.limit', $limit);
// Check if the ordering field is in the white list, otherwise use the incoming value.
$value = $this->getApplication()->getUserStateFromRequest($this->context . '.ordercol', 'filter_order', $this->defaultOrderColumn);
if (!in_array($value, $model->getSortFields()))
{
$value = $this->defaultOrderColumn;
$this->getApplication()->setUserState($this->context . '.ordercol', $value);
}
$state->set('list.ordering', $value);
// Check if the ordering direction is valid, otherwise use the incoming value.
$value = $this->getApplication()->getUserStateFromRequest($this->context . '.orderdirn', 'filter_order_Dir', $this->defaultDirection);
if (!in_array(strtoupper($value), array('ASC', 'DESC', '')))
{
$value = $this->defaultDirection;
$this->getApplication()->setUserState($this->context . '.orderdirn', $value);
}
$state->set('list.direction', $value);
$value = $this->getApplication()->getUserStateFromRequest($this->context . '.limitstart', 'limitstart', 0);
$limitstart = ($limit != 0 ? (floor($value / $limit) * $limit) : 0);
$state->set('list.start', $limitstart);
return $state;
}
}

View File

@ -0,0 +1,47 @@
<?php
/**
* Patch testing component for the Joomla! CMS
*
* @copyright Copyright (C) 2011 - 2012 Ian MacLennan, Copyright (C) 2013 - 2014 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later
*/
namespace PatchTester\Controller;
use PatchTester\Model\PullsModel;
/**
* Controller class to fetch remote data
*
* @since 2.0
*/
class FetchController extends \JControllerBase
{
/**
* Execute the controller.
*
* @return void Redirects the application
*
* @since 2.0
*/
public function execute()
{
try
{
// TODO - Decouple the model and context?
$model = new PullsModel('com_patchtester.fetch', null, \JFactory::getDbo());
$model->requestFromGithub();
$msg = \JText::_('COM_PATCHTESTER_FETCH_SUCCESSFUL');
$type = 'message';
}
catch (\Exception $e)
{
$msg = $e->getMessage();
$type = 'error';
}
$this->getApplication()->enqueueMessage($msg, $type);
$this->getApplication()->redirect(\JRoute::_('index.php?option=com_patchtester', false));
}
}

View File

@ -0,0 +1,46 @@
<?php
/**
* Patch testing component for the Joomla! CMS
*
* @copyright Copyright (C) 2011 - 2012 Ian MacLennan, Copyright (C) 2013 - 2014 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later
*/
namespace PatchTester\Controller;
use PatchTester\Model\PullModel;
/**
* Controller class to revert patches
*
* @since 2.0
*/
class RevertController extends \JControllerBase
{
/**
* Execute the controller.
*
* @return void Redirects the application
*
* @since 2.0
*/
public function execute()
{
try
{
$model = new PullModel;
$model->revert($this->getInput()->getUint('pull_id'));
$msg = \JText::_('COM_PATCHTESTER_APPLY_OK');
$type = 'message';
}
catch (\Exception $e)
{
$msg = $e->getMessage();
$type = 'error';
}
$this->getApplication()->enqueueMessage($msg, $type);
$this->getApplication()->redirect(\JRoute::_('index.php?option=com_patchtester', false));
}
}

View File

@ -1,33 +1,32 @@
<?php
/**
* @package PatchTester
* Patch testing component for the Joomla! CMS
*
* @copyright Copyright (C) 2011 - 2012 Ian MacLennan, Copyright (C) 2013 - 2014 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later
*/
defined('_JEXEC') or die;
namespace PatchTester;
use Joomla\Registry\Registry;
/**
* Helper class for the patch tester component
*
* @package PatchTester
* @since 2.0
* @since 2.0
*/
abstract class PatchtesterHelper
abstract class Helper
{
/**
* Initializes the JGithub object
*
* @return JGithub
* @return \JGithub
*
* @since 2.0
*/
public static function initializeGithub()
{
$params = JComponentHelper::getParams('com_patchtester');
$params = \JComponentHelper::getParams('com_patchtester');
$options = new Registry;
@ -45,9 +44,9 @@ abstract class PatchtesterHelper
// Display a message about the lowered API limit without credentials
else
{
JFactory::getApplication()->enqueueMessage(JText::_('COM_PATCHTESTER_NO_CREDENTIALS'), 'notice');
\JFactory::getApplication()->enqueueMessage(\JText::_('COM_PATCHTESTER_NO_CREDENTIALS'), 'notice');
}
return new JGithub($options);
return new \JGithub($options);
}
}

View File

@ -1,22 +1,23 @@
<?php
/**
* @package PatchTester
* Patch testing component for the Joomla! CMS
*
* @copyright Copyright (C) 2011 - 2012 Ian MacLennan, Copyright (C) 2013 - 2014 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later
*/
defined('_JEXEC') or die;
namespace PatchTester\Model;
use Joomla\Registry\Registry;
use PatchTester\Helper;
/**
* Methods supporting pull requests.
*
* @package PatchTester
* @since 1.0
* @since 2.0
*/
class PatchtesterModelPull extends JModelLegacy
class PullModel extends \JModelBase
{
/**
* Array containing top level non-production folders
@ -32,7 +33,7 @@ class PatchtesterModelPull extends JModelLegacy
* @return void
*
* @note Calling getState() in this method will result in recursion.
* @since 1.0
* @since 2.0
*/
protected function populateState()
{
@ -52,7 +53,7 @@ class PatchtesterModelPull extends JModelLegacy
*
* @return array Array of files within a patch
*
* @since 1.0
* @since 2.0
*/
protected function parsePatch($patch)
{
@ -71,7 +72,7 @@ class PatchtesterModelPull extends JModelLegacy
$state = 1;
}
$file = new stdClass;
$file = new \stdClass;
$file->action = 'modified';
break;
@ -137,13 +138,13 @@ class PatchtesterModelPull extends JModelLegacy
*
* @return boolean
*
* @since 1.0
* @throws Exception
* @since 2.0
* @throws \RuntimeException
*/
public function apply($id)
{
// Get the Github object
$github = PatchtesterHelper::initializeGithub();
$github = Helper::initializeGithub();
// Only act if there are API hits remaining
if ($github->authorization->getRateLimit()->rate->remaining > 0)
@ -152,7 +153,7 @@ class PatchtesterModelPull extends JModelLegacy
if (is_null($pull->head->repo))
{
throw new Exception(JText::_('COM_PATCHTESTER_REPO_IS_GONE'));
throw new \RuntimeException(\JText::_('COM_PATCHTESTER_REPO_IS_GONE'));
}
// Set up the JHttp object
@ -161,14 +162,14 @@ class PatchtesterModelPull extends JModelLegacy
$options->set('timeout', 120);
// Make sure we can use the cURL driver
$driver = JHttpFactory::getAvailableDriver($options, 'curl');
$driver = \JHttpFactory::getAvailableDriver($options, 'curl');
if (!($driver instanceof JHttpTransportCurl))
if (!($driver instanceof \JHttpTransportCurl))
{
throw new RuntimeException('Cannot use the PHP cURL adapter in this environment, cannot use patchtester', 500);
throw new \RuntimeException('Cannot use the PHP cURL adapter in this environment, cannot use patchtester', 500);
}
$transport = new JHttp($options, $driver);
$transport = new \JHttp($options, $driver);
$patch = $transport->get($pull->diff_url)->body;
@ -176,7 +177,8 @@ class PatchtesterModelPull extends JModelLegacy
if (!$files)
{
JFactory::getApplication()->enqueueMessage(JText::_('COM_PATCHTESTER_NO_FILES_TO_PATCH', 'message'));
// TODO - Should be a better way to enqueue messages without needing the application...
\JFactory::getApplication()->enqueueMessage(JText::_('COM_PATCHTESTER_NO_FILES_TO_PATCH', 'message'));
return true;
}
@ -185,7 +187,7 @@ class PatchtesterModelPull extends JModelLegacy
{
if ($file->action == 'deleted' && !file_exists(JPATH_ROOT . '/' . $file->old))
{
throw new Exception(sprintf(JText::_('COM_PATCHTESTER_FILE_DELETED_DOES_NOT_EXIST_S'), $file->old));
throw new \RuntimeException(sprintf(\JText::_('COM_PATCHTESTER_FILE_DELETED_DOES_NOT_EXIST_S'), $file->old));
}
if ($file->action == 'added' || $file->action == 'modified')
@ -193,12 +195,12 @@ class PatchtesterModelPull extends JModelLegacy
// If the backup file already exists, we can't apply the patch
if (file_exists(JPATH_COMPONENT . '/backups/' . md5($file->new) . '.txt'))
{
throw new Exception(sprintf(JText::_('COM_PATCHTESTER_CONFLICT_S'), $file->new));
throw new \RuntimeException(sprintf(\JText::_('COM_PATCHTESTER_CONFLICT_S'), $file->new));
}
if ($file->action == 'modified' && !file_exists(JPATH_ROOT . '/' . $file->old))
{
throw new Exception(sprintf(JText::_('COM_PATCHTESTER_FILE_MODIFIED_DOES_NOT_EXIST_S'), $file->old));
throw new \RuntimeException(sprintf(\JText::_('COM_PATCHTESTER_FILE_MODIFIED_DOES_NOT_EXIST_S'), $file->old));
}
$url = 'https://raw.github.com/' . $pull->head->user->login . '/' . $pull->head->repo->name . '/' . $pull->head->ref . '/' . $file->new;
@ -215,9 +217,9 @@ class PatchtesterModelPull extends JModelLegacy
// 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'))
if (!\JFile::copy(\JPath::clean(JPATH_ROOT . '/' . $file->old), JPATH_COMPONENT . '/backups/' . md5($file->old) . '.txt'))
{
throw new Exception(
throw new \RuntimeException(
sprintf('Can not copy file %s to %s', JPATH_ROOT . '/' . $file->old, JPATH_COMPONENT . '/backups/' . md5($file->old) . '.txt')
);
}
@ -227,38 +229,38 @@ class PatchtesterModelPull extends JModelLegacy
{
case 'modified':
case 'added':
if (!JFile::write(JPath::clean(JPATH_ROOT . '/' . $file->new), $file->body))
if (!\JFile::write(\JPath::clean(JPATH_ROOT . '/' . $file->new), $file->body))
{
throw new Exception(sprintf('Can not write the file: %s', JPATH_ROOT . '/' . $file->new));
throw new \RuntimeException(sprintf('Can not write the file: %s', JPATH_ROOT . '/' . $file->new));
}
break;
case 'deleted':
if (!JFile::delete(JPATH::clean(JPATH_ROOT . '/' . $file->old)))
if (!\JFile::delete(\JPath::clean(JPATH_ROOT . '/' . $file->old)))
{
throw new Exception(sprintf('Can not delete the file: %s', JPATH_ROOT . '/' . $file->old));
throw new \RuntimeException(sprintf('Can not delete the file: %s', JPATH_ROOT . '/' . $file->old));
}
break;
}
}
$table = JTable::getInstance('tests', 'PatchTesterTable');
$table = \JTable::getInstance('TestsTable', '\\PatchTester\\Table\\');
$table->pull_id = $pull->number;
$table->data = json_encode($files);
$table->patched_by = JFactory::getUser()->id;
$table->patched_by = \JFactory::getUser()->id;
$table->applied = 1;
$table->applied_version = JVERSION;
if (!$table->store())
{
throw new Exception($table->getError());
throw new \RuntimeException($table->getError());
}
}
else
{
throw new Exception(JText::sprintf('COM_PATCHTESTER_API_LIMIT_ACTION', JFactory::getDate($this->rate->reset)));
throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_API_LIMIT_ACTION', \JFactory::getDate($this->rate->reset)));
}
return true;
@ -271,12 +273,12 @@ class PatchtesterModelPull extends JModelLegacy
*
* @return boolean
*
* @since 1.0
* @throws Exception
* @since 2.0
* @throws \RuntimeException
*/
public function revert($id)
{
$table = JTable::getInstance('tests', 'PatchTesterTable');
$table = \JTable::getInstance('TestsTable', '\\PatchTester\\Table\\');
$table->load($id);
// We don't want to restore files from an older version
@ -291,7 +293,7 @@ class PatchtesterModelPull extends JModelLegacy
if (!$files)
{
throw new Exception(sprintf(JText::_('%s - Error retrieving table data (%s)'), __METHOD__, htmlentities($table->data)));
throw new \RuntimeException(sprintf(JText::_('%s - Error retrieving table data (%s)'), __METHOD__, htmlentities($table->data)));
}
jimport('joomla.filesystem.file');
@ -302,28 +304,28 @@ class PatchtesterModelPull extends JModelLegacy
{
case 'deleted':
case 'modified':
if (!JFile::copy(JPATH_COMPONENT . '/backups/' . md5($file->old) . '.txt', JPATH_ROOT . '/' . $file->old))
if (!\JFile::copy(JPATH_COMPONENT . '/backups/' . md5($file->old) . '.txt', JPATH_ROOT . '/' . $file->old))
{
throw new Exception(
throw new \RuntimeException(
sprintf(
JText::_('Can not copy file %s to %s'),
\JText::_('Can not copy file %s to %s'),
JPATH_COMPONENT . '/backups/' . md5($file->old) . '.txt',
JPATH_ROOT . '/' . $file->old
)
);
}
if (!JFile::delete(JPATH_COMPONENT . '/backups/' . md5($file->old) . '.txt'))
if (!\JFile::delete(JPATH_COMPONENT . '/backups/' . md5($file->old) . '.txt'))
{
throw new Exception(sprintf(JText::_('Can not delete the file: %s'), JPATH_COMPONENT . '/backups/' . md5($file->old) . '.txt'));
throw new \RuntimeException(sprintf(\JText::_('Can not delete the file: %s'), JPATH_COMPONENT . '/backups/' . md5($file->old) . '.txt'));
}
break;
case 'added':
if (!JFile::delete(JPath::clean(JPATH_ROOT . '/' . $file->new)))
if (!\JFile::delete(\JPath::clean(JPATH_ROOT . '/' . $file->new)))
{
throw new Exception(sprintf(JText::_('Can not delete the file: %s'), JPATH_ROOT . '/' . $file->new));
throw new \RuntimeException(sprintf(\JText::_('Can not delete the file: %s'), JPATH_ROOT . '/' . $file->new));
}
break;

View File

@ -0,0 +1,460 @@
<?php
/**
* Patch testing component for the Joomla! CMS
*
* @copyright Copyright (C) 2011 - 2012 Ian MacLennan, Copyright (C) 2013 - 2014 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later
*/
namespace PatchTester\Model;
use Joomla\Registry\Registry;
use PatchTester\Helper;
/**
* Model class for the pulls list view
*
* @since 2.0
*/
class PullsModel extends \JModelDatabase
{
/**
* The object context
*
* @var string
* @since 2.0
*/
protected $context;
/**
* Array of fields the list can be sorted on
*
* @var array
* @since 2.0
*/
protected $sortFields = array();
/**
* Instantiate the model.
*
* @param string $context The model context.
* @param Registry $state The model state.
* @param \JDatabaseDriver $db The database adpater.
*
* @since 2.0
*/
public function __construct($context, Registry $state = null, \JDatabaseDriver $db = null)
{
parent::__construct($state, $db);
$this->context = $context;
$this->sortFields = array('a.pull_id', 'a.title', 'applied');
}
/**
* Retrieves a list of applied patches
*
* @return mixed
*
* @since 1.0
*/
public function getAppliedPatches()
{
$db = $this->getDb();
$db->setQuery(
$db->getQuery(true)
->select('*')
->from($db->quoteName('#__patchtester_tests'))
->where($db->quoteName('applied') . ' = 1')
);
return $db->loadObjectList('pull_id');
}
/**
* Method to get an array of data items.
*
* @return mixed An array of data items on success, false on failure.
*
* @since 2.0
*/
public function getItems()
{
// Get a storage key.
$store = $this->getStoreId();
// Try to load the data from internal storage.
if (isset($this->cache[$store]))
{
return $this->cache[$store];
}
// Load the list items.
$query = $this->_getListQuery();
$items = $this->_getList($query, $this->getStart(), $this->getState()->get('list.limit'));
// Add the items to the internal cache.
$this->cache[$store] = $items;
return $this->cache[$store];
}
/**
* Method to get a JDatabaseQuery object for retrieving the data set from a database.
*
* @return \JDatabaseQuery A JDatabaseQuery object to retrieve the data set.
*
* @since 2.0
*/
protected function getListQuery()
{
// Create a new query object.
$db = $this->getDb();
$query = $db->getQuery(true);
// Select the required fields from the table.
$query->select($this->getState()->get('list.select', 'a.*'));
$query->from($db->quoteName('#__patchtester_pulls', 'a'));
// Join the tests table to get applied patches
$query->select($db->quoteName('t.id', 'applied'));
$query->join('LEFT', $db->quoteName('#__patchtester_tests', 't') . ' ON t.pull_id = a.pull_id');
// Filter by search
$search = $this->getState()->get('filter.search');
if (!empty($search))
{
if (stripos($search, 'id:') === 0)
{
$query->where($db->quoteName('a.pull_id') . ' = ' . (int) substr($search, 3));
}
else
{
$search = $db->quote('%' . $db->escape($search, true) . '%');
$query->where('(' . $db->quoteName('a.title') . ' LIKE ' . $search . ')');
}
}
// Handle the list ordering.
$ordering = $this->getState()->get('list.ordering');
$direction = $this->getState()->get('list.direction');
if (!empty($ordering))
{
$query->order($db->escape($ordering) . ' ' . $db->escape($direction));
}
// If $ordering is by applied patches, then append sort on pull_id also
if ($ordering === 'applied')
{
$query->order('a.pull_id ' . $db->escape($direction));
}
return $query;
}
/**
* Method to get a JPagination object for the data set.
*
* @return \JPagination A JPagination object for the data set.
*
* @since 2.0
*/
public function getPagination()
{
// Get a storage key.
$store = $this->getStoreId('getPagination');
// Try to load the data from internal storage.
if (isset($this->cache[$store]))
{
return $this->cache[$store];
}
// Create the pagination object.
$limit = (int) $this->getState()->get('list.limit') - (int) $this->getState()->get('list.links');
$page = new \JPagination($this->getTotal(), $this->getStart(), $limit);
// Add the object to the internal cache.
$this->cache[$store] = $page;
return $this->cache[$store];
}
/**
* Retrieves the array of authorized sort fields
*
* @return array
*
* @since 2.0
*/
public function getSortFields()
{
return $this->sortFields;
}
/**
* Method to get a store id based on the model configuration state.
*
* This is necessary because the model is used by the component and
* different modules that might need different sets of data or different
* ordering requirements.
*
* @param string $id An identifier string to generate the store id.
*
* @return string A store id.
*
* @since 2.0
*/
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);
}
/**
* Method to get the starting number of items for the data set.
*
* @return integer The starting number of items available in the data set.
*
* @since 2.0
*/
public function getStart()
{
$store = $this->getStoreId('getStart');
// Try to load the data from internal storage.
if (isset($this->cache[$store]))
{
return $this->cache[$store];
}
$start = $this->getState()->get('list.start');
$limit = $this->getState()->get('list.limit');
$total = $this->getTotal();
if ($start > $total - $limit)
{
$start = max(0, (int) (ceil($total / $limit) - 1) * $limit);
}
// Add the total to the internal cache.
$this->cache[$store] = $start;
return $this->cache[$store];
}
/**
* Method to get the total number of items for the data set.
*
* @return integer The total number of items available in the data set.
*
* @since 2.0
*/
public function getTotal()
{
// Get a storage key.
$store = $this->getStoreId('getTotal');
// Try to load the data from internal storage.
if (isset($this->cache[$store]))
{
return $this->cache[$store];
}
// Load the total.
$query = $this->_getListQuery();
$total = (int) $this->_getListCount($query);
// Add the total to the internal cache.
$this->cache[$store] = $total;
return $this->cache[$store];
}
/**
* Method to request new data from GitHub
*
* @return void
*
* @since 2.0
* @throws \RuntimeException
*/
public function requestFromGithub()
{
// Get the Github object
$github = Helper::initializeGithub();
// If over the API limit, we can't build this list
if ($github->authorization->getRateLimit()->rate->remaining > 0)
{
// Sanity check, ensure there aren't any applied patches
if (count($this->getAppliedPatches()) >= 1)
{
throw new \RuntimeException(\JText::_('COM_PATCHTESTER_ERROR_APPLIED_PATCHES'));
}
$pulls = array();
$page = 0;
do
{
$page++;
try
{
$items = $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()));
}
$count = is_array($items) ? count($items) : 0;
if ($count)
{
$pulls = array_merge($pulls, $items);
}
}
while ($count);
// Dump the old data now
$this->getDb()->truncateTable('#__patchtester_pulls');
foreach ($pulls as &$pull)
{
// Build the data object to store in the database
$data = new \stdClass;
$data->pull_id = $pull->number;
$data->title = $pull->title;
$data->description = $pull->body;
$data->pull_url = $pull->html_url;
// Try to find a Joomlacode issue number
$matches = array();
preg_match('#\[\#([0-9]+)\]#', $pull->title, $matches);
if (isset($matches[1]))
{
$data->joomlacode_id = (int) $matches[1];
}
else
{
preg_match('#(http://joomlacode[-\w\./\?\S]+)#', $pull->body, $matches);
if (isset($matches[1]))
{
preg_match('#tracker_item_id=([0-9]+)#', $matches[1], $matches);
if (isset($matches[1]))
{
$data->joomlacode_id = (int) $matches[1];
}
}
}
try
{
$this->getDb()->insertObject('#__patchtester_pulls', $data, 'id');
}
catch (\RuntimeException $e)
{
throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_ERROR_INSERT_DATABASE', $e->getMessage()));
}
}
}
else
{
throw new \RuntimeException(\JText::sprintf('COM_PATCHTESTER_API_LIMIT_LIST', \JFactory::getDate($this->rate->reset)));
}
}
/**
* Gets an array of objects from the results of database query.
*
* @param \JDatabaseQuery|string $query The query.
* @param integer $limitstart Offset.
* @param integer $limit The number of records.
*
* @return array An array of results.
*
* @since 2.0
* @throws RuntimeException
*/
protected function _getList($query, $limitstart = 0, $limit = 0)
{
$this->getDb()->setQuery($query, $limitstart, $limit);
$result = $this->getDb()->loadObjectList();
return $result;
}
/**
* Returns a record count for the query.
*
* @param \JDatabaseQuery|string $query The query.
*
* @return integer Number of rows for query.
*
* @since 2.0
*/
protected function _getListCount($query)
{
// Use fast COUNT(*) on JDatabaseQuery objects if there no GROUP BY or HAVING clause:
if ($query instanceof \JDatabaseQuery && $query->type == 'select' && $query->group === null && $query->having === null)
{
$query = clone $query;
$query->clear('select')->clear('order')->select('COUNT(*)');
$this->getDb()->setQuery($query);
return (int) $this->getDb()->loadResult();
}
// Otherwise fall back to inefficient way of counting all results.
else
{
$this->getDb()->setQuery($query)->execute();
return (int) $this->getDb()->getNumRows();
}
}
/**
* Method to cache the last query constructed.
*
* This method ensures that the query is constructed only once for a given state of the model.
*
* @return \JDatabaseQuery A JDatabaseQuery object
*
* @since 2.0
*/
protected function _getListQuery()
{
// Capture the last store id used.
static $lastStoreId;
// Compute the current store id.
$currentStoreId = $this->getStoreId();
// If the last store id is different from the current, refresh the query.
if ($lastStoreId != $currentStoreId || empty($this->query))
{
$lastStoreId = $currentStoreId;
$this->query = $this->getListQuery();
}
return $this->query;
}
}

View File

@ -1,27 +1,26 @@
<?php
/**
* @package PatchTester
* Patch testing component for the Joomla! CMS
*
* @copyright Copyright (C) 2011 - 2012 Ian MacLennan, Copyright (C) 2013 - 2014 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later
*/
defined('_JEXEC') or die;
namespace PatchTester\Table;
/**
* Pulls Table class
*
* @package PatchTester
* @since 1.0
* @since 2.0
*/
class PatchtesterTablePulls extends JTable
class PullsTable extends \JTable
{
/**
* Constructor
*
* @param JDatabaseDriver &$db JDatabaseDriver object.
* @param \JDatabaseDriver &$db JDatabaseDriver object.
*
* @since 1.0
* @since 2.0
*/
public function __construct(&$db)
{

View File

@ -1,27 +1,26 @@
<?php
/**
* @package PatchTester
* Patch testing component for the Joomla! CMS
*
* @copyright Copyright (C) 2011 - 2012 Ian MacLennan, Copyright (C) 2013 - 2014 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later
*/
defined('_JEXEC') or die;
namespace PatchTester\Table;
/**
* Tests Table class
*
* @package PatchTester
* @since 1.0
* @since 2.0
*/
class PatchtesterTableTests extends JTable
class TestsTable extends \JTable
{
/**
* Constructor
*
* @param JDatabaseDriver &$db JDatabaseDriver object.
* @param \JDatabaseDriver &$db JDatabaseDriver object.
*
* @since 1.0
* @since 2.0
*/
public function __construct(&$db)
{

View File

@ -0,0 +1,62 @@
<?php
/**
* Patch testing component for the Joomla! CMS
*
* @copyright Copyright (C) 2011 - 2012 Ian MacLennan, Copyright (C) 2013 - 2014 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later
*/
namespace PatchTester\View;
/**
* Default HTML view class.
*
* @since 2.0
*/
class DefaultHtmlView extends \JViewHtml
{
/**
* Load a template file -- first look in the templates folder for an override
*
* @param string $tpl The name of the template source file; automatically searches the template paths and compiles as needed.
*
* @return string The output of the the template script.
*
* @since 2.0
* @throws \RuntimeException
*/
public function loadTemplate($tpl = null)
{
// Get the path to the file
$file = isset($tpl) ? $this->getLayout() . '_' . $tpl : $this->getLayout();
$path = $this->getPath($file);
if ($path)
{
// Unset so as not to introduce into template scope
unset($tpl);
unset($file);
// Never allow a 'this' property
if (isset($this->this))
{
unset($this->this);
}
// Start an output buffer.
ob_start();
// Load the template.
include $path;
// Get the layout contents.
$output = ob_get_clean();
return $output;
}
else
{
throw new \RuntimeException(\JText::sprintf('JLIB_APPLICATION_ERROR_LAYOUTFILE_NOT_FOUND', $file), 500);
}
}
}

View File

@ -0,0 +1,126 @@
<?php
/**
* Patch testing component for the Joomla! CMS
*
* @copyright Copyright (C) 2011 - 2012 Ian MacLennan, Copyright (C) 2013 - 2014 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later
*/
namespace PatchTester\View\Pulls;
use PatchTester\View\DefaultHtmlView;
/**
* View class for a list of pull requests.
*
* @since 2.0
*/
class PullsHtmlView extends DefaultHtmlView
{
/**
* Array containing environment errors
*
* @var array
* @since 2.0
*/
protected $envErrors = array();
/**
* Array of open pull requests
*
* @var array
* @since 2.0
*/
protected $items;
/**
* The model object - redeclared for proper type hinting.
*
* @var \PatchTester\Model\PullsModel
* @since 2.0
*/
protected $model;
/**
* State object
*
* @var \Joomla\Registry\Registry
* @since 2.0
*/
protected $state;
/**
* Pagination object
*
* @var \JPagination
* @since 2.0
*/
protected $pagination;
/**
* Method to render the view.
*
* @return string The rendered view.
*
* @since 2.0
*/
public function render()
{
if (!extension_loaded('openssl'))
{
$this->envErrors[] = \JText::_('COM_PATCHTESTER_REQUIREMENT_OPENSSL');
}
if (!in_array('https', stream_get_wrappers()))
{
$this->envErrors[] = \JText::_('COM_PATCHTESTER_REQUIREMENT_HTTPS');
}
// Only process the data if there are no environment errors
if (!count($this->envErrors))
{
$this->state = $this->model->getState();
$this->items = $this->model->getItems();
$this->pagination = $this->model->getPagination();
}
$this->addToolbar();
return parent::render();
}
/**
* Add the page title and toolbar.
*
* @return void
*
* @since 2.0
*/
protected function addToolbar()
{
\JToolBarHelper::title(\JText::_('COM_PATCHTESTER'), 'patchtester');
if (!count($this->envErrors))
{
\JToolbarHelper::custom('pulls.fetch', 'refresh.png', 'refresh_f2.png', 'COM_PATCHTESTER_TOOLBAR_FETCH_DATA', false);
}
\JToolBarHelper::preferences('com_patchtester');
}
/**
* Returns an array of fields the table can be sorted by
*
* @return array Array containing the field name to sort by as the key and display text as value
*
* @since 2.0
*/
protected function getSortFields()
{
return array(
'a.title' => \JText::_('JGLOBAL_TITLE'),
'a.pull_id' => \JText::_('COM_PATCHTESTER_PULL_ID'),
'applied' => \JText::_('JSTATUS')
);
}
}

View File

@ -1,17 +1,15 @@
<?php
/**
* @package PatchTester
* Patch testing component for the Joomla! CMS
*
* @copyright Copyright (C) 2011 - 2012 Ian MacLennan, Copyright (C) 2013 - 2014 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later
*/
defined('_JEXEC') or die;
/** @type \PatchTester\View\Pulls\PullsHtmlView $this */
/** @type PatchtesterViewPulls $this */
JHtml::_('behavior.tooltip');
JHtml::_('behavior.modal');
\JHtml::_('behavior.tooltip');
\JHtml::_('behavior.modal');
$listOrder = $this->escape($this->state->get('list.ordering'));
$listDirn = $this->escape($this->state->get('list.direction'));
@ -41,36 +39,36 @@ else :
}
</script>
<form action="<?php echo JRoute::_('index.php?option=com_patchtester&view=pulls'); ?>" method="post" name="adminForm" id="adminForm">
<form action="<?php echo \JRoute::_('index.php?option=com_patchtester&view=pulls'); ?>" method="post" name="adminForm" id="adminForm">
<div id="filter-bar" class="btn-toolbar">
<div class="btn-group pull-right">
<label for="sortTable" class="element-invisible"><?php echo JText::_('JGLOBAL_SORT_BY'); ?></label>
<label for="sortTable" class="element-invisible"><?php echo \JText::_('JGLOBAL_SORT_BY'); ?></label>
<select name="sortTable" id="sortTable" class="input-medium" onchange="Joomla.orderTable()">
<option value=""><?php echo JText::_('JGLOBAL_SORT_BY'); ?></option>
<?php echo JHtml::_('select.options', $sortFields, 'value', 'text', $listOrder);?>
<option value=""><?php echo \JText::_('JGLOBAL_SORT_BY'); ?></option>
<?php echo \JHtml::_('select.options', $sortFields, 'value', 'text', $listOrder);?>
</select>
</div>
</div>
<div id="j-main-container">
<div id="filter-bar" class="btn-toolbar">
<div class="filter-search btn-group pull-left">
<label for="filter_search" class="element-invisible"><?php echo JText::_('COM_PATCHTESTER_FILTER_SEARCH_DESCRIPTION'); ?></label>
<input type="text" name="filter_search" placeholder="<?php echo JText::_('COM_PATCHTESTER_FILTER_SEARCH_DESCRIPTION'); ?>" id="filter_search" value="<?php echo $this->escape($this->state->get('filter.search')); ?>" title="<?php echo JText::_('COM_PATCHTESTER_FILTER_SEARCH_DESCRIPTION'); ?>" />
<label for="filter_search" class="element-invisible"><?php echo \JText::_('COM_PATCHTESTER_FILTER_SEARCH_DESCRIPTION'); ?></label>
<input type="text" name="filter_search" placeholder="<?php echo \JText::_('COM_PATCHTESTER_FILTER_SEARCH_DESCRIPTION'); ?>" id="filter_search" value="<?php echo $this->escape($this->state->get('filter.search')); ?>" title="<?php echo \JText::_('COM_PATCHTESTER_FILTER_SEARCH_DESCRIPTION'); ?>" />
</div>
<div class="btn-group pull-left hidden-phone">
<button class="btn tip hasTooltip" type="submit" title="<?php echo JText::_('JSEARCH_FILTER_SUBMIT'); ?>"><i class="icon-search"></i></button>
<button class="btn tip hasTooltip" type="button" onclick="document.id('filter_search').value='';this.form.submit();" title="<?php echo JText::_('JSEARCH_FILTER_CLEAR'); ?>"><i class="icon-remove"></i></button>
<button class="btn tip hasTooltip" type="submit" title="<?php echo \JText::_('JSEARCH_FILTER_SUBMIT'); ?>"><i class="icon-search"></i></button>
<button class="btn tip hasTooltip" type="button" onclick="document.id('filter_search').value='';this.form.submit();" title="<?php echo \JText::_('JSEARCH_FILTER_CLEAR'); ?>"><i class="icon-remove"></i></button>
</div>
<div class="btn-group pull-right hidden-phone">
<label for="limit" class="element-invisible"><?php echo JText::_('JFIELD_PLG_SEARCH_SEARCHLIMIT_DESC'); ?></label>
<label for="limit" class="element-invisible"><?php echo \JText::_('JFIELD_PLG_SEARCH_SEARCHLIMIT_DESC'); ?></label>
<?php echo $this->pagination->getLimitBox(); ?>
</div>
<div class="btn-group pull-right hidden-phone">
<label for="directionTable" class="element-invisible"><?php echo JText::_('JFIELD_ORDERING_DESC'); ?></label>
<label for="directionTable" class="element-invisible"><?php echo \JText::_('JFIELD_ORDERING_DESC'); ?></label>
<select name="directionTable" id="directionTable" class="input-medium" onchange="Joomla.orderTable()">
<option value=""><?php echo JText::_('JFIELD_ORDERING_DESC');?></option>
<option value="asc" <?php if ($listDirn == 'asc') echo 'selected="selected"'; ?>><?php echo JText::_('JGLOBAL_ORDER_ASCENDING'); ?></option>
<option value="desc" <?php if ($listDirn == 'desc') echo 'selected="selected"'; ?>><?php echo JText::_('JGLOBAL_ORDER_DESCENDING'); ?></option>
<option value=""><?php echo \JText::_('JFIELD_ORDERING_DESC');?></option>
<option value="asc" <?php if ($listDirn == 'asc') echo 'selected="selected"'; ?>><?php echo \JText::_('JGLOBAL_ORDER_ASCENDING'); ?></option>
<option value="desc" <?php if ($listDirn == 'desc') echo 'selected="selected"'; ?>><?php echo \JText::_('JGLOBAL_ORDER_DESCENDING'); ?></option>
</select>
</div>
</div>
@ -80,20 +78,20 @@ else :
<thead>
<tr>
<th width="5%" class="nowrap center">
<?php echo JText::_('COM_PATCHTESTER_PULL_ID'); ?>
<?php echo \JText::_('COM_PATCHTESTER_PULL_ID'); ?>
</th>
<th class="nowrap center">
<?php echo JText::_('JGLOBAL_TITLE'); ?>
<?php echo \JText::_('JGLOBAL_TITLE'); ?>
</th>
<th class="nowrap center">I</th>
<th class="nowrap center">
<?php echo JText::_('COM_PATCHTESTER_JOOMLACODE_ISSUE'); ?>
<?php echo \JText::_('COM_PATCHTESTER_JOOMLACODE_ISSUE'); ?>
</th>
<th width="20%" class="nowrap center">
<?php echo JText::_('JSTATUS'); ?>
<?php echo \JText::_('JSTATUS'); ?>
</th>
<th width="20%" class="nowrap center">
<?php echo JText::_('COM_PATCHTESTER_TEST_THIS_PATCH'); ?>
<?php echo \JText::_('COM_PATCHTESTER_TEST_THIS_PATCH'); ?>
</th>
</tr>
</thead>
@ -107,7 +105,7 @@ else :
<?php if (count($this->items)) :
echo $this->loadTemplate('items');
else : ?>
<td align="center" colspan="6"><?php echo JText::_('COM_PATCHTESTER_NO_ITEMS'); ?></td>
<td align="center" colspan="6"><?php echo \JText::_('COM_PATCHTESTER_NO_ITEMS'); ?></td>
<?php endif; ?>
</tbody>
</table>
@ -118,7 +116,7 @@ else :
<input type="hidden" name="pull_id" id="pull_id" value=""/>
<input type="hidden" name="filter_order" value="<?php echo $listOrder; ?>"/>
<input type="hidden" name="filter_order_Dir" value="<?php echo $listDirn; ?>"/>
<?php echo JHtml::_('form.token'); ?>
<?php echo \JHtml::_('form.token'); ?>
</div>
</form>
<?php endif;

View File

@ -1,17 +1,15 @@
<?php
/**
* @package PatchTester
* Patch testing component for the Joomla! CMS
*
* @copyright Copyright (C) 2011 - 2012 Ian MacLennan, Copyright (C) 2013 - 2014 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later
*/
defined('_JEXEC') or die;
/** @type PatchtesterViewPulls $this */
/** @type \PatchTester\View\Pulls\PullsHtmlView $this */
?>
<h3><?php echo JText::_('COM_PATCHTESTER_REQUIREMENTS_HEADING'); ?></h3>
<p><?php echo JText::_('COM_PATCHTESTER_REQUIREMENTS_NOT_MET'); ?></p>
<h3><?php echo \JText::_('COM_PATCHTESTER_REQUIREMENTS_HEADING'); ?></h3>
<p><?php echo \JText::_('COM_PATCHTESTER_REQUIREMENTS_NOT_MET'); ?></p>
<ul>
<?php foreach ($this->envErrors as $error) : ?>
<li><?php echo $error; ?></li>

View File

@ -1,14 +1,12 @@
<?php
/**
* @package PatchTester
* Patch testing component for the Joomla! CMS
*
* @copyright Copyright (C) 2011 - 2012 Ian MacLennan, Copyright (C) 2013 - 2014 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later
*/
defined('_JEXEC') or die;
/** @type PatchtesterViewPulls $this */
/** @type \PatchTester\View\Pulls\PullsHtmlView $this */
foreach ($this->items as $i => $item) :
$status = '';
@ -22,13 +20,13 @@ foreach ($this->items as $i => $item) :
<?php echo $item->pull_id; ?>
</td>
<td>
<a class="icon icon16-github hasTip" title="<?php echo JText::_('COM_PATCHTESTER_OPEN_IN_GITHUB'); ?>" href="<?php echo $item->pull_url; ?>" target="_blank">
<a class="icon icon16-github hasTip" title="<?php echo \JText::_('COM_PATCHTESTER_OPEN_IN_GITHUB'); ?>" href="<?php echo $item->pull_url; ?>" target="_blank">
<?php echo $this->escape($item->title); ?>
</a>
</td>
<td>
<?php if ($item->description) :
echo JHtml::_('tooltip', htmlspecialchars($item->description), 'Info');
echo \JHtml::_('tooltip', $this->escape(($item->description)), 'Info');
else :
echo '&nbsp;';
endif;
@ -36,7 +34,7 @@ foreach ($this->items as $i => $item) :
</td>
<td>
<?php if ($item->joomlacode_id) :
$title = ' title="Open link::' . JText::_('COM_PATCHTESTER_OPEN_IN_JOOMLACODE') . '"';
$title = ' title="Open link::' . \JText::_('COM_PATCHTESTER_OPEN_IN_JOOMLACODE') . '"';
echo '<a href="http://joomlacode.org/gf/project/joomla/tracker/?action=TrackerItemEdit&tracker_item_id=';
echo $item->joomlacode_id . '"' . $title . ' class="modal hasTip" rel="{handler: \'iframe\', size: {x: 900, y: 500}}">';
@ -46,19 +44,19 @@ foreach ($this->items as $i => $item) :
<td class="center">
<?php if ($item->applied) : ?>
<span class="label label-success">
<?php echo JText::_('COM_PATCHTESTER_APPLIED'); ?>
<?php echo \JText::_('COM_PATCHTESTER_APPLIED'); ?>
</span>
<?php else : ?>
<span class="label">
<?php echo JText::_('COM_PATCHTESTER_NOT_APPLIED'); ?>
<?php echo \JText::_('COM_PATCHTESTER_NOT_APPLIED'); ?>
</span>
<?php endif; ?>
</td>
<td class="center">
<?php if ($item->applied) :
echo '<a class="btn btn-small btn-success" href="javascript:submitpatch(\'pull.revert\', ' . (int) $item->applied . ');">' . JText::_('COM_PATCHTESTER_REVERT_PATCH') . '</a>';
echo '<a class="btn btn-small btn-success" href="javascript:submitpatch(\'pull.revert\', ' . (int) $item->applied . ');">' . \JText::_('COM_PATCHTESTER_REVERT_PATCH') . '</a>';
else :
echo '<a class="btn btn-small btn-primary" href="javascript:submitpatch(\'pull.apply\', ' . (int) $item->pull_id . ');">' . JText::_('COM_PATCHTESTER_APPLY_PATCH') . '</a>';
echo '<a class="btn btn-small btn-primary" href="javascript:submitpatch(\'pull.apply\', ' . (int) $item->pull_id . ');">' . \JText::_('COM_PATCHTESTER_APPLY_PATCH') . '</a>';
endif; ?>
</td>
</tr>

View File

@ -1,26 +0,0 @@
<?php
/**
* @package PatchTester
*
* @copyright Copyright (C) 2011 - 2012 Ian MacLennan, Copyright (C) 2013 - 2014 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later
*/
defined('_JEXEC') or die;
/**
* PatchTester Controller
*
* @package PatchTester
* @since 1.0
*/
class PatchTesterController extends JControllerLegacy
{
/**
* The default view for the display method.
*
* @var string
* @since 1.0
*/
protected $default_view = 'pulls';
}

View File

@ -1,68 +0,0 @@
<?php
/**
* @package PatchTester
*
* @copyright Copyright (C) 2011 - 2012 Ian MacLennan, Copyright (C) 2013 - 2014 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later
*/
defined('_JEXEC') or die;
/**
* Pull controller class
*
* @package PatchTester
* @since 1.0
*/
class PatchtesterControllerPull extends JControllerLegacy
{
/**
* Method to apply a patch
*
* @return void
*
* @since 1.0
*/
public function apply()
{
try
{
$this->getModel('pull')->apply(JFactory::getApplication()->input->getInt('pull_id'));
$msg = JText::_('COM_PATCHTESTER_APPLY_OK');
$type = 'message';
}
catch (Exception $e)
{
$msg = $e->getMessage();
$type = 'error';
}
$this->setRedirect(JRoute::_('index.php?option=com_patchtester&view=pulls', false), $msg, $type);
}
/**
* Method to revert a patch
*
* @return void
*
* @since 1.0
*/
public function revert()
{
try
{
$this->getModel('pull')->revert(JFactory::getApplication()->input->getInt('pull_id'));
$msg = JText::_('COM_PATCHTESTER_REVERT_OK');
$type = 'message';
}
catch (Exception $e)
{
$msg = $e->getMessage();
$type = 'error';
}
$this->setRedirect(JRoute::_('index.php?option=com_patchtester&view=pulls', false), $msg, $type);
}
}

View File

@ -1,43 +0,0 @@
<?php
/**
* @package PatchTester
*
* @copyright Copyright (C) 2011 - 2012 Ian MacLennan, Copyright (C) 2013 - 2014 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later
*/
defined('_JEXEC') or die;
/**
* Pulls controller class
*
* @package PatchTester
* @since 2.0
*/
class PatchtesterControllerPulls extends JControllerLegacy
{
/**
* Fetch pull request data from GitHub
*
* @return void
*
* @since 2.0
*/
public function fetch()
{
try
{
$this->getModel('pulls')->requestFromGithub();
$msg = JText::_('COM_PATCHTESTER_FETCH_SUCCESSFUL');
$type = 'message';
}
catch (Exception $e)
{
$msg = $e->getMessage();
$type = 'error';
}
$this->setRedirect(JRoute::_('index.php?option=com_patchtester&view=pulls', false), $msg, $type);
}
}

View File

@ -1,246 +0,0 @@
<?php
/**
* @package PatchTester
*
* @copyright Copyright (C) 2011 - 2012 Ian MacLennan, Copyright (C) 2013 - 2014 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later
*/
defined('_JEXEC') or die;
/**
* Methods supporting a list of pull requests.
*
* @package PatchTester
* @since 1.0
*/
class PatchtesterModelPulls extends JModelList
{
/**
* Constructor.
*
* @param array $config An optional associative array of configuration settings.
*
* @see JControllerLegacy
* @since 1.0
*/
public function __construct($config = array())
{
if (empty($config['filter_fields']))
{
$config['filter_fields'] = array(
'id', 'title', 'applied'
);
}
parent::__construct($config);
}
/**
* Method to auto-populate the model state.
*
* @param string $ordering An optional ordering field.
* @param string $direction An optional direction (asc|desc).
*
* @return void
*
* @note Calling getState() in this method will result in recursion.
* @since 1.0
*/
protected function populateState($ordering = null, $direction = null)
{
// Load the filter state.
$search = $this->getUserStateFromRequest($this->context . '.filter.search', 'filter_search', '');
$this->setState('filter.search', $search);
// Load the parameters.
$params = JComponentHelper::getParams('com_patchtester');
$this->setState('params', $params);
$this->setState('github_user', $params->get('org', 'joomla'));
$this->setState('github_repo', $params->get('repo', 'joomla-cms'));
// List state information.
parent::populateState('a.pull_id', 'desc');
}
/**
* Retrieves a list of applied patches
*
* @return mixed
*
* @since 1.0
*/
public function getAppliedPatches()
{
$db = $this->getDbo();
$db->setQuery(
$db->getQuery(true)
->select('*')
->from($db->quoteName('#__patchtester_tests'))
->where($db->quoteName('applied') . ' = 1')
);
try
{
$tests = $db->loadObjectList('pull_id');
return $tests;
}
catch (RuntimeException $e)
{
$this->setError($e->getMessage());
return false;
}
}
/**
* Method to get a JDatabaseQuery object for retrieving the data set from a database.
*
* @return JDatabaseQuery A JDatabaseQuery object to retrieve the data set.
*
* @since 2.0
*/
protected function getListQuery()
{
// Create a new query object.
$db = $this->getDbo();
$query = $db->getQuery(true);
// Select the required fields from the table.
$query->select($this->getState('list.select', 'a.*'));
$query->from($db->quoteName('#__patchtester_pulls', 'a'));
// Join the tests table to get applied patches
$query->select($db->quoteName('t.id', 'applied'));
$query->join('LEFT', $db->quoteName('#__patchtester_tests', 't') . ' ON t.pull_id = a.pull_id');
// Filter by search
$search = $this->getState('filter.search');
if (!empty($search))
{
if (stripos($search, 'id:') === 0)
{
$query->where($db->quoteName('a.pull_id') . ' = ' . (int) substr($search, 3));
}
else
{
$search = $db->quote('%' . $db->escape($search, true) . '%');
$query->where('(' . $db->quoteName('a.title') . ' LIKE ' . $search . ')');
}
}
// Handle the list ordering.
$ordering = $this->getState('list.ordering');
$direction = $this->getState('list.direction');
if (!empty($ordering))
{
$query->order($db->escape($ordering) . ' ' . $db->escape($direction));
}
return $query;
}
/**
* Method to request new data from GitHub
*
* @return void
*
* @since 2.0
* @throws RuntimeException
*/
public function requestFromGithub()
{
// Get the Github object
$github = PatchtesterHelper::initializeGithub();
// If over the API limit, we can't build this list
if ($github->authorization->getRateLimit()->rate->remaining > 0)
{
// Sanity check, ensure there aren't any applied patches
if (count($this->getAppliedPatches()) >= 1)
{
throw new RuntimeException(JText::_('COM_PATCHTESTER_ERROR_APPLIED_PATCHES'));
}
$pulls = array();
$page = 0;
do
{
$page++;
try
{
$items = $github->pulls->getList($this->getState('github_user'), $this->getState('github_repo'), 'open', $page, 100);
}
catch (DomainException $e)
{
throw new RuntimeException(JText::sprintf('COM_PATCHTESTER_ERROR_GITHUB_FETCH', $e->getMessage()));
}
$count = is_array($items) ? count($items) : 0;
if ($count)
{
$pulls = array_merge($pulls, $items);
}
}
while ($count);
// Dump the old data now
$this->getDbo()->truncateTable('#__patchtester_pulls');
foreach ($pulls as &$pull)
{
// Build the data object to store in the database
$data = new stdClass;
$data->pull_id = $pull->number;
$data->title = $pull->title;
$data->description = $pull->body;
$data->pull_url = $pull->html_url;
// Try to find a Joomlacode issue number
$matches = array();
preg_match('#\[\#([0-9]+)\]#', $pull->title, $matches);
if (isset($matches[1]))
{
$data->joomlacode_id = (int) $matches[1];
}
else
{
preg_match('#(http://joomlacode[-\w\./\?\S]+)#', $pull->body, $matches);
if (isset($matches[1]))
{
preg_match('#tracker_item_id=([0-9]+)#', $matches[1], $matches);
if (isset($matches[1]))
{
$data->joomlacode_id = (int) $matches[1];
}
}
}
try
{
$this->getDbo()->insertObject('#__patchtester_pulls', $data, 'id');
}
catch (RuntimeException $e)
{
throw new RuntimeException(JText::sprintf('COM_PATCHTESTER_ERROR_INSERT_DATABASE', $e->getMessage()));
}
}
}
else
{
throw new RuntimeException(JText::sprintf('COM_PATCHTESTER_API_LIMIT_LIST', JFactory::getDate($this->rate->reset)));
}
}
}

View File

@ -14,8 +14,23 @@ if (!JFactory::getUser()->authorise('core.manage', 'com_patchtester'))
return JError::raiseWarning(404, JText::_('JERROR_ALERTNOAUTHOR'));
}
JLoader::register('PatchtesterHelper', __DIR__ . '/helpers/patchtester.php');
// Application reference
$app = JFactory::getApplication();
$controller = JControllerLegacy::getInstance('PatchTester');
$controller->execute(JFactory::getApplication()->input->getCmd('task'));
$controller->redirect();
// Register the component namespace to the autoloader
JLoader::registerNamespace('PatchTester', __DIR__);
// Build the controller class name based on task
$task = $app->input->getCmd('task', 'display');
// If $task is an empty string, apply our default since JInput might not
if ($task === '')
{
$task = 'display';
}
$class = '\\PatchTester\\Controller\\' . ucfirst(strtolower($task)) . 'Controller';
// Instantiate and execute the controller
$controller = new $class($app->input, $app);
$controller->execute();

View File

@ -34,18 +34,14 @@
<administration>
<menu img="components/com_patchtester/assets/images/icon-16-patchtester.png">com_patchtester</menu>
<files folder="admin">
<folder>PatchTester</folder>
<folder>assets</folder>
<folder>backups</folder>
<folder>controllers</folder>
<folder>helpers</folder>
<folder>install</folder>
<folder>language</folder>
<folder>models</folder>
<folder>tables</folder>
<folder>views</folder>
<filename>access.xml</filename>
<filename>config.xml</filename>
<filename>controller.php</filename>
<filename>patchtester.php</filename>
</files>
</administration>

View File

@ -1,129 +0,0 @@
<?php
/**
* @package PatchTester
*
* @copyright Copyright (C) 2011 - 2012 Ian MacLennan, Copyright (C) 2013 - 2014 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later
*/
defined('_JEXEC') or die;
/**
* View class for a list of pull requests.
*
* @package PatchTester
* @since 1.0
*/
class PatchtesterViewPulls extends JViewLegacy
{
/**
* Array containing environment errors
*
* @var array
* @since 2.0
*/
protected $envErrors = array();
/**
* Array of open pull requests
*
* @var array
* @since 1.0
*/
protected $items;
/**
* State object
*
* @var \Joomla\Registry\Registry
* @since 1.0
*/
protected $state;
/**
* Pagination object
*
* @var JPagination
* @since 2.0
*/
protected $pagination;
/**
* Execute and display a template script.
*
* @param string $tpl The name of the template file to parse; automatically searches through the template paths.
*
* @return mixed A string if successful, otherwise a Error object.
*
* @since 1.0
*/
public function display($tpl = null)
{
if (!extension_loaded('openssl'))
{
$this->envErrors[] = JText::_('COM_PATCHTESTER_REQUIREMENT_OPENSSL');
}
if (!in_array('https', stream_get_wrappers()))
{
$this->envErrors[] = JText::_('COM_PATCHTESTER_REQUIREMENT_HTTPS');
}
// Only process the data if there are no environment errors
if (!count($this->envErrors))
{
$this->state = $this->get('State');
$this->items = $this->get('Items');
$this->pagination = $this->get('Pagination');
// Check for errors.
$errors = $this->get('Errors');
if (count($errors))
{
JError::raiseError(500, implode("\n", $errors));
return false;
}
}
$this->addToolbar();
return parent::display($tpl);
}
/**
* Add the page title and toolbar.
*
* @return void
*
* @since 1.0
*/
protected function addToolbar()
{
JToolBarHelper::title(JText::_('COM_PATCHTESTER'), 'patchtester');
if (!count($this->envErrors))
{
JToolbarHelper::custom('pulls.fetch', 'refresh.png', 'refresh_f2.png', 'COM_PATCHTESTER_TOOLBAR_FETCH_DATA', false);
}
JToolBarHelper::preferences('com_patchtester');
}
/**
* Returns an array of fields the table can be sorted by
*
* @return array Array containing the field name to sort by as the key and display text as value
*
* @since 2.0
*/
protected function getSortFields()
{
return array(
'a.title' => JText::_('JGLOBAL_TITLE'),
'a.pull_id' => JText::_('COM_PATCHTESTER_PULL_ID'),
'applied' => JText::_('JSTATUS')
);
}
}