Merge branch 'master' into 2.3

Conflicts:
	lib/Cake/VERSION.txt
This commit is contained in:
mark_story 2012-07-18 22:12:51 -04:00
commit 3c6b50953b
28 changed files with 390 additions and 49 deletions

View file

@ -414,7 +414,7 @@ class ModelTask extends BakeTask {
for ($i = 1, $m = $defaultChoice / 2; $i < $m; $i++) { for ($i = 1, $m = $defaultChoice / 2; $i < $m; $i++) {
$line = sprintf("%2d. %s", $i, $this->_validations[$i]); $line = sprintf("%2d. %s", $i, $this->_validations[$i]);
$optionText .= $line . str_repeat(" ", 31 - strlen($line)); $optionText .= $line . str_repeat(" ", 31 - strlen($line));
$optionText .= sprintf("%2d. %s", $m + $i, $this->_validations[$m + $i]); $optionText .= sprintf("%2d. %s\n", $m + $i, $this->_validations[$m + $i]);
} }
$this->out($optionText); $this->out($optionText);
$this->out(__d('cake_console', "%s - Do not do any validation on this field.", $defaultChoice)); $this->out(__d('cake_console', "%s - Do not do any validation on this field.", $defaultChoice));

View file

@ -165,23 +165,13 @@ class Shell extends Object {
if ($this->stdout == null) { if ($this->stdout == null) {
$this->stdout = new ConsoleOutput('php://stdout'); $this->stdout = new ConsoleOutput('php://stdout');
} }
CakeLog::config('stdout', array(
'engine' => 'ConsoleLog',
'types' => array('notice', 'info'),
'stream' => $this->stdout,
));
if ($this->stderr == null) { if ($this->stderr == null) {
$this->stderr = new ConsoleOutput('php://stderr'); $this->stderr = new ConsoleOutput('php://stderr');
} }
CakeLog::config('stderr', array(
'engine' => 'ConsoleLog',
'types' => array('emergency', 'alert', 'critical', 'error', 'warning', 'debug'),
'stream' => $this->stderr,
));
if ($this->stdin == null) { if ($this->stdin == null) {
$this->stdin = new ConsoleInput('php://stdin'); $this->stdin = new ConsoleInput('php://stdin');
} }
$this->_useLogger();
$parent = get_parent_class($this); $parent = get_parent_class($this);
if ($this->tasks !== null && $this->tasks !== false) { if ($this->tasks !== null && $this->tasks !== false) {
$this->_mergeVars(array('tasks'), $parent, true); $this->_mergeVars(array('tasks'), $parent, true);
@ -379,6 +369,10 @@ class Shell extends Object {
return false; return false;
} }
if (!empty($this->params['quiet'])) {
$this->_useLogger(false);
}
$this->command = $command; $this->command = $command;
if (!empty($this->params['help'])) { if (!empty($this->params['help'])) {
return $this->_displayHelp($command); return $this->_displayHelp($command);
@ -825,4 +819,29 @@ class Shell extends Object {
return current(App::path('plugins')) . $pluginName . DS; return current(App::path('plugins')) . $pluginName . DS;
} }
/**
* Used to enable or disable logging stream output to stdout and stderr
* If you don't wish to see in your stdout or stderr everything that is logged
* through CakeLog, call this function with first param as false
*
* @param boolean $enable wheter to enable CakeLog output or not
* @return void
**/
protected function _useLogger($enable = true) {
if (!$enable) {
CakeLog::drop('stdout');
CakeLog::drop('stderr');
return;
}
CakeLog::config('stdout', array(
'engine' => 'ConsoleLog',
'types' => array('notice', 'info'),
'stream' => $this->stdout,
));
CakeLog::config('stderr', array(
'engine' => 'ConsoleLog',
'types' => array('emergency', 'alert', 'critical', 'error', 'warning', 'debug'),
'stream' => $this->stderr,
));
}
} }

View file

@ -229,7 +229,7 @@ class SecurityComponent extends Component {
} }
} }
$this->generateToken($controller->request); $this->generateToken($controller->request);
if ($isPost) { if ($isPost && is_array($controller->request->data)) {
unset($controller->request->data['_Token']); unset($controller->request->data['_Token']);
} }
} }
@ -585,12 +585,13 @@ class SecurityComponent extends Component {
* @param string $method Method to execute * @param string $method Method to execute
* @param array $params Parameters to send to method * @param array $params Parameters to send to method
* @return mixed Controller callback method's response * @return mixed Controller callback method's response
* @throws BadRequestException When a the blackholeCallback is not callable.
*/ */
protected function _callback(Controller $controller, $method, $params = array()) { protected function _callback(Controller $controller, $method, $params = array()) {
if (is_callable(array($controller, $method))) { if (is_callable(array($controller, $method))) {
return call_user_func_array(array(&$controller, $method), empty($params) ? null : $params); return call_user_func_array(array(&$controller, $method), empty($params) ? null : $params);
} else { } else {
return null; throw new BadRequestException(__d('cake_dev', 'The request has been black-holed'));
} }
} }

View file

@ -49,11 +49,16 @@ class ConsoleLog extends BaseLog {
*/ */
public function __construct($config = array()) { public function __construct($config = array()) {
parent::__construct($config); parent::__construct($config);
if (DS == '\\' && !(bool)env('ANSICON')) {
$outputAs = ConsoleOutput::PLAIN;
} else {
$outputAs = ConsoleOutput::COLOR;
}
$config = Hash::merge(array( $config = Hash::merge(array(
'stream' => 'php://stderr', 'stream' => 'php://stderr',
'types' => null, 'types' => null,
'scopes' => array(), 'scopes' => array(),
'outputAs' => ConsoleOutput::COLOR, 'outputAs' => $outputAs,
), $this->_config); ), $this->_config);
$config = $this->config($config); $config = $this->config($config);
if ($config['stream'] instanceof ConsoleOutput) { if ($config['stream'] instanceof ConsoleOutput) {

View file

@ -393,6 +393,16 @@ class TranslateBehavior extends ModelBehavior {
$conditions = array('model' => $model->alias, 'foreign_key' => $model->id); $conditions = array('model' => $model->alias, 'foreign_key' => $model->id);
$RuntimeModel = $this->translateModel($model); $RuntimeModel = $this->translateModel($model);
$fields = array_merge($this->settings[$model->alias], $this->runtime[$model->alias]['fields']);
if ($created) {
foreach ($fields as $field) {
if (!isset($tempData[$field])) {
//set the field value to an empty string
$tempData[$field] = '';
}
}
}
foreach ($tempData as $field => $value) { foreach ($tempData as $field => $value) {
unset($conditions['content']); unset($conditions['content']);
$conditions['field'] = $field; $conditions['field'] = $field;

View file

@ -124,11 +124,13 @@ class TreeBehavior extends ModelBehavior {
*/ */
public function beforeDelete(Model $Model, $cascade = true) { public function beforeDelete(Model $Model, $cascade = true) {
extract($this->settings[$Model->alias]); extract($this->settings[$Model->alias]);
$data = current($Model->find('first', array( $data = $Model->find('first', array(
'conditions' => array($Model->alias . '.' . $Model->primaryKey => $Model->id), 'conditions' => array($Model->alias . '.' . $Model->primaryKey => $Model->id),
'fields' => array($Model->alias . '.' . $left, $Model->alias . '.' . $right), 'fields' => array($Model->alias . '.' . $left, $Model->alias . '.' . $right),
'recursive' => -1))); 'recursive' => -1));
$this->_deletedRow = $data; if ($data) {
$this->_deletedRow = current($data);
}
return true; return true;
} }
@ -369,8 +371,7 @@ class TreeBehavior extends ModelBehavior {
$valuePath = array('%s%s', '{n}.tree_prefix', $valuePath); $valuePath = array('%s%s', '{n}.tree_prefix', $valuePath);
} else { } else {
$valuePath[0] = '{' . (count($valuePath) - 1) . '}' . $valuePath[0]; array_unshift($valuePath, '%s' . $valuePath[0], '{n}.tree_prefix');
$valuePath[] = '{n}.tree_prefix';
} }
$order = $Model->alias . '.' . $left . ' asc'; $order = $Model->alias . '.' . $left . ' asc';
$results = $Model->find('all', compact('conditions', 'fields', 'order', 'recursive')); $results = $Model->find('all', compact('conditions', 'fields', 'order', 'recursive'));

View file

@ -285,6 +285,7 @@ class BehaviorCollection extends ObjectCollection implements CakeEventListener {
'Model.beforeFind' => 'trigger', 'Model.beforeFind' => 'trigger',
'Model.afterFind' => 'trigger', 'Model.afterFind' => 'trigger',
'Model.beforeValidate' => 'trigger', 'Model.beforeValidate' => 'trigger',
'Model.afterValidate' => 'trigger',
'Model.beforeSave' => 'trigger', 'Model.beforeSave' => 'trigger',
'Model.afterSave' => 'trigger', 'Model.afterSave' => 'trigger',
'Model.beforeDelete' => 'trigger', 'Model.beforeDelete' => 'trigger',

View file

@ -146,6 +146,17 @@ class ModelBehavior extends Object {
return true; return true;
} }
/**
* afterValidate is called just after model data was validated, you can use this callback
* to perform any data cleanup or preparation if needed
*
* @param Model $model Model using this behavior
* @return mixed False will stop this event from being passed to other behaviors
*/
public function afterValidate(Model $model) {
return true;
}
/** /**
* beforeSave is called before a model is saved. Returning false from a beforeSave callback * beforeSave is called before a model is saved. Returning false from a beforeSave callback
* will abort the save operation. * will abort the save operation.

View file

@ -175,7 +175,8 @@ class CakeRequest implements ArrayAccess {
if (env('HTTP_X_HTTP_METHOD_OVERRIDE')) { if (env('HTTP_X_HTTP_METHOD_OVERRIDE')) {
$this->data['_method'] = env('HTTP_X_HTTP_METHOD_OVERRIDE'); $this->data['_method'] = env('HTTP_X_HTTP_METHOD_OVERRIDE');
} }
if (isset($this->data['_method'])) { $isArray = is_array($this->data);
if ($isArray && isset($this->data['_method'])) {
if (!empty($_SERVER)) { if (!empty($_SERVER)) {
$_SERVER['REQUEST_METHOD'] = $this->data['_method']; $_SERVER['REQUEST_METHOD'] = $this->data['_method'];
} else { } else {
@ -183,8 +184,7 @@ class CakeRequest implements ArrayAccess {
} }
unset($this->data['_method']); unset($this->data['_method']);
} }
if ($isArray && isset($this->data['data'])) {
if (isset($this->data['data'])) {
$data = $this->data['data']; $data = $this->data['data'];
if (count($this->data) <= 1) { if (count($this->data) <= 1) {
$this->data = $data; $this->data = $data;

View file

@ -43,7 +43,7 @@ class Dispatcher implements CakeEventListener {
/** /**
* Event manager, used to handle dispatcher filters * Event manager, used to handle dispatcher filters
* *
* @var CakeEventMaanger * @var CakeEventManager
*/ */
protected $_eventManager; protected $_eventManager;
@ -62,7 +62,7 @@ class Dispatcher implements CakeEventListener {
* Returns the CakeEventManager instance or creates one if none was * Returns the CakeEventManager instance or creates one if none was
* creted. Attaches the default listeners and filters * creted. Attaches the default listeners and filters
* *
* @return CakeEventmanger * @return CakeEventManager
*/ */
public function getEventManager() { public function getEventManager() {
if (!$this->_eventManager) { if (!$this->_eventManager) {

View file

@ -497,9 +497,9 @@ class CakeRoute {
$named = array(); $named = array();
foreach ($params['named'] as $key => $value) { foreach ($params['named'] as $key => $value) {
if (is_array($value)) { if (is_array($value)) {
$flat = Hash::flatten($value, ']['); $flat = Hash::flatten($value, '%5D%5B');
foreach ($flat as $namedKey => $namedValue) { foreach ($flat as $namedKey => $namedValue) {
$named[] = $key . "[$namedKey]" . $separator . rawurlencode($namedValue); $named[] = $key . "%5B{$namedKey}%5D" . $separator . rawurlencode($namedValue);
} }
} else { } else {
$named[] = $key . $separator . rawurlencode($value); $named[] = $key . $separator . rawurlencode($value);

View file

@ -80,6 +80,10 @@ class ShellTestShell extends Shell {
return $this->_mergeVars($properties, $class, $normalize); return $this->_mergeVars($properties, $class, $normalize);
} }
public function useLogger($enable = true) {
$this->_useLogger($enable);
}
} }
/** /**
@ -840,4 +844,32 @@ TEXT;
$this->assertContains($this->Shell->testMessage, $contents); $this->assertContains($this->Shell->testMessage, $contents);
} }
/**
* Tests that _useLogger works properly
*
* @return void
**/
public function testProtectedUseLogger() {
CakeLog::drop('stdout');
CakeLog::drop('stderr');
$this->Shell->useLogger(true);
$this->assertNotEmpty(CakeLog::stream('stdout'));
$this->assertNotEmpty(CakeLog::stream('stderr'));
$this->Shell->useLogger(false);
$this->assertFalse(CakeLog::stream('stdout'));
$this->assertFalse(CakeLog::stream('stderr'));
}
/**
* Test file and console and logging quiet output
*/
public function testQuietLog() {
$output = $this->getMock('ConsoleOutput', array(), array(), '', false);
$error = $this->getMock('ConsoleOutput', array(), array(), '', false);
$in = $this->getMock('ConsoleInput', array(), array(), '', false);
$this->Shell = $this->getMock('ShellTestShell', array('_useLogger'), array($output, $error, $in));
$this->Shell->expects($this->once())->method('_useLogger')->with(false);
$this->Shell->runCommand('foo', array('--quiet'));
}
} }

View file

@ -107,6 +107,20 @@ class SecurityTestController extends Controller {
} }
class BrokenCallbackController extends Controller {
public $name = 'UncallableCallback';
public $components = array('Session', 'TestSecurity');
public function index() {
}
protected function _fail() {
}
}
/** /**
* SecurityComponentTest class * SecurityComponentTest class
* *
@ -161,6 +175,25 @@ class SecurityComponentTest extends CakeTestCase {
unset($this->Controller); unset($this->Controller);
} }
/**
* Test that requests are still blackholed when controller has incorrect
* visibility keyword in the blackhole callback
*
* @expectedException BadRequestException
*/
public function testBlackholeWithBrokenCallback() {
$request = new CakeRequest('posts/index', false);
$request->addParams(array(
'controller' => 'posts', 'action' => 'index')
);
$this->Controller = new BrokenCallbackController($request);
$this->Controller->Components->init($this->Controller);
$this->Controller->Security = $this->Controller->TestSecurity;
$this->Controller->Security->blackHoleCallback = '_fail';
$this->Controller->Security->startup($this->Controller);
$this->Controller->Security->blackHole($this->Controller, 'csrf');
}
/** /**
* test that initialize can set properties. * test that initialize can set properties.
* *

View file

@ -116,4 +116,21 @@ class ConsoleLogTest extends CakeTestCase {
$this->assertContains($message, $logOutput); $this->assertContains($message, $logOutput);
} }
/**
* test default value of stream 'outputAs'
*/
public function testDefaultOutputAs() {
TestCakeLog::config('test_console_log', array(
'engine' => 'TestConsoleLog',
));
if (DS == '\\' && !(bool)env('ANSICON')) {
$expected = ConsoleOutput::PLAIN;
} else {
$expected = ConsoleOutput::COLOR;
}
$stream = TestCakeLog::stream('test_console_log');
$config = $stream->config();
$this->assertEquals($expected, $config['outputAs']);
}
} }

View file

@ -531,6 +531,34 @@ class TranslateBehaviorTest extends CakeTestCase {
$this->assertEquals($expected, $result); $this->assertEquals($expected, $result);
} }
/**
* Test that saving only some of the translated fields allows the record to be found again.
*
* @return void
*/
public function testSavePartialFields() {
$this->loadFixtures('Translate', 'TranslatedItem');
$TestModel = new TranslatedItem();
$TestModel->locale = 'spa';
$data = array(
'slug' => 'fourth_translated',
'title' => 'Leyenda #4',
);
$TestModel->create($data);
$TestModel->save();
$result = $TestModel->read();
$expected = array(
'TranslatedItem' => array(
'id' => $TestModel->id,
'translated_article_id' => null,
'locale' => 'spa',
'content' => '',
) + $data
);
$this->assertEquals($expected, $result);
}
/** /**
* testSaveUpdate method * testSaveUpdate method
* *

View file

@ -914,6 +914,18 @@ class TreeBehaviorNumberTest extends CakeTestCase {
$this->assertSame($validTree, true); $this->assertSame($validTree, true);
} }
/**
* Test deleting a record that doesn't exist.
*
* @return void
*/
public function testDeleteDoesNotExist() {
extract($this->settings);
$this->Tree = new $modelClass();
$this->Tree->initialize(2, 2);
$this->Tree->delete(99999);
}
/** /**
* testRemove method * testRemove method
* *
@ -1269,6 +1281,26 @@ class TreeBehaviorNumberTest extends CakeTestCase {
$this->assertSame($expected, $result); $this->assertSame($expected, $result);
} }
/**
* Test the formatting options of generateTreeList()
*
* @return void
*/
public function testGenerateTreeListFormatting() {
extract($this->settings);
$this->Tree = new $modelClass();
$this->Tree->initialize(2, 2);
$result = $this->Tree->generateTreeList(
null,
"{n}.$modelClass.id",
array('%s - %s', "{n}.$modelClass.id", "{n}.$modelClass.name")
);
$this->assertEquals('1 - 1. Root', $result[1]);
$this->assertEquals('_2 - 1.1', $result[2]);
$this->assertEquals('__3 - 1.1.1', $result[3]);
}
/** /**
* testArraySyntax method * testArraySyntax method
* *

View file

@ -194,6 +194,29 @@ class TestBehavior extends ModelBehavior {
} }
} }
/**
* afterValidate method
*
* @param Model $model
* @param bool $cascade
* @return void
*/
public function afterValidate(Model $model) {
$settings = $this->settings[$model->alias];
if (!isset($settings['afterValidate']) || $settings['afterValidate'] == 'off') {
return parent::afterValidate($model);
}
switch ($settings['afterValidate']) {
case 'on':
return false;
break;
case 'test':
$model->data = array('foo');
return true;
break;
}
}
/** /**
* beforeDelete method * beforeDelete method
* *
@ -966,6 +989,27 @@ class BehaviorCollectionTest extends CakeTestCase {
$this->assertSame($Apple->whitelist, array('unknown', 'name')); $this->assertSame($Apple->whitelist, array('unknown', 'name'));
} }
/**
* testBehaviorValidateAfterCallback method
*
* @return void
*/
public function testBehaviorValidateAfterCallback() {
$Apple = new Apple();
$Apple->Behaviors->attach('Test');
$this->assertSame($Apple->validates(), true);
$Apple->Behaviors->attach('Test', array('afterValidate' => 'on'));
$this->assertSame($Apple->validates(), true);
$this->assertSame($Apple->validationErrors, array());
$Apple->Behaviors->attach('Test', array('afterValidate' => 'test'));
$Apple->data = array('bar');
$Apple->validates();
$this->assertEquals(array('foo'), $Apple->data);
}
/** /**
* testBehaviorValidateMethods method * testBehaviorValidateMethods method
* *

View file

@ -73,6 +73,7 @@ class CakeValidationRuleTest extends CakeTestCase {
$Rule->process('fieldName', $data, $methods); $Rule->process('fieldName', $data, $methods);
$this->assertTrue($Rule->isValid()); $this->assertTrue($Rule->isValid());
} }
/** /**
* tests that passing custom validation methods work * tests that passing custom validation methods work
* *
@ -98,6 +99,24 @@ class CakeValidationRuleTest extends CakeTestCase {
$this->assertFalse($Rule->isValid()); $this->assertFalse($Rule->isValid());
} }
/**
* Make sure errors are triggered when validation is missing.
*
* @expectedException PHPUnit_Framework_Error_Warning
* @expectedExceptionMessage Could not find validation handler totallyMissing for fieldName
* @return void
*/
public function testCustomMethodMissingError() {
$def = array('rule' => array('totallyMissing'));
$data = array(
'fieldName' => 'some data'
);
$methods = array('mytestrule' => array($this, 'myTestRule'));
$Rule = new CakeValidationRule($def);
$Rule->process('fieldName', $data, $methods);
}
/** /**
* Test isRequired method * Test isRequired method
* *

View file

@ -300,6 +300,22 @@ class CakeRequestTest extends CakeTestCase {
$this->assertEquals($data, $request->data); $this->assertEquals($data, $request->data);
} }
/**
* test parsing json PUT data into the object.
*
* @return void
*/
public function testPutParsingJSON() {
$_SERVER['REQUEST_METHOD'] = 'PUT';
$_SERVER['CONTENT_TYPE'] = 'application/json';
$request = $this->getMock('TestCakeRequest', array('_readInput'));
$request->expects($this->at(0))->method('_readInput')
->will($this->returnValue('{Article":["title"]}'));
$request->reConstruct();
$this->assertEquals('{Article":["title"]}', $request->data);
}
/** /**
* test parsing of FILES array * test parsing of FILES array
* *

View file

@ -1119,7 +1119,8 @@ class CakeEmailTest extends CakeTestCase {
$this->CakeEmail->template('image'); $this->CakeEmail->template('image');
$this->CakeEmail->emailFormat('html'); $this->CakeEmail->emailFormat('html');
$expected = '<img src="http://localhost/img/image.gif" alt="cool image" width="100" height="100" />'; $server = env('SERVER_NAME') ? env('SERVER_NAME') : 'localhost';
$expected = '<img src="http://' . $server . '/img/image.gif" alt="cool image" width="100" height="100" />';
$result = $this->CakeEmail->send(); $result = $this->CakeEmail->send();
$this->assertContains($expected, $result['message']); $this->assertContains($expected, $result['message']);
} }

View file

@ -798,7 +798,7 @@ class CakeRouteTest extends CakeTestCase {
) )
); );
$result = $route->match($url); $result = $route->match($url);
$expected = '/posts/index/filter[0]:one/filter[model]:value'; $expected = '/posts/index/filter%5B0%5D:one/filter%5Bmodel%5D:value';
$this->assertEquals($expected, $result); $this->assertEquals($expected, $result);
$url = array( $url = array(
@ -813,7 +813,7 @@ class CakeRouteTest extends CakeTestCase {
) )
); );
$result = $route->match($url); $result = $route->match($url);
$expected = '/posts/index/filter[0]:one/filter[model][0]:two/filter[model][order]:field'; $expected = '/posts/index/filter%5B0%5D:one/filter%5Bmodel%5D%5B0%5D:two/filter%5Bmodel%5D%5Border%5D:field';
$this->assertEquals($expected, $result); $this->assertEquals($expected, $result);
} }

View file

@ -1039,4 +1039,26 @@ XML;
$this->assertContains('mark &amp; mark', $result); $this->assertContains('mark &amp; mark', $result);
} }
/**
* Test that entity loading is disabled by default.
*
* @return void
*/
public function testNoEntityLoading() {
$file = CAKE . 'VERSION.txt';
$xml = <<<XML
<!DOCTYPE cakephp [
<!ENTITY payload SYSTEM "file://$file" >]>
<request>
<xxe>&payload;</xxe>
</request>
XML;
try {
$result = Xml::build($xml);
$this->assertEquals('', (string)$result->xxe);
} catch (Exception $e) {
$this->assertTrue(true, 'A warning was raised meaning external entities were not loaded');
}
}
} }

View file

@ -361,7 +361,14 @@ class HtmlHelperTest extends CakeTestCase {
$result = $this->Html->image('test.gif?one=two&three=four'); $result = $this->Html->image('test.gif?one=two&three=four');
$this->assertTags($result, array('img' => array('src' => 'img/test.gif?one=two&amp;three=four', 'alt' => ''))); $this->assertTags($result, array('img' => array('src' => 'img/test.gif?one=two&amp;three=four', 'alt' => '')));
}
/**
* Test that image() works with fullBase and a webroot not equal to /
*
* @return void
*/
public function testImageWithFullBase() {
$result = $this->Html->image('test.gif', array('fullBase' => true)); $result = $this->Html->image('test.gif', array('fullBase' => true));
$here = $this->Html->url('/', true); $here = $this->Html->url('/', true);
$this->assertTags($result, array('img' => array('src' => $here . 'img/test.gif', 'alt' => ''))); $this->assertTags($result, array('img' => array('src' => $here . 'img/test.gif', 'alt' => '')));
@ -369,6 +376,15 @@ class HtmlHelperTest extends CakeTestCase {
$result = $this->Html->image('sub/test.gif', array('fullBase' => true)); $result = $this->Html->image('sub/test.gif', array('fullBase' => true));
$here = $this->Html->url('/', true); $here = $this->Html->url('/', true);
$this->assertTags($result, array('img' => array('src' => $here . 'img/sub/test.gif', 'alt' => ''))); $this->assertTags($result, array('img' => array('src' => $here . 'img/sub/test.gif', 'alt' => '')));
$request = $this->Html->request;
$request->webroot = '/myproject/';
$request->base = '/myproject';
Router::setRequestInfo($request);
$result = $this->Html->image('sub/test.gif', array('fullBase' => true));
$here = $this->Html->url('/', true);
$this->assertTags($result, array('img' => array('src' => $here . 'img/sub/test.gif', 'alt' => '')));
} }
/** /**

View file

@ -194,6 +194,14 @@ class CakeTestFixture {
$db->execute($db->createSchema($this->Schema), array('log' => false)); $db->execute($db->createSchema($this->Schema), array('log' => false));
$this->created[] = $db->configKeyName; $this->created[] = $db->configKeyName;
} catch (Exception $e) { } catch (Exception $e) {
$msg = __d(
'cake_dev',
'Fixture creation for "%s" failed "%s"',
$this->table,
$e->getMessage()
);
CakeLog::error($msg);
trigger_error($msg, E_USER_WARNING);
return false; return false;
} }
return true; return true;

View file

@ -201,6 +201,9 @@ class CakeBaseReporter extends PHPUnit_TextUI_ResultPrinter {
*/ */
public function endTest(PHPUnit_Framework_Test $test, $time) { public function endTest(PHPUnit_Framework_Test $test, $time) {
$this->numAssertions += $test->getNumAssertions(); $this->numAssertions += $test->getNumAssertions();
if ($test->hasFailed()) {
return;
}
$this->paintPass($test, $time); $this->paintPass($test, $time);
} }

View file

@ -74,6 +74,8 @@ class Xml {
* ### Options * ### Options
* *
* - `return` Can be 'simplexml' to return object of SimpleXMLElement or 'domdocument' to return DOMDocument. * - `return` Can be 'simplexml' to return object of SimpleXMLElement or 'domdocument' to return DOMDocument.
* - `loadEntities` Defaults to false. Set to true to enable loading of `<!ENTITY` definitions. This
* is disabled by default for security reasons.
* - If using array as input, you can pass `options` from Xml::fromArray. * - If using array as input, you can pass `options` from Xml::fromArray.
* *
* @param string|array $input XML string, a path to a file, an URL or an array * @param string|array $input XML string, a path to a file, an URL or an array
@ -86,32 +88,50 @@ class Xml {
$options = array('return' => (string)$options); $options = array('return' => (string)$options);
} }
$defaults = array( $defaults = array(
'return' => 'simplexml' 'return' => 'simplexml',
'loadEntities' => false,
); );
$options = array_merge($defaults, $options); $options = array_merge($defaults, $options);
if (is_array($input) || is_object($input)) { if (is_array($input) || is_object($input)) {
return self::fromArray((array)$input, $options); return self::fromArray((array)$input, $options);
} elseif (strpos($input, '<') !== false) { } elseif (strpos($input, '<') !== false) {
if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') { return self::_loadXml($input, $options);
return new SimpleXMLElement($input, LIBXML_NOCDATA);
}
$dom = new DOMDocument();
$dom->loadXML($input);
return $dom;
} elseif (file_exists($input) || strpos($input, 'http://') === 0 || strpos($input, 'https://') === 0) { } elseif (file_exists($input) || strpos($input, 'http://') === 0 || strpos($input, 'https://') === 0) {
if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') { $input = file_get_contents($input);
return new SimpleXMLElement($input, LIBXML_NOCDATA, true); return self::_loadXml($input, $options);
}
$dom = new DOMDocument();
$dom->load($input);
return $dom;
} elseif (!is_string($input)) { } elseif (!is_string($input)) {
throw new XmlException(__d('cake_dev', 'Invalid input.')); throw new XmlException(__d('cake_dev', 'Invalid input.'));
} }
throw new XmlException(__d('cake_dev', 'XML cannot be read.')); throw new XmlException(__d('cake_dev', 'XML cannot be read.'));
} }
/**
* Parse the input data and create either a SimpleXmlElement object or a DOMDocument.
*
* @param string $input The input to load.
* @param array $options The options to use. See Xml::build()
* @return SimpleXmlElement|DOMDocument.
*/
protected static function _loadXml($input, $options) {
$hasDisable = function_exists('libxml_disable_entity_loader');
$internalErrors = libxml_use_internal_errors(true);
if ($hasDisable && !$options['loadEntities']) {
libxml_disable_entity_loader(true);
}
if ($options['return'] === 'simplexml' || $options['return'] === 'simplexmlelement') {
$xml = new SimpleXMLElement($input, LIBXML_NOCDATA);
} else {
$xml = new DOMDocument();
$xml->loadXML($input);
}
if ($hasDisable && !$options['loadEntities']) {
libxml_disable_entity_loader(false);
}
libxml_use_internal_errors($internalErrors);
return $xml;
}
/** /**
* Transform an array into a SimpleXMLElement * Transform an array into a SimpleXMLElement
* *

View file

@ -313,10 +313,12 @@ class Helper extends Object {
$path = h($this->assetTimestamp($this->webroot($path))); $path = h($this->assetTimestamp($this->webroot($path)));
if (!empty($options['fullBase'])) { if (!empty($options['fullBase'])) {
if ($path[0] == '/') { $base = $this->url('/', true);
$path = substr($path, 1); $len = strlen($this->request->webroot);
if ($len) {
$base = substr($base, 0, -$len);
} }
$path = $this->url('/', true) . $path; $path = $base . $path;
} }
} }

View file

@ -701,7 +701,7 @@ class PaginatorHelper extends AppHelper {
if ($offset < $start - 1) { if ($offset < $start - 1) {
$out .= $this->first($offset, compact('tag', 'separator', 'ellipsis', 'class')); $out .= $this->first($offset, compact('tag', 'separator', 'ellipsis', 'class'));
} else { } else {
$out .= $this->first($offset, compact('tag', 'separator', 'class') + array('after' => $separator)); $out .= $this->first($offset, compact('tag', 'separator', 'class', 'ellipsis') + array('after' => $separator));
} }
} }
@ -735,7 +735,7 @@ class PaginatorHelper extends AppHelper {
if ($offset <= $last && $params['pageCount'] - $end > $offset) { if ($offset <= $last && $params['pageCount'] - $end > $offset) {
$out .= $this->last($offset, compact('tag', 'separator', 'ellipsis', 'class')); $out .= $this->last($offset, compact('tag', 'separator', 'ellipsis', 'class'));
} else { } else {
$out .= $this->last($offset, compact('tag', 'separator', 'class') + array('before' => $separator)); $out .= $this->last($offset, compact('tag', 'separator', 'class', 'ellipsis') + array('before' => $separator));
} }
} }