From c8650165bb8f87fabb745b9ccc8898046cc5893a Mon Sep 17 00:00:00 2001 From: Kyle Robinson Young Date: Thu, 22 Dec 2011 23:16:43 -0800 Subject: [PATCH 01/44] Add returnPath to test on SmtpTransport --- lib/Cake/Test/Case/Network/Email/SmtpTransportTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Cake/Test/Case/Network/Email/SmtpTransportTest.php b/lib/Cake/Test/Case/Network/Email/SmtpTransportTest.php index fca4b0029..cab826213 100644 --- a/lib/Cake/Test/Case/Network/Email/SmtpTransportTest.php +++ b/lib/Cake/Test/Case/Network/Email/SmtpTransportTest.php @@ -214,6 +214,7 @@ class SmtpTransportTest extends CakeTestCase { $this->getMock('CakeEmail', array('message'), array(), 'SmtpCakeEmail'); $email = new SmtpCakeEmail(); $email->from('noreply@cakephp.org', 'CakePHP Test'); + $email->returnPath('pleasereply@cakephp.org', 'CakePHP Return'); $email->to('cake@cakephp.org', 'CakePHP'); $email->cc(array('mark@cakephp.org' => 'Mark Story', 'juan@cakephp.org' => 'Juan Basso')); $email->bcc('phpnut@cakephp.org'); @@ -222,6 +223,7 @@ class SmtpTransportTest extends CakeTestCase { $email->expects($this->any())->method('message')->will($this->returnValue(array('First Line', 'Second Line', ''))); $data = "From: CakePHP Test \r\n"; + $data .= "Return-Path: CakePHP Return \r\n"; $data .= "To: CakePHP \r\n"; $data .= "Cc: Mark Story , Juan Basso \r\n"; $data .= "X-Mailer: CakePHP Email\r\n"; From 2c0e030831be41a7adc2771e75430b305b092df9 Mon Sep 17 00:00:00 2001 From: mark_story Date: Fri, 23 Dec 2011 20:40:26 -0500 Subject: [PATCH 02/44] Move call to tagIsInvalid to FormHelper tagIsInvalid() doesn't exist in Helper, and shouldn't be called from that class. Fixes #2411 --- lib/Cake/View/Helper.php | 3 --- lib/Cake/View/Helper/FormHelper.php | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Cake/View/Helper.php b/lib/Cake/View/Helper.php index 6cba6380b..ade59beec 100644 --- a/lib/Cake/View/Helper.php +++ b/lib/Cake/View/Helper.php @@ -672,9 +672,6 @@ class Helper extends Object { $options = $this->_name($options); $options = $this->value($options); $options = $this->domId($options); - if ($this->tagIsInvalid() !== false) { - $options = $this->addClass($options, 'form-error'); - } return $options; } diff --git a/lib/Cake/View/Helper/FormHelper.php b/lib/Cake/View/Helper/FormHelper.php index d39ddc0e8..470a61239 100644 --- a/lib/Cake/View/Helper/FormHelper.php +++ b/lib/Cake/View/Helper/FormHelper.php @@ -2495,6 +2495,9 @@ class FormHelper extends AppHelper { } $result = parent::_initInputField($field, $options); + if ($this->tagIsInvalid() !== false) { + $result = $this->addClass($result, 'form-error'); + } if ($secure === self::SECURE_SKIP) { return $result; } From a1690a13bf9d09b7bbdbcbd35d90c3e35c211932 Mon Sep 17 00:00:00 2001 From: Dien Vu Date: Sat, 24 Dec 2011 09:18:51 +0700 Subject: [PATCH 03/44] Edit memcached's website: http://www.danga.com/memcached/ moved to http://memcached.org/ --- app/Config/core.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Config/core.php b/app/Config/core.php index fb9c951bc..aa95b8130 100644 --- a/app/Config/core.php +++ b/app/Config/core.php @@ -268,7 +268,7 @@ * 'password' => 'password', //plaintext password (xcache.admin.pass) * )); * - * Memcache (http://www.danga.com/memcached/) + * Memcache (http://memcached.org/) * * Cache::config('default', array( * 'engine' => 'Memcache', //[required] From 4e7e06fa9f3e330884acb2c2dbcc239b32f3470e Mon Sep 17 00:00:00 2001 From: Kyle Robinson Young Date: Sat, 24 Dec 2011 21:25:07 -0800 Subject: [PATCH 04/44] Prevent TextHelper::truncate() from breaking HTML Fixes #2397 --- .../Test/Case/View/Helper/TextHelperTest.php | 47 ++++++++++++++++++- lib/Cake/View/Helper/TextHelper.php | 26 ++++++---- 2 files changed, 62 insertions(+), 11 deletions(-) diff --git a/lib/Cake/Test/Case/View/Helper/TextHelperTest.php b/lib/Cake/Test/Case/View/Helper/TextHelperTest.php index a10a211dc..54752c548 100644 --- a/lib/Cake/Test/Case/View/Helper/TextHelperTest.php +++ b/lib/Cake/Test/Case/View/Helper/TextHelperTest.php @@ -60,7 +60,7 @@ class TextHelperTest extends CakeTestCase { $text5 = '01234567890'; $text6 = '

Extra dates have been announced for this year\'s tour.

Tickets for the new shows in

'; $text7 = 'El moño está en el lugar correcto. Eso fue lo que dijo la niña, ¿habrá dicho la verdad?'; - $text8 = 'Vive la R'.chr(195).chr(169).'publique de France'; + $text8 = 'Vive la R' . chr(195) . chr(169) . 'publique de France'; $text9 = 'НОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыь'; $this->assertSame($this->Text->truncate($text1, 15), 'The quick br...'); @@ -86,6 +86,51 @@ class TextHelperTest extends CakeTestCase { $this->assertSame($this->Text->truncate($text7, 15), 'El moño está...'); $this->assertSame($this->Text->truncate($text8, 15), 'Vive la R'.chr(195).chr(169).'pu...'); $this->assertSame($this->Text->truncate($text9, 10), 'НОПРСТУ...'); + + $text = '

Iamatestwithnospacesandhtml

'; + $result = $this->Text->truncate($text, 10, array( + 'ending' => '...', + 'exact' => false, + 'html' => true + )); + $expected = '

...

'; + $this->assertEquals($expected, $result); + + $text = '

El biógrafo de Steve Jobs, Walter +Isaacson, explica porqué Jobs le pidió que le hiciera su biografía en +este artículo de El País.

+

Por qué Steve era distinto.

+

http://www.elpais.com/articulo/primer/plano/ +Steve/era/distinto/elpepueconeg/20111009elpneglse_4/Tes

+

Ya se ha publicado la biografía de +Steve Jobs escrita por Walter Isaacson "Steve Jobs by Walter +Isaacson", aquí os dejamos la dirección de amazon donde +podeís adquirirla.

+

http://www.amazon.com/Steve- +Jobs-Walter-Isaacson/dp/1451648537

'; + $result = $this->Text->truncate($text, 500, array( + 'ending' => '... ', + 'exact' => false, + 'html' => true + )); + $expected = '

El biógrafo de Steve Jobs, Walter +Isaacson, explica porqué Jobs le pidió que le hiciera su biografía en +este artículo de El País.

+

Por qué Steve era distinto.

+

http://www.elpais.com/articulo/primer/plano/ +Steve/era/distinto/elpepueconeg/20111009elpneglse_4/Tes

+

Ya se ha publicado la biografía de +Steve Jobs escrita por Walter Isaacson "Steve Jobs by Walter +Isaacson", aquí os dejamos la dirección de amazon donde +podeís adquirirla.

+

...

'; + $this->assertEquals($expected, $result); } /** diff --git a/lib/Cake/View/Helper/TextHelper.php b/lib/Cake/View/Helper/TextHelper.php index ee11b037b..2dbb28df4 100644 --- a/lib/Cake/View/Helper/TextHelper.php +++ b/lib/Cake/View/Helper/TextHelper.php @@ -275,20 +275,26 @@ class TextHelper extends AppHelper { } if (!$exact) { $spacepos = mb_strrpos($truncate, ' '); - if (isset($spacepos)) { - if ($html) { - $bits = mb_substr($truncate, $spacepos); - preg_match_all('/<\/([a-z]+)>/', $bits, $droppedTags, PREG_SET_ORDER); - if (!empty($droppedTags)) { - foreach ($droppedTags as $closingTag) { - if (!in_array($closingTag[1], $openTags)) { - array_unshift($openTags, $closingTag[1]); - } + if ($html) { + $truncateCheck = mb_substr($truncate, 0, $spacepos); + $lastOpenTag = mb_strrpos($truncateCheck, '<'); + $lastCloseTag = mb_strrpos($truncateCheck, '>'); + if ($lastOpenTag > $lastCloseTag) { + preg_match_all('/<[\w]+[^>]*>/s', $truncate, $lastTagMatches); + $lastTag = array_pop($lastTagMatches[0]); + $spacepos = mb_strrpos($truncate, $lastTag) + mb_strlen($lastTag); + } + $bits = mb_substr($truncate, $spacepos); + preg_match_all('/<\/([a-z]+)>/', $bits, $droppedTags, PREG_SET_ORDER); + if (!empty($droppedTags)) { + foreach ($droppedTags as $closingTag) { + if (!in_array($closingTag[1], $openTags)) { + array_unshift($openTags, $closingTag[1]); } } } - $truncate = mb_substr($truncate, 0, $spacepos); } + $truncate = mb_substr($truncate, 0, $spacepos); } $truncate .= $ending; From acca796d10255fc7ebbf8a51dcc1f29baf87234b Mon Sep 17 00:00:00 2001 From: Kyle Robinson Young Date: Sun, 25 Dec 2011 09:10:50 -0800 Subject: [PATCH 05/44] Fix tag order when closing open tags with TextHelper::truncate() --- lib/Cake/Test/Case/View/Helper/TextHelperTest.php | 2 +- lib/Cake/View/Helper/TextHelper.php | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/Cake/Test/Case/View/Helper/TextHelperTest.php b/lib/Cake/Test/Case/View/Helper/TextHelperTest.php index 54752c548..81c36dcd6 100644 --- a/lib/Cake/Test/Case/View/Helper/TextHelperTest.php +++ b/lib/Cake/Test/Case/View/Helper/TextHelperTest.php @@ -129,7 +129,7 @@ Steve/era/distinto/elpepueconeg/20111009elpneglse_4/Tes

Steve Jobs escrita por Walter Isaacson "Steve Jobs by Walter Isaacson", aquí os dejamos la dirección de amazon donde podeís adquirirla.

-

...

'; +

...

'; $this->assertEquals($expected, $result); } diff --git a/lib/Cake/View/Helper/TextHelper.php b/lib/Cake/View/Helper/TextHelper.php index 2dbb28df4..fc6fc10d3 100644 --- a/lib/Cake/View/Helper/TextHelper.php +++ b/lib/Cake/View/Helper/TextHelper.php @@ -287,9 +287,15 @@ class TextHelper extends AppHelper { $bits = mb_substr($truncate, $spacepos); preg_match_all('/<\/([a-z]+)>/', $bits, $droppedTags, PREG_SET_ORDER); if (!empty($droppedTags)) { - foreach ($droppedTags as $closingTag) { - if (!in_array($closingTag[1], $openTags)) { - array_unshift($openTags, $closingTag[1]); + if (!empty($openTags)) { + foreach ($droppedTags as $closingTag) { + if (!in_array($closingTag[1], $openTags)) { + array_unshift($openTags, $closingTag[1]); + } + } + } else { + foreach ($droppedTags as $closingTag) { + array_push($openTags, $closingTag[1]); } } } From bbad5d86bce51ba9c854319e1de9e7cb970ba2f4 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 25 Dec 2011 23:27:01 -0500 Subject: [PATCH 06/44] Adding louder more informative error to i18n. When a plural form is wrong, or the Plural-Forms header is wrong, we should give a more useful error message than strlen() does. Also make a dumb guess at what the correct translation is. Fixes #2045 --- lib/Cake/I18n/I18n.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/Cake/I18n/I18n.php b/lib/Cake/I18n/I18n.php index f2f6af97b..8bab6fa1f 100644 --- a/lib/Cake/I18n/I18n.php +++ b/lib/Cake/I18n/I18n.php @@ -185,6 +185,18 @@ class I18n { if (is_array($trans)) { if (isset($trans[$plurals])) { $trans = $trans[$plurals]; + } else { + trigger_error( + __d('cake_dev', + 'Missing plural form translation for "%s" in "%s" domain, "%s" locale. ' . + ' Check your po file for correct plurals and valid Plural-Forms header.', + $singular, + $domain, + $_this->_lang + ), + E_USER_WARNING + ); + $trans = $trans[0]; } } if (strlen($trans)) { From d8bc13f996f69cff47d2aed329a9ce02721a780b Mon Sep 17 00:00:00 2001 From: mark_story Date: Mon, 26 Dec 2011 10:03:14 -0500 Subject: [PATCH 07/44] Fix incorrect time handling in deconstruct() Apply patch from 'Amit Badkas' to solve issues where invalid times were treated as valid. Re-structure tests to use a dataprovider instead of copy + paste. Fixes #2412 --- lib/Cake/Model/Model.php | 12 +- .../Test/Case/Model/ModelIntegrationTest.php | 140 +++++++++--------- 2 files changed, 78 insertions(+), 74 deletions(-) diff --git a/lib/Cake/Model/Model.php b/lib/Cake/Model/Model.php index 85b5f739b..1fb2f7c57 100644 --- a/lib/Cake/Model/Model.php +++ b/lib/Cake/Model/Model.php @@ -1149,7 +1149,17 @@ class Model extends Object { $timeFields = array('H' => 'hour', 'i' => 'min', 's' => 'sec'); $date = array(); - if (isset($data['hour']) && isset($data['meridian']) && $data['hour'] != 12 && 'pm' == $data['meridian']) { + if (isset($data['meridian']) && empty($data['meridian'])) { + return null; + } + + if ( + isset($data['hour']) && + isset($data['meridian']) && + !empty($data['hour']) && + $data['hour'] != 12 && + 'pm' == $data['meridian'] + ) { $data['hour'] = $data['hour'] + 12; } if (isset($data['hour']) && isset($data['meridian']) && $data['hour'] == 12 && 'am' == $data['meridian']) { diff --git a/lib/Cake/Test/Case/Model/ModelIntegrationTest.php b/lib/Cake/Test/Case/Model/ModelIntegrationTest.php index 2ac6758cb..bd76cd878 100644 --- a/lib/Cake/Test/Case/Model/ModelIntegrationTest.php +++ b/lib/Cake/Test/Case/Model/ModelIntegrationTest.php @@ -662,91 +662,85 @@ class ModelIntegrationTest extends BaseModelTest { } /** - * test deconstruct() with time fields. + * data provider for time tests. * + * @return array + */ + public static function timeProvider() { + $db = ConnectionManager::getDataSource('test'); + $now = $db->expression('NOW()'); + return array( + // blank + array( + array('hour' => '', 'min' => '', 'meridian' => ''), + '' + ), + // missing hour + array( + array('hour' => '', 'min' => '00', 'meridian' => 'pm'), + '' + ), + // all blank + array( + array('hour' => '', 'min' => '', 'sec' => ''), + '' + ), + // set and empty merdian + array( + array('hour' => '1', 'min' => '00', 'meridian' => ''), + '' + ), + // midnight + array( + array('hour' => '12', 'min' => '0', 'meridian' => 'am'), + '00:00:00' + ), + array( + array('hour' => '00', 'min' => '00'), + '00:00:00' + ), + // 3am + array( + array('hour' => '03', 'min' => '04', 'sec' => '04'), + '03:04:04' + ), + array( + array('hour' => '3', 'min' => '4', 'sec' => '4'), + '03:04:04' + ), + array( + array('hour' => '03', 'min' => '4', 'sec' => '4'), + '03:04:04' + ), + array( + $now, + $now + ) + ); + } + +/** + * test deconstruct with time fields. + * + * @dataProvider timeProvider * @return void */ - public function testDeconstructFieldsTime() { + public function testDeconstructFieldsTime($input, $result) { $this->skipIf($this->db instanceof Sqlserver, 'This test is not compatible with SQL Server.'); $this->loadFixtures('Apple'); $TestModel = new Apple(); - $data = array(); - $data['Apple']['mytime']['hour'] = ''; - $data['Apple']['mytime']['min'] = ''; - $data['Apple']['mytime']['sec'] = ''; + $data = array( + 'Apple' => array( + 'mytime' => $input + ) + ); $TestModel->data = null; $TestModel->set($data); - $expected = array('Apple' => array('mytime' => '')); + $expected = array('Apple' => array('mytime' => $result)); $this->assertEquals($TestModel->data, $expected); - - $data = array(); - $data['Apple']['mytime']['hour'] = ''; - $data['Apple']['mytime']['min'] = ''; - $data['Apple']['mytime']['meridan'] = ''; - - $TestModel->data = null; - $TestModel->set($data); - $expected = array('Apple' => array('mytime' => '')); - $this->assertEquals($TestModel->data, $expected, 'Empty values are not returning properly. %s'); - - $data = array(); - $data['Apple']['mytime']['hour'] = '12'; - $data['Apple']['mytime']['min'] = '0'; - $data['Apple']['mytime']['meridian'] = 'am'; - - $TestModel->data = null; - $TestModel->set($data); - $expected = array('Apple' => array('mytime' => '00:00:00')); - $this->assertEquals($TestModel->data, $expected, 'Midnight is not returning proper values. %s'); - - $data = array(); - $data['Apple']['mytime']['hour'] = '00'; - $data['Apple']['mytime']['min'] = '00'; - - $TestModel->data = null; - $TestModel->set($data); - $expected = array('Apple' => array('mytime' => '00:00:00')); - $this->assertEquals($TestModel->data, $expected, 'Midnight is not returning proper values. %s'); - - $data = array(); - $data['Apple']['mytime']['hour'] = '03'; - $data['Apple']['mytime']['min'] = '04'; - $data['Apple']['mytime']['sec'] = '04'; - - $TestModel->data = null; - $TestModel->set($data); - $expected = array('Apple' => array('mytime' => '03:04:04')); - $this->assertEquals($TestModel->data, $expected); - - $data = array(); - $data['Apple']['mytime']['hour'] = '3'; - $data['Apple']['mytime']['min'] = '4'; - $data['Apple']['mytime']['sec'] = '4'; - - $TestModel->data = null; - $TestModel->set($data); - $expected = array('Apple' => array('mytime' => '03:04:04')); - $this->assertEquals($TestModel->data, $expected); - - $data = array(); - $data['Apple']['mytime']['hour'] = '03'; - $data['Apple']['mytime']['min'] = '4'; - $data['Apple']['mytime']['sec'] = '4'; - - $TestModel->data = null; - $TestModel->set($data); - $expected = array('Apple' => array('mytime' => '03:04:04')); - $this->assertEquals($TestModel->data, $expected); - - $db = ConnectionManager::getDataSource('test'); - $data = array(); - $data['Apple']['mytime'] = $db->expression('NOW()'); - $TestModel->data = null; - $TestModel->set($data); - $this->assertEquals($TestModel->data, $data); } /** From 0750069126b1941060926a63300e6f61f6fec6a0 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 26 Dec 2011 11:56:44 -0500 Subject: [PATCH 08/44] Remove pointless condition. --- lib/Cake/Model/Model.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/Cake/Model/Model.php b/lib/Cake/Model/Model.php index 1fb2f7c57..2b2a55948 100644 --- a/lib/Cake/Model/Model.php +++ b/lib/Cake/Model/Model.php @@ -1169,9 +1169,7 @@ class Model extends Object { foreach ($timeFields as $key => $val) { if (!isset($data[$val]) || $data[$val] === '0' || $data[$val] === '00') { $data[$val] = '00'; - } elseif ($data[$val] === '') { - $data[$val] = ''; - } else { + } elseif ($data[$val] !== '') { $data[$val] = sprintf('%02d', $data[$val]); } if (!empty($data[$val])) { From eda916d85b4d16d7696c280043529587c94d44e7 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 26 Dec 2011 12:03:48 -0500 Subject: [PATCH 09/44] Update tests to use assertContains --- lib/Cake/Test/Case/Network/Email/CakeEmailTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php b/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php index 29f4273a3..ba0b86afa 100644 --- a/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php +++ b/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php @@ -980,19 +980,19 @@ class CakeEmailTest extends CakeTestCase { $this->CakeEmail->config(array()); $this->CakeEmail->attachments(array(CAKE . 'basics.php')); $result = $this->CakeEmail->send('body'); - $this->assertTrue((bool)strpos($result['message'], "Content-Type: application/octet-stream\r\nContent-Transfer-Encoding: base64\r\nContent-Disposition: attachment; filename=\"basics.php\"")); + $this->assertContains("Content-Type: application/octet-stream\r\nContent-Transfer-Encoding: base64\r\nContent-Disposition: attachment; filename=\"basics.php\"", $result['message']); $this->CakeEmail->attachments(array('my.file.txt' => CAKE . 'basics.php')); $result = $this->CakeEmail->send('body'); - $this->assertTrue((bool)strpos($result['message'], "Content-Type: application/octet-stream\r\nContent-Transfer-Encoding: base64\r\nContent-Disposition: attachment; filename=\"my.file.txt\"")); + $this->assertContains("Content-Type: application/octet-stream\r\nContent-Transfer-Encoding: base64\r\nContent-Disposition: attachment; filename=\"my.file.txt\"", $result['message']); $this->CakeEmail->attachments(array('file.txt' => array('file' => CAKE . 'basics.php', 'mimetype' => 'text/plain'))); $result = $this->CakeEmail->send('body'); - $this->assertTrue((bool)strpos($result['message'], "Content-Type: text/plain\r\nContent-Transfer-Encoding: base64\r\nContent-Disposition: attachment; filename=\"file.txt\"")); + $this->assertContains("Content-Type: text/plain\r\nContent-Transfer-Encoding: base64\r\nContent-Disposition: attachment; filename=\"file.txt\"", $result['message']); $this->CakeEmail->attachments(array('file2.txt' => array('file' => CAKE . 'basics.php', 'mimetype' => 'text/plain', 'contentId' => 'a1b1c1'))); $result = $this->CakeEmail->send('body'); - $this->assertTrue((bool)strpos($result['message'], "Content-Type: text/plain\r\nContent-Transfer-Encoding: base64\r\nContent-ID: \r\nContent-Disposition: inline; filename=\"file2.txt\"")); + $this->assertContains("Content-Type: text/plain\r\nContent-Transfer-Encoding: base64\r\nContent-ID: \r\nContent-Disposition: inline; filename=\"file2.txt\"", $result['message']); } /** From 7f46ede097fae191b3594672e944dc6755b1847b Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 27 Dec 2011 10:08:36 -0500 Subject: [PATCH 10/44] Add more complete api docs for label() Fixes #2415 --- lib/Cake/View/Helper/FormHelper.php | 42 ++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/lib/Cake/View/Helper/FormHelper.php b/lib/Cake/View/Helper/FormHelper.php index 470a61239..80dca5840 100644 --- a/lib/Cake/View/Helper/FormHelper.php +++ b/lib/Cake/View/Helper/FormHelper.php @@ -737,8 +737,48 @@ class FormHelper extends AppHelper { * Returns a formatted LABEL element for HTML FORMs. Will automatically generate * a for attribute if one is not provided. * + * ### Options + * + * - `for` - Set the for attribute, if its not defined the for attribute + * will be generated from the $fieldName parameter using + * FormHelper::domId(). + * + * Examples: + * + * The text and for attribute are generated off of the fieldname + * + * {{{ + * echo $this->Form->label('Post.published'); + * + * }}} + * + * Custom text: + * + * {{{ + * echo $this->Form->label('Post.published', 'Publish'); + * + * }}} + * + * Custom class name: + * + * {{{ + * echo $this->Form->label('Post.published', 'Publish', 'required'); + * + * }}} + * + * Custom attributes: + * + * {{{ + * echo $this->Form->label('Post.published', 'Publish', array( + * 'for' => 'post-publish' + * )); + * + * }}} + * * @param string $fieldName This should be "Modelname.fieldname" - * @param string $text Text that will appear in the label field. + * @param string $text Text that will appear in the label field. If + * $text is left undefined the text will be inflected from the + * fieldName. * @param mixed $options An array of HTML attributes, or a string, to be used as a class name. * @return string The formatted LABEL element * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::label From 2cc38b5ba7fed66b28bce13cc66e2ffe63397984 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 27 Dec 2011 19:37:14 -0500 Subject: [PATCH 11/44] Fix failing test in PHP5.4 --- lib/Cake/Config/config.php | 3 ++- lib/Cake/Test/Case/Model/ModelWriteTest.php | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/Cake/Config/config.php b/lib/Cake/Config/config.php index 5a3cdce5d..c533de097 100644 --- a/lib/Cake/Config/config.php +++ b/lib/Cake/Config/config.php @@ -16,4 +16,5 @@ * @since CakePHP(tm) v 1.1.11.4062 * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ -return $config['Cake.version'] = '2.0.4'; +$config['Cake.version'] = '2.0.4'; +return $config; diff --git a/lib/Cake/Test/Case/Model/ModelWriteTest.php b/lib/Cake/Test/Case/Model/ModelWriteTest.php index 795549301..744e95f40 100644 --- a/lib/Cake/Test/Case/Model/ModelWriteTest.php +++ b/lib/Cake/Test/Case/Model/ModelWriteTest.php @@ -2770,7 +2770,7 @@ class ModelWriteTest extends BaseModelTest { $this->assertEquals(count($result['Tag']), 2); $this->assertEquals($result['Tag'][0]['tag'], 'tag1'); $this->assertEquals(count($result['Comment']), 1); - $this->assertEquals(count($result['Comment'][0]['comment']['Article comment']), 1); + $this->assertEquals(count($result['Comment'][0]['comment']), 1); } /** @@ -4098,7 +4098,7 @@ class ModelWriteTest extends BaseModelTest { $this->assertEquals(count($result['Tag']), 2); $this->assertEquals($result['Tag'][0]['tag'], 'tag1'); $this->assertEquals(count($result['Comment']), 1); - $this->assertEquals(count($result['Comment'][0]['comment']['Article comment']), 1); + $this->assertEquals(count($result['Comment'][0]['comment']), 1); } /** From e9813d7a978475d5d2d264e9c8cf5526cdfbab27 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 27 Dec 2011 21:38:14 -0500 Subject: [PATCH 12/44] Fix errors with illegal string offsets. If $_list becomes a string notice errors are triggered when trying to do offsets in PHP 5.4 --- lib/Cake/Utility/Set.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/Cake/Utility/Set.php b/lib/Cake/Utility/Set.php index 8f7729b94..a62726708 100644 --- a/lib/Cake/Utility/Set.php +++ b/lib/Cake/Utility/Set.php @@ -671,7 +671,7 @@ class Set { if (is_numeric($key) && intval($key) > 0 || $key === '0') { $key = intval($key); } - if ($i === $count - 1) { + if ($i === $count - 1 && is_array($_list)) { $_list[$key] = $data; } else { if (!isset($_list[$key])) { @@ -679,6 +679,9 @@ class Set { } $_list =& $_list[$key]; } + if (!is_array($_list)) { + return array(); + } } return $list; } From 21ac8492b11a1ed7c54b199b4f5a8b9d493a9622 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 27 Dec 2011 21:57:33 -0500 Subject: [PATCH 13/44] Adding an int cast. PHP 5.4 doesn't like floats for string offsets. --- lib/Cake/I18n/Multibyte.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Cake/I18n/Multibyte.php b/lib/Cake/I18n/Multibyte.php index 7116e29aa..d6b0e17db 100644 --- a/lib/Cake/I18n/Multibyte.php +++ b/lib/Cake/I18n/Multibyte.php @@ -982,7 +982,7 @@ class Multibyte { $parts = array(); $maxchars = floor(($length * 3) / 4); while (strlen($string) > $maxchars) { - $i = $maxchars; + $i = (int)$maxchars; $test = ord($string[$i]); while ($test >= 128 && $test <= 191) { $i--; From 8414d37e48d1e2eb839be28ec1a97632a46bc5ce Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 26 Dec 2011 12:57:51 -0500 Subject: [PATCH 14/44] Add docblock examples for attachments. --- lib/Cake/Network/Email/CakeEmail.php | 31 ++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/lib/Cake/Network/Email/CakeEmail.php b/lib/Cake/Network/Email/CakeEmail.php index 7165f9859..f2b3bac6d 100644 --- a/lib/Cake/Network/Email/CakeEmail.php +++ b/lib/Cake/Network/Email/CakeEmail.php @@ -835,10 +835,37 @@ class CakeEmail { } /** - * Attachments + * Add attachments to the email message + * + * Attachments can be defined in a few forms depending on how much control you need: + * + * Attach a single file: + * + * {{{ + * $email->attachments('path/to/file'); + * }}} + * + * Attach a file with a different filename: + * + * {{{ + * $email->attachments(array('custom_name.txt' => 'path/to/file.txt')); + * }}} + * + * Attach a file and specify additional properties: + * + * {{{ + * $email->attachments(array('custom_name.png' => array( + * 'file' => 'path/to/file', + * 'mimetype' => 'image/png', + * 'contentId' => 'abc123' + * )); + * }}} + * + * The `contentId` key allows you to specify an inline attachment. In your email text, you + * can use `` to display the image inline. * * @param mixed $attachments String with the filename or array with filenames - * @return mixed + * @return mixed Either the array of attachments when getting or $this when setting. * @throws SocketException */ public function attachments($attachments = null) { From bbef4aa36d6383c7ee481ce18fdd437bc32a18fd Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 26 Dec 2011 17:59:50 -0500 Subject: [PATCH 15/44] Convert assertions to assetContains. --- .../Test/Case/Network/Email/CakeEmailTest.php | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php b/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php index ba0b86afa..701e29cb2 100644 --- a/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php +++ b/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php @@ -789,9 +789,9 @@ class CakeEmailTest extends CakeTestCase { $this->CakeEmail->template('default', 'default'); $result = $this->CakeEmail->send(); - $this->assertTrue((bool)strpos($result['message'], 'This email was sent using the CakePHP Framework')); - $this->assertTrue((bool)strpos($result['headers'], 'Message-ID: ')); - $this->assertTrue((bool)strpos($result['headers'], 'To: ')); + $this->assertContains('This email was sent using the CakePHP Framework', $result['message']); + $this->assertContains('Message-ID: ', $result['headers']); + $this->assertContains('To: ', $result['headers']); } /** @@ -814,9 +814,9 @@ class CakeEmailTest extends CakeTestCase { $result = $this->CakeEmail->send(); $expected = mb_convert_encoding('CakePHP Framework を使って送信したメールです。 http://cakephp.org.','ISO-2022-JP'); - $this->assertTrue((bool)strpos($result['message'], $expected)); - $this->assertTrue((bool)strpos($result['headers'], 'Message-ID: ')); - $this->assertTrue((bool)strpos($result['headers'], 'To: ')); + $this->assertContains($expected, $result['message']); + $this->assertContains('Message-ID: ', $result['headers']); + $this->assertContains('To: ', $result['headers']); } /** @@ -836,7 +836,7 @@ class CakeEmailTest extends CakeTestCase { $this->CakeEmail->viewVars(array('value' => 12345)); $result = $this->CakeEmail->send(); - $this->assertTrue((bool)strpos($result['message'], 'Here is your value: 12345')); + $this->assertContains('Here is your value: 12345', $result['message']); } /** @@ -908,21 +908,21 @@ class CakeEmailTest extends CakeTestCase { $this->CakeEmail->config(array('empty')); $result = $this->CakeEmail->template('TestPlugin.test_plugin_tpl', 'default')->send(); - $this->assertTrue((bool)strpos($result['message'], 'Into TestPlugin.')); - $this->assertTrue((bool)strpos($result['message'], 'This email was sent using the CakePHP Framework')); + $this->assertContains('Into TestPlugin.', $result['message']); + $this->assertContains('This email was sent using the CakePHP Framework', $result['message']); $result = $this->CakeEmail->template('TestPlugin.test_plugin_tpl', 'TestPlugin.plug_default')->send(); - $this->assertTrue((bool)strpos($result['message'], 'Into TestPlugin.')); - $this->assertTrue((bool)strpos($result['message'], 'This email was sent using the TestPlugin.')); + $this->assertContains('Into TestPlugin.', $result['message']); + $this->assertContains('This email was sent using the TestPlugin.', $result['message']); $result = $this->CakeEmail->template('TestPlugin.test_plugin_tpl', 'plug_default')->send(); - $this->assertTrue((bool)strpos($result['message'], 'Into TestPlugin.')); - $this->assertTrue((bool)strpos($result['message'], 'This email was sent using the TestPlugin.')); + $this->assertContains('Into TestPlugin.', $result['message']); + $this->assertContains('This email was sent using the TestPlugin.', $result['message']); $this->CakeEmail->viewVars(array('value' => 12345)); $result = $this->CakeEmail->template('custom', 'TestPlugin.plug_default')->send(); - $this->assertTrue((bool)strpos($result['message'], 'Here is your value: 12345')); - $this->assertTrue((bool)strpos($result['message'], 'This email was sent using the TestPlugin.')); + $this->assertContains('Here is your value: 12345', $result['message']); + $this->assertContains('This email was sent using the TestPlugin.', $result['message']); $this->setExpectedException('MissingViewException'); $this->CakeEmail->template('test_plugin_tpl', 'plug_default')->send(); @@ -949,10 +949,10 @@ class CakeEmailTest extends CakeTestCase { $message = $this->CakeEmail->message(); $boundary = $this->CakeEmail->getBoundary(); $this->assertFalse(empty($boundary)); - $this->assertFalse(in_array('--' . $boundary, $message)); - $this->assertFalse(in_array('--' . $boundary . '--', $message)); - $this->assertTrue(in_array('--alt-' . $boundary, $message)); - $this->assertTrue(in_array('--alt-' . $boundary . '--', $message)); + $this->assertNotContains('--' . $boundary, $message); + $this->assertNotContains('--' . $boundary . '--', $message); + $this->assertContains('--alt-' . $boundary, $message); + $this->assertContains('--alt-' . $boundary . '--', $message); $this->CakeEmail->attachments(array('fake.php' => __FILE__)); $this->CakeEmail->send(); @@ -960,10 +960,10 @@ class CakeEmailTest extends CakeTestCase { $message = $this->CakeEmail->message(); $boundary = $this->CakeEmail->getBoundary(); $this->assertFalse(empty($boundary)); - $this->assertTrue(in_array('--' . $boundary, $message)); - $this->assertTrue(in_array('--' . $boundary . '--', $message)); - $this->assertTrue(in_array('--alt-' . $boundary, $message)); - $this->assertTrue(in_array('--alt-' . $boundary . '--', $message)); + $this->assertContains('--' . $boundary, $message); + $this->assertContains('--' . $boundary . '--', $message); + $this->assertContains('--alt-' . $boundary, $message); + $this->assertContains('--alt-' . $boundary . '--', $message); } /** @@ -1047,14 +1047,14 @@ class CakeEmailTest extends CakeTestCase { $result = $this->CakeEmail->send(); $expected = '

This email was sent using the CakePHP Framework

'; - $this->assertTrue((bool)strpos($this->CakeEmail->message(CakeEmail::MESSAGE_HTML), $expected)); + $this->assertContains($expected, $this->CakeEmail->message(CakeEmail::MESSAGE_HTML)); $expected = 'This email was sent using the CakePHP Framework, http://cakephp.org.'; - $this->assertTrue((bool)strpos($this->CakeEmail->message(CakeEmail::MESSAGE_TEXT), $expected)); + $this->assertContains($expected, $this->CakeEmail->message(CakeEmail::MESSAGE_TEXT)); $message = $this->CakeEmail->message(); - $this->assertTrue(in_array('Content-Type: text/plain; charset=UTF-8', $message)); - $this->assertTrue(in_array('Content-Type: text/html; charset=UTF-8', $message)); + $this->assertContains('Content-Type: text/plain; charset=UTF-8', $message); + $this->assertContains('Content-Type: text/html; charset=UTF-8', $message); // UTF-8 is 8bit $this->assertTrue($this->checkContentTransferEncoding($message, '8bit')); @@ -1063,8 +1063,8 @@ class CakeEmailTest extends CakeTestCase { $this->CakeEmail->charset = 'ISO-2022-JP'; $this->CakeEmail->send(); $message = $this->CakeEmail->message(); - $this->assertTrue(in_array('Content-Type: text/plain; charset=ISO-2022-JP', $message)); - $this->assertTrue(in_array('Content-Type: text/html; charset=ISO-2022-JP', $message)); + $this->assertContains('Content-Type: text/plain; charset=ISO-2022-JP', $message); + $this->assertContains('Content-Type: text/html; charset=ISO-2022-JP', $message); // ISO-2022-JP is 7bit $this->assertTrue($this->checkContentTransferEncoding($message, '7bit')); From 34eedcc017533fd4f97431ad9b38a5309c424f18 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 26 Dec 2011 19:36:35 -0500 Subject: [PATCH 16/44] Add a few regression tests for CakeEmail. --- .../Test/Case/Network/Email/CakeEmailTest.php | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php b/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php index 701e29cb2..4ee443c4e 100644 --- a/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php +++ b/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php @@ -745,6 +745,68 @@ class CakeEmailTest extends CakeTestCase { $this->CakeEmail->send("Forgot to set To"); } +/** + * Test send() with no template. + * + * @return void + */ + public function testSendNoTemplateWithAttachments() { + $this->CakeEmail->transport('debug'); + $this->CakeEmail->from('cake@cakephp.org'); + $this->CakeEmail->to('cake@cakephp.org'); + $this->CakeEmail->subject('My title'); + $this->CakeEmail->emailFormat('text'); + $this->CakeEmail->attachments(array(CAKE . 'basics.php')); + $result = $this->CakeEmail->send('Hello'); + + $boundary = $this->CakeEmail->getBoundary(); + $this->assertContains('Content-Type: multipart/mixed; boundary="' . $boundary . '"', $result['headers']); + $expected = "--$boundary\r\n" . + "Content-Type: text/plain; charset=UTF-8\r\n" . + "Content-Transfer-Encoding: 8bit\r\n" . + "\r\n" . + "Hello" . + "\r\n" . + "\r\n" . + "\r\n" . + "--$boundary\r\n" . + "Content-Type: application/octet-stream\r\n" . + "Content-Transfer-Encoding: base64\r\n" . + "Content-Disposition: attachment; filename=\"basics.php\"\r\n\r\n"; + $this->assertContains($expected, $result['message']); + } + +/** + * Test send() with no template as both + * + * @return void + */ + public function testSendNoTemplateWithAttachmentsAsBoth() { + $this->CakeEmail->transport('debug'); + $this->CakeEmail->from('cake@cakephp.org'); + $this->CakeEmail->to('cake@cakephp.org'); + $this->CakeEmail->subject('My title'); + $this->CakeEmail->emailFormat('both'); + $this->CakeEmail->attachments(array(CAKE . 'VERSION.txt')); + $result = $this->CakeEmail->send('Hello'); + + $boundary = $this->CakeEmail->getBoundary(); + $this->assertContains('Content-Type: multipart/mixed; boundary="' . $boundary . '"', $result['headers']); + $expected = "--$boundary\r\n" . + "Content-Type: multipart/alternative; boundary=\"alt-$boundary\"\r\n" . + "Content-Transfer-Encoding: 8bit\r\n" . + "\r\n" . + "Hello" . + "\r\n" . + "\r\n" . + "\r\n" . + "--$boundary\r\n" . + "Content-Type: application/octet-stream\r\n" . + "Content-Transfer-Encoding: base64\r\n" . + "Content-Disposition: attachment; filename=\"VERSION.txt\"\r\n\r\n"; + $this->assertContains($expected, $result['message']); + } + /** * testSendWithLog method * From 53598c722c4fe680e8edd735373ad541d8511961 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 26 Dec 2011 19:54:30 -0500 Subject: [PATCH 17/44] Pulling out view rendering from boundary setting. --- lib/Cake/Network/Email/CakeEmail.php | 90 ++++++++++++++++++---------- 1 file changed, 58 insertions(+), 32 deletions(-) diff --git a/lib/Cake/Network/Email/CakeEmail.php b/lib/Cake/Network/Email/CakeEmail.php index f2b3bac6d..f4934ee00 100644 --- a/lib/Cake/Network/Email/CakeEmail.php +++ b/lib/Cake/Network/Email/CakeEmail.php @@ -976,6 +976,8 @@ class CakeEmail { $this->_createBoundary(); $message = $this->_wrap($content); + // two methods doing similar things seems silly. + // both handle attachments. if (empty($this->_template)) { $message = $this->_formatMessage($message); } else { @@ -984,6 +986,7 @@ class CakeEmail { $message[] = ''; $this->_message = $message; + // should be part of a compose method. if (!empty($this->_attachments)) { $this->_attachFiles(); @@ -991,6 +994,7 @@ class CakeEmail { $this->_message[] = '--' . $this->_boundary . '--'; $this->_message[] = ''; } + $contents = $this->transportClass()->send($this); if (!empty($this->_config['log'])) { $level = LOG_DEBUG; @@ -1240,6 +1244,7 @@ class CakeEmail { } $formatted[] = trim(substr($tmpLine, 0, $lastSpace)); $tmpLine = substr($tmpLine, $lastSpace + 1); + $tmpLineLength = strlen($tmpLine); } } @@ -1327,31 +1332,11 @@ class CakeEmail { * @return array Email ready to be sent */ protected function _render($content) { - $viewClass = $this->_viewRender; - - if ($viewClass !== 'View') { - list($plugin, $viewClass) = pluginSplit($viewClass, true); - $viewClass .= 'View'; - App::uses($viewClass, $plugin . 'View'); - } - - $View = new $viewClass(null); - $View->viewVars = $this->_viewVars; - $View->helpers = $this->_helpers; - $msg = array(); - - list($templatePlugin, $template) = pluginSplit($this->_template); - list($layoutPlugin, $layout) = pluginSplit($this->_layout); - if ($templatePlugin) { - $View->plugin = $templatePlugin; - } elseif ($layoutPlugin) { - $View->plugin = $layoutPlugin; - } - $content = implode("\n", $content); + $rendered = $this->_renderTemplates($content); + $msg = array(); if ($this->_emailFormat === 'both') { - $originalContent = $content; if (!empty($this->_attachments)) { $msg[] = '--' . $this->_boundary; $msg[] = 'Content-Type: multipart/alternative; boundary="alt-' . $this->_boundary . '"'; @@ -1362,9 +1347,7 @@ class CakeEmail { $msg[] = 'Content-Transfer-Encoding: ' . $this->_getContentTransferEncoding(); $msg[] = ''; - $View->viewPath = $View->layoutPath = 'Emails' . DS . 'text'; - $View->viewVars['content'] = $originalContent; - $this->_textMessage = str_replace(array("\r\n", "\r"), "\n", $View->render($template, $layout)); + $this->_textMessage = $rendered['text']; $content = explode("\n", $this->_textMessage); $msg = array_merge($msg, $content); @@ -1374,10 +1357,7 @@ class CakeEmail { $msg[] = 'Content-Transfer-Encoding: ' . $this->_getContentTransferEncoding(); $msg[] = ''; - $View->viewPath = $View->layoutPath = 'Emails' . DS . 'html'; - $View->viewVars['content'] = $originalContent; - $View->hasRendered = false; - $this->_htmlMessage = str_replace(array("\r\n", "\r"), "\n", $View->render($template, $layout)); + $this->_htmlMessage = $rendered['html']; $content = explode("\n", $this->_htmlMessage); $msg = array_merge($msg, $content); @@ -1403,9 +1383,7 @@ class CakeEmail { } } - $View->viewPath = $View->layoutPath = 'Emails' . DS . $this->_emailFormat; - $View->viewVars['content'] = $content; - $rendered = $this->_encodeString($View->render($template, $layout), $this->charset); + $rendered = $this->_encodeString($rendered[$this->_emailFormat], $this->charset); $content = explode("\n", $rendered); if ($this->_emailFormat === 'html') { @@ -1417,6 +1395,54 @@ class CakeEmail { return array_merge($msg, $content); } +/** + * Build and set all the view properties needed to render the templated emails. + * Returns false if the email is not templated. + * + * @param string $content The content passed in from send() in most cases. + * @return array The rendered content with html and text keys. + */ + public function _renderTemplates($content) { + if (empty($this->_template)) { + return false; + } + $viewClass = $this->_viewRender; + if ($viewClass !== 'View') { + list($plugin, $viewClass) = pluginSplit($viewClass, true); + $viewClass .= 'View'; + App::uses($viewClass, $plugin . 'View'); + } + + $View = new $viewClass(null); + $View->viewVars = $this->_viewVars; + $View->helpers = $this->_helpers; + + list($templatePlugin, $template) = pluginSplit($this->_template); + list($layoutPlugin, $layout) = pluginSplit($this->_layout); + if ($templatePlugin) { + $View->plugin = $templatePlugin; + } elseif ($layoutPlugin) { + $View->plugin = $layoutPlugin; + } + + $types = array($this->_emailFormat); + if ($this->_emailFormat == 'both') { + $types = array('html', 'text'); + } + + $rendered = array(); + foreach ($types as $type) { + $View->set('content', $content); + $View->hasRendered = false; + $View->viewPath = $View->layoutPath = 'Emails' . DS . $type; + + $render = $View->render($template, $layout); + $render = str_replace(array("\r\n", "\r"), "\n", $render); + $rendered[$type] = $render; + } + return $rendered; + } + /** * Return the Content-Transfer Encoding value based on the set charset * From 0e4af546d66b4ccd90632be117aee74ef9986e02 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 26 Dec 2011 22:07:52 -0500 Subject: [PATCH 18/44] Update sending attachments. Both inline and external attachments, as well as mixed sets of inline and external attachments should work now. Re-built the internals of message rendering to remove duplication and redundant code paths. Fixes #2413 Fixes #2320 --- lib/Cake/Network/Email/CakeEmail.php | 258 +++++++++--------- .../Test/Case/Network/Email/CakeEmailTest.php | 18 +- 2 files changed, 145 insertions(+), 131 deletions(-) diff --git a/lib/Cake/Network/Email/CakeEmail.php b/lib/Cake/Network/Email/CakeEmail.php index f4934ee00..c078edd2d 100644 --- a/lib/Cake/Network/Email/CakeEmail.php +++ b/lib/Cake/Network/Email/CakeEmail.php @@ -963,38 +963,9 @@ class CakeEmail { } $this->_textMessage = $this->_htmlMessage = ''; - if ($content !== null) { - if ($this->_emailFormat === 'text') { - $this->_textMessage = $content; - } elseif ($this->_emailFormat === 'html') { - $this->_htmlMessage = $content; - } elseif ($this->_emailFormat === 'both') { - $this->_textMessage = $this->_htmlMessage = $content; - } - } - $this->_createBoundary(); + $this->_message = $this->_render($this->_wrap($content)); - $message = $this->_wrap($content); - // two methods doing similar things seems silly. - // both handle attachments. - if (empty($this->_template)) { - $message = $this->_formatMessage($message); - } else { - $message = $this->_render($message); - } - $message[] = ''; - $this->_message = $message; - - // should be part of a compose method. - if (!empty($this->_attachments)) { - $this->_attachFiles(); - - $this->_message[] = ''; - $this->_message[] = '--' . $this->_boundary . '--'; - $this->_message[] = ''; - } - $contents = $this->transportClass()->send($this); if (!empty($this->_config['log'])) { $level = LOG_DEBUG; @@ -1269,142 +1240,179 @@ class CakeEmail { } /** - * Attach files by adding file contents inside boundaries. + * Attach non-embedded files by adding file contents inside boundaries. * - * @return void + * @return array An array of lines to add to the message */ protected function _attachFiles() { + $msg = array(); foreach ($this->_attachments as $filename => $fileInfo) { - $handle = fopen($fileInfo['file'], 'rb'); - $data = fread($handle, filesize($fileInfo['file'])); - $data = chunk_split(base64_encode($data)) ; - fclose($handle); - - $this->_message[] = '--' . $this->_boundary; - $this->_message[] = 'Content-Type: ' . $fileInfo['mimetype']; - $this->_message[] = 'Content-Transfer-Encoding: base64'; - if (empty($fileInfo['contentId'])) { - $this->_message[] = 'Content-Disposition: attachment; filename="' . $filename . '"'; - } else { - $this->_message[] = 'Content-ID: <' . $fileInfo['contentId'] . '>'; - $this->_message[] = 'Content-Disposition: inline; filename="' . $filename . '"'; + if (!empty($fileInfo['contentId'])) { + continue; } - $this->_message[] = ''; - $this->_message[] = $data; - $this->_message[] = ''; + $data = $this->_readFile($fileInfo['file']); + + $msg[] = '--' . $this->_boundary; + $msg[] = 'Content-Type: ' . $fileInfo['mimetype']; + $msg[] = 'Content-Transfer-Encoding: base64'; + $msg[] = 'Content-Disposition: attachment; filename="' . $filename . '"'; + $msg[] = ''; + $msg[] = $data; + $msg[] = ''; } + return $msg; } /** - * Format the message by seeing if it has attachments. + * Read the file contents and return a base64 version of the file contents. * - * @param array $message Message to format - * @return array + * @param string $file The file to read. + * @return string File contents in base64 encoding */ - protected function _formatMessage($message) { - if (!empty($this->_attachments)) { - $prefix = array('--' . $this->_boundary); - if ($this->_emailFormat === 'text') { - $prefix[] = 'Content-Type: text/plain; charset=' . $this->charset; - } elseif ($this->_emailFormat === 'html') { - $prefix[] = 'Content-Type: text/html; charset=' . $this->charset; - } elseif ($this->_emailFormat === 'both') { - $prefix[] = 'Content-Type: multipart/alternative; boundary="alt-' . $this->_boundary . '"'; - } - $prefix[] = 'Content-Transfer-Encoding: ' . $this->_getContentTransferEncoding(); - $prefix[] = ''; - $message = array_merge($prefix, $message); - } - - $tmp = array(); - foreach ($message as $msg) { - $tmp[] = $this->_encodeString($msg, $this->charset); - } - $message = $tmp; - - return $message; + protected function _readFile($file) { + $handle = fopen($file, 'rb'); + $data = fread($handle, filesize($file)); + $data = chunk_split(base64_encode($data)) ; + fclose($handle); + return $data; } /** - * Render the contents using the current layout and template. + * Attach inline/embedded files to the message. + * + * @return array An array of lines to add to the message + */ + protected function _attachInlineFiles() { + $msg = array(); + foreach ($this->_attachments as $filename => $fileInfo) { + if (empty($fileInfo['contentId'])) { + continue; + } + $data = $this->_readFile($fileInfo['file']); + + $msg[] = '--' . $this->_boundary; + $msg[] = 'Content-Type: ' . $fileInfo['mimetype']; + $msg[] = 'Content-Transfer-Encoding: base64'; + $msg[] = 'Content-ID: <' . $fileInfo['contentId'] . '>'; + $msg[] = 'Content-Disposition: inline; filename="' . $filename . '"'; + $msg[] = ''; + $msg[] = $data; + $msg[] = ''; + } + return $msg; + } + +/** + * Render the body of the email. * * @param string $content Content to render - * @return array Email ready to be sent + * @return array Email body ready to be sent */ protected function _render($content) { $content = implode("\n", $content); $rendered = $this->_renderTemplates($content); $msg = array(); - if ($this->_emailFormat === 'both') { - if (!empty($this->_attachments)) { - $msg[] = '--' . $this->_boundary; - $msg[] = 'Content-Type: multipart/alternative; boundary="alt-' . $this->_boundary . '"'; - $msg[] = ''; - } - $msg[] = '--alt-' . $this->_boundary; - $msg[] = 'Content-Type: text/plain; charset=' . $this->charset; - $msg[] = 'Content-Transfer-Encoding: ' . $this->_getContentTransferEncoding(); - $msg[] = ''; - $this->_textMessage = $rendered['text']; - $content = explode("\n", $this->_textMessage); - $msg = array_merge($msg, $content); + $contentIds = array_filter((array)Set::classicExtract($this->_attachments, '{s}.contentId')); + $hasInlineAttachments = count($contentIds) > 0; + $hasAttachments = !empty($this->_attachments); + $hasMultipleTypes = count($rendered) > 1; - $msg[] = ''; - $msg[] = '--alt-' . $this->_boundary; - $msg[] = 'Content-Type: text/html; charset=' . $this->charset; - $msg[] = 'Content-Transfer-Encoding: ' . $this->_getContentTransferEncoding(); - $msg[] = ''; - - $this->_htmlMessage = $rendered['html']; - $content = explode("\n", $this->_htmlMessage); - $msg = array_merge($msg, $content); + $boundary = $relBoundary = $textBoundary = $this->_boundary; + if ($hasInlineAttachments) { + $msg[] = '--' . $boundary; + $msg[] = 'Content-Type: multipart/related; boundary="rel-' . $boundary . '"'; $msg[] = ''; - $msg[] = '--alt-' . $this->_boundary . '--'; - $msg[] = ''; - - return $msg; + $relBoundary = 'rel-' . $boundary; } - if (!empty($this->_attachments)) { - if ($this->_emailFormat === 'html') { - $msg[] = ''; - $msg[] = '--' . $this->_boundary; - $msg[] = 'Content-Type: text/html; charset=' . $this->charset; - $msg[] = 'Content-Transfer-Encoding: ' . $this->_getContentTransferEncoding(); - $msg[] = ''; - } else { - $msg[] = '--' . $this->_boundary; + if ($hasMultipleTypes) { + $msg[] = '--' . $relBoundary; + $msg[] = 'Content-Type: multipart/alternative; boundary="alt-' . $boundary . '"'; + $msg[] = ''; + $textBoundary = 'alt-' . $boundary; + } + + if (isset($rendered['text'])) { + if ($textBoundary !== $boundary || $hasAttachments) { + $msg[] = '--' . $textBoundary; $msg[] = 'Content-Type: text/plain; charset=' . $this->charset; $msg[] = 'Content-Transfer-Encoding: ' . $this->_getContentTransferEncoding(); $msg[] = ''; } + $this->_textMessage = $rendered['text']; + $content = explode("\n", $this->_textMessage); + $msg = array_merge($msg, $content); + $msg[] = ''; + } + + if (isset($rendered['html'])) { + if ($textBoundary !== $boundary || $hasAttachments) { + $msg[] = '--' . $textBoundary; + $msg[] = 'Content-Type: text/html; charset=' . $this->charset; + $msg[] = 'Content-Transfer-Encoding: ' . $this->_getContentTransferEncoding(); + $msg[] = ''; + } + $this->_htmlMessage = $rendered['html']; + $content = explode("\n", $this->_htmlMessage); + $msg = array_merge($msg, $content); + $msg[] = ''; } - $rendered = $this->_encodeString($rendered[$this->_emailFormat], $this->charset); - $content = explode("\n", $rendered); - - if ($this->_emailFormat === 'html') { - $this->_htmlMessage = $rendered; - } else { - $this->_textMessage = $rendered; + if ($hasMultipleTypes) { + $msg[] = '--' . $textBoundary . '--'; + $msg[] = ''; } - return array_merge($msg, $content); + if ($hasInlineAttachments) { + $attachments = $this->_attachInlineFiles(); + $msg = array_merge($msg, $attachments); + $msg[] = ''; + $msg[] = '--' . $relBoundary . '--'; + $msg[] = ''; + } + + if ($hasAttachments) { + $attachments = $this->_attachFiles(); + $msg = array_merge($msg, $attachments); + $msg[] = ''; + $msg[] = '--' . $boundary . '--'; + $msg[] = ''; + } + return $msg; + } + +/** + * Gets the text body types that are in this email message + * + * @return array Array of types. Valid types are 'text' and 'html' + */ + protected function _getTypes() { + $types = array($this->_emailFormat); + if ($this->_emailFormat == 'both') { + $types = array('html', 'text'); + } + return $types; } /** * Build and set all the view properties needed to render the templated emails. - * Returns false if the email is not templated. + * If there is no template set, the $content will be returned in a hash + * of the text content types for the email. * * @param string $content The content passed in from send() in most cases. * @return array The rendered content with html and text keys. */ - public function _renderTemplates($content) { + protected function _renderTemplates($content) { + $types = $this->_getTypes(); + $rendered = array(); if (empty($this->_template)) { - return false; + foreach ($types as $type) { + $rendered[$type] = $this->_encodeString($content, $this->charset); + } + return $rendered; } $viewClass = $this->_viewRender; if ($viewClass !== 'View') { @@ -1425,12 +1433,6 @@ class CakeEmail { $View->plugin = $layoutPlugin; } - $types = array($this->_emailFormat); - if ($this->_emailFormat == 'both') { - $types = array('html', 'text'); - } - - $rendered = array(); foreach ($types as $type) { $View->set('content', $content); $View->hasRendered = false; @@ -1438,7 +1440,7 @@ class CakeEmail { $render = $View->render($template, $layout); $render = str_replace(array("\r\n", "\r"), "\n", $render); - $rendered[$type] = $render; + $rendered[$type] = $this->_encodeString($render, $this->charset); } return $rendered; } diff --git a/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php b/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php index 4ee443c4e..47a47fd33 100644 --- a/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php +++ b/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php @@ -794,12 +794,25 @@ class CakeEmailTest extends CakeTestCase { $this->assertContains('Content-Type: multipart/mixed; boundary="' . $boundary . '"', $result['headers']); $expected = "--$boundary\r\n" . "Content-Type: multipart/alternative; boundary=\"alt-$boundary\"\r\n" . + "\r\n" . + "--alt-$boundary\r\n" . + "Content-Type: text/plain; charset=UTF-8\r\n" . "Content-Transfer-Encoding: 8bit\r\n" . "\r\n" . "Hello" . "\r\n" . "\r\n" . "\r\n" . + "--alt-$boundary\r\n" . + "Content-Type: text/html; charset=UTF-8\r\n" . + "Content-Transfer-Encoding: 8bit\r\n" . + "\r\n" . + "Hello" . + "\r\n" . + "\r\n" . + "\r\n" . + "--alt-{$boundary}--\r\n" . + "\r\n" . "--$boundary\r\n" . "Content-Type: application/octet-stream\r\n" . "Content-Transfer-Encoding: base64\r\n" . @@ -875,7 +888,7 @@ class CakeEmailTest extends CakeTestCase { $this->CakeEmail->charset = 'ISO-2022-JP'; $result = $this->CakeEmail->send(); - $expected = mb_convert_encoding('CakePHP Framework を使って送信したメールです。 http://cakephp.org.','ISO-2022-JP'); + $expected = mb_convert_encoding('CakePHP Framework を使って送信したメールです。 http://cakephp.org.', 'ISO-2022-JP'); $this->assertContains($expected, $result['message']); $this->assertContains('Message-ID: ', $result['headers']); $this->assertContains('To: ', $result['headers']); @@ -1011,7 +1024,7 @@ class CakeEmailTest extends CakeTestCase { $message = $this->CakeEmail->message(); $boundary = $this->CakeEmail->getBoundary(); $this->assertFalse(empty($boundary)); - $this->assertNotContains('--' . $boundary, $message); + $this->assertContains('--' . $boundary, $message); $this->assertNotContains('--' . $boundary . '--', $message); $this->assertContains('--alt-' . $boundary, $message); $this->assertContains('--alt-' . $boundary . '--', $message); @@ -1121,7 +1134,6 @@ class CakeEmailTest extends CakeTestCase { // UTF-8 is 8bit $this->assertTrue($this->checkContentTransferEncoding($message, '8bit')); - $this->CakeEmail->charset = 'ISO-2022-JP'; $this->CakeEmail->send(); $message = $this->CakeEmail->message(); From f366a9ff834b96f3b971568916b40b06e1f8cc16 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 26 Dec 2011 22:52:29 -0500 Subject: [PATCH 19/44] Add test for inline attachments. --- .../Test/Case/Network/Email/CakeEmailTest.php | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php b/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php index 47a47fd33..0470b60f1 100644 --- a/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php +++ b/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php @@ -820,6 +820,61 @@ class CakeEmailTest extends CakeTestCase { $this->assertContains($expected, $result['message']); } +/** + * Test setting inline attachments and messages. + * + * @return void + */ + public function testSendWithInlineAttachments() { + $this->CakeEmail->transport('debug'); + $this->CakeEmail->from('cake@cakephp.org'); + $this->CakeEmail->to('cake@cakephp.org'); + $this->CakeEmail->subject('My title'); + $this->CakeEmail->emailFormat('both'); + $this->CakeEmail->attachments(array( + 'cake.png' => array( + 'file' => CAKE . 'VERSION.txt', + 'contentId' => 'abc123' + ) + )); + $result = $this->CakeEmail->send('Hello'); + + $boundary = $this->CakeEmail->getBoundary(); + $this->assertContains('Content-Type: multipart/mixed; boundary="' . $boundary . '"', $result['headers']); + $expected = "--$boundary\r\n" . + "Content-Type: multipart/related; boundary=\"rel-$boundary\"\r\n" . + "\r\n" . + "--rel-$boundary\r\n" . + "Content-Type: multipart/alternative; boundary=\"alt-$boundary\"\r\n" . + "\r\n" . + "--alt-$boundary\r\n" . + "Content-Type: text/plain; charset=UTF-8\r\n" . + "Content-Transfer-Encoding: 8bit\r\n" . + "\r\n" . + "Hello" . + "\r\n" . + "\r\n" . + "\r\n" . + "--alt-$boundary\r\n" . + "Content-Type: text/html; charset=UTF-8\r\n" . + "Content-Transfer-Encoding: 8bit\r\n" . + "\r\n" . + "Hello" . + "\r\n" . + "\r\n" . + "\r\n" . + "--alt-{$boundary}--\r\n" . + "\r\n" . + "--$boundary\r\n" . + "Content-Type: application/octet-stream\r\n" . + "Content-Transfer-Encoding: base64\r\n" . + "Content-ID: \r\n" . + "Content-Disposition: inline; filename=\"cake.png\"\r\n\r\n"; + $this->assertContains($expected, $result['message']); + $this->assertContains('--rel-' . $boundary . '--', $result['message']); + $this->assertContains('--' . $boundary . '--', $result['message']); +} + /** * testSendWithLog method * From 83b28c42cf1470924b47df6e88e0282d05ecc441 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Tue, 27 Dec 2011 23:38:21 -0500 Subject: [PATCH 20/44] Fix failing tests and missing boundary markers. When sending html + text emails, there were duplicate multipart/alternative sections and the trailing top level boundary was missing. --- lib/Cake/Network/Email/CakeEmail.php | 6 +-- .../Component/EmailComponentTest.php | 38 ++++++++++++------- .../Test/Case/Network/Email/CakeEmailTest.php | 2 +- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/lib/Cake/Network/Email/CakeEmail.php b/lib/Cake/Network/Email/CakeEmail.php index c078edd2d..8ce813456 100644 --- a/lib/Cake/Network/Email/CakeEmail.php +++ b/lib/Cake/Network/Email/CakeEmail.php @@ -662,14 +662,12 @@ class CakeEmail { } $headers['MIME-Version'] = '1.0'; - if (!empty($this->_attachments)) { + if (!empty($this->_attachments) || $this->_emailFormat === 'both') { $headers['Content-Type'] = 'multipart/mixed; boundary="' . $this->_boundary . '"'; } elseif ($this->_emailFormat === 'text') { $headers['Content-Type'] = 'text/plain; charset=' . $this->charset; } elseif ($this->_emailFormat === 'html') { $headers['Content-Type'] = 'text/html; charset=' . $this->charset; - } elseif ($this->_emailFormat === 'both') { - $headers['Content-Type'] = 'multipart/alternative; boundary="alt-' . $this->_boundary . '"'; } $headers['Content-Transfer-Encoding'] = $this->_getContentTransferEncoding(); @@ -1377,6 +1375,8 @@ class CakeEmail { if ($hasAttachments) { $attachments = $this->_attachFiles(); $msg = array_merge($msg, $attachments); + } + if ($hasAttachments || $hasMultipleTypes) { $msg[] = ''; $msg[] = '--' . $boundary . '--'; $msg[] = ''; diff --git a/lib/Cake/Test/Case/Controller/Component/EmailComponentTest.php b/lib/Cake/Test/Case/Controller/Component/EmailComponentTest.php index c844ed1d2..162e5e08a 100644 --- a/lib/Cake/Test/Case/Controller/Component/EmailComponentTest.php +++ b/lib/Cake/Test/Case/Controller/Component/EmailComponentTest.php @@ -228,12 +228,6 @@ MSGBLOC; $expect = str_replace('{CONTENTTYPE}', 'text/html; charset=UTF-8', $message); $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message')); $this->assertEquals(DebugCompTransport::$lastEmail, $this->__osFix($expect)); - - // TODO: better test for format of message sent? - $this->Controller->EmailTest->sendAs = 'both'; - $expect = str_replace('{CONTENTTYPE}', 'multipart/alternative; boundary="alt-"', $message); - $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message')); - $this->assertEquals(preg_replace('/alt-[a-z0-9]{32}/i', 'alt-', DebugCompTransport::$lastEmail), $this->__osFix($expect)); } /** @@ -307,13 +301,29 @@ HTMLBLOC; $this->assertEquals(DebugCompTransport::$lastEmail, $this->__osFix($expect)); $this->Controller->EmailTest->sendAs = 'both'; - $expect = str_replace('{CONTENTTYPE}', 'multipart/alternative; boundary="alt-"', $header); - $expect .= '--alt-' . "\n" . 'Content-Type: text/plain; charset=UTF-8' . "\n" . 'Content-Transfer-Encoding: 8bit' . "\n\n" . $text . "\n\n"; - $expect .= '--alt-' . "\n" . 'Content-Type: text/html; charset=UTF-8' . "\n" . 'Content-Transfer-Encoding: 8bit' . "\n\n" . $html . "\n\n"; - $expect = '
' . $expect . '--alt---' . "\n\n" . '
'; + $expect = str_replace('{CONTENTTYPE}', 'multipart/mixed; boundary="{boundary}"', $header); + $expect .= "--{boundary}\n" . + 'Content-Type: multipart/alternative; boundary="alt-{boundary}"' . "\n\n" . + '--alt-{boundary}' . "\n" . + 'Content-Type: text/plain; charset=UTF-8' . "\n" . + 'Content-Transfer-Encoding: 8bit' . "\n\n" . + $text . + "\n\n" . + '--alt-{boundary}' . "\n" . + 'Content-Type: text/html; charset=UTF-8' . "\n" . + 'Content-Transfer-Encoding: 8bit' . "\n\n" . + $html . + "\n\n" . + '--alt-{boundary}--' . "\n\n\n" . + '--{boundary}--' . "\n"; + + $expect = '
' . $expect . '
'; $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message')); - $this->assertEquals(preg_replace('/alt-[a-z0-9]{32}/i', 'alt-', DebugCompTransport::$lastEmail), $this->__osFix($expect)); + $this->assertEquals( + $this->__osFix($expect), + preg_replace('/[a-z0-9]{32}/i', '{boundary}', DebugCompTransport::$lastEmail) + ); $html = << @@ -441,7 +451,7 @@ HTMLBLOC; $this->Controller->EmailTest->delivery = 'DebugComp'; - $text = $html = 'This is the body of the message'; + $text = $html = "This is the body of the message\n"; $this->Controller->EmailTest->sendAs = 'both'; $this->assertTrue($this->Controller->EmailTest->send('This is the body of the message')); @@ -740,8 +750,8 @@ HTMLBLOC; $this->assertTrue($this->Controller->EmailTest->send($body)); $msg = DebugCompTransport::$lastEmail; - $this->assertNotRegExp('/text\/plain/', $msg); - $this->assertNotRegExp('/text\/html/', $msg); + $this->assertRegExp('/text\/plain/', $msg); + $this->assertRegExp('/text\/html/', $msg); $this->assertRegExp('/multipart\/alternative/', $msg); } diff --git a/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php b/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php index 0470b60f1..7479e1678 100644 --- a/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php +++ b/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php @@ -1080,7 +1080,7 @@ class CakeEmailTest extends CakeTestCase { $boundary = $this->CakeEmail->getBoundary(); $this->assertFalse(empty($boundary)); $this->assertContains('--' . $boundary, $message); - $this->assertNotContains('--' . $boundary . '--', $message); + $this->assertContains('--' . $boundary . '--', $message); $this->assertContains('--alt-' . $boundary, $message); $this->assertContains('--alt-' . $boundary . '--', $message); From 339259c841462cb0ab01b477e3db4a6a7d9caab0 Mon Sep 17 00:00:00 2001 From: mark_story Date: Wed, 28 Dec 2011 22:00:12 -0500 Subject: [PATCH 21/44] Update version to 2.0.5 --- lib/Cake/Config/config.php | 2 +- lib/Cake/VERSION.txt | 3 ++- lib/Cake/View/Pages/home.ctp | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/Cake/Config/config.php b/lib/Cake/Config/config.php index c533de097..9bbbb5cde 100644 --- a/lib/Cake/Config/config.php +++ b/lib/Cake/Config/config.php @@ -16,5 +16,5 @@ * @since CakePHP(tm) v 1.1.11.4062 * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ -$config['Cake.version'] = '2.0.4'; +$config['Cake.version'] = '2.0.5'; return $config; diff --git a/lib/Cake/VERSION.txt b/lib/Cake/VERSION.txt index 9ada9ffe0..a555d42c1 100644 --- a/lib/Cake/VERSION.txt +++ b/lib/Cake/VERSION.txt @@ -17,4 +17,5 @@ // @license MIT License (http://www.opensource.org/licenses/mit-license.php) // +--------------------------------------------------------------------------------------------+ // //////////////////////////////////////////////////////////////////////////////////////////////////// -2.0.4 +2.0.5 + diff --git a/lib/Cake/View/Pages/home.ctp b/lib/Cake/View/Pages/home.ctp index cc99d9e76..612023ea7 100644 --- a/lib/Cake/View/Pages/home.ctp +++ b/lib/Cake/View/Pages/home.ctp @@ -24,7 +24,7 @@ App::uses('Debugger', 'Utility');

For updates and important announcements, visit http://cakefest.org

- + 0): Debugger::checkSecurityKeys(); From b1aae5b5ab92e0f77b629b488aa6db209c9858c8 Mon Sep 17 00:00:00 2001 From: Rachman Chavik Date: Thu, 29 Dec 2011 19:17:06 +0700 Subject: [PATCH 22/44] add fullTableName tests with empty schemaName --- .../Test/Case/Model/Datasource/DboSourceTest.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php b/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php index 9cb6ed0f2..a1755bebc 100644 --- a/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php +++ b/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php @@ -708,6 +708,22 @@ class DboSourceTest extends CakeTestCase { $Article->tablePrefix = ''; $result = $this->testDb->fullTableName($Article, true, false); $this->assertEquals($result, '`with spaces`'); + + $this->loadFixtures('Article'); + $Article->useTable = $Article->table = 'articles'; + $Article->setDataSource('test'); + $testdb = $Article->getDataSource(); + $result = $testdb->fullTableName($Article, false, true); + $this->assertEquals($testdb->getSchemaName() . '.articles', $result); + + // tests for empty schemaName + $noschema = ConnectionManager::create('noschema', array( + 'datasource' => 'DboTestSource' + )); + $Article->setDataSource('noschema'); + $Article->schemaName = null; + $result = $noschema->fullTableName($Article, false, true); + $this->assertEquals('articles', $result); } /** From 1d333fd8b7c80f7a342cb15244b653f812df9b9b Mon Sep 17 00:00:00 2001 From: Rachman Chavik Date: Thu, 29 Dec 2011 19:18:37 +0700 Subject: [PATCH 23/44] fullTableName needs to check for empty schemaName --- lib/Cake/Model/Datasource/DataSource.php | 2 +- lib/Cake/Model/Datasource/DboSource.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Cake/Model/Datasource/DataSource.php b/lib/Cake/Model/Datasource/DataSource.php index 0a752c171..84127de93 100644 --- a/lib/Cake/Model/Datasource/DataSource.php +++ b/lib/Cake/Model/Datasource/DataSource.php @@ -413,7 +413,7 @@ class DataSource extends Object { * @access public */ public function getSchemaName() { - return false; + return null; } /** diff --git a/lib/Cake/Model/Datasource/DboSource.php b/lib/Cake/Model/Datasource/DboSource.php index 4d000f774..632b450c8 100644 --- a/lib/Cake/Model/Datasource/DboSource.php +++ b/lib/Cake/Model/Datasource/DboSource.php @@ -930,14 +930,14 @@ class DboSource extends DataSource { } if ($quote) { - if ($schema && isset($schemaName)) { + if ($schema && !empty($schemaName)) { if (false == strstr($table, '.')) { return $this->name($schemaName) . '.' . $this->name($table); } } return $this->name($table); } - if ($schema && isset($schemaName)) { + if ($schema && !empty($schemaName)) { if (false == strstr($table, '.')) { return $schemaName . '.' . $table; } From 929a40364f2d4ef72ff23850b297aa8205a77972 Mon Sep 17 00:00:00 2001 From: Ceeram Date: Thu, 29 Dec 2011 16:22:08 +0100 Subject: [PATCH 24/44] adding magic method__isset() for overloaded properties --- lib/Cake/View/View.php | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/lib/Cake/View/View.php b/lib/Cake/View/View.php index 83702e55a..55f81f2da 100644 --- a/lib/Cake/View/View.php +++ b/lib/Cake/View/View.php @@ -465,7 +465,7 @@ class View extends Object { * the 'meta', 'css', and 'script' blocks. They are appended in that order. * * Deprecated features: - * + * * - `$scripts_for_layout` is deprecated and will be removed in CakePHP 3.0. * Use the block features instead. `meta`, `css` and `script` will be populated * by the matching methods on HtmlHelper. @@ -600,7 +600,7 @@ class View extends Object { } /** - * Append to an existing or new block. Appending to a new + * Append to an existing or new block. Appending to a new * block will create the block. * * @param string $name Name of the block @@ -650,7 +650,7 @@ class View extends Object { } /** - * Provides view or element extension/inheritance. Views can extends a + * Provides view or element extension/inheritance. Views can extends a * parent view and populate blocks in the parent template. * * @param string $name The view or element to 'extend' the current one with. @@ -668,7 +668,7 @@ class View extends Object { case self::TYPE_LAYOUT: $parent = $this->_getLayoutFileName($name); break; - + } if ($parent == $this->_current) { throw new LogicException(__d('cake_dev', 'You cannot have views extend themselves.')); @@ -775,7 +775,7 @@ class View extends Object { /** * Magic accessor for deprecated attributes. - * + * * @param string $name Name of the attribute to set. * @param string $value Value of the attribute to set. * @return mixed @@ -789,6 +789,16 @@ class View extends Object { } } +/** + * Magic isset check for deprecated attributes. + * + * @param string $name Name of the attribute to check. + * @return boolean + */ + public function __isset($name) { + return isset($this->name); + } + /** * Interact with the HelperCollection to load all the helpers. * @@ -845,7 +855,7 @@ class View extends Object { * Sandbox method to evaluate a template / view script in. * * @param string $___viewFn Filename of the view - * @param array $___dataForView Data to include in rendered view. + * @param array $___dataForView Data to include in rendered view. * If empty the current View::$viewVars will be used. * @return string Rendered output */ From 5936fa5930ef4a86115f74ac9586c7251c8b9e71 Mon Sep 17 00:00:00 2001 From: mark_story Date: Thu, 29 Dec 2011 11:10:25 -0500 Subject: [PATCH 25/44] Fix strict errors. --- lib/Cake/Utility/ObjectCollection.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Cake/Utility/ObjectCollection.php b/lib/Cake/Utility/ObjectCollection.php index 539de1928..79fff9b8f 100644 --- a/lib/Cake/Utility/ObjectCollection.php +++ b/lib/Cake/Utility/ObjectCollection.php @@ -109,7 +109,8 @@ abstract class ObjectCollection { $options[$opt] = $event->{$opt}; } } - $callback = array_pop(explode('.', $event->name())); + $parts = explode('.', $event->name()); + $callback = array_pop($parts); } $options = array_merge( array( From 9cdf8042bf3a55cc5269eb77db4248254f035356 Mon Sep 17 00:00:00 2001 From: mark_story Date: Thu, 29 Dec 2011 11:58:05 -0500 Subject: [PATCH 26/44] Fix issue with scripts_for_layout compatibility. --- lib/Cake/View/View.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Cake/View/View.php b/lib/Cake/View/View.php index 55f81f2da..ce35bae0f 100644 --- a/lib/Cake/View/View.php +++ b/lib/Cake/View/View.php @@ -493,7 +493,7 @@ class View extends Object { $this->getEventManager()->dispatch(new CakeEvent('View.beforeLayout', $this, array($layoutFileName))); $scripts = implode("\n\t", $this->_scripts); - $scripts .= $this->get('meta') . $this->get('css') . $this->get('script'); + $scripts .= $this->Blocks->get('meta') . $this->Blocks->get('css') . $this->Blocks->get('script'); $this->viewVars = array_merge($this->viewVars, array( 'content_for_layout' => $content, From 015f9957bed4ca529a7ac854c6bd2441963bfcef Mon Sep 17 00:00:00 2001 From: tigrang Date: Wed, 28 Dec 2011 18:33:01 -0800 Subject: [PATCH 27/44] Changed order of controller var merging From AppController -> PluginAppController to PluginAppConroller -> AppController Fixes #2420 Signed-off-by: mark_story --- lib/Cake/Controller/Controller.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/Cake/Controller/Controller.php b/lib/Cake/Controller/Controller.php index b32e2ba24..61d18585a 100644 --- a/lib/Cake/Controller/Controller.php +++ b/lib/Cake/Controller/Controller.php @@ -534,6 +534,18 @@ class Controller extends Object { $pluginDot = $this->plugin . '.'; } + if ($pluginController && $this->plugin != null) { + $merge = array('components', 'helpers'); + $appVars = get_class_vars($pluginController); + if ( + ($this->uses !== null || $this->uses !== false) && + is_array($this->uses) && !empty($appVars['uses']) + ) { + $this->uses = array_merge($this->uses, array_diff($appVars['uses'], $this->uses)); + } + $this->_mergeVars($merge, $pluginController); + } + if (is_subclass_of($this, $this->_mergeParent) || !empty($pluginController)) { $appVars = get_class_vars($this->_mergeParent); $uses = $appVars['uses']; @@ -556,18 +568,6 @@ class Controller extends Object { } $this->_mergeVars($merge, $this->_mergeParent, true); } - - if ($pluginController && $this->plugin != null) { - $merge = array('components', 'helpers'); - $appVars = get_class_vars($pluginController); - if ( - ($this->uses !== null || $this->uses !== false) && - is_array($this->uses) && !empty($appVars['uses']) - ) { - $this->uses = array_merge($this->uses, array_diff($appVars['uses'], $this->uses)); - } - $this->_mergeVars($merge, $pluginController); - } } /** From ff29b194ed59f31f9051c301e4bf4f54f9060ab0 Mon Sep 17 00:00:00 2001 From: mark_story Date: Thu, 29 Dec 2011 20:12:45 -0500 Subject: [PATCH 28/44] Fix version config. --- lib/Cake/Config/config.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Cake/Config/config.php b/lib/Cake/Config/config.php index 9bbbb5cde..d90976331 100644 --- a/lib/Cake/Config/config.php +++ b/lib/Cake/Config/config.php @@ -16,5 +16,5 @@ * @since CakePHP(tm) v 1.1.11.4062 * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ -$config['Cake.version'] = '2.0.5'; -return $config; +$versionFile = file(CAKE . 'VERSION.txt'); +return $config['Cake.version'] = trim(array_pop($versionFile)); From ed43c685b98d396ffb115169987eed7e9f576e28 Mon Sep 17 00:00:00 2001 From: mark_story Date: Thu, 29 Dec 2011 22:01:46 -0500 Subject: [PATCH 29/44] Fix string offset error in PHP 5.4 --- lib/Cake/Utility/Debugger.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/Cake/Utility/Debugger.php b/lib/Cake/Utility/Debugger.php index a90d658dc..fd7739492 100644 --- a/lib/Cake/Utility/Debugger.php +++ b/lib/Cake/Utility/Debugger.php @@ -646,7 +646,10 @@ class Debugger { $data += $defaults; $files = $this->trace(array('start' => $data['start'], 'format' => 'points')); - $code = $this->excerpt($files[0]['file'], $files[0]['line'] - 1, 1); + $code = ''; + if (isset($files[0]['file'])) { + $code = $this->excerpt($files[0]['file'], $files[0]['line'] - 1, 1); + } $trace = $this->trace(array('start' => $data['start'], 'depth' => '20')); $insertOpts = array('before' => '{:', 'after' => '}'); $context = array(); From 6c902a19b3b436d9f6d436ba193c85e15dccb53d Mon Sep 17 00:00:00 2001 From: mark_story Date: Fri, 30 Dec 2011 14:56:31 -0500 Subject: [PATCH 30/44] Fix issue with rendering elements inside blocks. Fixes exceptions being raised when you tried to render elements inside blocks. Instead compare the number of open blocks. This should not change before/after rendering a view. --- lib/Cake/View/View.php | 13 ++++++++----- lib/Cake/View/ViewBlock.php | 9 +++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/Cake/View/View.php b/lib/Cake/View/View.php index ce35bae0f..0e8b04b31 100644 --- a/lib/Cake/View/View.php +++ b/lib/Cake/View/View.php @@ -827,12 +827,10 @@ class View extends Object { $data = $this->viewVars; } $this->_current = $viewFile; + $initialBlocks = count($this->Blocks->unclosed()); $this->getEventManager()->dispatch(new CakeEvent('View.beforeRenderFile', $this, array($viewFile))); $content = $this->_evaluate($viewFile, $data); - if ($this->Blocks->active()) { - throw new CakeException(__d('cake_dev', 'The "%s" block was left open.', $this->Blocks->active())); - } $afterEvent = new CakeEvent('View.afterRenderFile', $this, array($viewFile, $content)); //TODO: For BC puporses, set extra info in the event object. Remove when appropriate $afterEvent->modParams = 1; @@ -843,11 +841,16 @@ class View extends Object { $this->_stack[] = $this->fetch('content'); $this->assign('content', $content); - $content = $this->_render($this->_parents[$viewFile], $data); - + $content = $this->_render($this->_parents[$viewFile]); $this->assign('content', array_pop($this->_stack)); } + $remainingBlocks = count($this->Blocks->unclosed()); + + if ($initialBlocks !== $remainingBlocks) { + throw new CakeException(__d('cake_dev', 'The "%s" block was left open. Blocks are not allowed to cross files.', $this->Blocks->active())); + } + return $content; } diff --git a/lib/Cake/View/ViewBlock.php b/lib/Cake/View/ViewBlock.php index 51f5e77bb..fe725f25c 100644 --- a/lib/Cake/View/ViewBlock.php +++ b/lib/Cake/View/ViewBlock.php @@ -145,4 +145,13 @@ class ViewBlock { public function active() { return end($this->_active); } + +/** + * Get the names of the unclosed/active blocks. + * + * @return array An array of unclosed blocks. + */ + public function unclosed() { + return $this->_active; + } } From b5f918765e296245b31cca2270112c4c1311c64e Mon Sep 17 00:00:00 2001 From: mark_story Date: Fri, 30 Dec 2011 20:39:04 -0500 Subject: [PATCH 31/44] Adding docs + exceptions for name translation. Closes #2367 --- lib/Cake/Model/Behavior/TranslateBehavior.php | 10 +++++++++- .../Case/Model/Behavior/TranslateBehaviorTest.php | 12 ++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/Cake/Model/Behavior/TranslateBehavior.php b/lib/Cake/Model/Behavior/TranslateBehavior.php index 165ed8065..5ddebb57c 100644 --- a/lib/Cake/Model/Behavior/TranslateBehavior.php +++ b/lib/Cake/Model/Behavior/TranslateBehavior.php @@ -368,7 +368,10 @@ class TranslateBehavior extends ModelBehavior { /** * Bind translation for fields, optionally with hasMany association for - * fake field + * fake field. + * + * *Note* You should avoid binding translations that overlap existing model properties. + * This can cause un-expected and un-desirable behavior. * * @param Model $model instance of model * @param string|array $fields string with field or array(field1, field2=>AssocName, field3) @@ -391,6 +394,11 @@ class TranslateBehavior extends ModelBehavior { $field = $key; $association = $value; } + if ($field === 'name') { + throw new CakeException( + __d('cake_dev', 'You cannot bind a translation named "name".') + ); + } if (array_key_exists($field, $this->settings[$model->alias])) { unset($this->settings[$model->alias][$field]); diff --git a/lib/Cake/Test/Case/Model/Behavior/TranslateBehaviorTest.php b/lib/Cake/Test/Case/Model/Behavior/TranslateBehaviorTest.php index 8c342a5e4..89fa6a864 100644 --- a/lib/Cake/Test/Case/Model/Behavior/TranslateBehaviorTest.php +++ b/lib/Cake/Test/Case/Model/Behavior/TranslateBehaviorTest.php @@ -868,4 +868,16 @@ class TranslateBehaviorTest extends CakeTestCase { $this->assertFalse($result); } + +/** + * Test that an exception is raised when you try to over-write the name attribute. + * + * @expectedException CakeException + * @return void + */ + public function testExceptionOnNameTranslation() { + $this->loadFixtures('Translate', 'TranslatedItem'); + $TestModel = new TranslatedItem(); + $TestModel->bindTranslation(array('name' => 'name')); + } } From c3e9a931d6d141f03207dd7345d3daa7bb6fb95c Mon Sep 17 00:00:00 2001 From: mark_story Date: Fri, 30 Dec 2011 20:54:26 -0500 Subject: [PATCH 32/44] Should have been association name, not field name. --- lib/Cake/Model/Behavior/TranslateBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Cake/Model/Behavior/TranslateBehavior.php b/lib/Cake/Model/Behavior/TranslateBehavior.php index 5ddebb57c..5c2460d06 100644 --- a/lib/Cake/Model/Behavior/TranslateBehavior.php +++ b/lib/Cake/Model/Behavior/TranslateBehavior.php @@ -394,7 +394,7 @@ class TranslateBehavior extends ModelBehavior { $field = $key; $association = $value; } - if ($field === 'name') { + if ($association === 'name') { throw new CakeException( __d('cake_dev', 'You cannot bind a translation named "name".') ); From 8b9770d34dd402019245389c062acf850f4323e0 Mon Sep 17 00:00:00 2001 From: Kyle Robinson Young Date: Sat, 31 Dec 2011 14:51:24 -0800 Subject: [PATCH 33/44] Fix types in docblocks in App class --- lib/Cake/Core/App.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Cake/Core/App.php b/lib/Cake/Core/App.php index ba55143a3..707b345dc 100644 --- a/lib/Cake/Core/App.php +++ b/lib/Cake/Core/App.php @@ -204,7 +204,7 @@ class App { * * @param string $type type of path * @param string $plugin name of plugin - * @return string array + * @return array * @link http://book.cakephp.org/2.0/en/core-utility-libraries/app.html#App::path */ public static function path($type, $plugin = null) { @@ -361,7 +361,7 @@ class App { * `App::core('Cache/Engine'); will return the full path to the cache engines package` * * @param string $type - * @return string full path to package + * @return array full path to package * @link http://book.cakephp.org/2.0/en/core-utility-libraries/app.html#App::core */ public static function core($type) { @@ -573,7 +573,7 @@ class App { * based on Inflector::underscore($name) . ".$ext"; * @param array $search paths to search for files, array('path 1', 'path 2', 'path 3'); * @param string $file full name of the file to search for including extension - * @param boolean $return, return the loaded file, the file must have a return + * @param boolean $return Return the loaded file, the file must have a return * statement in it to work: return $variable; * @return boolean true if Class is already in memory or if file is found and loaded, false if not */ From ac408b38e3dda9458795f4df7f01a59db3f78443 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 1 Jan 2012 12:57:35 -0500 Subject: [PATCH 34/44] Add Windows Phone OS to mobile browser list. Fixes #2428 --- lib/Cake/Network/CakeRequest.php | 2 +- lib/Cake/Test/Case/Network/CakeRequestTest.php | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/Cake/Network/CakeRequest.php b/lib/Cake/Network/CakeRequest.php index 6adb2842b..8793944eb 100644 --- a/lib/Cake/Network/CakeRequest.php +++ b/lib/Cake/Network/CakeRequest.php @@ -107,7 +107,7 @@ class CakeRequest implements ArrayAccess { 'Android', 'AvantGo', 'BlackBerry', 'DoCoMo', 'Fennec', 'iPod', 'iPhone', 'J2ME', 'MIDP', 'NetFront', 'Nokia', 'Opera Mini', 'Opera Mobi', 'PalmOS', 'PalmSource', 'portalmmm', 'Plucker', 'ReqwirelessWeb', 'SonyEricsson', 'Symbian', 'UP\\.Browser', - 'webOS', 'Windows CE', 'Xiino' + 'webOS', 'Windows CE', 'Windows Phone OS', 'Xiino' )) ); diff --git a/lib/Cake/Test/Case/Network/CakeRequestTest.php b/lib/Cake/Test/Case/Network/CakeRequestTest.php index 33bc02d82..6104be403 100644 --- a/lib/Cake/Test/Case/Network/CakeRequestTest.php +++ b/lib/Cake/Test/Case/Network/CakeRequestTest.php @@ -636,6 +636,10 @@ class CakeRequestTest extends CakeTestCase { $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 5.1; rv:2.0b6pre) Gecko/20100902 Firefox/4.0b6pre Fennec/2.0b1pre'; $this->assertTrue($request->is('mobile')); $this->assertTrue($request->isMobile()); + + $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; SAMSUNG; OMNIA7)'; + $this->assertTrue($request->is('mobile')); + $this->assertTrue($request->isMobile()); } /** From 997aa8a37bda12d4fed63364c77cb1d27d74468c Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 1 Jan 2012 21:29:13 -0500 Subject: [PATCH 35/44] I'm an idiot. Fix the parse error. --- lib/Cake/Network/CakeRequest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Cake/Network/CakeRequest.php b/lib/Cake/Network/CakeRequest.php index 66376525e..3f085d5d5 100644 --- a/lib/Cake/Network/CakeRequest.php +++ b/lib/Cake/Network/CakeRequest.php @@ -107,7 +107,7 @@ class CakeRequest implements ArrayAccess { 'Android', 'AvantGo', 'BlackBerry', 'DoCoMo', 'Fennec', 'iPod', 'iPhone', 'J2ME', 'MIDP', 'NetFront', 'Nokia', 'Opera Mini', 'Opera Mobi', 'PalmOS', 'PalmSource', 'portalmmm', 'Plucker', 'ReqwirelessWeb', 'SonyEricsson', 'Symbian', 'UP\\.Browser', - 'webOS', 'Windows CE', 'Windows Phone OS', t'Xiino' + 'webOS', 'Windows CE', 'Windows Phone OS', 'Xiino' )), 'requested' => array('param' => 'requested', 'value' => 1) ); From 886480527746cb43adc46582c36f2fc829a0e1b1 Mon Sep 17 00:00:00 2001 From: Veselin Todorov Date: Mon, 2 Jan 2012 13:53:21 +0200 Subject: [PATCH 36/44] Adding resourceMap accessor --- lib/Cake/Routing/Router.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/Cake/Routing/Router.php b/lib/Cake/Routing/Router.php index 04679aded..95fe11907 100644 --- a/lib/Cake/Routing/Router.php +++ b/lib/Cake/Routing/Router.php @@ -174,6 +174,20 @@ class Router { return self::$_namedExpressions; } +/** + * Resource map getter & setter. + * + * @param array $resourceMap Resource map + * @return mixed + * @see Router::$_resourceMap + */ + public static function resourceMap($resourceMap = null) { + if ($resourceMap === null) { + return self::$_resourceMap; + } + self::$_resourceMap = $resourceMap; + } + /** * Connects a new Route in the router. * From 3eea2e207336e4eaf5b4bc33e48e1242e8f2931c Mon Sep 17 00:00:00 2001 From: Veselin Todorov Date: Mon, 2 Jan 2012 13:53:32 +0200 Subject: [PATCH 37/44] Adding resourceMap accessor test --- lib/Cake/Test/Case/Routing/RouterTest.php | 31 +++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/lib/Cake/Test/Case/Routing/RouterTest.php b/lib/Cake/Test/Case/Routing/RouterTest.php index 3fcd431c1..a4066545e 100644 --- a/lib/Cake/Test/Case/Routing/RouterTest.php +++ b/lib/Cake/Test/Case/Routing/RouterTest.php @@ -2454,6 +2454,37 @@ class RouterTest extends CakeTestCase { $this->assertFalse($result); } +/** + * Tests resourceMap as getter and setter. + * + * @return void + */ + public function testResourceMap() { + $default = Router::resourceMap(); + $exepcted = array( + array('action' => 'index', 'method' => 'GET', 'id' => false), + array('action' => 'view', 'method' => 'GET', 'id' => true), + array('action' => 'add', 'method' => 'POST', 'id' => false), + array('action' => 'edit', 'method' => 'PUT', 'id' => true), + array('action' => 'delete', 'method' => 'DELETE', 'id' => true), + array('action' => 'edit', 'method' => 'POST', 'id' => true) + ); + $this->assertEquals($default, $exepcted); + + $custom = array( + array('action' => 'index', 'method' => 'GET', 'id' => false), + array('action' => 'view', 'method' => 'GET', 'id' => true), + array('action' => 'add', 'method' => 'POST', 'id' => false), + array('action' => 'edit', 'method' => 'PUT', 'id' => true), + array('action' => 'delete', 'method' => 'DELETE', 'id' => true), + array('action' => 'update', 'method' => 'POST', 'id' => true) + ); + Router::resourceMap($custom); + $this->assertEquals($custom, Router::resourceMap()); + + Router::resourceMap($default); + } + /** * test setting redirect routes * From b4faa00703d45567d714122930bdd3a96ad3663c Mon Sep 17 00:00:00 2001 From: mark_story Date: Mon, 2 Jan 2012 13:46:35 -0500 Subject: [PATCH 38/44] Adding error to find(threaded). When the model has no parent_id trigger a warning about the impending failure and return an empty result. Fixes #2341 --- lib/Cake/Model/Model.php | 7 +++++++ lib/Cake/Test/Case/Model/ModelReadTest.php | 16 ++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/lib/Cake/Model/Model.php b/lib/Cake/Model/Model.php index 7f9a4aad5..aa17dd579 100644 --- a/lib/Cake/Model/Model.php +++ b/lib/Cake/Model/Model.php @@ -2818,6 +2818,13 @@ class Model extends Object implements CakeEventListener { foreach ($results as $result) { $result['children'] = array(); $id = $result[$this->alias][$this->primaryKey]; + if (!isset($result[$this->alias]['parent_id'])) { + trigger_error( + __d('cake_dev', 'You cannot use find("threaded") on models without a "parent_id" field.'), + E_USER_WARNING + ); + return $return; + } $parentId = $result[$this->alias]['parent_id']; if (isset($idMap[$id]['children'])) { $idMap[$id] = array_merge($result, (array)$idMap[$id]); diff --git a/lib/Cake/Test/Case/Model/ModelReadTest.php b/lib/Cake/Test/Case/Model/ModelReadTest.php index 51ed48ce0..71072a6a9 100644 --- a/lib/Cake/Test/Case/Model/ModelReadTest.php +++ b/lib/Cake/Test/Case/Model/ModelReadTest.php @@ -2987,13 +2987,21 @@ class ModelReadTest extends BaseModelTest { $noAfterFindData = $noAfterFindModel->find('all'); $this->assertFalse($afterFindModel == $noAfterFindModel); - // Limitation of PHP 4 and PHP 5 > 5.1.6 when comparing recursive objects - if (PHP_VERSION === '5.1.6') { - $this->assertFalse($afterFindModel != $duplicateModel); - } $this->assertEquals($afterFindData, $noAfterFindData); } +/** + * find(threaded) should trigger errors whne there is no parent_id field. + * + * @expectedException PHPUnit_Framework_Error_Warning + * @return void + */ + public function testFindThreadedError() { + $this->loadFixtures('Apple', 'Sample'); + $Apple = new Apple(); + $Apple->find('threaded'); + } + /** * testFindAllThreaded method * From 101148c10f95108a9764b09e524292c6686b4978 Mon Sep 17 00:00:00 2001 From: mark_story Date: Mon, 2 Jan 2012 20:18:01 -0500 Subject: [PATCH 39/44] Return 0 if there is no result. The documented return is an integer. Stick to that. Fixes #2430 --- lib/Cake/Model/Datasource/DboSource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Cake/Model/Datasource/DboSource.php b/lib/Cake/Model/Datasource/DboSource.php index ca47d200f..575af5493 100644 --- a/lib/Cake/Model/Datasource/DboSource.php +++ b/lib/Cake/Model/Datasource/DboSource.php @@ -484,7 +484,7 @@ class DboSource extends DataSource { if ($this->hasResult()) { return $this->_result->rowCount(); } - return null; + return 0; } /** From c43b0998942a8aa38794e3e13ece755dafb100b9 Mon Sep 17 00:00:00 2001 From: mark_story Date: Tue, 3 Jan 2012 20:32:34 -0500 Subject: [PATCH 40/44] Fix null bytea columns. Apply patch from 'opiazer' Fixes #2432 --- lib/Cake/Model/Datasource/Database/Postgres.php | 2 +- lib/Cake/Test/Case/Model/Datasource/Database/PostgresTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Cake/Model/Datasource/Database/Postgres.php b/lib/Cake/Model/Datasource/Database/Postgres.php index 531adb62c..3e47f52f2 100644 --- a/lib/Cake/Model/Datasource/Database/Postgres.php +++ b/lib/Cake/Model/Datasource/Database/Postgres.php @@ -716,7 +716,7 @@ class Postgres extends DboSource { break; case 'binary': case 'bytea': - $resultRow[$table][$column] = stream_get_contents($row[$index]); + $resultRow[$table][$column] = is_null($row[$index]) ? null : stream_get_contents($row[$index]); break; default: $resultRow[$table][$column] = $row[$index]; diff --git a/lib/Cake/Test/Case/Model/Datasource/Database/PostgresTest.php b/lib/Cake/Test/Case/Model/Datasource/Database/PostgresTest.php index 0b60aa1c8..f1b400763 100644 --- a/lib/Cake/Test/Case/Model/Datasource/Database/PostgresTest.php +++ b/lib/Cake/Test/Case/Model/Datasource/Database/PostgresTest.php @@ -759,7 +759,7 @@ class PostgresTest extends CakeTestCase { */ function testVirtualFieldAsAConstant() { $this->loadFixtures('Article', 'Comment'); - $Article =& ClassRegistry::init('Article'); + $Article = ClassRegistry::init('Article'); $Article->virtualFields = array( 'empty' => "NULL", 'number' => 43, From 3e1c567f083f22b2beed15f61347ad41e6eca2ab Mon Sep 17 00:00:00 2001 From: mark_story Date: Tue, 3 Jan 2012 21:07:22 -0500 Subject: [PATCH 41/44] Remove duplicate methods. Fix regression where #1345 was re-introduced. Fixes #2434 --- .../Test/Case/View/Helper/HtmlHelperTest.php | 4 + lib/Cake/View/Helper.php | 6 +- lib/Cake/View/Helper/HtmlHelper.php | 89 ------------------- 3 files changed, 7 insertions(+), 92 deletions(-) diff --git a/lib/Cake/Test/Case/View/Helper/HtmlHelperTest.php b/lib/Cake/Test/Case/View/Helper/HtmlHelperTest.php index 5c3844eb3..80e018d34 100644 --- a/lib/Cake/Test/Case/View/Helper/HtmlHelperTest.php +++ b/lib/Cake/Test/Case/View/Helper/HtmlHelperTest.php @@ -1521,6 +1521,10 @@ class HtmlHelperTest extends CakeTestCase { } $this->assertEquals($helper->parseAttributes(array('compact')), ' compact="compact"'); + $attrs = array('class' => array('foo', 'bar')); + $expected = ' class="foo bar"'; + $this->assertEquals(' class="foo bar"', $helper->parseAttributes($attrs)); + $helper = new Html5TestHelper($this->View); $expected = ' require'; $this->assertEquals($helper->parseAttributes(array('require')), $expected); diff --git a/lib/Cake/View/Helper.php b/lib/Cake/View/Helper.php index ade59beec..3e3270221 100644 --- a/lib/Cake/View/Helper.php +++ b/lib/Cake/View/Helper.php @@ -356,7 +356,7 @@ class Helper extends Object { * @param string $insertBefore String to be inserted before options. * @param string $insertAfter String to be inserted after options. * @return string Composed attributes. - * @deprecated This method has been moved to HtmlHelper + * @deprecated This method will be moved to HtmlHelper in 3.0 */ protected function _parseAttributes($options, $exclude = null, $insertBefore = ' ', $insertAfter = null) { if (!is_string($options)) { @@ -390,12 +390,12 @@ class Helper extends Object { * @param string $value The value of the attribute to create. * @param boolean $escape Define if the value must be escaped * @return string The composed attribute. - * @deprecated This method has been moved to HtmlHelper + * @deprecated This method will be moved to HtmlHelper in 3.0 */ protected function _formatAttribute($key, $value, $escape = true) { $attribute = ''; if (is_array($value)) { - $value = ''; + $value = implode(' ' , $value); } if (is_numeric($key)) { diff --git a/lib/Cake/View/Helper/HtmlHelper.php b/lib/Cake/View/Helper/HtmlHelper.php index 1396922e2..dd5157d3d 100644 --- a/lib/Cake/View/Helper/HtmlHelper.php +++ b/lib/Cake/View/Helper/HtmlHelper.php @@ -1029,93 +1029,4 @@ class HtmlHelper extends AppHelper { return $configs; } -/** - * Returns a space-delimited string with items of the $options array. If a - * key of $options array happens to be one of: - * - * - 'compact' - * - 'checked' - * - 'declare' - * - 'readonly' - * - 'disabled' - * - 'selected' - * - 'defer' - * - 'ismap' - * - 'nohref' - * - 'noshade' - * - 'nowrap' - * - 'multiple' - * - 'noresize' - * - * And its value is one of: - * - * - '1' (string) - * - 1 (integer) - * - true (boolean) - * - 'true' (string) - * - * Then the value will be reset to be identical with key's name. - * If the value is not one of these 3, the parameter is not output. - * - * 'escape' is a special option in that it controls the conversion of - * attributes to their html-entity encoded equivalents. Set to false to disable html-encoding. - * - * If value for any option key is set to `null` or `false`, that option will be excluded from output. - * - * @param array $options Array of options. - * @param array $exclude Array of options to be excluded, the options here will not be part of the return. - * @param string $insertBefore String to be inserted before options. - * @param string $insertAfter String to be inserted after options. - * @return string Composed attributes. - */ - protected function _parseAttributes($options, $exclude = null, $insertBefore = ' ', $insertAfter = null) { - if (is_array($options)) { - $options = array_merge(array('escape' => true), $options); - - if (!is_array($exclude)) { - $exclude = array(); - } - $filtered = array_diff_key($options, array_merge(array_flip($exclude), array('escape' => true))); - $escape = $options['escape']; - $attributes = array(); - - foreach ($filtered as $key => $value) { - if ($value !== false && $value !== null) { - $attributes[] = $this->_formatAttribute($key, $value, $escape); - } - } - $out = implode(' ', $attributes); - } else { - $out = $options; - } - return $out ? $insertBefore . $out . $insertAfter : ''; - } - -/** - * Formats an individual attribute, and returns the string value of the composed attribute. - * Works with minimized attributes that have the same value as their name such as 'disabled' and 'checked' - * - * @param string $key The name of the attribute to create - * @param string $value The value of the attribute to create. - * @param boolean $escape Define if the value must be escaped - * @return string The composed attribute. - */ - protected function _formatAttribute($key, $value, $escape = true) { - $attribute = ''; - if (is_array($value)) { - $value = ''; - } - - if (is_numeric($key)) { - $attribute = sprintf($this->_minimizedAttributeFormat, $value, $value); - } elseif (in_array($key, $this->_minimizedAttributes)) { - if ($value === 1 || $value === true || $value === 'true' || $value === '1' || $value == $key) { - $attribute = sprintf($this->_minimizedAttributeFormat, $key, $key); - } - } else { - $attribute = sprintf($this->_attributeFormat, $key, ($escape ? h($value) : $value)); - } - return $attribute; - } - } From ba02cf7a9a60a79b107c884e06656984de3e1507 Mon Sep 17 00:00:00 2001 From: Majna Date: Wed, 4 Jan 2012 21:40:14 +0100 Subject: [PATCH 42/44] Fix incorrect doc block in CakeSession. --- lib/Cake/Model/Datasource/CakeSession.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Cake/Model/Datasource/CakeSession.php b/lib/Cake/Model/Datasource/CakeSession.php index e0d89222f..3cb8c0302 100644 --- a/lib/Cake/Model/Datasource/CakeSession.php +++ b/lib/Cake/Model/Datasource/CakeSession.php @@ -121,7 +121,7 @@ class CakeSession { /** * Number of requests that can occur during a session time without the session being renewed. - * This feature is only used when `Session.harden` is set to true. + * This feature is only used when `Session.autoRegenerate` is set to true. * * @var integer * @see CakeSession::_checkValid() From 75b4e7d5500728f849fb319e4d698ef8221bb252 Mon Sep 17 00:00:00 2001 From: Majna Date: Wed, 4 Jan 2012 22:09:40 +0100 Subject: [PATCH 43/44] Remove unused property CakeSession::$security. --- lib/Cake/Model/Datasource/CakeSession.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/Cake/Model/Datasource/CakeSession.php b/lib/Cake/Model/Datasource/CakeSession.php index 3cb8c0302..b6ef75f70 100644 --- a/lib/Cake/Model/Datasource/CakeSession.php +++ b/lib/Cake/Model/Datasource/CakeSession.php @@ -70,13 +70,6 @@ class CakeSession { */ public static $lastError = null; -/** - * 'Security.level' setting, "high", "medium", or "low". - * - * @var string - */ - public static $security = null; - /** * Start time for this session. * From 8ad989cabe6a18b106aee74bfd74c139ed8357da Mon Sep 17 00:00:00 2001 From: Majna Date: Thu, 5 Jan 2012 18:31:00 +0100 Subject: [PATCH 44/44] Fix dispatching event when event key name is used. --- lib/Cake/Event/CakeEventManager.php | 2 +- .../Test/Case/Event/CakeEventManagerTest.php | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/Cake/Event/CakeEventManager.php b/lib/Cake/Event/CakeEventManager.php index f4aaaa596..8b3ceb0f7 100644 --- a/lib/Cake/Event/CakeEventManager.php +++ b/lib/Cake/Event/CakeEventManager.php @@ -224,7 +224,7 @@ class CakeEventManager { */ public function dispatch($event) { if (is_string($event)) { - $Event = new CakeEvent($event); + $event = new CakeEvent($event); } if (!$this->_isGlobal) { diff --git a/lib/Cake/Test/Case/Event/CakeEventManagerTest.php b/lib/Cake/Test/Case/Event/CakeEventManagerTest.php index f43feeebb..98d1df9e0 100644 --- a/lib/Cake/Test/Case/Event/CakeEventManagerTest.php +++ b/lib/Cake/Test/Case/Event/CakeEventManagerTest.php @@ -210,6 +210,22 @@ class CakeEventManagerTest extends CakeTestCase { $manager->dispatch($event); } +/** + * Tests event dispatching using event key name + * + * @return void + */ + public function testDispatchWithKeyName() { + $manager = new CakeEventManager; + $listener = new CakeEventTestListener; + $manager->attach(array($listener, 'listenerFunction'), 'fake.event'); + $event = 'fake.event'; + $manager->dispatch($event); + + $expected = array('listenerFunction'); + $this->assertEquals($expected, $listener->callStack); + } + /** * Tests event dispatching with a return value *