TranslateBehaviorTest.php 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\TestCase\ORM\Behavior;
  16. use Cake\Collection\Collection;
  17. use Cake\I18n\I18n;
  18. use Cake\ORM\Behavior\Translate\TranslateTrait;
  19. use Cake\ORM\Entity;
  20. use Cake\ORM\TableRegistry;
  21. use Cake\TestSuite\TestCase;
  22. /**
  23. * Stub entity class
  24. */
  25. class Article extends Entity
  26. {
  27. use TranslateTrait;
  28. }
  29. /**
  30. * Translate behavior test case
  31. */
  32. class TranslateBehaviorTest extends TestCase
  33. {
  34. /**
  35. * fixtures
  36. *
  37. * @var array
  38. */
  39. public $fixtures = [
  40. 'core.articles',
  41. 'core.authors',
  42. 'core.comments',
  43. 'core.translates'
  44. ];
  45. public function tearDown()
  46. {
  47. parent::tearDown();
  48. I18n::locale(I18n::defaultLocale());
  49. TableRegistry::clear();
  50. }
  51. /**
  52. * Returns an array with all the translations found for a set of records
  53. *
  54. * @param array|\Traversable $data
  55. * @return Collection
  56. */
  57. protected function _extractTranslations($data)
  58. {
  59. return (new Collection($data))->map(function ($row) {
  60. $translations = $row->get('_translations');
  61. if (!$translations) {
  62. return [];
  63. }
  64. return array_map(function ($t) {
  65. return $t->toArray();
  66. }, $translations);
  67. });
  68. }
  69. /**
  70. * Tests that custom translation tables are respected
  71. *
  72. * @return void
  73. */
  74. public function testCustomTranslationTable()
  75. {
  76. $table = TableRegistry::get('Articles');
  77. $table->addBehavior('Translate', [
  78. 'translationTable' => '\TestApp\Model\Table\I18nTable',
  79. 'fields' => ['title', 'body']
  80. ]);
  81. $items = $table->associations();
  82. $i18n = $items->getByProperty('_i18n');
  83. $this->assertEquals('\TestApp\Model\Table\I18nTable', $i18n->name());
  84. $this->assertInstanceOf('TestApp\Model\Table\I18nTable', $i18n->target());
  85. $this->assertEquals('test_custom_i18n_datasource', $i18n->target()->connection()->configName());
  86. $this->assertEquals('custom_i18n_table', $i18n->target()->table());
  87. }
  88. /**
  89. * Tests that the strategy can be changed for i18n
  90. *
  91. * @return void
  92. */
  93. public function testStrategy()
  94. {
  95. $table = TableRegistry::get('Articles');
  96. $table->addBehavior('Translate', [
  97. 'strategy' => 'select',
  98. 'fields' => ['title', 'body']
  99. ]);
  100. $items = $table->associations();
  101. $i18n = $items->getByProperty('_i18n');
  102. $this->assertEquals('select', $i18n->strategy());
  103. }
  104. /**
  105. * Tests that fields from a translated model are overridden
  106. *
  107. * @return void
  108. */
  109. public function testFindSingleLocale()
  110. {
  111. $table = TableRegistry::get('Articles');
  112. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  113. $table->locale('eng');
  114. $results = $table->find()->combine('title', 'body', 'id')->toArray();
  115. $expected = [
  116. 1 => ['Title #1' => 'Content #1'],
  117. 2 => ['Title #2' => 'Content #2'],
  118. 3 => ['Title #3' => 'Content #3'],
  119. ];
  120. $this->assertSame($expected, $results);
  121. }
  122. /**
  123. * Test that iterating in a formatResults() does not drop data.
  124. *
  125. * @return void
  126. */
  127. public function testFindTranslationsFormatResultsIteration()
  128. {
  129. $table = TableRegistry::get('Articles');
  130. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  131. $table->locale('eng');
  132. $results = $table->find('translations')
  133. ->limit(1)
  134. ->formatResults(function ($results) {
  135. foreach ($results as $res) {
  136. $res->first = 'val';
  137. }
  138. foreach ($results as $res) {
  139. $res->second = 'loop';
  140. }
  141. return $results;
  142. })
  143. ->toArray();
  144. $this->assertCount(1, $results);
  145. $this->assertSame('Title #1', $results[0]->title);
  146. $this->assertSame('val', $results[0]->first);
  147. $this->assertSame('loop', $results[0]->second);
  148. $this->assertNotEmpty($results[0]->_translations);
  149. }
  150. /**
  151. * Tests that fields from a translated model use the I18n class locale
  152. * and that it propogates to associated models
  153. *
  154. * @return void
  155. */
  156. public function testFindSingleLocaleAssociatedEnv()
  157. {
  158. I18n::locale('eng');
  159. $table = TableRegistry::get('Articles');
  160. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  161. $table->hasMany('Comments');
  162. $table->Comments->addBehavior('Translate', ['fields' => ['comment']]);
  163. $results = $table->find()
  164. ->select(['id', 'title', 'body'])
  165. ->contain(['Comments' => ['fields' => ['article_id', 'comment']]])
  166. ->hydrate(false)
  167. ->toArray();
  168. $expected = [
  169. [
  170. 'id' => 1,
  171. 'title' => 'Title #1',
  172. 'body' => 'Content #1',
  173. 'comments' => [
  174. ['article_id' => 1, 'comment' => 'Comment #1', '_locale' => 'eng'],
  175. ['article_id' => 1, 'comment' => 'Comment #2', '_locale' => 'eng'],
  176. ['article_id' => 1, 'comment' => 'Comment #3', '_locale' => 'eng'],
  177. ['article_id' => 1, 'comment' => 'Comment #4', '_locale' => 'eng']
  178. ],
  179. '_locale' => 'eng'
  180. ],
  181. [
  182. 'id' => 2,
  183. 'title' => 'Title #2',
  184. 'body' => 'Content #2',
  185. 'comments' => [
  186. ['article_id' => 2, 'comment' => 'First Comment for Second Article', '_locale' => 'eng'],
  187. ['article_id' => 2, 'comment' => 'Second Comment for Second Article', '_locale' => 'eng']
  188. ],
  189. '_locale' => 'eng'
  190. ],
  191. [
  192. 'id' => 3,
  193. 'title' => 'Title #3',
  194. 'body' => 'Content #3',
  195. 'comments' => [],
  196. '_locale' => 'eng'
  197. ]
  198. ];
  199. $this->assertSame($expected, $results);
  200. I18n::locale('spa');
  201. $results = $table->find()
  202. ->select(['id', 'title', 'body'])
  203. ->contain([
  204. 'Comments' => [
  205. 'fields' => ['article_id', 'comment'],
  206. 'sort' => ['Comments.id' => 'ASC']
  207. ]
  208. ])
  209. ->hydrate(false)
  210. ->toArray();
  211. $expected = [
  212. [
  213. 'id' => 1,
  214. 'title' => 'First Article',
  215. 'body' => 'Contenido #1',
  216. 'comments' => [
  217. ['article_id' => 1, 'comment' => 'First Comment for First Article', '_locale' => 'spa'],
  218. ['article_id' => 1, 'comment' => 'Second Comment for First Article', '_locale' => 'spa'],
  219. ['article_id' => 1, 'comment' => 'Third Comment for First Article', '_locale' => 'spa'],
  220. ['article_id' => 1, 'comment' => 'Comentario #4', '_locale' => 'spa']
  221. ],
  222. '_locale' => 'spa'
  223. ],
  224. [
  225. 'id' => 2,
  226. 'title' => 'Second Article',
  227. 'body' => 'Second Article Body',
  228. 'comments' => [
  229. ['article_id' => 2, 'comment' => 'First Comment for Second Article', '_locale' => 'spa'],
  230. ['article_id' => 2, 'comment' => 'Second Comment for Second Article', '_locale' => 'spa']
  231. ],
  232. '_locale' => 'spa'
  233. ],
  234. [
  235. 'id' => 3,
  236. 'title' => 'Third Article',
  237. 'body' => 'Third Article Body',
  238. 'comments' => [],
  239. '_locale' => 'spa'
  240. ]
  241. ];
  242. $this->assertSame($expected, $results);
  243. }
  244. /**
  245. * Tests that fields from a translated model are not overridden if translation
  246. * is null
  247. *
  248. * @return void
  249. */
  250. public function testFindSingleLocaleWithNullTranslation()
  251. {
  252. $table = TableRegistry::get('Comments');
  253. $table->addBehavior('Translate', ['fields' => ['comment']]);
  254. $table->locale('spa');
  255. $results = $table->find()
  256. ->where(['Comments.id' => 6])
  257. ->combine('id', 'comment')->toArray();
  258. $expected = [6 => 'Second Comment for Second Article'];
  259. $this->assertSame($expected, $results);
  260. }
  261. /**
  262. * Tests that overriding fields with the translate behavior works when
  263. * using conditions and that all other columns are preserved
  264. *
  265. * @return void
  266. */
  267. public function testFindSingleLocaleWithConditions()
  268. {
  269. $table = TableRegistry::get('Articles');
  270. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  271. $table->locale('eng');
  272. $results = $table->find()
  273. ->where(['Articles.id' => 2])
  274. ->all();
  275. $this->assertCount(1, $results);
  276. $row = $results->first();
  277. $expected = [
  278. 'id' => 2,
  279. 'title' => 'Title #2',
  280. 'body' => 'Content #2',
  281. 'author_id' => 3,
  282. 'published' => 'Y',
  283. '_locale' => 'eng'
  284. ];
  285. $this->assertEquals($expected, $row->toArray());
  286. }
  287. /**
  288. * Tests that translating fields work when other formatters are used
  289. *
  290. * @return void
  291. */
  292. public function testFindList()
  293. {
  294. $table = TableRegistry::get('Articles');
  295. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  296. $table->locale('eng');
  297. $results = $table->find('list')->toArray();
  298. $expected = [1 => 'Title #1', 2 => 'Title #2', 3 => 'Title #3'];
  299. $this->assertSame($expected, $results);
  300. }
  301. /**
  302. * Tests that the query count return the correct results
  303. *
  304. * @return void
  305. */
  306. public function testFindCount()
  307. {
  308. $table = TableRegistry::get('Articles');
  309. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  310. $table->locale('eng');
  311. $this->assertEquals(3, $table->find()->count());
  312. }
  313. /**
  314. * Tests that it is possible to get all translated fields at once
  315. *
  316. * @return void
  317. */
  318. public function testFindTranslations()
  319. {
  320. $table = TableRegistry::get('Articles');
  321. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  322. $results = $table->find('translations');
  323. $expected = [
  324. [
  325. 'eng' => ['title' => 'Title #1', 'body' => 'Content #1', 'description' => 'Description #1', 'locale' => 'eng'],
  326. 'deu' => ['title' => 'Titel #1', 'body' => 'Inhalt #1', 'locale' => 'deu'],
  327. 'cze' => ['title' => 'Titulek #1', 'body' => 'Obsah #1', 'locale' => 'cze'],
  328. 'spa' => ['body' => 'Contenido #1', 'locale' => 'spa', 'description' => '']
  329. ],
  330. [
  331. 'eng' => ['title' => 'Title #2', 'body' => 'Content #2', 'locale' => 'eng'],
  332. 'deu' => ['title' => 'Titel #2', 'body' => 'Inhalt #2', 'locale' => 'deu'],
  333. 'cze' => ['title' => 'Titulek #2', 'body' => 'Obsah #2', 'locale' => 'cze']
  334. ],
  335. [
  336. 'eng' => ['title' => 'Title #3', 'body' => 'Content #3', 'locale' => 'eng'],
  337. 'deu' => ['title' => 'Titel #3', 'body' => 'Inhalt #3', 'locale' => 'deu'],
  338. 'cze' => ['title' => 'Titulek #3', 'body' => 'Obsah #3', 'locale' => 'cze']
  339. ]
  340. ];
  341. $translations = $this->_extractTranslations($results);
  342. $this->assertEquals($expected, $translations->toArray());
  343. $expected = [
  344. 1 => ['First Article' => 'First Article Body'],
  345. 2 => ['Second Article' => 'Second Article Body'],
  346. 3 => ['Third Article' => 'Third Article Body']
  347. ];
  348. $grouped = $results->combine('title', 'body', 'id');
  349. $this->assertEquals($expected, $grouped->toArray());
  350. }
  351. /**
  352. * Tests that it is possible to request just a few translations
  353. *
  354. * @return void
  355. */
  356. public function testFindFilteredTranslations()
  357. {
  358. $table = TableRegistry::get('Articles');
  359. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  360. $results = $table->find('translations', ['locales' => ['deu', 'cze']]);
  361. $expected = [
  362. [
  363. 'deu' => ['title' => 'Titel #1', 'body' => 'Inhalt #1', 'locale' => 'deu'],
  364. 'cze' => ['title' => 'Titulek #1', 'body' => 'Obsah #1', 'locale' => 'cze']
  365. ],
  366. [
  367. 'deu' => ['title' => 'Titel #2', 'body' => 'Inhalt #2', 'locale' => 'deu'],
  368. 'cze' => ['title' => 'Titulek #2', 'body' => 'Obsah #2', 'locale' => 'cze']
  369. ],
  370. [
  371. 'deu' => ['title' => 'Titel #3', 'body' => 'Inhalt #3', 'locale' => 'deu'],
  372. 'cze' => ['title' => 'Titulek #3', 'body' => 'Obsah #3', 'locale' => 'cze']
  373. ]
  374. ];
  375. $translations = $this->_extractTranslations($results);
  376. $this->assertEquals($expected, $translations->toArray());
  377. $expected = [
  378. 1 => ['First Article' => 'First Article Body'],
  379. 2 => ['Second Article' => 'Second Article Body'],
  380. 3 => ['Third Article' => 'Third Article Body']
  381. ];
  382. $grouped = $results->combine('title', 'body', 'id');
  383. $this->assertEquals($expected, $grouped->toArray());
  384. }
  385. /**
  386. * Tests that it is possible to combine find('list') and find('translations')
  387. *
  388. * @return void
  389. */
  390. public function testFindTranslationsList()
  391. {
  392. $table = TableRegistry::get('Articles');
  393. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  394. $results = $table
  395. ->find('list', [
  396. 'keyField' => 'title',
  397. 'valueField' => '_translations.deu.title',
  398. 'groupField' => 'id'
  399. ])
  400. ->find('translations', ['locales' => ['deu']]);
  401. $expected = [
  402. 1 => ['First Article' => 'Titel #1'],
  403. 2 => ['Second Article' => 'Titel #2'],
  404. 3 => ['Third Article' => 'Titel #3']
  405. ];
  406. $this->assertEquals($expected, $results->toArray());
  407. }
  408. /**
  409. * Tests that you can both override fields and find all translations
  410. *
  411. * @return void
  412. */
  413. public function testFindTranslationsWithFieldOverriding()
  414. {
  415. $table = TableRegistry::get('Articles');
  416. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  417. $table->locale('cze');
  418. $results = $table->find('translations', ['locales' => ['deu', 'cze']]);
  419. $expected = [
  420. [
  421. 'deu' => ['title' => 'Titel #1', 'body' => 'Inhalt #1', 'locale' => 'deu'],
  422. 'cze' => ['title' => 'Titulek #1', 'body' => 'Obsah #1', 'locale' => 'cze']
  423. ],
  424. [
  425. 'deu' => ['title' => 'Titel #2', 'body' => 'Inhalt #2', 'locale' => 'deu'],
  426. 'cze' => ['title' => 'Titulek #2', 'body' => 'Obsah #2', 'locale' => 'cze']
  427. ],
  428. [
  429. 'deu' => ['title' => 'Titel #3', 'body' => 'Inhalt #3', 'locale' => 'deu'],
  430. 'cze' => ['title' => 'Titulek #3', 'body' => 'Obsah #3', 'locale' => 'cze']
  431. ]
  432. ];
  433. $translations = $this->_extractTranslations($results);
  434. $this->assertEquals($expected, $translations->toArray());
  435. $expected = [
  436. 1 => ['Titulek #1' => 'Obsah #1'],
  437. 2 => ['Titulek #2' => 'Obsah #2'],
  438. 3 => ['Titulek #3' => 'Obsah #3']
  439. ];
  440. $grouped = $results->combine('title', 'body', 'id');
  441. $this->assertEquals($expected, $grouped->toArray());
  442. }
  443. /**
  444. * Tests that fields can be overridden in a hasMany association
  445. *
  446. * @return void
  447. */
  448. public function testFindSingleLocaleHasMany()
  449. {
  450. $table = TableRegistry::get('Articles');
  451. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  452. $table->hasMany('Comments');
  453. $comments = $table->hasMany('Comments')->target();
  454. $comments->addBehavior('Translate', ['fields' => ['comment']]);
  455. $table->locale('eng');
  456. $comments->locale('eng');
  457. $results = $table->find()->contain(['Comments' => function ($q) {
  458. return $q->select(['id', 'comment', 'article_id']);
  459. }]);
  460. $list = new Collection($results->first()->comments);
  461. $expected = [
  462. 1 => 'Comment #1',
  463. 2 => 'Comment #2',
  464. 3 => 'Comment #3',
  465. 4 => 'Comment #4'
  466. ];
  467. $this->assertEquals($expected, $list->combine('id', 'comment')->toArray());
  468. }
  469. /**
  470. * Test that it is possible to bring translations from hasMany relations
  471. *
  472. * @return void
  473. */
  474. public function testTranslationsHasMany()
  475. {
  476. $table = TableRegistry::get('Articles');
  477. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  478. $table->hasMany('Comments');
  479. $comments = $table->hasMany('Comments')->target();
  480. $comments->addBehavior('Translate', ['fields' => ['comment']]);
  481. $results = $table->find('translations')->contain([
  482. 'Comments' => function ($q) {
  483. return $q->find('translations')->select(['id', 'comment', 'article_id']);
  484. }
  485. ]);
  486. $comments = $results->first()->comments;
  487. $expected = [
  488. [
  489. 'eng' => ['comment' => 'Comment #1', 'locale' => 'eng']
  490. ],
  491. [
  492. 'eng' => ['comment' => 'Comment #2', 'locale' => 'eng']
  493. ],
  494. [
  495. 'eng' => ['comment' => 'Comment #3', 'locale' => 'eng']
  496. ],
  497. [
  498. 'eng' => ['comment' => 'Comment #4', 'locale' => 'eng'],
  499. 'spa' => ['comment' => 'Comentario #4', 'locale' => 'spa']
  500. ]
  501. ];
  502. $translations = $this->_extractTranslations($comments);
  503. $this->assertEquals($expected, $translations->toArray());
  504. }
  505. /**
  506. * Tests that it is possible to both override fields with a translation and
  507. * also find separately other translations
  508. *
  509. * @return void
  510. */
  511. public function testTranslationsHasManyWithOverride()
  512. {
  513. $table = TableRegistry::get('Articles');
  514. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  515. $table->hasMany('Comments');
  516. $comments = $table->hasMany('Comments')->target();
  517. $comments->addBehavior('Translate', ['fields' => ['comment']]);
  518. $table->locale('cze');
  519. $comments->locale('eng');
  520. $results = $table->find('translations')->contain([
  521. 'Comments' => function ($q) {
  522. return $q->find('translations')->select(['id', 'comment', 'article_id']);
  523. }
  524. ]);
  525. $comments = $results->first()->comments;
  526. $expected = [
  527. 1 => 'Comment #1',
  528. 2 => 'Comment #2',
  529. 3 => 'Comment #3',
  530. 4 => 'Comment #4'
  531. ];
  532. $list = new Collection($comments);
  533. $this->assertEquals($expected, $list->combine('id', 'comment')->toArray());
  534. $expected = [
  535. [
  536. 'eng' => ['comment' => 'Comment #1', 'locale' => 'eng']
  537. ],
  538. [
  539. 'eng' => ['comment' => 'Comment #2', 'locale' => 'eng']
  540. ],
  541. [
  542. 'eng' => ['comment' => 'Comment #3', 'locale' => 'eng']
  543. ],
  544. [
  545. 'eng' => ['comment' => 'Comment #4', 'locale' => 'eng'],
  546. 'spa' => ['comment' => 'Comentario #4', 'locale' => 'spa']
  547. ]
  548. ];
  549. $translations = $this->_extractTranslations($comments);
  550. $this->assertEquals($expected, $translations->toArray());
  551. $this->assertEquals('Titulek #1', $results->first()->title);
  552. $this->assertEquals('Obsah #1', $results->first()->body);
  553. }
  554. /**
  555. * Tests that it is possible to translate belongsTo associations
  556. *
  557. * @return void
  558. */
  559. public function testFindSingleLocaleBelongsto()
  560. {
  561. $table = TableRegistry::get('Articles');
  562. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  563. $authors = $table->belongsTo('Authors')->target();
  564. $authors->addBehavior('Translate', ['fields' => ['name']]);
  565. $table->locale('eng');
  566. $authors->locale('eng');
  567. $results = $table->find()
  568. ->select(['title', 'body'])
  569. ->order(['title' => 'asc'])
  570. ->contain(['Authors' => function ($q) {
  571. return $q->select(['id', 'name']);
  572. }]);
  573. $expected = [
  574. [
  575. 'title' => 'Title #1',
  576. 'body' => 'Content #1',
  577. 'author' => ['id' => 1, 'name' => 'May-rianoh', '_locale' => 'eng'],
  578. '_locale' => 'eng'
  579. ],
  580. [
  581. 'title' => 'Title #2',
  582. 'body' => 'Content #2',
  583. 'author' => ['id' => 3, 'name' => 'larry', '_locale' => 'eng'],
  584. '_locale' => 'eng'
  585. ],
  586. [
  587. 'title' => 'Title #3',
  588. 'body' => 'Content #3',
  589. 'author' => ['id' => 1, 'name' => 'May-rianoh', '_locale' => 'eng'],
  590. '_locale' => 'eng'
  591. ]
  592. ];
  593. $results = array_map(function ($r) {
  594. return $r->toArray();
  595. }, $results->toArray());
  596. $this->assertEquals($expected, $results);
  597. }
  598. /**
  599. * Tests that updating an existing record translations work
  600. *
  601. * @return void
  602. */
  603. public function testUpdateSingleLocale()
  604. {
  605. $table = TableRegistry::get('Articles');
  606. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  607. $table->locale('eng');
  608. $article = $table->find()->first();
  609. $this->assertEquals(1, $article->get('id'));
  610. $article->set('title', 'New translated article');
  611. $table->save($article);
  612. $this->assertNull($article->get('_i18n'));
  613. $article = $table->find()->first();
  614. $this->assertEquals(1, $article->get('id'));
  615. $this->assertEquals('New translated article', $article->get('title'));
  616. $this->assertEquals('Content #1', $article->get('body'));
  617. $table->locale(false);
  618. $article = $table->find()->first();
  619. $this->assertEquals(1, $article->get('id'));
  620. $this->assertEquals('First Article', $article->get('title'));
  621. $table->locale('eng');
  622. $article->set('title', 'Wow, such translated article');
  623. $article->set('body', 'A translated body');
  624. $table->save($article);
  625. $this->assertNull($article->get('_i18n'));
  626. $article = $table->find()->first();
  627. $this->assertEquals(1, $article->get('id'));
  628. $this->assertEquals('Wow, such translated article', $article->get('title'));
  629. $this->assertEquals('A translated body', $article->get('body'));
  630. }
  631. /**
  632. * Tests adding new translation to a record
  633. *
  634. * @return void
  635. */
  636. public function testInsertNewTranslations()
  637. {
  638. $table = TableRegistry::get('Articles');
  639. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  640. $table->locale('fra');
  641. $article = $table->find()->first();
  642. $this->assertEquals(1, $article->get('id'));
  643. $article->set('title', 'Le titre');
  644. $table->save($article);
  645. $this->assertEquals('fra', $article->get('_locale'));
  646. $article = $table->find()->first();
  647. $this->assertEquals(1, $article->get('id'));
  648. $this->assertEquals('Le titre', $article->get('title'));
  649. $this->assertEquals('First Article Body', $article->get('body'));
  650. $article->set('title', 'Un autre titre');
  651. $article->set('body', 'Le contenu');
  652. $table->save($article);
  653. $this->assertNull($article->get('_i18n'));
  654. $article = $table->find()->first();
  655. $this->assertEquals('Un autre titre', $article->get('title'));
  656. $this->assertEquals('Le contenu', $article->get('body'));
  657. }
  658. /**
  659. * Tests that it is possible to use the _locale property to specify the language
  660. * to use for saving an entity
  661. *
  662. * @return void
  663. */
  664. public function testUpdateTranslationWithLocaleInEntity()
  665. {
  666. $table = TableRegistry::get('Articles');
  667. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  668. $article = $table->find()->first();
  669. $this->assertEquals(1, $article->get('id'));
  670. $article->set('_locale', 'fra');
  671. $article->set('title', 'Le titre');
  672. $table->save($article);
  673. $this->assertNull($article->get('_i18n'));
  674. $article = $table->find()->first();
  675. $this->assertEquals(1, $article->get('id'));
  676. $this->assertEquals('First Article', $article->get('title'));
  677. $this->assertEquals('First Article Body', $article->get('body'));
  678. $table->locale('fra');
  679. $article = $table->find()->first();
  680. $this->assertEquals(1, $article->get('id'));
  681. $this->assertEquals('Le titre', $article->get('title'));
  682. $this->assertEquals('First Article Body', $article->get('body'));
  683. }
  684. /**
  685. * Tests that translations are added to the whitelist of associations to be
  686. * saved
  687. *
  688. * @return void
  689. */
  690. public function testSaveTranslationWithAssociationWhitelist()
  691. {
  692. $table = TableRegistry::get('Articles');
  693. $table->hasMany('Comments');
  694. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  695. $table->locale('fra');
  696. $article = $table->find()->first();
  697. $this->assertEquals(1, $article->get('id'));
  698. $article->set('title', 'Le titre');
  699. $table->save($article, ['associated' => ['Comments']]);
  700. $this->assertNull($article->get('_i18n'));
  701. $article = $table->find()->first();
  702. $this->assertEquals('Le titre', $article->get('title'));
  703. }
  704. /**
  705. * Tests that after deleting a translated entity, all translations are also removed
  706. *
  707. * @return void
  708. */
  709. public function testDelete()
  710. {
  711. $table = TableRegistry::get('Articles');
  712. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  713. $article = $table->find()->first();
  714. $this->assertTrue($table->delete($article));
  715. $translations = TableRegistry::get('I18n')->find()
  716. ->where(['model' => 'Articles', 'foreign_key' => $article->id])
  717. ->count();
  718. $this->assertEquals(0, $translations);
  719. }
  720. /**
  721. * Tests saving multiple translations at once when the translations already
  722. * exist in the database
  723. *
  724. * @return void
  725. */
  726. public function testSaveMultipleTranslations()
  727. {
  728. $table = TableRegistry::get('Articles');
  729. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  730. $article = $results = $table->find('translations')->first();
  731. $translations = $article->get('_translations');
  732. $translations['deu']->set('title', 'Another title');
  733. $translations['eng']->set('body', 'Another body');
  734. $article->set('_translations', $translations);
  735. $table->save($article);
  736. $this->assertNull($article->get('_i18n'));
  737. $article = $results = $table->find('translations')->first();
  738. $translations = $article->get('_translations');
  739. $this->assertEquals('Another title', $translations['deu']->get('title'));
  740. $this->assertEquals('Another body', $translations['eng']->get('body'));
  741. }
  742. /**
  743. * Tests saving multiple existing translations and adding new ones
  744. *
  745. * @return void
  746. */
  747. public function testSaveMultipleNewTranslations()
  748. {
  749. $table = TableRegistry::get('Articles');
  750. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  751. $article = $results = $table->find('translations')->first();
  752. $translations = $article->get('_translations');
  753. $translations['deu']->set('title', 'Another title');
  754. $translations['eng']->set('body', 'Another body');
  755. $translations['spa'] = new Entity(['title' => 'Titulo']);
  756. $translations['fre'] = new Entity(['title' => 'Titre']);
  757. $article->set('_translations', $translations);
  758. $table->save($article);
  759. $this->assertNull($article->get('_i18n'));
  760. $article = $results = $table->find('translations')->first();
  761. $translations = $article->get('_translations');
  762. $this->assertEquals('Another title', $translations['deu']->get('title'));
  763. $this->assertEquals('Another body', $translations['eng']->get('body'));
  764. $this->assertEquals('Titulo', $translations['spa']->get('title'));
  765. $this->assertEquals('Titre', $translations['fre']->get('title'));
  766. }
  767. /**
  768. * Tests that iterating a resultset twice when using the translations finder
  769. * will not cause any errors nor information loss
  770. *
  771. * @return void
  772. */
  773. public function testUseCountInFindTranslations()
  774. {
  775. $table = TableRegistry::get('Articles');
  776. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  777. $articles = $results = $table->find('translations');
  778. $all = $articles->all();
  779. $this->assertCount(3, $all);
  780. $article = $all->first();
  781. $this->assertNotEmpty($article->get('_translations'));
  782. }
  783. /**
  784. * Tests that multiple translations saved when having a default locale
  785. * are correctly saved
  786. *
  787. * @return void
  788. */
  789. public function testSavingWithNonDefaultLocale()
  790. {
  791. $table = TableRegistry::get('Articles');
  792. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  793. $table->entityClass(__NAMESPACE__ . '\Article');
  794. I18n::locale('fra');
  795. $translations = [
  796. 'fra' => ['title' => 'Un article'],
  797. 'spa' => ['title' => 'Un artículo']
  798. ];
  799. $article = $table->get(1);
  800. foreach ($translations as $lang => $data) {
  801. $article->translation($lang)->set($data, ['guard' => false]);
  802. }
  803. $table->save($article);
  804. $article = $table->find('translations')->where(['Articles.id' => 1])->first();
  805. $this->assertEquals('Un article', $article->translation('fra')->title);
  806. $this->assertEquals('Un artículo', $article->translation('spa')->title);
  807. }
  808. /**
  809. * Tests that translation queries are added to union queries as well.
  810. *
  811. * @return void
  812. */
  813. public function testTranslationWithUnionQuery()
  814. {
  815. $table = TableRegistry::get('Comments');
  816. $table->addBehavior('Translate', ['fields' => ['comment']]);
  817. $table->locale('spa');
  818. $query = $table->find()->where(['Comments.id' => 6]);
  819. $query2 = $table->find()->where(['Comments.id' => 5]);
  820. $query->union($query2);
  821. $results = $query->sortBy('id', SORT_ASC)->toList();
  822. $this->assertCount(2, $results);
  823. $this->assertEquals('First Comment for Second Article', $results[0]->comment);
  824. $this->assertEquals('Second Comment for Second Article', $results[1]->comment);
  825. }
  826. /**
  827. * Tests the use of `referenceName` config option.
  828. *
  829. * @return void
  830. */
  831. public function testAutoReferenceName()
  832. {
  833. $table = TableRegistry::get('Articles');
  834. $table->hasMany('OtherComments', ['className' => 'Comments']);
  835. $table->OtherComments->addBehavior(
  836. 'Translate',
  837. ['fields' => ['comment']]
  838. );
  839. $items = $table->OtherComments->associations();
  840. $association = $items->getByProperty('comment_translation');
  841. $this->assertNotEmpty($association, 'Translation association not found');
  842. $found = false;
  843. foreach ($association->conditions() as $key => $value) {
  844. if (strpos($key, 'comment_translation.model') !== false) {
  845. $found = true;
  846. $this->assertEquals('Comments', $value);
  847. break;
  848. }
  849. }
  850. $this->assertTrue($found, '`referenceName` field condition on a Translation association was not found');
  851. }
  852. /**
  853. * Tests the use of unconventional `referenceName` config option.
  854. *
  855. * @return void
  856. */
  857. public function testChangingReferenceName()
  858. {
  859. $table = TableRegistry::get('Articles');
  860. $table->alias('FavoritePost');
  861. $table->addBehavior(
  862. 'Translate',
  863. ['fields' => ['body'], 'referenceName' => 'Posts']
  864. );
  865. $items = $table->associations();
  866. $association = $items->getByProperty('body_translation');
  867. $this->assertNotEmpty($association, 'Translation association not found');
  868. $found = false;
  869. foreach ($association->conditions() as $key => $value) {
  870. if (strpos($key, 'body_translation.model') !== false) {
  871. $found = true;
  872. $this->assertEquals('Posts', $value);
  873. break;
  874. }
  875. }
  876. $this->assertTrue($found, '`referenceName` field condition on a Translation association was not found');
  877. }
  878. /**
  879. * Tests that onlyTranslated will remove records from the result set
  880. * if they are not fully translated
  881. *
  882. * @return void
  883. */
  884. public function testFilterUntranslated()
  885. {
  886. $table = TableRegistry::get('Articles');
  887. $table->addBehavior('Translate', [
  888. 'fields' => ['title', 'body'],
  889. 'onlyTranslated' => true
  890. ]);
  891. $table->locale('eng');
  892. $results = $table->find()->where(['Articles.id' => 1])->all();
  893. $this->assertCount(1, $results);
  894. $table->locale('fr');
  895. $results = $table->find()->where(['Articles.id' => 1])->all();
  896. $this->assertCount(0, $results);
  897. }
  898. /**
  899. * Tests that records not translated in the current locale will not be
  900. * present in the results for the translations finder, and also proves
  901. * that this can be overridden.
  902. *
  903. * @return void
  904. */
  905. public function testFilterUntranslatedWithFinder()
  906. {
  907. $table = TableRegistry::get('Comments');
  908. $table->addBehavior('Translate', [
  909. 'fields' => ['comment'],
  910. 'onlyTranslated' => true
  911. ]);
  912. $table->locale('eng');
  913. $results = $table->find('translations')->all();
  914. $this->assertCount(4, $results);
  915. $table->locale('spa');
  916. $results = $table->find('translations')->all();
  917. $this->assertCount(1, $results);
  918. $table->locale('spa');
  919. $results = $table->find('translations', ['filterByCurrentLocale' => false])->all();
  920. $this->assertCount(6, $results);
  921. $table->locale('spa');
  922. $results = $table->find('translations')->all();
  923. $this->assertCount(1, $results);
  924. }
  925. /**
  926. * Tests that allowEmptyTranslations takes effect
  927. *
  928. * @return void
  929. */
  930. public function testEmptyTranslations()
  931. {
  932. $table = TableRegistry::get('Articles');
  933. $table->addBehavior('Translate', [
  934. 'fields' => ['title', 'body', 'description'],
  935. 'allowEmptyTranslations' => false,
  936. ]);
  937. $table->locale('spa');
  938. $result = $table->find()->first();
  939. $this->assertNull($result->description);
  940. }
  941. /**
  942. * Test save with clean translate fields
  943. *
  944. * @return void
  945. */
  946. public function testSaveWithCleanFields()
  947. {
  948. $table = TableRegistry::get('Articles');
  949. $table->addBehavior('Translate', ['fields' => ['title']]);
  950. $table->entityClass(__NAMESPACE__ . '\Article');
  951. I18n::locale('fra');
  952. $article = $table->get(1);
  953. $article->set('body', 'New Body');
  954. $table->save($article);
  955. $result = $table->get(1);
  956. $this->assertEquals('New Body', $result->body);
  957. $this->assertSame($article->title, $result->title);
  958. }
  959. }