mirror of
https://github.com/Oreolek/kohana-migrations.git
synced 2024-06-16 15:01:08 +03:00
Starting to refactor stuff.
Renaming locations -> groups Cut down the size of fetch_required_migrations()
This commit is contained in:
parent
8b550a43d4
commit
e0fe87f443
|
@ -34,11 +34,11 @@ abstract class Minion_Migration_Base {
|
|||
public function get_database_connection()
|
||||
{
|
||||
$config = Kohana::config('minion/migration');
|
||||
$location = $this->_info['location'];
|
||||
$group = $this->_info['group'];
|
||||
|
||||
if (isset($config->location_connection[$location]))
|
||||
if (isset($config->group_connection[$group]))
|
||||
{
|
||||
return $config->location_connection[$location];
|
||||
return $config->group_connection[$group];
|
||||
}
|
||||
|
||||
return Database::$default;
|
||||
|
|
|
@ -14,7 +14,7 @@ class Minion_Migration_Exception extends Kohana_Exception {
|
|||
public function __construct($message, array $migration, array $variables = array(), $code = 0)
|
||||
{
|
||||
$variables[':migration-id'] = $migration['id'];
|
||||
$variables[':migration-location'] = $migration['location'];
|
||||
$variables[':migration-group'] = $migration['group'];
|
||||
|
||||
$this->_migration = $migration;
|
||||
|
||||
|
|
|
@ -116,49 +116,49 @@ class Minion_Migration_Manager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Run migrations in the specified locations so as to reach specified targets
|
||||
* Run migrations in the specified groups so as to reach specified targets
|
||||
*
|
||||
* There are three methods for specifying target versions:
|
||||
*
|
||||
* 1. Pass them in with the array of locations, i.e.
|
||||
* 1. Pass them in with the array of groups, i.e.
|
||||
*
|
||||
* array(
|
||||
* location => target_version
|
||||
* group => target_version
|
||||
* )
|
||||
*
|
||||
* 2. Pass them in separately, with param1 containing an array of
|
||||
* locations like:
|
||||
* groups like:
|
||||
*
|
||||
* array(
|
||||
* location,
|
||||
* location2,
|
||||
* group,
|
||||
* group2,
|
||||
* )
|
||||
*
|
||||
* And param2 containing an array structured in the same way as in #1
|
||||
*
|
||||
* 3. Perform a mix of the above two methods
|
||||
*
|
||||
* It may seem odd to use two arrays to specify locations and versions, but
|
||||
* it's this way to allow users to upgrade / downgrade all locations while
|
||||
* migrating a specific location to a specific version
|
||||
* It may seem odd to use two arrays to specify groups and versions, but
|
||||
* it's this way to allow users to upgrade / downgrade all groups while
|
||||
* migrating a specific group to a specific version
|
||||
*
|
||||
* If no locations are specified then migrations from all locations will be
|
||||
* If no groups are specified then migrations from all groups will be
|
||||
* run and be brought up to the latest available version
|
||||
*
|
||||
* @param array Set of locations to update, empty array means all
|
||||
* @param array Versions for specified locations
|
||||
* @param array Set of groups to update, empty array means all
|
||||
* @param array Versions for specified groups
|
||||
* @param boolean The default direction (up/down) for migrations without a specific version
|
||||
* @return array Array of all migrations that were successfully applied
|
||||
*/
|
||||
public function run_migration(array $locations = array(), $versions = array(), $default_direction = TRUE)
|
||||
public function run_migration(array $groups = array(), $versions = array(), $default_direction = TRUE)
|
||||
{
|
||||
$migrations = $this->_model->fetch_required_migrations($locations, $versions, $default_direction);
|
||||
$migrations = $this->_model->fetch_required_migrations($groups, $versions, $default_direction);
|
||||
|
||||
foreach ($migrations as $path => $location)
|
||||
foreach ($migrations as $path => $group)
|
||||
{
|
||||
$method = $location['direction'] ? 'up' : 'down';
|
||||
$method = $group['direction'] ? 'up' : 'down';
|
||||
|
||||
foreach ($location['migrations'] as $migration)
|
||||
foreach ($group['migrations'] as $migration)
|
||||
{
|
||||
$filename = Minion_Migration_Util::get_filename_from_migration($migration);
|
||||
|
||||
|
@ -198,7 +198,7 @@ class Minion_Migration_Manager {
|
|||
}
|
||||
else
|
||||
{
|
||||
$this->_model->mark_migration($migration, $location['direction']);
|
||||
$this->_model->mark_migration($migration, $group['direction']);
|
||||
}
|
||||
|
||||
$this->_executed_migrations[] = $migration;
|
||||
|
|
|
@ -30,7 +30,7 @@ class Minion_Migration_Util {
|
|||
{
|
||||
$migration = Minion_Migration_Util::get_migration_from_filename($file);
|
||||
|
||||
$migrations[$migration['location'].':'.$migration['timestamp']] = $migration;
|
||||
$migrations[$migration['group'].':'.$migration['timestamp']] = $migration;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,10 +43,10 @@ class Minion_Migration_Util {
|
|||
* Returns an array like:
|
||||
*
|
||||
* array(
|
||||
* 'location' => 'mylocation',
|
||||
* 'group' => 'mygroup',
|
||||
* 'timestamp' => '1293214439',
|
||||
* 'description' => 'initial-setup',
|
||||
* 'id' => 'mylocation:1293214439'
|
||||
* 'id' => 'mygroup:1293214439'
|
||||
* );
|
||||
*
|
||||
* @param string The migration's filename
|
||||
|
@ -57,33 +57,33 @@ class Minion_Migration_Util {
|
|||
$migration = array();
|
||||
|
||||
// Get rid of the file's "migrations/" prefix, the file extension and then
|
||||
// the filename itself. The "location" is essentially a slash delimited
|
||||
// the filename itself. The "group" is essentially a slash delimited
|
||||
// path from the migrations folder to the migration file
|
||||
$migration['location'] = dirname(substr($file, 11, -strlen(EXT)));
|
||||
$migration['group'] = dirname(substr($file, 11, -strlen(EXT)));
|
||||
|
||||
list($migration['timestamp'], $migration['description'])
|
||||
= explode('_', basename($file, EXT), 2);
|
||||
|
||||
$migration['id'] = $migration['location'].':'.$migration['timestamp'];
|
||||
$migration['id'] = $migration['group'].':'.$migration['timestamp'];
|
||||
|
||||
return $migration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a migration file from its timestamp, description and location
|
||||
* Gets a migration file from its timestamp, description and group
|
||||
*
|
||||
* @param integer|array The migration's ID or an array of timestamp, description
|
||||
* @param string The migration location
|
||||
* @param string The migration group
|
||||
* @return string Path to the migration file
|
||||
*/
|
||||
public static function get_filename_from_migration(array $migration)
|
||||
{
|
||||
$location = $migration['location'];
|
||||
$group = $migration['group'];
|
||||
$migration = $migration['timestamp'].'_'.$migration['description'];
|
||||
|
||||
$location = ( ! empty($location)) ? (rtrim($location, '/').'/') : '';
|
||||
$group = ( ! empty($group)) ? (rtrim($group, '/').'/') : '';
|
||||
|
||||
return $location.$migration.EXT;
|
||||
return $group.$migration.EXT;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -101,7 +101,7 @@ class Minion_Migration_Util {
|
|||
}
|
||||
else
|
||||
{
|
||||
$migration = str_replace('/', ' ', $migration['location']).'_'.$migration['timestamp'];
|
||||
$migration = str_replace('/', ' ', $migration['group']).'_'.$migration['timestamp'];
|
||||
}
|
||||
|
||||
return 'Migration_'.str_replace(array(' ', '-'), '_', ucwords($migration));
|
||||
|
|
|
@ -5,15 +5,15 @@
|
|||
*
|
||||
* Available config options are:
|
||||
*
|
||||
* --location=path/to/migration/location
|
||||
* --group=path/to/migration/group
|
||||
*
|
||||
* This is a required config option, use it specify in which location the
|
||||
* This is a required config option, use it specify in which group the
|
||||
* migration should be stored. Due to the nature of the cascading filesystem
|
||||
* minion doesn't automatically know where a migration is stored so make sure
|
||||
* you pass in the full path to your migrations folder, e.g.
|
||||
*
|
||||
* # The location of the migrations folder is modules/myapp/migrations/myapp/
|
||||
* --location=modules/myapp/migrations/myapp/
|
||||
* # The group of the migrations folder is modules/myapp/migrations/myapp/
|
||||
* --group=modules/myapp/migrations/myapp/
|
||||
*
|
||||
* On nix based systems you should be able to tab complete the path
|
||||
*
|
||||
|
@ -34,7 +34,7 @@ class Minion_Task_Db_Generate extends Minion_Task
|
|||
* @var array
|
||||
*/
|
||||
protected $_config = array(
|
||||
'location',
|
||||
'group',
|
||||
'description'
|
||||
);
|
||||
|
||||
|
@ -45,19 +45,19 @@ class Minion_Task_Db_Generate extends Minion_Task
|
|||
*/
|
||||
public function execute(array $config)
|
||||
{
|
||||
if (empty($config['location']) OR empty($config['description']))
|
||||
if (empty($config['group']) OR empty($config['description']))
|
||||
{
|
||||
return 'Please provide --location and --description'.PHP_EOL.
|
||||
return 'Please provide --group and --description'.PHP_EOL.
|
||||
'See help for more info'.PHP_EOL;
|
||||
}
|
||||
|
||||
$location = rtrim(realpath($config['location']), '/').'/';
|
||||
$group = rtrim(realpath($config['group']), '/').'/';
|
||||
$description = $config['description'];
|
||||
|
||||
// {year}{month}{day}{hour}{minute}{second}
|
||||
$time = date('YmdHis');
|
||||
$class = $this->_generate_classname($location, $time);
|
||||
$file = $this->_generate_filename($location, $time, $description);
|
||||
$class = $this->_generate_classname($group, $time);
|
||||
$file = $this->_generate_filename($group, $time, $description);
|
||||
|
||||
|
||||
$data = Kohana::FILE_SECURITY.View::factory('minion/task/db/generate/template')
|
||||
|
@ -71,20 +71,20 @@ class Minion_Task_Db_Generate extends Minion_Task
|
|||
}
|
||||
|
||||
/**
|
||||
* Generate a class name from the location
|
||||
* Generate a class name from the group
|
||||
*
|
||||
* @param string location
|
||||
* @param string group
|
||||
* @param string Timestamp
|
||||
* @return string Class name
|
||||
*/
|
||||
protected function _generate_classname($location, $time)
|
||||
protected function _generate_classname($group, $time)
|
||||
{
|
||||
// Chop up everything up until the relative path
|
||||
$location = substr($location, strrpos($location, 'migrations/') + 11);
|
||||
$group = substr($group, strrpos($group, 'migrations/') + 11);
|
||||
|
||||
$class = ucwords(str_replace('/', ' ', $location));
|
||||
$class = ucwords(str_replace('/', ' ', $group));
|
||||
|
||||
// If location is empty then we want to avoid double underscore in the
|
||||
// If group is empty then we want to avoid double underscore in the
|
||||
// class name
|
||||
if ( ! empty($class))
|
||||
{
|
||||
|
@ -97,18 +97,18 @@ class Minion_Task_Db_Generate extends Minion_Task
|
|||
}
|
||||
|
||||
/**
|
||||
* Generates a filename from the location, time and description
|
||||
* Generates a filename from the group, time and description
|
||||
*
|
||||
* @param string Location to store migration
|
||||
* @param string Timestamp
|
||||
* @param string Description
|
||||
* @return string Filename
|
||||
*/
|
||||
public function _generate_filename($location, $time, $description)
|
||||
public function _generate_filename($group, $time, $description)
|
||||
{
|
||||
$description = substr(strtolower($description), 0, 100);
|
||||
|
||||
return $location.$time.'_'.preg_replace('~[^a-z]+~', '-', $description).EXT;
|
||||
return $group.$time.'_'.preg_replace('~[^a-z]+~', '-', $description).EXT;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
*
|
||||
* Available config options are:
|
||||
*
|
||||
* --versions=[location:]version
|
||||
* --versions=[group:]version
|
||||
*
|
||||
* Used to specify the version to migrate the database to. The location prefix
|
||||
* is used to specify the target version of an individual location. Version
|
||||
* Used to specify the version to migrate the database to. The group prefix
|
||||
* is used to specify the target version of an individual group. Version
|
||||
* specifies the target version, which can be either:
|
||||
*
|
||||
* * A migration version (migrates up/down to that version)
|
||||
|
@ -19,16 +19,16 @@
|
|||
*
|
||||
* An example of a migration version is 20101229015800
|
||||
*
|
||||
* If you specify TRUE / FALSE without a location then the default migration
|
||||
* direction for locations without a specified version will be up / down respectively.
|
||||
* If you specify TRUE / FALSE without a group then the default migration
|
||||
* direction for groups without a specified version will be up / down respectively.
|
||||
*
|
||||
* If you're only specifying a migration version then you *must* specify a location
|
||||
* If you're only specifying a migration version then you *must* specify a group
|
||||
*
|
||||
* --locations=location[,location2[,location3...]]
|
||||
* --groups=group[,group2[,group3...]]
|
||||
*
|
||||
* A list of locations (under the migrations folder in the cascading
|
||||
* A list of groups (under the migrations folder in the cascading
|
||||
* filesystem) that will be used to source migration files. By default
|
||||
* migrations will be loaded from all available locations
|
||||
* migrations will be loaded from all available groups
|
||||
*
|
||||
* --dry-run
|
||||
*
|
||||
|
@ -57,7 +57,7 @@ class Minion_Task_Db_Migrate extends Minion_Task
|
|||
*/
|
||||
protected $_config = array(
|
||||
'versions',
|
||||
'locations',
|
||||
'groups',
|
||||
'dry-run',
|
||||
'quiet'
|
||||
);
|
||||
|
@ -72,13 +72,13 @@ class Minion_Task_Db_Migrate extends Minion_Task
|
|||
$k_config = Kohana::config('minion/migration');
|
||||
|
||||
// Grab user input, using sensible defaults
|
||||
$specified_locations = Arr::get($config, 'locations', NULL);
|
||||
$specified_groups = Arr::get($config, 'groups', NULL);
|
||||
$versions = Arr::get($config, 'versions', NULL);
|
||||
$dry_run = array_key_exists('dry-run', $config);
|
||||
$quiet = array_key_exists('quiet', $config);
|
||||
|
||||
$targets = $this->_parse_target_versions($versions);
|
||||
$locations = $this->_parse_locations($specified_locations);
|
||||
$groups = $this->_parse_groups($specified_groups);
|
||||
|
||||
$db = Database::instance();
|
||||
$model = new Model_Minion_Migration($db);
|
||||
|
@ -96,8 +96,8 @@ class Minion_Task_Db_Migrate extends Minion_Task
|
|||
|
||||
try
|
||||
{
|
||||
// Run migrations for specified locations & versions
|
||||
$manager->run_migration($locations, $targets, $this->_default_direction);
|
||||
// Run migrations for specified groups & versions
|
||||
$manager->run_migration($groups, $targets, $this->_default_direction);
|
||||
}
|
||||
catch(Minion_Migration_Exception $e)
|
||||
{
|
||||
|
@ -112,39 +112,39 @@ class Minion_Task_Db_Migrate extends Minion_Task
|
|||
->set('quiet', $quiet)
|
||||
->set('dry_run_sql', $manager->get_dry_run_sql())
|
||||
->set('executed_migrations', $manager->get_executed_migrations())
|
||||
->set('location_versions', $model->fetch_current_versions());
|
||||
->set('group_versions', $model->fetch_current_versions());
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a comma delimited set of locations and returns an array of them
|
||||
* Parses a comma delimited set of groups and returns an array of them
|
||||
*
|
||||
* @param string Comma delimited string of locations
|
||||
* @param string Comma delimited string of groups
|
||||
* @return array Locations
|
||||
*/
|
||||
protected function _parse_locations($location)
|
||||
protected function _parse_groups($group)
|
||||
{
|
||||
if (is_array($location))
|
||||
return $location;
|
||||
if (is_array($group))
|
||||
return $group;
|
||||
|
||||
$location = trim($location);
|
||||
$group = trim($group);
|
||||
|
||||
if (empty($location))
|
||||
if (empty($group))
|
||||
return array();
|
||||
|
||||
$locations = array();
|
||||
$location = explode(',', trim($location, ','));
|
||||
$groups = array();
|
||||
$group = explode(',', trim($group, ','));
|
||||
|
||||
if ( ! empty($location))
|
||||
if ( ! empty($group))
|
||||
{
|
||||
foreach ($location as $a_location)
|
||||
foreach ($group as $a_group)
|
||||
{
|
||||
$locations[] = trim($a_location, '/');
|
||||
$groups[] = trim($a_group, '/');
|
||||
}
|
||||
}
|
||||
|
||||
return $locations;
|
||||
return $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -156,7 +156,7 @@ class Minion_Task_Db_Migrate extends Minion_Task
|
|||
*
|
||||
* FALSE
|
||||
*
|
||||
* {location}:(TRUE|FALSE|{migration_id})
|
||||
* {group}:(TRUE|FALSE|{migration_id})
|
||||
*
|
||||
* @param string Target version(s) specified by user
|
||||
* @return array Versions
|
||||
|
@ -179,9 +179,9 @@ class Minion_Task_Db_Migrate extends Minion_Task
|
|||
|
||||
if (is_array($target))
|
||||
{
|
||||
list($location, $version) = $target;
|
||||
list($group, $version) = $target;
|
||||
|
||||
$targets[$location] = $version;
|
||||
$targets[$group] = $version;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
/**
|
||||
* Displays the current status of migrations in all locations
|
||||
* Displays the current status of migrations in all groups
|
||||
*
|
||||
* This task takes no config options
|
||||
*
|
||||
|
@ -21,7 +21,7 @@ class Minion_Task_Db_Status extends Minion_Task {
|
|||
|
||||
$view = new View('minion/task/db/status');
|
||||
|
||||
$view->locations = $model->get_location_statuses();
|
||||
$view->groups = $model->get_group_statuses();
|
||||
|
||||
echo $view;
|
||||
}
|
||||
|
|
|
@ -59,27 +59,27 @@ class Model_Minion_Migration extends Model
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets the status of all locations, whether they're in the db or not.
|
||||
* Gets the status of all groups, whether they're in the db or not.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_location_statuses()
|
||||
public function get_group_statuses()
|
||||
{
|
||||
// Start out using all the installed locations
|
||||
$locations = $this->fetch_current_versions('location', 'id');
|
||||
// Start out using all the installed groups
|
||||
$groups = $this->fetch_current_versions('group', 'id');
|
||||
$available = $this->available_migrations();
|
||||
|
||||
foreach ($available as $migration)
|
||||
{
|
||||
if (array_key_exists($migration['id'], $locations))
|
||||
if (array_key_exists($migration['id'], $groups))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$locations[$migration['location']] = NULL;
|
||||
$groups[$migration['group']] = NULL;
|
||||
}
|
||||
|
||||
return $locations;
|
||||
return $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -109,7 +109,7 @@ class Model_Minion_Migration extends Model
|
|||
*/
|
||||
protected function _select()
|
||||
{
|
||||
return DB::select('*', DB::expr('CONCAT(`location`, ":", CAST(`timestamp` AS CHAR)) AS `id`'))->from($this->_table);
|
||||
return DB::select('*', DB::expr('CONCAT(`group`, ":", CAST(`timestamp` AS CHAR)) AS `id`'))->from($this->_table);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -120,8 +120,8 @@ class Model_Minion_Migration extends Model
|
|||
*/
|
||||
public function add_migration(array $migration)
|
||||
{
|
||||
DB::insert($this->_table, array('timestamp', 'location', 'description'))
|
||||
->values(array($migration['timestamp'], $migration['location'], $migration['description']))
|
||||
DB::insert($this->_table, array('timestamp', 'group', 'description'))
|
||||
->values(array($migration['timestamp'], $migration['group'], $migration['description']))
|
||||
->execute($this->_db);
|
||||
|
||||
return $this;
|
||||
|
@ -133,21 +133,21 @@ class Model_Minion_Migration extends Model
|
|||
* @param string Migration ID
|
||||
* @return array Migration info
|
||||
*/
|
||||
public function get_migration($location, $timestamp = NULL)
|
||||
public function get_migration($group, $timestamp = NULL)
|
||||
{
|
||||
if ($timestamp === NULL)
|
||||
{
|
||||
if (empty($location) OR strpos(':', $location) === FALSE)
|
||||
if (empty($group) OR strpos(':', $group) === FALSE)
|
||||
{
|
||||
throw new Kohana_Exception('Invalid migration id :id', array(':id' => $location));
|
||||
throw new Kohana_Exception('Invalid migration id :id', array(':id' => $group));
|
||||
}
|
||||
|
||||
list($location, $timestamp) = explode(':', $location);
|
||||
list($group, $timestamp) = explode(':', $group);
|
||||
}
|
||||
|
||||
return $this->_select()
|
||||
->where('timestamp', '=', (string) $timestamp)
|
||||
->where('location', '=', (string) $location)
|
||||
->where('group', '=', (string) $group)
|
||||
->execute($this->_db)
|
||||
->current();
|
||||
}
|
||||
|
@ -163,16 +163,16 @@ class Model_Minion_Migration extends Model
|
|||
if (is_array($migration))
|
||||
{
|
||||
$timestamp = $migration['timestamp'];
|
||||
$location = $migration['location'];
|
||||
$group = $migration['group'];
|
||||
}
|
||||
else
|
||||
{
|
||||
list($timestamp, $location) = explode(':', $migration);
|
||||
list($timestamp, $group) = explode(':', $migration);
|
||||
}
|
||||
|
||||
DB::delete($this->_table)
|
||||
->where('timestamp', '=', $timestamp)
|
||||
->where('location', '=', $location)
|
||||
->where('group', '=', $group)
|
||||
->execute($this->_db);
|
||||
|
||||
return $this;
|
||||
|
@ -202,7 +202,7 @@ class Model_Minion_Migration extends Model
|
|||
DB::update($this->_table)
|
||||
->set($set)
|
||||
->where('timestamp', '=', $current['timestamp'])
|
||||
->where('location', '=', $current['location'])
|
||||
->where('group', '=', $current['group'])
|
||||
->execute($this->_db);
|
||||
}
|
||||
|
||||
|
@ -221,7 +221,7 @@ class Model_Minion_Migration extends Model
|
|||
DB::update($this->_table)
|
||||
->set(array('applied' => (int) $applied))
|
||||
->where('timestamp', '=', $migration['timestamp'])
|
||||
->where('location', '=', $migration['location'])
|
||||
->where('group', '=', $migration['group'])
|
||||
->execute($this->_db);
|
||||
|
||||
return $this;
|
||||
|
@ -240,14 +240,14 @@ class Model_Minion_Migration extends Model
|
|||
}
|
||||
|
||||
/**
|
||||
* Fetches the latest version for all installed locations
|
||||
* Fetches the latest version for all installed groups
|
||||
*
|
||||
* If a location does not have any applied migrations then no result will be
|
||||
* If a group does not have any applied migrations then no result will be
|
||||
* returned for it
|
||||
*
|
||||
* @return Kohana_Database_Result
|
||||
*/
|
||||
public function fetch_current_versions($key = 'location', $value = NULL)
|
||||
public function fetch_current_versions($key = 'group', $value = NULL)
|
||||
{
|
||||
// Little hack needed to do an order by before a group by
|
||||
return DB::select()
|
||||
|
@ -257,176 +257,89 @@ class Model_Minion_Migration extends Model
|
|||
->order_by('timestamp', 'DESC'),
|
||||
'temp_table'
|
||||
))
|
||||
->group_by('location')
|
||||
->group_by('group')
|
||||
->execute($this->_db)
|
||||
->as_array($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a list of locations
|
||||
* Fetches a list of groups
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function fetch_locations($location_as_key = FALSE)
|
||||
public function fetch_groups($group_as_key = FALSE)
|
||||
{
|
||||
return DB::select()
|
||||
->from($this->_table)
|
||||
->group_by('location')
|
||||
->group_by('group')
|
||||
->execute($this->_db)
|
||||
->as_array($location_as_key ? 'location' : NULL, 'location');
|
||||
->as_array($group_as_key ? 'group' : NULL, 'group');
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a list of migrations that need to be applied in order to reach the
|
||||
* required version
|
||||
*
|
||||
* @param string Migration's location
|
||||
* @param string Target migration id
|
||||
* @param boolean Default direction of versionless migrations
|
||||
* @param string The groups to get migrations for
|
||||
* @param mixed Target version
|
||||
*/
|
||||
public function fetch_required_migrations($locations = NULL, $target_version = TRUE, $default_direction = TRUE)
|
||||
public function fetch_required_migrations($group = NULL, $target = TRUE)
|
||||
{
|
||||
if ( ! empty($locations) AND ! is_array($locations))
|
||||
{
|
||||
$locations = array(
|
||||
$locations => is_array($target_version)
|
||||
? $default_direction
|
||||
: $target_version
|
||||
);
|
||||
}
|
||||
$migrations = array();
|
||||
|
||||
// Get an array of the latest migrations, with the location name as the
|
||||
// array key
|
||||
$migrations = $this->fetch_current_versions('location');
|
||||
$query = $this->_select();
|
||||
|
||||
// The user wants to run all available migrations
|
||||
if (empty($locations))
|
||||
if (is_bool($target))
|
||||
{
|
||||
// Fetch a mirrored array of locations => locations
|
||||
$locations = $this->fetch_locations(TRUE);
|
||||
}
|
||||
// If the calling script has been lazy and given us a numerically
|
||||
// indexed array of locations then we need to convert it to a mirrored
|
||||
// array
|
||||
// We will decide the target version for these within the loop below
|
||||
elseif ( ! Arr::is_assoc($locations))
|
||||
{
|
||||
foreach ($locations as $_pos => $location)
|
||||
// If we want to limit this migration to certain groups
|
||||
if ($group !== NULL)
|
||||
{
|
||||
unset($locations[$_pos]);
|
||||
|
||||
$locations[$location] = $location;
|
||||
}
|
||||
}
|
||||
|
||||
// Merge locations with specified target versions
|
||||
if ( ! empty($target_version) AND is_array($target_version))
|
||||
{
|
||||
$locations = $target_version + $locations;
|
||||
}
|
||||
|
||||
$migrations_to_apply = array();
|
||||
|
||||
// What follows is a bit of icky code, but there aren't many "nice" ways around it
|
||||
|
||||
// Basically we need to get a list of migrations that need to be performed, but
|
||||
// the ordering of the migrations varies depending on whether we're wanting to
|
||||
// migrate up or migrate down. As such, we can't just apply a generic "order by x"
|
||||
// condition, we have to run an individual query for each location
|
||||
|
||||
// Again, icky, but this appears to be the only "sane" way of doing it with multiple
|
||||
// locations
|
||||
|
||||
// If you have a better way of doing this, please let me know :)
|
||||
|
||||
foreach ($locations as $location => $target)
|
||||
{
|
||||
// By default all migrations go "up"
|
||||
$migrations_to_apply[$location]['direction'] = TRUE;
|
||||
$migrations_to_apply[$location]['migrations'] = array();
|
||||
|
||||
$query = $this->_select()->where('location', '=', $location);
|
||||
|
||||
// If this migration was auto-selected from the db then use the
|
||||
// default migration direction
|
||||
if ($target === $location)
|
||||
{
|
||||
$target = is_bool($target_version)
|
||||
? $target_version
|
||||
: (bool) $default_direction;
|
||||
}
|
||||
|
||||
// If the user is rolling this location to either extreme up or
|
||||
// extreme down
|
||||
if (is_bool($target))
|
||||
{
|
||||
// We're "undoing" all applied migrations, i.e. rolling back
|
||||
if ($target === FALSE)
|
||||
if (is_array($group))
|
||||
{
|
||||
$migrations_to_apply[$location]['direction'] = FALSE;
|
||||
|
||||
$query
|
||||
->where('applied', '=', 1)
|
||||
->order_by('timestamp', 'DESC');
|
||||
$query->where('group', 'IN', $group);
|
||||
}
|
||||
// We're rolling forward
|
||||
else
|
||||
{
|
||||
$query
|
||||
->where('applied', '=', 0)
|
||||
->order_by('timestamp', 'ASC');
|
||||
$query->where('group', '=', $group);
|
||||
}
|
||||
}
|
||||
// Else if the user explicitly specified a target version of some kind
|
||||
|
||||
// If we're migrating up
|
||||
if ($target === TRUE)
|
||||
{
|
||||
$query
|
||||
->where('applied', '=', 0)
|
||||
->order_by('timestamp', 'ASC');
|
||||
}
|
||||
// If we're migrating down
|
||||
else
|
||||
{
|
||||
$timestamp = $target;
|
||||
$current_timestamp = isset($migrations[$location])
|
||||
? $migrations[$location]['timestamp']
|
||||
: NULL;
|
||||
|
||||
// If the current version is the requested version then nothing
|
||||
// needs to be done
|
||||
if ($current_timestamp === $timestamp)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// If they haven't applied any migrations for this location
|
||||
// yet and are justwhere wanting to apply all migrations
|
||||
// (i.e. roll forward)
|
||||
if ($current_timestamp === NULL)
|
||||
{
|
||||
$query
|
||||
->and_where('timestamp', '<=', $timestamp)
|
||||
->order_by('timestamp', 'ASC');
|
||||
}
|
||||
// If we need to move forward
|
||||
elseif ($timestamp > $current_timestamp)
|
||||
{
|
||||
$query
|
||||
->and_where('timestamp', '<=', $timestamp)
|
||||
->and_where('applied', '=', 0)
|
||||
->order_by('timestamp', 'ASC');
|
||||
}
|
||||
// If we want to roll back
|
||||
elseif ($timestamp < $current_timestamp)
|
||||
{
|
||||
$query
|
||||
->and_where('timestamp', '<', $current_timestamp)
|
||||
->and_where('timestamp', '>', $timestamp)
|
||||
->and_where('applied', '=', 1)
|
||||
->order_by('timestamp', 'DESC');
|
||||
|
||||
$migrations_to_apply[$location]['direction'] = FALSE;
|
||||
}
|
||||
$query
|
||||
->where('applied', '>', 0)
|
||||
->order_by('timestamp', 'DESC');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
list($target, $up) = $this->resolve_target($group, $target);
|
||||
|
||||
$migrations_to_apply[$location]['migrations'] = $query->execute($this->_db)->as_array();
|
||||
$query->where('group', '=', $group);
|
||||
|
||||
unset($query);
|
||||
if ($up)
|
||||
{
|
||||
$query
|
||||
->where('timestamp', '<=', $target)
|
||||
->where('applied', '=', 0)
|
||||
->order_by('timestamp', 'ASC');
|
||||
}
|
||||
else
|
||||
{
|
||||
$query
|
||||
->where('timestamp', '>', $target)
|
||||
->order_by('timestamp', 'DESC');
|
||||
}
|
||||
}
|
||||
|
||||
return $migrations_to_apply;
|
||||
return $query->execute($this->_db)->as_array();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
|
||||
return array(
|
||||
// A mapping of location_connections => db_connection to use
|
||||
'location_connection' => array(
|
||||
// A mapping of group_connections => db_connection to use
|
||||
'group_connection' => array(
|
||||
|
||||
),
|
||||
);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
CREATE TABLE `minion_migrations` (
|
||||
`timestamp` varchar(14) NOT NULL,
|
||||
`description` varchar(100) NOT NULL,
|
||||
`location` varchar(100) NOT NULL,
|
||||
`group` varchar(100) NOT NULL,
|
||||
`applied` tinyint(1) DEFAULT '0',
|
||||
PRIMARY KEY (`timestamp`,`location`),
|
||||
PRIMARY KEY (`timestamp`,`group`),
|
||||
UNIQUE KEY `MIGRATION_ID` (`timestamp`,`description`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<dataset>
|
||||
<test_minion_migrations timestamp="20101215164400" description="create-tables" location="app" applied="1" />
|
||||
<test_minion_migrations timestamp="20101215165000" description="add-name-column-to-members" location="app" applied="0" />
|
||||
<test_minion_migrations timestamp="20101216000000" description="add-index-on-name" location="app" applied="0" />
|
||||
<test_minion_migrations timestamp="20101216080000" description="remove-password-salt-column" location="app" applied="1" />
|
||||
<test_minion_migrations timestamp="20101215164400" description="create-tables" group="app" applied="1" />
|
||||
<test_minion_migrations timestamp="20101215165000" description="add-name-column-to-members" group="app" applied="0" />
|
||||
<test_minion_migrations timestamp="20101216000000" description="add-index-on-name" group="app" applied="0" />
|
||||
<test_minion_migrations timestamp="20101216080000" description="remove-password-salt-column" group="app" applied="1" />
|
||||
|
||||
<test_minion_migrations timestamp="20101215164500" description="create-table" location="dblogger" applied="1" />
|
||||
<test_minion_migrations timestamp="20101225000000" description="remove-unique-index" location="dblogger" applied="1" />
|
||||
<test_minion_migrations timestamp="20101226112100" description="add-pk" location="dblogger" applied="0" />
|
||||
<test_minion_migrations timestamp="20101215164500" description="create-table" group="dblogger" applied="1" />
|
||||
<test_minion_migrations timestamp="20101225000000" description="remove-unique-index" group="dblogger" applied="1" />
|
||||
<test_minion_migrations timestamp="20101226112100" description="add-pk" group="dblogger" applied="0" />
|
||||
</dataset>
|
|
@ -39,7 +39,7 @@ class Minion_Migration_ModelTest extends Kohana_Unittest_Database_TestCase
|
|||
public function getDataSet()
|
||||
{
|
||||
return $this->createFlatXMLDataSet(
|
||||
Kohana::find_file('tests/test_data', 'minion/migration/model', 'xml')
|
||||
Kohana::find_file('tests/datasets', 'minion/migration/model', 'xml')
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -78,7 +78,7 @@ class Minion_Migration_ModelTest extends Kohana_Unittest_Database_TestCase
|
|||
public function test_fetch_current_versions()
|
||||
{
|
||||
$versions = $this->getModel()
|
||||
->fetch_current_versions('location', 'timestamp');
|
||||
->fetch_current_versions('group', 'timestamp');
|
||||
|
||||
$this->assertSame(
|
||||
array (
|
||||
|
@ -89,120 +89,6 @@ class Minion_Migration_ModelTest extends Kohana_Unittest_Database_TestCase
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test data for test_fetch_required_migrations
|
||||
*
|
||||
* @return array Test data
|
||||
*/
|
||||
public function provider_fetch_required_migrations()
|
||||
{
|
||||
return array(
|
||||
// Test a call with no params (i.e. upgrade everything to latest)
|
||||
array(
|
||||
array (
|
||||
'app' => array(
|
||||
'direction' => true,
|
||||
'migrations' => array(
|
||||
array (
|
||||
'timestamp' => '20101215165000',
|
||||
'description' => 'add-name-column-to-members',
|
||||
'location' => 'app',
|
||||
'applied' => '0',
|
||||
'id' => 'app:20101215165000'
|
||||
),
|
||||
array (
|
||||
'timestamp' => '20101216000000',
|
||||
'description' => 'add-index-on-name',
|
||||
'location' => 'app',
|
||||
'applied' => '0',
|
||||
'id' => 'app:20101216000000'
|
||||
),
|
||||
),
|
||||
),
|
||||
'dblogger' => array(
|
||||
'direction' => true,
|
||||
'migrations' => array(
|
||||
array (
|
||||
'timestamp' => '20101226112100',
|
||||
'description' => 'add-pk',
|
||||
'location' => 'dblogger',
|
||||
'applied' => '0',
|
||||
'id' => 'dblogger:20101226112100'
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
NULL,
|
||||
TRUE,
|
||||
TRUE
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'app' => array(
|
||||
'direction' => FALSE,
|
||||
'migrations' => array(
|
||||
array(
|
||||
'timestamp' => '20101216080000',
|
||||
'description' => 'remove-password-salt-column',
|
||||
'location' => 'app',
|
||||
'applied' => '1',
|
||||
'id' => 'app:20101216080000'
|
||||
),
|
||||
array(
|
||||
'timestamp' => '20101215164400',
|
||||
'description' => 'create-tables',
|
||||
'location' => 'app',
|
||||
'applied' => '1',
|
||||
'id' => 'app:20101215164400'
|
||||
),
|
||||
)
|
||||
),
|
||||
'dblogger' => array(
|
||||
'direction' => FALSE,
|
||||
'migrations' => array(
|
||||
array(
|
||||
'timestamp' => '20101225000000',
|
||||
'description' => 'remove-unique-index',
|
||||
'location' => 'dblogger',
|
||||
'applied' => '1',
|
||||
'id' => 'dblogger:20101225000000'
|
||||
),
|
||||
array(
|
||||
'timestamp' => '20101215164500',
|
||||
'description' => 'create-table',
|
||||
'location' => 'dblogger',
|
||||
'applied' => '1',
|
||||
'id' => 'dblogger:20101215164500'
|
||||
),
|
||||
)
|
||||
),
|
||||
),
|
||||
NULL,
|
||||
FALSE,
|
||||
TRUE
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that fetch_required_migrations() produces an accurate list of
|
||||
* migrations that need applying.
|
||||
*
|
||||
* @test
|
||||
* @covers Model_Minion_Migration::fetch_required_migrations
|
||||
* @dataProvider provider_fetch_required_migrations
|
||||
* @param array Expected output
|
||||
* @param NULL|string|array Input Locations
|
||||
* @param bool|string|array Input Target
|
||||
* @param bool Input default direction
|
||||
*/
|
||||
public function test_fetch_required_migrations($expected, $locations, $target, $default_direction)
|
||||
{
|
||||
$results = $this->getModel()
|
||||
->fetch_required_migrations($locations, $target, $default_direction);
|
||||
|
||||
$this->assertSame($expected, $results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test data for test_get_migration
|
||||
|
@ -216,7 +102,7 @@ class Minion_Migration_ModelTest extends Kohana_Unittest_Database_TestCase
|
|||
array(
|
||||
'timestamp' => '20101215164400',
|
||||
'description' => 'create-tables',
|
||||
'location' => 'app',
|
||||
'group' => 'app',
|
||||
'applied' => '1',
|
||||
'id' => 'app:20101215164400'
|
||||
),
|
||||
|
@ -234,14 +120,14 @@ class Minion_Migration_ModelTest extends Kohana_Unittest_Database_TestCase
|
|||
* @covers Model_Minion_Migration::get_migration
|
||||
* @dataProvider provider_get_migration
|
||||
* @param array Expected migration
|
||||
* @param string The migration's location
|
||||
* @param string The migration's group
|
||||
* @param string The migration's timestamp
|
||||
*/
|
||||
public function test_get_migration($expected, $location, $timestamp)
|
||||
public function test_get_migration($expected, $group, $timestamp)
|
||||
{
|
||||
$this->assertSame(
|
||||
$expected,
|
||||
$this->getModel()->get_migration($location, $timestamp)
|
||||
$this->getModel()->get_migration($group, $timestamp)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -267,9 +153,9 @@ class Minion_Migration_ModelTest extends Kohana_Unittest_Database_TestCase
|
|||
* @dataProvider provider_get_migration_throws_exception_on_invalid_input
|
||||
* @expectedException Kohana_Exception
|
||||
*/
|
||||
public function test_get_migration_throws_exception_on_invalid_input($location, $timestamp)
|
||||
public function test_get_migration_throws_exception_on_invalid_input($group, $timestamp)
|
||||
{
|
||||
$this->getModel()->get_migration($location, $timestamp);
|
||||
$this->getModel()->get_migration($group, $timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -284,13 +170,13 @@ class Minion_Migration_ModelTest extends Kohana_Unittest_Database_TestCase
|
|||
array(
|
||||
'timestamp' => '20101215165000',
|
||||
'description' => 'add-name-column-to-members',
|
||||
'location' => 'app',
|
||||
'group' => 'app',
|
||||
'applied' => '1',
|
||||
'id' => 'app:20101215165000',
|
||||
),
|
||||
array(
|
||||
'timestamp' => '20101215165000',
|
||||
'location' => 'app',
|
||||
'group' => 'app',
|
||||
'description' => 'add-name-column-to-members',
|
||||
),
|
||||
TRUE
|
||||
|
@ -299,13 +185,13 @@ class Minion_Migration_ModelTest extends Kohana_Unittest_Database_TestCase
|
|||
array(
|
||||
'timestamp' => '20101215165000',
|
||||
'description' => 'add-name-column-to-members',
|
||||
'location' => 'app',
|
||||
'group' => 'app',
|
||||
'applied' => '0',
|
||||
'id' => 'app:20101215165000',
|
||||
),
|
||||
array(
|
||||
'timestamp' => '20101215165000',
|
||||
'location' => 'app',
|
||||
'group' => 'app',
|
||||
'description' => 'add-name-column-to-members',
|
||||
),
|
||||
FALSE
|
||||
|
@ -332,7 +218,7 @@ class Minion_Migration_ModelTest extends Kohana_Unittest_Database_TestCase
|
|||
|
||||
$this->assertSame(
|
||||
$expected,
|
||||
$model->get_migration($migration['location'], $migration['timestamp'])
|
||||
$model->get_migration($migration['group'], $migration['timestamp'])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,15 +19,15 @@ class Minion_Migration_UtilTest extends Kohana_Unittest_TestCase {
|
|||
return array(
|
||||
array(
|
||||
array(
|
||||
'myapp:015151051' => array('location' => 'myapp', 'description' => 'setup', 'timestamp' => '015151051', 'id' => 'myapp:015151051'),
|
||||
'myapp:015161051' => array('location' => 'myapp', 'description' => 'add-comments', 'timestamp' => '015161051', 'id' => 'myapp:015161051'),
|
||||
'myapp:015151051' => array('group' => 'myapp', 'description' => 'setup', 'timestamp' => '015151051', 'id' => 'myapp:015151051'),
|
||||
'myapp:015161051' => array('group' => 'myapp', 'description' => 'add-comments', 'timestamp' => '015161051', 'id' => 'myapp:015161051'),
|
||||
),
|
||||
array(
|
||||
'migrations/myapp' => array(
|
||||
'migrations/myapp/015151051_setup.php'
|
||||
=> '/var/www/app/locations/myapp/migrations/myapp/015151051_setup.php',
|
||||
=> '/var/www/app/groups/myapp/migrations/myapp/015151051_setup.php',
|
||||
'migrations/myapp/015161051_add-comments.php'
|
||||
=> '/var/www/app/locations/myapp/migrations/myapp/015161051_add-comments.php',
|
||||
=> '/var/www/app/groups/myapp/migrations/myapp/015161051_add-comments.php',
|
||||
),
|
||||
)
|
||||
),
|
||||
|
@ -62,7 +62,7 @@ class Minion_Migration_UtilTest extends Kohana_Unittest_TestCase {
|
|||
return array(
|
||||
array(
|
||||
array(
|
||||
'location' => 'myapp',
|
||||
'group' => 'myapp',
|
||||
'description' => 'initial-setup',
|
||||
'timestamp' => '1293214439',
|
||||
'id' => 'myapp:1293214439',
|
||||
|
@ -101,7 +101,7 @@ class Minion_Migration_UtilTest extends Kohana_Unittest_TestCase {
|
|||
array(
|
||||
'myapp/1293214439_initial-setup.php',
|
||||
array(
|
||||
'location' => 'myapp',
|
||||
'group' => 'myapp',
|
||||
'timestamp' => '1293214439',
|
||||
'description' => 'initial-setup',
|
||||
'id' => 'myapp:1293214439'
|
||||
|
@ -120,13 +120,13 @@ class Minion_Migration_UtilTest extends Kohana_Unittest_TestCase {
|
|||
* @dataProvider provider_get_filename_from_migration
|
||||
* @param string Expected output
|
||||
* @param mixed Migration id
|
||||
* @param mixed location
|
||||
* @param mixed group
|
||||
*/
|
||||
public function test_get_filename_from_migration($expected, $migration, $location)
|
||||
public function test_get_filename_from_migration($expected, $migration, $group)
|
||||
{
|
||||
$this->assertSame(
|
||||
$expected,
|
||||
Minion_Migration_Util::get_filename_from_migration($migration, $location)
|
||||
Minion_Migration_Util::get_filename_from_migration($migration, $group)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -144,7 +144,7 @@ class Minion_Migration_UtilTest extends Kohana_Unittest_TestCase {
|
|||
),
|
||||
array(
|
||||
'Migration_Kohana_201012290258',
|
||||
array('location' => 'kohana', 'timestamp' => '201012290258'),
|
||||
array('group' => 'kohana', 'timestamp' => '201012290258'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
<?php if( ! $quiet): ?>
|
||||
Executed <?php echo count($executed_migrations); ?> migrations
|
||||
|
||||
Current versions of locations:
|
||||
<?php foreach($location_versions as $location): ?>
|
||||
* <?php echo $location['location'] ?> : <?php echo $location['timestamp'] ?> (<?php echo $location['description']; ?>)
|
||||
Current versions of groups:
|
||||
<?php foreach($group_versions as $group): ?>
|
||||
* <?php echo $group['group'] ?> : <?php echo $group['timestamp'] ?> (<?php echo $group['description']; ?>)
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php if($dry_run): ?>
|
||||
This was a dry run, if it was a real run the following SQL would've been executed:
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
<?php foreach($dry_run_sql as $location => $migrations): ?>
|
||||
<?php foreach($dry_run_sql as $group => $migrations): ?>
|
||||
|
||||
<?php $location_padding = str_repeat('#', strlen($location)); ?>
|
||||
##################<?php echo $location_padding ?>##
|
||||
# Begin Location: <?php echo $location; ?> #
|
||||
##################<?php echo $location_padding ?>##
|
||||
<?php $group_padding = str_repeat('#', strlen($group)); ?>
|
||||
##################<?php echo $group_padding ?>##
|
||||
# Begin Location: <?php echo $group; ?> #
|
||||
##################<?php echo $group_padding ?>##
|
||||
|
||||
<?php foreach($migrations as $timestamp => $sql): ?>
|
||||
# Begin <?php echo $timestamp; ?>
|
||||
|
@ -29,8 +29,8 @@ This was a dry run, if it was a real run the following SQL would've been execute
|
|||
|
||||
<?php endforeach; ?>
|
||||
|
||||
################<?php echo $location_padding ?>##
|
||||
# End Location: <?php echo $location; ?> #
|
||||
################<?php echo $location_padding ?>##
|
||||
################<?php echo $group_padding ?>##
|
||||
# End Location: <?php echo $group; ?> #
|
||||
################<?php echo $group_padding ?>##
|
||||
<?php endforeach; ?>
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php foreach($locations as $location => $status): ?>
|
||||
* <?php echo $location ?> <?php echo ($status !== NULL ? $status : 'Not installed'); ?>
|
||||
<?php foreach($groups as $group => $status): ?>
|
||||
* <?php echo $group ?> <?php echo ($status !== NULL ? $status : 'Not installed'); ?>
|
||||
|
||||
<?php endforeach; ?>
|
||||
|
|
Loading…
Reference in a new issue