2012-12-19 14:20:06 +01:00
|
|
|
<?php
|
2012-12-19 15:18:23 +01:00
|
|
|
/**
|
|
|
|
* Requires PHP's mycrypt extension in order to set the database name as an encrypted cookie.
|
|
|
|
*/
|
2012-12-19 14:20:06 +01:00
|
|
|
class TestSessionController extends Controller {
|
|
|
|
|
2013-04-09 01:08:34 +02:00
|
|
|
private static $allowed_actions = array(
|
2012-12-19 14:20:06 +01:00
|
|
|
'index',
|
|
|
|
'start',
|
2012-12-19 15:18:23 +01:00
|
|
|
'set',
|
2012-12-19 14:20:06 +01:00
|
|
|
'end',
|
2012-12-19 15:18:23 +01:00
|
|
|
'clear',
|
2013-12-10 18:53:26 +01:00
|
|
|
'StartForm',
|
|
|
|
'ProgressForm',
|
2012-12-19 14:20:06 +01:00
|
|
|
);
|
|
|
|
|
2013-06-07 01:36:55 +02:00
|
|
|
private static $alternative_database_name = -1;
|
|
|
|
|
2013-12-12 14:18:18 +01:00
|
|
|
/**
|
|
|
|
* @var String Absolute path to a folder containing *.sql dumps.
|
|
|
|
*/
|
|
|
|
private static $database_templates_path;
|
|
|
|
|
2012-12-19 14:20:06 +01:00
|
|
|
public function init() {
|
|
|
|
parent::init();
|
|
|
|
|
2012-12-19 14:30:18 +01:00
|
|
|
$canAccess = (
|
|
|
|
!Director::isLive()
|
2013-12-12 01:08:29 +01:00
|
|
|
&& (Director::isDev() || Director::isTest() || Director::is_cli() || Permission::check("ADMIN"))
|
2012-12-19 14:30:18 +01:00
|
|
|
);
|
2012-12-19 14:20:06 +01:00
|
|
|
if(!$canAccess) return Security::permissionFailure($this);
|
2013-12-12 14:18:18 +01:00
|
|
|
|
|
|
|
Requirements::javascript('framework/thirdparty/jquery/jquery.js');
|
|
|
|
Requirements::javascript('testsession/javascript/testsession.js');
|
2012-12-19 14:20:06 +01:00
|
|
|
}
|
2012-12-19 15:18:23 +01:00
|
|
|
|
|
|
|
public function Link($action = null) {
|
|
|
|
return Controller::join_links(Director::baseUrl(), 'dev/testsession', $action);
|
|
|
|
}
|
2013-12-10 18:53:26 +01:00
|
|
|
|
|
|
|
public function index() {
|
|
|
|
if(Session::get('testsession.started')) {
|
|
|
|
return $this->renderWith('TestSession_inprogress');
|
|
|
|
} else {
|
|
|
|
return $this->renderWith('TestSession_start');
|
|
|
|
}
|
|
|
|
}
|
2012-12-19 14:20:06 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Start a test session.
|
|
|
|
*/
|
2013-12-10 18:53:26 +01:00
|
|
|
public function start() {
|
|
|
|
$this->extend('onBeforeStart');
|
|
|
|
$params = $this->request->requestVars();
|
|
|
|
$this->setState($params);
|
|
|
|
$this->extend('onAfterStart');
|
2012-12-19 15:18:23 +01:00
|
|
|
|
2013-12-10 18:53:26 +01:00
|
|
|
return $this->renderWith('TestSession_inprogress');
|
2012-12-19 15:18:23 +01:00
|
|
|
}
|
2012-12-19 14:20:06 +01:00
|
|
|
|
2013-12-10 18:53:26 +01:00
|
|
|
public function StartForm() {
|
2013-12-12 14:18:18 +01:00
|
|
|
$databaseTemplates = $this->getDatabaseTemplates();
|
2013-12-10 18:53:26 +01:00
|
|
|
$fields = new FieldList(
|
|
|
|
new CheckboxField('createDatabase', 'Create temporary database?', 1)
|
|
|
|
);
|
2013-12-12 14:18:18 +01:00
|
|
|
if($databaseTemplates) {
|
|
|
|
$fields->push(
|
|
|
|
(new DropdownField('createDatabaseTemplate', false))
|
|
|
|
->setSource($databaseTemplates)
|
|
|
|
->setEmptyString('Empty database')
|
|
|
|
);
|
|
|
|
}
|
2013-12-10 18:53:26 +01:00
|
|
|
$fields->merge($this->getBaseFields());
|
2013-12-10 16:50:23 +01:00
|
|
|
$form = new Form(
|
|
|
|
$this,
|
2013-12-10 18:53:26 +01:00
|
|
|
'StartForm',
|
|
|
|
$fields,
|
2013-12-10 16:50:23 +01:00
|
|
|
new FieldList(
|
2013-12-10 18:53:26 +01:00
|
|
|
new FormAction('start', 'Start Session')
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
$this->extend('updateStartForm', $form);
|
|
|
|
|
|
|
|
return $form;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Shows state which is allowed to be modified while a test session is in progress.
|
|
|
|
*/
|
|
|
|
public function ProgressForm() {
|
|
|
|
$fields = $this->getBaseFields();
|
|
|
|
$form = new Form(
|
|
|
|
$this,
|
|
|
|
'ProgressForm',
|
|
|
|
$fields,
|
2013-12-10 16:50:23 +01:00
|
|
|
new FieldList(
|
|
|
|
new FormAction('set', 'Set testing state')
|
|
|
|
)
|
|
|
|
);
|
2013-12-10 18:53:26 +01:00
|
|
|
|
|
|
|
|
|
|
|
$form->setFormAction($this->Link('set'));
|
|
|
|
|
|
|
|
$this->extend('updateProgressForm', $form);
|
|
|
|
|
|
|
|
return $form;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function getBaseFields() {
|
|
|
|
$fields = new FieldList(
|
|
|
|
(new TextField('fixture', 'Fixture YAML file path'))
|
|
|
|
->setAttribute('placeholder', 'Example: framework/tests/security/MemberTest.yml'),
|
|
|
|
$datetimeField = new DatetimeField('datetime', 'Custom date'),
|
|
|
|
new HiddenField('flush', null, 1)
|
|
|
|
);
|
2013-12-10 16:50:23 +01:00
|
|
|
$datetimeField->getDateField()
|
|
|
|
->setConfig('dateformat', 'yyyy-MM-dd')
|
|
|
|
->setConfig('showcalendar', true)
|
2013-12-10 18:53:26 +01:00
|
|
|
->setAttribute('placeholder', 'Date (yyyy-MM-dd)');
|
2013-12-10 16:50:23 +01:00
|
|
|
$datetimeField->getTimeField()
|
2013-12-10 18:53:26 +01:00
|
|
|
->setConfig('timeformat', 'HH:mm:ss')
|
|
|
|
->setAttribute('placeholder', 'Time (HH:mm:ss)');
|
|
|
|
$datetimeField->setValue(Session::get('testsession.datetime'));
|
2013-12-10 16:50:23 +01:00
|
|
|
|
2013-12-10 18:53:26 +01:00
|
|
|
$this->extend('updateBaseFields', $fields);
|
2013-12-10 16:50:23 +01:00
|
|
|
|
2013-12-10 18:53:26 +01:00
|
|
|
return $fields;
|
2013-12-10 16:50:23 +01:00
|
|
|
}
|
|
|
|
|
2013-06-07 01:22:18 +02:00
|
|
|
public function DatabaseName() {
|
2013-06-07 01:36:55 +02:00
|
|
|
// Workaround for bug in Cookie::get(), fixed in 3.1-rc1
|
|
|
|
if(self::$alternative_database_name != -1) {
|
|
|
|
return self::$alternative_database_name;
|
|
|
|
} else if ($dbname = DB::get_alternative_database_name()) {
|
2013-06-07 01:22:18 +02:00
|
|
|
return $dbname;
|
|
|
|
} else {
|
|
|
|
$db = DB::getConn();
|
|
|
|
if(method_exists($db, 'currentDatabase')) return $db->currentDatabase();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-12-10 18:53:26 +01:00
|
|
|
public function set() {
|
|
|
|
if(!Session::get('testsession.started')) {
|
|
|
|
throw new LogicException("No test session in progress.");
|
2012-12-19 14:20:06 +01:00
|
|
|
}
|
2012-12-19 15:18:23 +01:00
|
|
|
|
2013-12-10 18:53:26 +01:00
|
|
|
$params = $this->request->requestVars();
|
2013-12-10 16:50:23 +01:00
|
|
|
$this->extend('onBeforeSet', $params);
|
|
|
|
$this->setState($params);
|
2013-12-09 13:29:46 +01:00
|
|
|
$this->extend('onAfterSet');
|
2012-12-19 15:18:23 +01:00
|
|
|
|
|
|
|
return $this->renderWith('TestSession_inprogress');
|
2012-12-19 14:20:06 +01:00
|
|
|
}
|
|
|
|
|
2013-12-10 18:53:26 +01:00
|
|
|
public function clear() {
|
|
|
|
if(!Session::get('testsession.started')) {
|
|
|
|
throw new LogicException("No test session in progress.");
|
2012-12-19 14:20:06 +01:00
|
|
|
}
|
2012-12-19 15:18:23 +01:00
|
|
|
|
2013-12-09 13:29:46 +01:00
|
|
|
$this->extend('onBeforeClear');
|
|
|
|
|
2013-12-10 18:53:26 +01:00
|
|
|
if(SapphireTest::using_temp_db()) {
|
|
|
|
SapphireTest::empty_temp_db();
|
|
|
|
}
|
2013-11-27 19:29:44 +01:00
|
|
|
|
|
|
|
if(isset($_SESSION['_testsession_codeblocks'])) {
|
|
|
|
unset($_SESSION['_testsession_codeblocks']);
|
|
|
|
}
|
2012-12-19 15:18:23 +01:00
|
|
|
|
2013-12-09 13:29:46 +01:00
|
|
|
$this->extend('onAfterClear');
|
|
|
|
|
2012-12-19 15:18:23 +01:00
|
|
|
return "Cleared database and test state";
|
|
|
|
}
|
|
|
|
|
|
|
|
public function end() {
|
2013-12-10 18:53:26 +01:00
|
|
|
if(!Session::get('testsession.started')) {
|
|
|
|
throw new LogicException("No test session in progress.");
|
2012-12-19 14:20:06 +01:00
|
|
|
}
|
|
|
|
|
2013-12-09 13:29:46 +01:00
|
|
|
$this->extend('onBeforeEnd');
|
|
|
|
|
2013-12-10 18:53:26 +01:00
|
|
|
if(SapphireTest::using_temp_db()) {
|
|
|
|
SapphireTest::kill_temp_db();
|
|
|
|
DB::set_alternative_database_name(null);
|
|
|
|
// Workaround for bug in Cookie::get(), fixed in 3.1-rc1
|
|
|
|
self::$alternative_database_name = null;
|
|
|
|
}
|
|
|
|
|
2012-12-19 15:18:23 +01:00
|
|
|
Session::clear('testsession');
|
2012-12-19 14:20:06 +01:00
|
|
|
|
2013-12-09 13:29:46 +01:00
|
|
|
$this->extend('onAfterEnd');
|
|
|
|
|
2012-12-19 15:18:23 +01:00
|
|
|
return $this->renderWith('TestSession_end');
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function loadFixtureIntoDb($fixtureFile) {
|
|
|
|
$realFile = realpath(BASE_PATH.'/'.$fixtureFile);
|
|
|
|
$baseDir = realpath(Director::baseFolder());
|
|
|
|
if(!$realFile || !file_exists($realFile)) {
|
|
|
|
throw new LogicException("Fixture file doesn't exist");
|
|
|
|
} else if(substr($realFile,0,strlen($baseDir)) != $baseDir) {
|
|
|
|
throw new LogicException("Fixture file must be inside $baseDir");
|
|
|
|
} else if(substr($realFile,-4) != '.yml') {
|
|
|
|
throw new LogicException("Fixture file must be a .yml file");
|
|
|
|
} else if(!preg_match('/^([^\/.][^\/]+)\/tests\//', $fixtureFile)) {
|
|
|
|
throw new LogicException("Fixture file must be inside the tests subfolder of one of your modules.");
|
2012-12-19 14:20:06 +01:00
|
|
|
}
|
2012-12-19 15:18:23 +01:00
|
|
|
|
2013-04-09 01:08:34 +02:00
|
|
|
$factory = Injector::inst()->create('FixtureFactory');
|
2012-12-19 15:18:23 +01:00
|
|
|
$fixture = Injector::inst()->create('YamlFixture', $fixtureFile);
|
2013-04-09 01:08:34 +02:00
|
|
|
$fixture->writeInto($factory);
|
2012-12-19 15:18:23 +01:00
|
|
|
|
|
|
|
Session::add_to_array('testsession.fixtures', $fixtureFile);
|
|
|
|
|
|
|
|
return $fixture;
|
2012-12-19 14:20:06 +01:00
|
|
|
}
|
2012-12-19 15:18:23 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
public function isTesting() {
|
|
|
|
return SapphireTest::using_temp_db();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setState($data) {
|
2013-12-10 16:50:23 +01:00
|
|
|
// Filter keys
|
|
|
|
$data = array_diff_key(
|
|
|
|
$data,
|
2013-12-10 18:53:26 +01:00
|
|
|
array(
|
|
|
|
'action_set' => true,
|
|
|
|
'action_start' => true,
|
|
|
|
'SecurityID' => true,
|
|
|
|
'url' => true,
|
|
|
|
'flush' => true,
|
|
|
|
)
|
2013-12-10 16:50:23 +01:00
|
|
|
);
|
|
|
|
|
2012-12-19 15:18:23 +01:00
|
|
|
// Database
|
2013-12-10 18:53:26 +01:00
|
|
|
if(
|
|
|
|
!Session::get('testsession.started')
|
|
|
|
&& (@$data['createDatabase'] || @$data['database'])
|
|
|
|
) {
|
|
|
|
$dbName = (isset($data['database'])) ? $data['database'] : null;
|
|
|
|
if($dbName) {
|
|
|
|
$dbExists = (bool)DB::query(
|
|
|
|
sprintf("SHOW DATABASES LIKE '%s'", Convert::raw2sql($dbName))
|
|
|
|
)->value();
|
|
|
|
} else {
|
|
|
|
$dbExists = false;
|
|
|
|
}
|
|
|
|
if(!$dbExists) {
|
|
|
|
// Create a new one with a randomized name
|
|
|
|
$dbName = SapphireTest::create_temp_db();
|
|
|
|
}
|
|
|
|
|
2012-12-19 15:18:23 +01:00
|
|
|
// Set existing one, assumes it already has been created
|
|
|
|
$prefix = defined('SS_DATABASE_PREFIX') ? SS_DATABASE_PREFIX : 'ss_';
|
|
|
|
$pattern = strtolower(sprintf('#^%stmpdb\d{7}#', $prefix));
|
2013-12-10 18:53:26 +01:00
|
|
|
if(!preg_match($pattern, $dbName)) {
|
2012-12-19 15:18:23 +01:00
|
|
|
throw new InvalidArgumentException("Invalid database name format");
|
2012-12-19 14:20:06 +01:00
|
|
|
}
|
2013-12-10 18:53:26 +01:00
|
|
|
DB::set_alternative_database_name($dbName);
|
2013-06-07 01:36:55 +02:00
|
|
|
// Workaround for bug in Cookie::get(), fixed in 3.1-rc1
|
2013-12-10 18:53:26 +01:00
|
|
|
self::$alternative_database_name = $dbName;
|
2013-08-13 13:58:50 +02:00
|
|
|
|
|
|
|
// Database name is set in cookie (next request), ensure its available on this request already
|
|
|
|
global $databaseConfig;
|
2013-12-10 18:53:26 +01:00
|
|
|
DB::connect(array_merge($databaseConfig, array('database' => $dbName)));
|
|
|
|
if(isset($data['database'])) unset($data['database']);
|
2013-12-12 14:18:18 +01:00
|
|
|
|
|
|
|
// Import database template if required
|
|
|
|
if(isset($data['createDatabaseTemplate']) && $data['createDatabaseTemplate']) {
|
|
|
|
$sql = file_get_contents($data['createDatabaseTemplate']);
|
|
|
|
// Split into individual query commands, removing comments
|
|
|
|
$sqlCmds = array_filter(
|
|
|
|
preg_split('/\s*;\s*/',
|
|
|
|
preg_replace(array('/^$\n/m', '/^(\/|#).*$\n/m'), '', $sql)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Execute each query
|
|
|
|
foreach($sqlCmds as $sqlCmd) {
|
|
|
|
DB::query($sqlCmd);
|
|
|
|
}
|
|
|
|
|
|
|
|
// In case the dump involved CREATE TABLE commands, we need to ensure
|
|
|
|
// the schema is still up to date
|
|
|
|
$dbAdmin = new DatabaseAdmin();
|
|
|
|
$dbAdmin->doBuild(true /*quiet*/, false /*populate*/);
|
|
|
|
}
|
2013-12-10 18:53:26 +01:00
|
|
|
}
|
2012-12-19 15:18:23 +01:00
|
|
|
|
|
|
|
// Fixtures
|
|
|
|
$fixtureFile = (isset($data['fixture'])) ? $data['fixture'] : null;
|
|
|
|
if($fixtureFile) {
|
|
|
|
$this->loadFixtureIntoDb($fixtureFile);
|
2013-11-27 19:29:44 +01:00
|
|
|
unset($data['fixture']);
|
2013-09-14 18:33:18 +02:00
|
|
|
}
|
2012-12-19 15:47:39 +01:00
|
|
|
|
|
|
|
// Mailer
|
|
|
|
$mailer = (isset($data['mailer'])) ? $data['mailer'] : null;
|
|
|
|
if($mailer) {
|
|
|
|
if(!class_exists($mailer) || !is_subclass_of($mailer, 'Mailer')) {
|
|
|
|
throw new InvalidArgumentException(sprintf(
|
|
|
|
'Class "%s" is not a valid class, or subclass of Mailer',
|
|
|
|
$mailer
|
|
|
|
));
|
|
|
|
}
|
|
|
|
Session::set('testsession.mailer', $mailer);
|
2013-11-27 19:29:44 +01:00
|
|
|
unset($data['mailer']);
|
2012-12-19 15:47:39 +01:00
|
|
|
}
|
|
|
|
|
2013-12-10 18:53:26 +01:00
|
|
|
// Date and time
|
2013-12-10 19:01:22 +01:00
|
|
|
if(@$data['datetime']['date']) {
|
2012-12-19 15:47:39 +01:00
|
|
|
require_once 'Zend/Date.php';
|
2013-12-10 16:50:23 +01:00
|
|
|
// Convert DatetimeField format
|
2013-12-10 19:01:22 +01:00
|
|
|
$datetime = $data['datetime']['date'];
|
|
|
|
$datetime .= ' ';
|
|
|
|
$datetime .= (@$data['datetime']['time']) ? $data['datetime']['time'] : '00:00:00';
|
2013-12-10 18:53:26 +01:00
|
|
|
if(!Zend_Date::isDate($datetime, 'yyyy-MM-dd HH:mm:ss')) {
|
2012-12-19 15:47:39 +01:00
|
|
|
throw new LogicException(sprintf(
|
|
|
|
'Invalid date format "%s", use yyyy-MM-dd HH:mm:ss',
|
2013-12-10 18:53:26 +01:00
|
|
|
$datetime
|
2012-12-19 15:47:39 +01:00
|
|
|
));
|
|
|
|
}
|
2013-12-10 18:53:26 +01:00
|
|
|
Session::set('testsession.datetime', $datetime);
|
|
|
|
unset($data['datetime']);
|
|
|
|
} else {
|
|
|
|
unset($data['datetime']);
|
2013-11-27 19:29:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Set all other keys without special handling
|
|
|
|
if($data) foreach($data as $k => $v) {
|
|
|
|
Session::set('testsession.' . $k, $v);
|
2012-12-19 15:47:39 +01:00
|
|
|
}
|
2013-12-10 18:53:26 +01:00
|
|
|
|
|
|
|
Session::set('testsession.started', true);
|
2012-12-19 14:20:06 +01:00
|
|
|
}
|
2012-12-19 14:30:18 +01:00
|
|
|
|
2012-12-19 15:18:23 +01:00
|
|
|
/**
|
|
|
|
* @return ArrayList
|
|
|
|
*/
|
|
|
|
public function getState() {
|
|
|
|
$state = array();
|
2013-12-10 18:53:26 +01:00
|
|
|
$state[] = new ArrayData(array(
|
2012-12-19 15:18:23 +01:00
|
|
|
'Name' => 'Database',
|
2013-12-10 18:53:26 +01:00
|
|
|
'Value' => DB::getConn()->currentDatabase(),
|
2012-12-19 15:18:23 +01:00
|
|
|
));
|
2013-11-27 19:29:44 +01:00
|
|
|
$sessionStates = Session::get('testsession');
|
|
|
|
if($sessionStates) foreach($sessionStates as $k => $v) {
|
2012-12-19 15:18:23 +01:00
|
|
|
$state[] = new ArrayData(array(
|
2013-11-27 19:29:44 +01:00
|
|
|
'Name' => $k,
|
2013-12-10 16:50:07 +01:00
|
|
|
'Value' => var_export($v, true)
|
2013-11-27 19:29:44 +01:00
|
|
|
));
|
2012-12-19 15:47:39 +01:00
|
|
|
}
|
2012-12-19 14:20:06 +01:00
|
|
|
|
2012-12-19 15:18:23 +01:00
|
|
|
return new ArrayList($state);
|
2012-12-19 14:20:06 +01:00
|
|
|
}
|
|
|
|
|
2013-12-12 14:18:18 +01:00
|
|
|
/**
|
|
|
|
* Get all *.sql database files located in a specific path,
|
|
|
|
* keyed by their file name.
|
|
|
|
*
|
|
|
|
* @param String $path Absolute folder path
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
protected function getDatabaseTemplates($path = null) {
|
|
|
|
$templates = array();
|
|
|
|
|
|
|
|
if(!$path) {
|
|
|
|
$path = $this->config()->database_templates_path;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO Remove once we can set BASE_PATH through the config layer
|
|
|
|
if($path && !Director::is_absolute($path)) {
|
|
|
|
$path = BASE_PATH . '/' . $path;
|
|
|
|
}
|
|
|
|
|
|
|
|
if($path && file_exists($path)) {
|
|
|
|
$it = new FilesystemIterator($path);
|
|
|
|
foreach($it as $fileinfo) {
|
|
|
|
if($fileinfo->getExtension() != 'sql') continue;
|
|
|
|
$templates[$fileinfo->getRealPath()] = $fileinfo->getFilename();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $templates;
|
|
|
|
}
|
|
|
|
|
2012-12-19 14:20:06 +01:00
|
|
|
}
|