TranslateBehaviorTest.php 61 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (https://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. (https://cakefoundation.org)
  11. * @link https://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license https://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\Locator\TableLocator;
  21. use Cake\TestSuite\TestCase;
  22. use Cake\Validation\Validator;
  23. /**
  24. * Stub entity class
  25. */
  26. class Article extends Entity
  27. {
  28. use TranslateTrait;
  29. }
  30. /**
  31. * Translate behavior test case
  32. */
  33. class TranslateBehaviorTest extends TestCase
  34. {
  35. /**
  36. * fixtures
  37. *
  38. * @var array
  39. */
  40. public $fixtures = [
  41. 'core.articles',
  42. 'core.authors',
  43. 'core.groups',
  44. 'core.special_tags',
  45. 'core.tags',
  46. 'core.comments',
  47. 'core.translates'
  48. ];
  49. public function tearDown()
  50. {
  51. parent::tearDown();
  52. I18n::setLocale(I18n::getDefaultLocale());
  53. $this->getTableLocator()->clear();
  54. }
  55. /**
  56. * Returns an array with all the translations found for a set of records
  57. *
  58. * @param array|\Traversable $data
  59. * @return Collection
  60. */
  61. protected function _extractTranslations($data)
  62. {
  63. return (new Collection($data))->map(function ($row) {
  64. $translations = $row->get('_translations');
  65. if (!$translations) {
  66. return [];
  67. }
  68. return array_map(function ($t) {
  69. return $t->toArray();
  70. }, $translations);
  71. });
  72. }
  73. /**
  74. * Tests that custom translation tables are respected
  75. *
  76. * @return void
  77. */
  78. public function testCustomTranslationTable()
  79. {
  80. $table = $this->getTableLocator()->get('Articles');
  81. $table->addBehavior('Translate', [
  82. 'translationTable' => '\TestApp\Model\Table\I18nTable',
  83. 'fields' => ['title', 'body']
  84. ]);
  85. $items = $table->associations();
  86. $i18n = $items->getByProperty('_i18n');
  87. $this->assertEquals('\TestApp\Model\Table\I18nTable', $i18n->getName());
  88. $this->assertInstanceOf('TestApp\Model\Table\I18nTable', $i18n->getTarget());
  89. $this->assertEquals('test_custom_i18n_datasource', $i18n->getTarget()->getConnection()->configName());
  90. $this->assertEquals('custom_i18n_table', $i18n->getTarget()->getTable());
  91. }
  92. /**
  93. * Tests that the strategy can be changed for i18n
  94. *
  95. * @return void
  96. */
  97. public function testStrategy()
  98. {
  99. $table = $this->getTableLocator()->get('Articles');
  100. $table->addBehavior('Translate', [
  101. 'strategy' => 'select',
  102. 'fields' => ['title', 'body']
  103. ]);
  104. $items = $table->associations();
  105. $i18n = $items->getByProperty('_i18n');
  106. $this->assertEquals('select', $i18n->getStrategy());
  107. }
  108. /**
  109. * Tests that fields from a translated model are overridden
  110. *
  111. * @return void
  112. */
  113. public function testFindSingleLocale()
  114. {
  115. $table = $this->getTableLocator()->get('Articles');
  116. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  117. $table->setLocale('eng');
  118. $results = $table->find()->combine('title', 'body', 'id')->toArray();
  119. $expected = [
  120. 1 => ['Title #1' => 'Content #1'],
  121. 2 => ['Title #2' => 'Content #2'],
  122. 3 => ['Title #3' => 'Content #3'],
  123. ];
  124. $this->assertSame($expected, $results);
  125. }
  126. /**
  127. * Test that iterating in a formatResults() does not drop data.
  128. *
  129. * @return void
  130. */
  131. public function testFindTranslationsFormatResultsIteration()
  132. {
  133. $table = $this->getTableLocator()->get('Articles');
  134. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  135. $table->setLocale('eng');
  136. $results = $table->find('translations')
  137. ->limit(1)
  138. ->formatResults(function ($results) {
  139. foreach ($results as $res) {
  140. $res->first = 'val';
  141. }
  142. foreach ($results as $res) {
  143. $res->second = 'loop';
  144. }
  145. return $results;
  146. })
  147. ->toArray();
  148. $this->assertCount(1, $results);
  149. $this->assertSame('Title #1', $results[0]->title);
  150. $this->assertSame('val', $results[0]->first);
  151. $this->assertSame('loop', $results[0]->second);
  152. $this->assertNotEmpty($results[0]->_translations);
  153. }
  154. /**
  155. * Tests that fields from a translated model use the I18n class locale
  156. * and that it propagates to associated models
  157. *
  158. * @return void
  159. */
  160. public function testFindSingleLocaleAssociatedEnv()
  161. {
  162. I18n::setLocale('eng');
  163. $table = $this->getTableLocator()->get('Articles');
  164. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  165. $table->hasMany('Comments');
  166. $table->Comments->addBehavior('Translate', ['fields' => ['comment']]);
  167. $results = $table->find()
  168. ->select(['id', 'title', 'body'])
  169. ->contain(['Comments' => ['fields' => ['article_id', 'comment']]])
  170. ->enableHydration(false)
  171. ->toArray();
  172. $expected = [
  173. [
  174. 'id' => 1,
  175. 'title' => 'Title #1',
  176. 'body' => 'Content #1',
  177. 'comments' => [
  178. ['article_id' => 1, 'comment' => 'Comment #1', '_locale' => 'eng'],
  179. ['article_id' => 1, 'comment' => 'Comment #2', '_locale' => 'eng'],
  180. ['article_id' => 1, 'comment' => 'Comment #3', '_locale' => 'eng'],
  181. ['article_id' => 1, 'comment' => 'Comment #4', '_locale' => 'eng']
  182. ],
  183. '_locale' => 'eng'
  184. ],
  185. [
  186. 'id' => 2,
  187. 'title' => 'Title #2',
  188. 'body' => 'Content #2',
  189. 'comments' => [
  190. ['article_id' => 2, 'comment' => 'First Comment for Second Article', '_locale' => 'eng'],
  191. ['article_id' => 2, 'comment' => 'Second Comment for Second Article', '_locale' => 'eng']
  192. ],
  193. '_locale' => 'eng'
  194. ],
  195. [
  196. 'id' => 3,
  197. 'title' => 'Title #3',
  198. 'body' => 'Content #3',
  199. 'comments' => [],
  200. '_locale' => 'eng'
  201. ]
  202. ];
  203. $this->assertSame($expected, $results);
  204. I18n::setLocale('spa');
  205. $results = $table->find()
  206. ->select(['id', 'title', 'body'])
  207. ->contain([
  208. 'Comments' => [
  209. 'fields' => ['article_id', 'comment'],
  210. 'sort' => ['Comments.id' => 'ASC']
  211. ]
  212. ])
  213. ->enableHydration(false)
  214. ->toArray();
  215. $expected = [
  216. [
  217. 'id' => 1,
  218. 'title' => 'First Article',
  219. 'body' => 'Contenido #1',
  220. 'comments' => [
  221. ['article_id' => 1, 'comment' => 'First Comment for First Article', '_locale' => 'spa'],
  222. ['article_id' => 1, 'comment' => 'Second Comment for First Article', '_locale' => 'spa'],
  223. ['article_id' => 1, 'comment' => 'Third Comment for First Article', '_locale' => 'spa'],
  224. ['article_id' => 1, 'comment' => 'Comentario #4', '_locale' => 'spa']
  225. ],
  226. '_locale' => 'spa'
  227. ],
  228. [
  229. 'id' => 2,
  230. 'title' => 'Second Article',
  231. 'body' => 'Second Article Body',
  232. 'comments' => [
  233. ['article_id' => 2, 'comment' => 'First Comment for Second Article', '_locale' => 'spa'],
  234. ['article_id' => 2, 'comment' => 'Second Comment for Second Article', '_locale' => 'spa']
  235. ],
  236. '_locale' => 'spa'
  237. ],
  238. [
  239. 'id' => 3,
  240. 'title' => 'Third Article',
  241. 'body' => 'Third Article Body',
  242. 'comments' => [],
  243. '_locale' => 'spa'
  244. ]
  245. ];
  246. $this->assertSame($expected, $results);
  247. }
  248. /**
  249. * Tests that fields from a translated model are not overridden if translation
  250. * is null
  251. *
  252. * @return void
  253. */
  254. public function testFindSingleLocaleWithNullTranslation()
  255. {
  256. $table = $this->getTableLocator()->get('Comments');
  257. $table->addBehavior('Translate', ['fields' => ['comment']]);
  258. $table->setLocale('spa');
  259. $results = $table->find()
  260. ->where(['Comments.id' => 6])
  261. ->combine('id', 'comment')->toArray();
  262. $expected = [6 => 'Second Comment for Second Article'];
  263. $this->assertSame($expected, $results);
  264. }
  265. /**
  266. * Tests that overriding fields with the translate behavior works when
  267. * using conditions and that all other columns are preserved
  268. *
  269. * @return void
  270. */
  271. public function testFindSingleLocaleWithgetConditions()
  272. {
  273. $table = $this->getTableLocator()->get('Articles');
  274. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  275. $table->setLocale('eng');
  276. $results = $table->find()
  277. ->where(['Articles.id' => 2])
  278. ->all();
  279. $this->assertCount(1, $results);
  280. $row = $results->first();
  281. $expected = [
  282. 'id' => 2,
  283. 'title' => 'Title #2',
  284. 'body' => 'Content #2',
  285. 'author_id' => 3,
  286. 'published' => 'Y',
  287. '_locale' => 'eng'
  288. ];
  289. $this->assertEquals($expected, $row->toArray());
  290. }
  291. /**
  292. * Tests the deprecated locale method.
  293. *
  294. * @group deprecated
  295. * @return void
  296. */
  297. public function testLocale()
  298. {
  299. $this->deprecated(function () {
  300. $table = $this->getTableLocator()->get('Articles');
  301. $table->addBehavior('Translate');
  302. $this->assertEquals('en_US', $table->locale());
  303. $table->locale('fr_FR');
  304. $this->assertEquals('fr_FR', $table->locale());
  305. $table->locale(false);
  306. $this->assertEquals('en_US', $table->locale());
  307. I18n::setLocale('fr_FR');
  308. $this->assertEquals('fr_FR', $table->locale());
  309. });
  310. }
  311. /**
  312. * Tests the locale setter/getter.
  313. *
  314. * @return void
  315. */
  316. public function testSetGetLocale()
  317. {
  318. $table = $this->getTableLocator()->get('Articles');
  319. $table->addBehavior('Translate');
  320. $this->assertEquals('en_US', $table->getLocale());
  321. $table->setLocale('fr_FR');
  322. $this->assertEquals('fr_FR', $table->getLocale());
  323. $table->setLocale(null);
  324. $this->assertEquals('en_US', $table->getLocale());
  325. I18n::setLocale('fr_FR');
  326. $this->assertEquals('fr_FR', $table->getLocale());
  327. }
  328. /**
  329. * Tests translationField method for translated fields.
  330. *
  331. * @return void
  332. */
  333. public function testTranslationFieldForTranslatedFields()
  334. {
  335. $table = $this->getTableLocator()->get('Articles');
  336. $table->addBehavior('Translate', [
  337. 'fields' => ['title', 'body'],
  338. 'defaultLocale' => 'en_US'
  339. ]);
  340. $expectedSameLocale = 'Articles.title';
  341. $expectedOtherLocale = 'Articles_title_translation.content';
  342. $field = $table->translationField('title');
  343. $this->assertSame($expectedSameLocale, $field);
  344. I18n::setLocale('es_ES');
  345. $field = $table->translationField('title');
  346. $this->assertSame($expectedOtherLocale, $field);
  347. I18n::setLocale('en');
  348. $field = $table->translationField('title');
  349. $this->assertSame($expectedOtherLocale, $field);
  350. $table->removeBehavior('Translate');
  351. $table->addBehavior('Translate', [
  352. 'fields' => ['title', 'body'],
  353. 'defaultLocale' => 'de_DE'
  354. ]);
  355. I18n::setLocale('de_DE');
  356. $field = $table->translationField('title');
  357. $this->assertSame($expectedSameLocale, $field);
  358. I18n::setLocale('en_US');
  359. $field = $table->translationField('title');
  360. $this->assertSame($expectedOtherLocale, $field);
  361. $table->setLocale('de_DE');
  362. $field = $table->translationField('title');
  363. $this->assertSame($expectedSameLocale, $field);
  364. $table->setLocale('es');
  365. $field = $table->translationField('title');
  366. $this->assertSame($expectedOtherLocale, $field);
  367. }
  368. /**
  369. * Tests translationField method for other fields.
  370. *
  371. * @return void
  372. */
  373. public function testTranslationFieldForOtherFields()
  374. {
  375. $table = $this->getTableLocator()->get('Articles');
  376. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  377. $expected = 'Articles.foo';
  378. $field = $table->translationField('foo');
  379. $this->assertSame($expected, $field);
  380. }
  381. /**
  382. * Tests that translating fields work when other formatters are used
  383. *
  384. * @return void
  385. */
  386. public function testFindList()
  387. {
  388. $table = $this->getTableLocator()->get('Articles');
  389. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  390. $table->setLocale('eng');
  391. $results = $table->find('list')->toArray();
  392. $expected = [1 => 'Title #1', 2 => 'Title #2', 3 => 'Title #3'];
  393. $this->assertSame($expected, $results);
  394. }
  395. /**
  396. * Tests that the query count return the correct results
  397. *
  398. * @return void
  399. */
  400. public function testFindCount()
  401. {
  402. $table = $this->getTableLocator()->get('Articles');
  403. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  404. $table->setLocale('eng');
  405. $this->assertEquals(3, $table->find()->count());
  406. }
  407. /**
  408. * Tests that it is possible to get all translated fields at once
  409. *
  410. * @return void
  411. */
  412. public function testFindTranslations()
  413. {
  414. $table = $this->getTableLocator()->get('Articles');
  415. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  416. $results = $table->find('translations');
  417. $expected = [
  418. [
  419. 'eng' => ['title' => 'Title #1', 'body' => 'Content #1', 'description' => 'Description #1', 'locale' => 'eng'],
  420. 'deu' => ['title' => 'Titel #1', 'body' => 'Inhalt #1', 'locale' => 'deu'],
  421. 'cze' => ['title' => 'Titulek #1', 'body' => 'Obsah #1', 'locale' => 'cze'],
  422. 'spa' => ['body' => 'Contenido #1', 'locale' => 'spa', 'description' => '']
  423. ],
  424. [
  425. 'eng' => ['title' => 'Title #2', 'body' => 'Content #2', 'locale' => 'eng'],
  426. 'deu' => ['title' => 'Titel #2', 'body' => 'Inhalt #2', 'locale' => 'deu'],
  427. 'cze' => ['title' => 'Titulek #2', 'body' => 'Obsah #2', 'locale' => 'cze']
  428. ],
  429. [
  430. 'eng' => ['title' => 'Title #3', 'body' => 'Content #3', 'locale' => 'eng'],
  431. 'deu' => ['title' => 'Titel #3', 'body' => 'Inhalt #3', 'locale' => 'deu'],
  432. 'cze' => ['title' => 'Titulek #3', 'body' => 'Obsah #3', 'locale' => 'cze']
  433. ]
  434. ];
  435. $translations = $this->_extractTranslations($results);
  436. $this->assertEquals($expected, $translations->toArray());
  437. $expected = [
  438. 1 => ['First Article' => 'First Article Body'],
  439. 2 => ['Second Article' => 'Second Article Body'],
  440. 3 => ['Third Article' => 'Third Article Body']
  441. ];
  442. $grouped = $results->combine('title', 'body', 'id');
  443. $this->assertEquals($expected, $grouped->toArray());
  444. }
  445. /**
  446. * Tests that it is possible to request just a few translations
  447. *
  448. * @return void
  449. */
  450. public function testFindFilteredTranslations()
  451. {
  452. $table = $this->getTableLocator()->get('Articles');
  453. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  454. $results = $table->find('translations', ['locales' => ['deu', 'cze']]);
  455. $expected = [
  456. [
  457. 'deu' => ['title' => 'Titel #1', 'body' => 'Inhalt #1', 'locale' => 'deu'],
  458. 'cze' => ['title' => 'Titulek #1', 'body' => 'Obsah #1', 'locale' => 'cze']
  459. ],
  460. [
  461. 'deu' => ['title' => 'Titel #2', 'body' => 'Inhalt #2', 'locale' => 'deu'],
  462. 'cze' => ['title' => 'Titulek #2', 'body' => 'Obsah #2', 'locale' => 'cze']
  463. ],
  464. [
  465. 'deu' => ['title' => 'Titel #3', 'body' => 'Inhalt #3', 'locale' => 'deu'],
  466. 'cze' => ['title' => 'Titulek #3', 'body' => 'Obsah #3', 'locale' => 'cze']
  467. ]
  468. ];
  469. $translations = $this->_extractTranslations($results);
  470. $this->assertEquals($expected, $translations->toArray());
  471. $expected = [
  472. 1 => ['First Article' => 'First Article Body'],
  473. 2 => ['Second Article' => 'Second Article Body'],
  474. 3 => ['Third Article' => 'Third Article Body']
  475. ];
  476. $grouped = $results->combine('title', 'body', 'id');
  477. $this->assertEquals($expected, $grouped->toArray());
  478. }
  479. /**
  480. * Tests that it is possible to combine find('list') and find('translations')
  481. *
  482. * @return void
  483. */
  484. public function testFindTranslationsList()
  485. {
  486. $table = $this->getTableLocator()->get('Articles');
  487. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  488. $results = $table
  489. ->find('list', [
  490. 'keyField' => 'title',
  491. 'valueField' => '_translations.deu.title',
  492. 'groupField' => 'id'
  493. ])
  494. ->find('translations', ['locales' => ['deu']]);
  495. $expected = [
  496. 1 => ['First Article' => 'Titel #1'],
  497. 2 => ['Second Article' => 'Titel #2'],
  498. 3 => ['Third Article' => 'Titel #3']
  499. ];
  500. $this->assertEquals($expected, $results->toArray());
  501. }
  502. /**
  503. * Tests that you can both override fields and find all translations
  504. *
  505. * @return void
  506. */
  507. public function testFindTranslationsWithFieldOverriding()
  508. {
  509. $table = $this->getTableLocator()->get('Articles');
  510. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  511. $table->setLocale('cze');
  512. $results = $table->find('translations', ['locales' => ['deu', 'cze']]);
  513. $expected = [
  514. [
  515. 'deu' => ['title' => 'Titel #1', 'body' => 'Inhalt #1', 'locale' => 'deu'],
  516. 'cze' => ['title' => 'Titulek #1', 'body' => 'Obsah #1', 'locale' => 'cze']
  517. ],
  518. [
  519. 'deu' => ['title' => 'Titel #2', 'body' => 'Inhalt #2', 'locale' => 'deu'],
  520. 'cze' => ['title' => 'Titulek #2', 'body' => 'Obsah #2', 'locale' => 'cze']
  521. ],
  522. [
  523. 'deu' => ['title' => 'Titel #3', 'body' => 'Inhalt #3', 'locale' => 'deu'],
  524. 'cze' => ['title' => 'Titulek #3', 'body' => 'Obsah #3', 'locale' => 'cze']
  525. ]
  526. ];
  527. $translations = $this->_extractTranslations($results);
  528. $this->assertEquals($expected, $translations->toArray());
  529. $expected = [
  530. 1 => ['Titulek #1' => 'Obsah #1'],
  531. 2 => ['Titulek #2' => 'Obsah #2'],
  532. 3 => ['Titulek #3' => 'Obsah #3']
  533. ];
  534. $grouped = $results->combine('title', 'body', 'id');
  535. $this->assertEquals($expected, $grouped->toArray());
  536. }
  537. /**
  538. * Tests that fields can be overridden in a hasMany association
  539. *
  540. * @return void
  541. */
  542. public function testFindSingleLocaleHasMany()
  543. {
  544. $table = $this->getTableLocator()->get('Articles');
  545. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  546. $table->hasMany('Comments');
  547. $comments = $table->hasMany('Comments')->getTarget();
  548. $comments->addBehavior('Translate', ['fields' => ['comment']]);
  549. $table->setLocale('eng');
  550. $comments->setLocale('eng');
  551. $results = $table->find()->contain(['Comments' => function ($q) {
  552. return $q->select(['id', 'comment', 'article_id']);
  553. }]);
  554. $list = new Collection($results->first()->comments);
  555. $expected = [
  556. 1 => 'Comment #1',
  557. 2 => 'Comment #2',
  558. 3 => 'Comment #3',
  559. 4 => 'Comment #4'
  560. ];
  561. $this->assertEquals($expected, $list->combine('id', 'comment')->toArray());
  562. }
  563. /**
  564. * Test that it is possible to bring translations from hasMany relations
  565. *
  566. * @return void
  567. */
  568. public function testTranslationsHasMany()
  569. {
  570. $table = $this->getTableLocator()->get('Articles');
  571. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  572. $table->hasMany('Comments');
  573. $comments = $table->hasMany('Comments')->getTarget();
  574. $comments->addBehavior('Translate', ['fields' => ['comment']]);
  575. $results = $table->find('translations')->contain([
  576. 'Comments' => function ($q) {
  577. return $q->find('translations')->select(['id', 'comment', 'article_id']);
  578. }
  579. ]);
  580. $comments = $results->first()->comments;
  581. $expected = [
  582. [
  583. 'eng' => ['comment' => 'Comment #1', 'locale' => 'eng']
  584. ],
  585. [
  586. 'eng' => ['comment' => 'Comment #2', 'locale' => 'eng']
  587. ],
  588. [
  589. 'eng' => ['comment' => 'Comment #3', 'locale' => 'eng']
  590. ],
  591. [
  592. 'eng' => ['comment' => 'Comment #4', 'locale' => 'eng'],
  593. 'spa' => ['comment' => 'Comentario #4', 'locale' => 'spa']
  594. ]
  595. ];
  596. $translations = $this->_extractTranslations($comments);
  597. $this->assertEquals($expected, $translations->toArray());
  598. }
  599. /**
  600. * Tests that it is possible to both override fields with a translation and
  601. * also find separately other translations
  602. *
  603. * @return void
  604. */
  605. public function testTranslationsHasManyWithOverride()
  606. {
  607. $table = $this->getTableLocator()->get('Articles');
  608. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  609. $table->hasMany('Comments');
  610. $comments = $table->hasMany('Comments')->getTarget();
  611. $comments->addBehavior('Translate', ['fields' => ['comment']]);
  612. $table->setLocale('cze');
  613. $comments->setLocale('eng');
  614. $results = $table->find('translations')->contain([
  615. 'Comments' => function ($q) {
  616. return $q->find('translations')->select(['id', 'comment', 'article_id']);
  617. }
  618. ]);
  619. $comments = $results->first()->comments;
  620. $expected = [
  621. 1 => 'Comment #1',
  622. 2 => 'Comment #2',
  623. 3 => 'Comment #3',
  624. 4 => 'Comment #4'
  625. ];
  626. $list = new Collection($comments);
  627. $this->assertEquals($expected, $list->combine('id', 'comment')->toArray());
  628. $expected = [
  629. [
  630. 'eng' => ['comment' => 'Comment #1', 'locale' => 'eng']
  631. ],
  632. [
  633. 'eng' => ['comment' => 'Comment #2', 'locale' => 'eng']
  634. ],
  635. [
  636. 'eng' => ['comment' => 'Comment #3', 'locale' => 'eng']
  637. ],
  638. [
  639. 'eng' => ['comment' => 'Comment #4', 'locale' => 'eng'],
  640. 'spa' => ['comment' => 'Comentario #4', 'locale' => 'spa']
  641. ]
  642. ];
  643. $translations = $this->_extractTranslations($comments);
  644. $this->assertEquals($expected, $translations->toArray());
  645. $this->assertEquals('Titulek #1', $results->first()->title);
  646. $this->assertEquals('Obsah #1', $results->first()->body);
  647. }
  648. /**
  649. * Tests that it is possible to translate belongsTo associations
  650. *
  651. * @return void
  652. */
  653. public function testFindSingleLocaleBelongsto()
  654. {
  655. $table = $this->getTableLocator()->get('Articles');
  656. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  657. $authors = $table->belongsTo('Authors')->getTarget();
  658. $authors->addBehavior('Translate', ['fields' => ['name']]);
  659. $table->setLocale('eng');
  660. $authors->setLocale('eng');
  661. $results = $table->find()
  662. ->select(['title', 'body'])
  663. ->order(['title' => 'asc'])
  664. ->contain(['Authors' => function ($q) {
  665. return $q->select(['id', 'name']);
  666. }]);
  667. $expected = [
  668. [
  669. 'title' => 'Title #1',
  670. 'body' => 'Content #1',
  671. 'author' => ['id' => 1, 'name' => 'May-rianoh', '_locale' => 'eng'],
  672. '_locale' => 'eng'
  673. ],
  674. [
  675. 'title' => 'Title #2',
  676. 'body' => 'Content #2',
  677. 'author' => ['id' => 3, 'name' => 'larry', '_locale' => 'eng'],
  678. '_locale' => 'eng'
  679. ],
  680. [
  681. 'title' => 'Title #3',
  682. 'body' => 'Content #3',
  683. 'author' => ['id' => 1, 'name' => 'May-rianoh', '_locale' => 'eng'],
  684. '_locale' => 'eng'
  685. ]
  686. ];
  687. $results = array_map(function ($r) {
  688. return $r->toArray();
  689. }, $results->toArray());
  690. $this->assertEquals($expected, $results);
  691. }
  692. /**
  693. * Tests that it is possible to translate belongsToMany associations
  694. *
  695. * @return void
  696. */
  697. public function testFindSingleLocaleBelongsToMany()
  698. {
  699. $table = $this->getTableLocator()->get('Articles');
  700. $specialTags = $this->getTableLocator()->get('SpecialTags');
  701. $specialTags->addBehavior('Translate', ['fields' => ['extra_info']]);
  702. $table->belongsToMany('Tags', [
  703. 'through' => $specialTags
  704. ]);
  705. $specialTags->setLocale('eng');
  706. $result = $table->get(2, ['contain' => 'Tags']);
  707. $this->assertNotEmpty($result);
  708. $this->assertNotEmpty($result->tags);
  709. $this->assertEquals('Translated Info', $result->tags[0]->special_tags[0]->extra_info);
  710. }
  711. /**
  712. * Tests that updating an existing record translations work
  713. *
  714. * @return void
  715. */
  716. public function testUpdateSingleLocale()
  717. {
  718. $table = $this->getTableLocator()->get('Articles');
  719. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  720. $table->setLocale('eng');
  721. $article = $table->find()->first();
  722. $this->assertEquals(1, $article->get('id'));
  723. $article->set('title', 'New translated article');
  724. $table->save($article);
  725. $this->assertNull($article->get('_i18n'));
  726. $article = $table->find()->first();
  727. $this->assertEquals(1, $article->get('id'));
  728. $this->assertEquals('New translated article', $article->get('title'));
  729. $this->assertEquals('Content #1', $article->get('body'));
  730. $table->setLocale(false);
  731. $article = $table->find()->first();
  732. $this->assertEquals(1, $article->get('id'));
  733. $this->assertEquals('First Article', $article->get('title'));
  734. $table->setLocale('eng');
  735. $article->set('title', 'Wow, such translated article');
  736. $article->set('body', 'A translated body');
  737. $table->save($article);
  738. $this->assertNull($article->get('_i18n'));
  739. $article = $table->find()->first();
  740. $this->assertEquals(1, $article->get('id'));
  741. $this->assertEquals('Wow, such translated article', $article->get('title'));
  742. $this->assertEquals('A translated body', $article->get('body'));
  743. }
  744. /**
  745. * Tests adding new translation to a record
  746. *
  747. * @return void
  748. */
  749. public function testInsertNewTranslations()
  750. {
  751. $table = $this->getTableLocator()->get('Articles');
  752. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  753. $table->setLocale('fra');
  754. $article = $table->find()->first();
  755. $this->assertEquals(1, $article->get('id'));
  756. $article->set('title', 'Le titre');
  757. $table->save($article);
  758. $this->assertEquals('fra', $article->get('_locale'));
  759. $article = $table->find()->first();
  760. $this->assertEquals(1, $article->get('id'));
  761. $this->assertEquals('Le titre', $article->get('title'));
  762. $this->assertEquals('First Article Body', $article->get('body'));
  763. $article->set('title', 'Un autre titre');
  764. $article->set('body', 'Le contenu');
  765. $table->save($article);
  766. $this->assertNull($article->get('_i18n'));
  767. $article = $table->find()->first();
  768. $this->assertEquals('Un autre titre', $article->get('title'));
  769. $this->assertEquals('Le contenu', $article->get('body'));
  770. }
  771. /**
  772. * Tests adding new translation to a record
  773. *
  774. * @return void
  775. */
  776. public function testAllowEmptyFalse()
  777. {
  778. $table = $this->getTableLocator()->get('Articles');
  779. $table->addBehavior('Translate', ['fields' => ['title'], 'allowEmptyTranslations' => false]);
  780. $article = $table->find()->first();
  781. $this->assertEquals(1, $article->get('id'));
  782. $article = $table->patchEntity($article, [
  783. '_translations' => [
  784. 'fra' => [
  785. 'title' => ''
  786. ]
  787. ]
  788. ]);
  789. $table->save($article);
  790. // Remove the Behavior to unset the content != '' condition
  791. $table->removeBehavior('Translate');
  792. $noFra = $table->I18n->find()->where(['locale' => 'fra'])->first();
  793. $this->assertEmpty($noFra);
  794. }
  795. /**
  796. * Tests adding new translation to a record
  797. *
  798. * @return void
  799. */
  800. public function testMixedAllowEmptyFalse()
  801. {
  802. $table = $this->getTableLocator()->get('Articles');
  803. $table->addBehavior('Translate', ['fields' => ['title', 'body'], 'allowEmptyTranslations' => false]);
  804. $article = $table->find()->first();
  805. $this->assertEquals(1, $article->get('id'));
  806. $article = $table->patchEntity($article, [
  807. '_translations' => [
  808. 'fra' => [
  809. 'title' => '',
  810. 'body' => 'Bonjour'
  811. ]
  812. ]
  813. ]);
  814. $table->save($article);
  815. $fra = $table->I18n->find()
  816. ->where([
  817. 'locale' => 'fra',
  818. 'field' => 'body'
  819. ])
  820. ->first();
  821. $this->assertSame('Bonjour', $fra->content);
  822. // Remove the Behavior to unset the content != '' condition
  823. $table->removeBehavior('Translate');
  824. $noTitle = $table->I18n->find()
  825. ->where([
  826. 'locale' => 'fra',
  827. 'field' => 'title'
  828. ])
  829. ->first();
  830. $this->assertEmpty($noTitle);
  831. }
  832. /**
  833. * Tests adding new translation to a record
  834. *
  835. * @return void
  836. */
  837. public function testMultipleAllowEmptyFalse()
  838. {
  839. $table = $this->getTableLocator()->get('Articles');
  840. $table->addBehavior('Translate', ['fields' => ['title', 'body'], 'allowEmptyTranslations' => false]);
  841. $article = $table->find()->first();
  842. $this->assertEquals(1, $article->get('id'));
  843. $article = $table->patchEntity($article, [
  844. '_translations' => [
  845. 'fra' => [
  846. 'title' => '',
  847. 'body' => 'Bonjour'
  848. ],
  849. 'de' => [
  850. 'title' => 'Titel',
  851. 'body' => 'Hallo'
  852. ]
  853. ]
  854. ]);
  855. $table->save($article);
  856. $fra = $table->I18n->find()
  857. ->where([
  858. 'locale' => 'fra',
  859. 'field' => 'body'
  860. ])
  861. ->first();
  862. $this->assertSame('Bonjour', $fra->content);
  863. $deTitle = $table->I18n->find()
  864. ->where([
  865. 'locale' => 'de',
  866. 'field' => 'title'
  867. ])
  868. ->first();
  869. $this->assertSame('Titel', $deTitle->content);
  870. $deBody = $table->I18n->find()
  871. ->where([
  872. 'locale' => 'de',
  873. 'field' => 'body'
  874. ])
  875. ->first();
  876. $this->assertSame('Hallo', $deBody->content);
  877. // Remove the Behavior to unset the content != '' condition
  878. $table->removeBehavior('Translate');
  879. $noTitle = $table->I18n->find()
  880. ->where([
  881. 'locale' => 'fra',
  882. 'field' => 'title'
  883. ])
  884. ->first();
  885. $this->assertEmpty($noTitle);
  886. }
  887. /**
  888. * Tests that it is possible to use the _locale property to specify the language
  889. * to use for saving an entity
  890. *
  891. * @return void
  892. */
  893. public function testUpdateTranslationWithLocaleInEntity()
  894. {
  895. $table = $this->getTableLocator()->get('Articles');
  896. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  897. $article = $table->find()->first();
  898. $this->assertEquals(1, $article->get('id'));
  899. $article->set('_locale', 'fra');
  900. $article->set('title', 'Le titre');
  901. $table->save($article);
  902. $this->assertNull($article->get('_i18n'));
  903. $article = $table->find()->first();
  904. $this->assertEquals(1, $article->get('id'));
  905. $this->assertEquals('First Article', $article->get('title'));
  906. $this->assertEquals('First Article Body', $article->get('body'));
  907. $table->setLocale('fra');
  908. $article = $table->find()->first();
  909. $this->assertEquals(1, $article->get('id'));
  910. $this->assertEquals('Le titre', $article->get('title'));
  911. $this->assertEquals('First Article Body', $article->get('body'));
  912. }
  913. /**
  914. * Tests that translations are added to the whitelist of associations to be
  915. * saved
  916. *
  917. * @return void
  918. */
  919. public function testSaveTranslationWithAssociationWhitelist()
  920. {
  921. $table = $this->getTableLocator()->get('Articles');
  922. $table->hasMany('Comments');
  923. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  924. $table->setLocale('fra');
  925. $article = $table->find()->first();
  926. $this->assertEquals(1, $article->get('id'));
  927. $article->set('title', 'Le titre');
  928. $table->save($article, ['associated' => ['Comments']]);
  929. $this->assertNull($article->get('_i18n'));
  930. $article = $table->find()->first();
  931. $this->assertEquals('Le titre', $article->get('title'));
  932. }
  933. /**
  934. * Tests that after deleting a translated entity, all translations are also removed
  935. *
  936. * @return void
  937. */
  938. public function testDelete()
  939. {
  940. $table = $this->getTableLocator()->get('Articles');
  941. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  942. $article = $table->find()->first();
  943. $this->assertTrue($table->delete($article));
  944. $translations = $this->getTableLocator()->get('I18n')->find()
  945. ->where(['model' => 'Articles', 'foreign_key' => $article->id])
  946. ->count();
  947. $this->assertEquals(0, $translations);
  948. }
  949. /**
  950. * Tests saving multiple translations at once when the translations already
  951. * exist in the database
  952. *
  953. * @return void
  954. */
  955. public function testSaveMultipleTranslations()
  956. {
  957. $table = $this->getTableLocator()->get('Articles');
  958. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  959. $article = $results = $table->find('translations')->first();
  960. $translations = $article->get('_translations');
  961. $translations['deu']->set('title', 'Another title');
  962. $translations['eng']->set('body', 'Another body');
  963. $article->set('_translations', $translations);
  964. $table->save($article);
  965. $this->assertNull($article->get('_i18n'));
  966. $article = $results = $table->find('translations')->first();
  967. $translations = $article->get('_translations');
  968. $this->assertEquals('Another title', $translations['deu']->get('title'));
  969. $this->assertEquals('Another body', $translations['eng']->get('body'));
  970. }
  971. /**
  972. * Tests saving multiple existing translations and adding new ones
  973. *
  974. * @return void
  975. */
  976. public function testSaveMultipleNewTranslations()
  977. {
  978. $table = $this->getTableLocator()->get('Articles');
  979. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  980. $article = $results = $table->find('translations')->first();
  981. $translations = $article->get('_translations');
  982. $translations['deu']->set('title', 'Another title');
  983. $translations['eng']->set('body', 'Another body');
  984. $translations['spa'] = new Entity(['title' => 'Titulo']);
  985. $translations['fre'] = new Entity(['title' => 'Titre']);
  986. $article->set('_translations', $translations);
  987. $table->save($article);
  988. $this->assertNull($article->get('_i18n'));
  989. $article = $results = $table->find('translations')->first();
  990. $translations = $article->get('_translations');
  991. $this->assertEquals('Another title', $translations['deu']->get('title'));
  992. $this->assertEquals('Another body', $translations['eng']->get('body'));
  993. $this->assertEquals('Titulo', $translations['spa']->get('title'));
  994. $this->assertEquals('Titre', $translations['fre']->get('title'));
  995. }
  996. /**
  997. * Tests that iterating a resultset twice when using the translations finder
  998. * will not cause any errors nor information loss
  999. *
  1000. * @return void
  1001. */
  1002. public function testUseCountInFindTranslations()
  1003. {
  1004. $table = $this->getTableLocator()->get('Articles');
  1005. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  1006. $articles = $results = $table->find('translations');
  1007. $all = $articles->all();
  1008. $this->assertCount(3, $all);
  1009. $article = $all->first();
  1010. $this->assertNotEmpty($article->get('_translations'));
  1011. }
  1012. /**
  1013. * Tests that multiple translations saved when having a default locale
  1014. * are correctly saved
  1015. *
  1016. * @return void
  1017. */
  1018. public function testSavingWithNonDefaultLocale()
  1019. {
  1020. $table = $this->getTableLocator()->get('Articles');
  1021. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  1022. $table->setEntityClass(__NAMESPACE__ . '\Article');
  1023. I18n::setLocale('fra');
  1024. $translations = [
  1025. 'fra' => ['title' => 'Un article'],
  1026. 'spa' => ['title' => 'Un artículo']
  1027. ];
  1028. $article = $table->get(1);
  1029. foreach ($translations as $lang => $data) {
  1030. $article->translation($lang)->set($data, ['guard' => false]);
  1031. }
  1032. $table->save($article);
  1033. $article = $table->find('translations')->where(['Articles.id' => 1])->first();
  1034. $this->assertEquals('Un article', $article->translation('fra')->title);
  1035. $this->assertEquals('Un artículo', $article->translation('spa')->title);
  1036. }
  1037. /**
  1038. * Tests that translation queries are added to union queries as well.
  1039. *
  1040. * @return void
  1041. */
  1042. public function testTranslationWithUnionQuery()
  1043. {
  1044. $table = $this->getTableLocator()->get('Comments');
  1045. $table->addBehavior('Translate', ['fields' => ['comment']]);
  1046. $table->setLocale('spa');
  1047. $query = $table->find()->where(['Comments.id' => 6]);
  1048. $query2 = $table->find()->where(['Comments.id' => 5]);
  1049. $query->union($query2);
  1050. $results = $query->sortBy('id', SORT_ASC)->toList();
  1051. $this->assertCount(2, $results);
  1052. $this->assertEquals('First Comment for Second Article', $results[0]->comment);
  1053. $this->assertEquals('Second Comment for Second Article', $results[1]->comment);
  1054. }
  1055. /**
  1056. * Tests the use of `referenceName` config option.
  1057. *
  1058. * @return void
  1059. */
  1060. public function testAutoReferenceName()
  1061. {
  1062. $table = $this->getTableLocator()->get('Articles');
  1063. $table->hasMany('OtherComments', ['className' => 'Comments']);
  1064. $table->OtherComments->addBehavior(
  1065. 'Translate',
  1066. ['fields' => ['comment']]
  1067. );
  1068. $items = $table->OtherComments->associations();
  1069. $association = $items->getByProperty('comment_translation');
  1070. $this->assertNotEmpty($association, 'Translation association not found');
  1071. $found = false;
  1072. foreach ($association->getConditions() as $key => $value) {
  1073. if (strpos($key, 'comment_translation.model') !== false) {
  1074. $found = true;
  1075. $this->assertEquals('Comments', $value);
  1076. break;
  1077. }
  1078. }
  1079. $this->assertTrue($found, '`referenceName` field condition on a Translation association was not found');
  1080. }
  1081. /**
  1082. * Tests the use of unconventional `referenceName` config option.
  1083. *
  1084. * @return void
  1085. */
  1086. public function testChangingReferenceName()
  1087. {
  1088. $table = $this->getTableLocator()->get('Articles');
  1089. $table->setAlias('FavoritePost');
  1090. $table->addBehavior(
  1091. 'Translate',
  1092. ['fields' => ['body'], 'referenceName' => 'Posts']
  1093. );
  1094. $items = $table->associations();
  1095. $association = $items->getByProperty('body_translation');
  1096. $this->assertNotEmpty($association, 'Translation association not found');
  1097. $found = false;
  1098. foreach ($association->getConditions() as $key => $value) {
  1099. if (strpos($key, 'body_translation.model') !== false) {
  1100. $found = true;
  1101. $this->assertEquals('Posts', $value);
  1102. break;
  1103. }
  1104. }
  1105. $this->assertTrue($found, '`referenceName` field condition on a Translation association was not found');
  1106. }
  1107. /**
  1108. * Tests that onlyTranslated will remove records from the result set
  1109. * if they are not fully translated
  1110. *
  1111. * @return void
  1112. */
  1113. public function testFilterUntranslated()
  1114. {
  1115. $table = $this->getTableLocator()->get('Articles');
  1116. $table->addBehavior('Translate', [
  1117. 'fields' => ['title', 'body'],
  1118. 'onlyTranslated' => true
  1119. ]);
  1120. $table->setLocale('eng');
  1121. $results = $table->find()->where(['Articles.id' => 1])->all();
  1122. $this->assertCount(1, $results);
  1123. $table->setLocale('fr');
  1124. $results = $table->find()->where(['Articles.id' => 1])->all();
  1125. $this->assertCount(0, $results);
  1126. }
  1127. /**
  1128. * Tests that records not translated in the current locale will not be
  1129. * present in the results for the translations finder, and also proves
  1130. * that this can be overridden.
  1131. *
  1132. * @return void
  1133. */
  1134. public function testFilterUntranslatedWithFinder()
  1135. {
  1136. $table = $this->getTableLocator()->get('Comments');
  1137. $table->addBehavior('Translate', [
  1138. 'fields' => ['comment'],
  1139. 'onlyTranslated' => true
  1140. ]);
  1141. $table->setLocale('eng');
  1142. $results = $table->find('translations')->all();
  1143. $this->assertCount(4, $results);
  1144. $table->setLocale('spa');
  1145. $results = $table->find('translations')->all();
  1146. $this->assertCount(1, $results);
  1147. $table->setLocale('spa');
  1148. $results = $table->find('translations', ['filterByCurrentLocale' => false])->all();
  1149. $this->assertCount(6, $results);
  1150. $table->setLocale('spa');
  1151. $results = $table->find('translations')->all();
  1152. $this->assertCount(1, $results);
  1153. }
  1154. /**
  1155. * Tests that allowEmptyTranslations takes effect
  1156. *
  1157. * @return void
  1158. */
  1159. public function testEmptyTranslations()
  1160. {
  1161. $table = $this->getTableLocator()->get('Articles');
  1162. $table->addBehavior('Translate', [
  1163. 'fields' => ['title', 'body', 'description'],
  1164. 'allowEmptyTranslations' => false,
  1165. ]);
  1166. $table->setLocale('spa');
  1167. $result = $table->find()->first();
  1168. $this->assertNull($result->description);
  1169. }
  1170. /**
  1171. * Test save with clean translate fields
  1172. *
  1173. * @return void
  1174. */
  1175. public function testSaveWithCleanFields()
  1176. {
  1177. $table = $this->getTableLocator()->get('Articles');
  1178. $table->addBehavior('Translate', ['fields' => ['title']]);
  1179. $table->setEntityClass(__NAMESPACE__ . '\Article');
  1180. I18n::setLocale('fra');
  1181. $article = $table->get(1);
  1182. $article->set('body', 'New Body');
  1183. $table->save($article);
  1184. $result = $table->get(1);
  1185. $this->assertEquals('New Body', $result->body);
  1186. $this->assertSame($article->title, $result->title);
  1187. }
  1188. /**
  1189. * Test save new entity with _translations field
  1190. *
  1191. * @return void
  1192. */
  1193. public function testSaveNewRecordWithTranslatesField()
  1194. {
  1195. $table = $this->getTableLocator()->get('Articles');
  1196. $table->addBehavior('Translate', [
  1197. 'fields' => ['title'],
  1198. 'validator' => (new \Cake\Validation\Validator)->add('title', 'notBlank', ['rule' => 'notBlank'])
  1199. ]);
  1200. $table->setEntityClass(__NAMESPACE__ . '\Article');
  1201. $data = [
  1202. 'author_id' => 1,
  1203. 'published' => 'N',
  1204. '_translations' => [
  1205. 'en' => [
  1206. 'title' => 'Title EN',
  1207. 'body' => 'Body EN'
  1208. ],
  1209. 'es' => [
  1210. 'title' => 'Title ES'
  1211. ]
  1212. ]
  1213. ];
  1214. $article = $table->patchEntity($table->newEntity(), $data);
  1215. $result = $table->save($article);
  1216. $this->assertNotFalse($result);
  1217. $expected = [
  1218. [
  1219. 'en' => [
  1220. 'title' => 'Title EN',
  1221. 'locale' => 'en'
  1222. ],
  1223. 'es' => [
  1224. 'title' => 'Title ES',
  1225. 'locale' => 'es'
  1226. ]
  1227. ]
  1228. ];
  1229. $result = $table->find('translations')->where(['id' => $result->id]);
  1230. $this->assertEquals($expected, $this->_extractTranslations($result)->toArray());
  1231. }
  1232. /**
  1233. * Tests adding new translation to a record where the only field is the translated one and it's not the default locale
  1234. *
  1235. * @return void
  1236. */
  1237. public function testSaveNewRecordWithOnlyTranslationsNotDefaultLocale()
  1238. {
  1239. $table = $this->getTableLocator()->get('Groups');
  1240. $table->addBehavior('Translate', [
  1241. 'fields' => ['title'],
  1242. 'validator' => (new \Cake\Validation\Validator)->add('title', 'notBlank', ['rule' => 'notBlank'])
  1243. ]);
  1244. $data = [
  1245. '_translations' => [
  1246. 'es' => [
  1247. 'title' => 'Title ES'
  1248. ]
  1249. ]
  1250. ];
  1251. $group = $table->newEntity($data);
  1252. $result = $table->save($group);
  1253. $this->assertNotFalse($result, 'Record should save.');
  1254. $expected = [
  1255. [
  1256. 'es' => [
  1257. 'title' => 'Title ES',
  1258. 'locale' => 'es'
  1259. ]
  1260. ]
  1261. ];
  1262. $result = $table->find('translations')->where(['id' => $result->id]);
  1263. $this->assertEquals($expected, $this->_extractTranslations($result)->toArray());
  1264. }
  1265. /**
  1266. * Test that existing records can be updated when only translations
  1267. * are modified/dirty.
  1268. *
  1269. * @return void
  1270. */
  1271. public function testSaveExistingRecordOnlyTranslations()
  1272. {
  1273. $table = $this->getTableLocator()->get('Articles');
  1274. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  1275. $table->setEntityClass(__NAMESPACE__ . '\Article');
  1276. $data = [
  1277. '_translations' => [
  1278. 'es' => [
  1279. 'title' => 'Spanish Translation',
  1280. ],
  1281. ]
  1282. ];
  1283. $article = $table->find()->first();
  1284. $article = $table->patchEntity($article, $data);
  1285. $this->assertNotFalse($table->save($article));
  1286. $results = $this->_extractTranslations(
  1287. $table->find('translations')->where(['id' => 1])
  1288. )->first();
  1289. $this->assertArrayHasKey('es', $results, 'New translation added');
  1290. $this->assertArrayHasKey('eng', $results, 'Old translations present');
  1291. $this->assertEquals('Spanish Translation', $results['es']['title']);
  1292. }
  1293. /**
  1294. * Test update entity with _translations field.
  1295. *
  1296. * @return void
  1297. */
  1298. public function testSaveExistingRecordWithTranslatesField()
  1299. {
  1300. $table = $this->getTableLocator()->get('Articles');
  1301. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  1302. $table->setEntityClass(__NAMESPACE__ . '\Article');
  1303. $data = [
  1304. 'author_id' => 1,
  1305. 'published' => 'Y',
  1306. '_translations' => [
  1307. 'eng' => [
  1308. 'title' => 'First Article1',
  1309. 'body' => 'First Article content has been updated'
  1310. ],
  1311. 'spa' => [
  1312. 'title' => 'Mi nuevo titulo',
  1313. 'body' => 'Contenido Actualizado'
  1314. ]
  1315. ]
  1316. ];
  1317. $article = $table->find()->first();
  1318. $article = $table->patchEntity($article, $data);
  1319. $this->assertNotFalse($table->save($article));
  1320. $results = $this->_extractTranslations(
  1321. $table->find('translations')->where(['id' => 1])
  1322. )->first();
  1323. $this->assertEquals('Mi nuevo titulo', $results['spa']['title']);
  1324. $this->assertEquals('Contenido Actualizado', $results['spa']['body']);
  1325. $this->assertEquals('First Article1', $results['eng']['title']);
  1326. $this->assertEquals('Description #1', $results['eng']['description']);
  1327. }
  1328. /**
  1329. * Tests that default locale saves ok.
  1330. *
  1331. * @return void
  1332. */
  1333. public function testSaveDefaultLocale()
  1334. {
  1335. $table = $this->getTableLocator()->get('Articles');
  1336. $table->hasMany('Comments');
  1337. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  1338. $article = $table->get(1);
  1339. $data = [
  1340. 'title' => 'New title',
  1341. 'body' => 'New body',
  1342. ];
  1343. $article = $table->patchEntity($article, $data);
  1344. $table->save($article);
  1345. $this->assertNull($article->get('_i18n'));
  1346. $article = $table->get(1);
  1347. $this->assertEquals('New title', $article->get('title'));
  1348. $this->assertEquals('New body', $article->get('body'));
  1349. }
  1350. /**
  1351. * Tests that translations are added to the whitelist of associations to be
  1352. * saved
  1353. *
  1354. * @return void
  1355. */
  1356. public function testSaveTranslationDefaultLocale()
  1357. {
  1358. $table = $this->getTableLocator()->get('Articles');
  1359. $table->hasMany('Comments');
  1360. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  1361. $article = $table->get(1);
  1362. $data = [
  1363. 'title' => 'New title',
  1364. 'body' => 'New body',
  1365. '_translations' => [
  1366. 'es' => [
  1367. 'title' => 'ES title',
  1368. 'body' => 'ES body'
  1369. ]
  1370. ]
  1371. ];
  1372. $article = $table->patchEntity($article, $data);
  1373. $table->save($article);
  1374. $this->assertNull($article->get('_i18n'));
  1375. $article = $table->find('translations')->where(['id' => 1])->first();
  1376. $this->assertEquals('New title', $article->get('title'));
  1377. $this->assertEquals('New body', $article->get('body'));
  1378. $this->assertEquals('ES title', $article->_translations['es']->title);
  1379. $this->assertEquals('ES body', $article->_translations['es']->body);
  1380. }
  1381. /**
  1382. * Test that no properties are enabled when the translations
  1383. * option is off.
  1384. *
  1385. * @return void
  1386. */
  1387. public function testBuildMarshalMapTranslationsOff()
  1388. {
  1389. $table = $this->getTableLocator()->get('Articles');
  1390. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  1391. $marshaller = $table->marshaller();
  1392. $translate = $table->behaviors()->get('Translate');
  1393. $result = $translate->buildMarshalMap($marshaller, [], ['translations' => false]);
  1394. $this->assertSame([], $result);
  1395. }
  1396. /**
  1397. * Test building a marshal map with translations on.
  1398. *
  1399. * @return void
  1400. */
  1401. public function testBuildMarshalMapTranslationsOn()
  1402. {
  1403. $table = $this->getTableLocator()->get('Articles');
  1404. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  1405. $marshaller = $table->marshaller();
  1406. $translate = $table->behaviors()->get('Translate');
  1407. $result = $translate->buildMarshalMap($marshaller, [], ['translations' => true]);
  1408. $this->assertArrayHasKey('_translations', $result);
  1409. $this->assertInstanceOf('Closure', $result['_translations']);
  1410. $result = $translate->buildMarshalMap($marshaller, [], []);
  1411. $this->assertArrayHasKey('_translations', $result);
  1412. $this->assertInstanceOf('Closure', $result['_translations']);
  1413. }
  1414. /**
  1415. * Test marshalling non-array data
  1416. *
  1417. * @return void
  1418. */
  1419. public function testBuildMarshalMapNonArrayData()
  1420. {
  1421. $table = $this->getTableLocator()->get('Articles');
  1422. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  1423. $translate = $table->behaviors()->get('Translate');
  1424. $map = $translate->buildMarshalMap($table->marshaller(), [], []);
  1425. $entity = $table->newEntity();
  1426. $result = $map['_translations']('garbage', $entity);
  1427. $this->assertNull($result, 'Non-array should not error out.');
  1428. $this->assertEmpty($entity->getErrors());
  1429. $this->assertEmpty($entity->get('_translations'));
  1430. }
  1431. /**
  1432. * Test buildMarshalMap() builds new entities.
  1433. *
  1434. * @return void
  1435. */
  1436. public function testBuildMarshalMapBuildEntities()
  1437. {
  1438. $table = $this->getTableLocator()->get('Articles');
  1439. $table->addBehavior('Translate', ['fields' => ['title', 'body']]);
  1440. $translate = $table->behaviors()->get('Translate');
  1441. $map = $translate->buildMarshalMap($table->marshaller(), [], []);
  1442. $entity = $table->newEntity();
  1443. $data = [
  1444. 'en' => [
  1445. 'title' => 'English Title',
  1446. 'body' => 'English Content'
  1447. ],
  1448. 'es' => [
  1449. 'title' => 'Titulo Español',
  1450. 'body' => 'Contenido Español'
  1451. ]
  1452. ];
  1453. $result = $map['_translations']($data, $entity);
  1454. $this->assertEmpty($entity->getErrors(), 'No validation errors.');
  1455. $this->assertCount(2, $result);
  1456. $this->assertArrayHasKey('en', $result);
  1457. $this->assertArrayHasKey('es', $result);
  1458. $this->assertEquals('English Title', $result['en']->title);
  1459. $this->assertEquals('Titulo Español', $result['es']->title);
  1460. }
  1461. /**
  1462. * Test that validation errors are added to the original entity.
  1463. *
  1464. * @return void
  1465. */
  1466. public function testBuildMarshalMapBuildEntitiesValidationErrors()
  1467. {
  1468. $table = $this->getTableLocator()->get('Articles');
  1469. $table->addBehavior('Translate', [
  1470. 'fields' => ['title', 'body'],
  1471. 'validator' => 'custom'
  1472. ]);
  1473. $validator = (new Validator)->add('title', 'notBlank', ['rule' => 'notBlank']);
  1474. $table->setValidator('custom', $validator);
  1475. $translate = $table->behaviors()->get('Translate');
  1476. $entity = $table->newEntity();
  1477. $map = $translate->buildMarshalMap($table->marshaller(), [], []);
  1478. $data = [
  1479. 'en' => [
  1480. 'title' => 'English Title',
  1481. 'body' => 'English Content'
  1482. ],
  1483. 'es' => [
  1484. 'title' => '',
  1485. 'body' => 'Contenido Español'
  1486. ]
  1487. ];
  1488. $result = $map['_translations']($data, $entity);
  1489. $this->assertNotEmpty($entity->getErrors(), 'Needs validation errors.');
  1490. $expected = [
  1491. 'title' => [
  1492. '_empty' => 'This field cannot be left empty'
  1493. ]
  1494. ];
  1495. $this->assertEquals($expected, $entity->getError('es'));
  1496. $this->assertEquals('English Title', $result['en']->title);
  1497. $this->assertEquals('', $result['es']->title);
  1498. }
  1499. /**
  1500. * Test that marshalling updates existing translation entities.
  1501. *
  1502. * @return void
  1503. */
  1504. public function testBuildMarshalMapUpdateExistingEntities()
  1505. {
  1506. $table = $this->getTableLocator()->get('Articles');
  1507. $table->addBehavior('Translate', [
  1508. 'fields' => ['title', 'body'],
  1509. ]);
  1510. $translate = $table->behaviors()->get('Translate');
  1511. $entity = $table->newEntity();
  1512. $es = $table->newEntity(['title' => 'Old title', 'body' => 'Old body']);
  1513. $en = $table->newEntity(['title' => 'Old title', 'body' => 'Old body']);
  1514. $entity->set('_translations', [
  1515. 'es' => $es,
  1516. 'en' => $en,
  1517. ]);
  1518. $map = $translate->buildMarshalMap($table->marshaller(), [], []);
  1519. $data = [
  1520. 'en' => [
  1521. 'title' => 'English Title',
  1522. ],
  1523. 'es' => [
  1524. 'title' => 'Spanish Title',
  1525. ]
  1526. ];
  1527. $result = $map['_translations']($data, $entity);
  1528. $this->assertEmpty($entity->getErrors(), 'No validation errors.');
  1529. $this->assertSame($en, $result['en']);
  1530. $this->assertSame($es, $result['es']);
  1531. $this->assertSame($en, $entity->get('_translations')['en']);
  1532. $this->assertSame($es, $entity->get('_translations')['es']);
  1533. $this->assertEquals('English Title', $result['en']->title);
  1534. $this->assertEquals('Spanish Title', $result['es']->title);
  1535. $this->assertEquals('Old body', $result['en']->body);
  1536. $this->assertEquals('Old body', $result['es']->body);
  1537. }
  1538. /**
  1539. * Test that updating translation records works with validations.
  1540. *
  1541. * @return void
  1542. */
  1543. public function testBuildMarshalMapUpdateEntitiesValidationErrors()
  1544. {
  1545. $table = $this->getTableLocator()->get('Articles');
  1546. $table->addBehavior('Translate', [
  1547. 'fields' => ['title', 'body'],
  1548. 'validator' => 'custom'
  1549. ]);
  1550. $validator = (new Validator)->add('title', 'notBlank', ['rule' => 'notBlank']);
  1551. $table->setValidator('custom', $validator);
  1552. $translate = $table->behaviors()->get('Translate');
  1553. $entity = $table->newEntity();
  1554. $es = $table->newEntity(['title' => 'Old title', 'body' => 'Old body']);
  1555. $en = $table->newEntity(['title' => 'Old title', 'body' => 'Old body']);
  1556. $entity->set('_translations', [
  1557. 'es' => $es,
  1558. 'en' => $en,
  1559. ]);
  1560. $map = $translate->buildMarshalMap($table->marshaller(), [], []);
  1561. $data = [
  1562. 'en' => [
  1563. 'title' => 'English Title',
  1564. 'body' => 'English Content'
  1565. ],
  1566. 'es' => [
  1567. 'title' => '',
  1568. 'body' => 'Contenido Español'
  1569. ]
  1570. ];
  1571. $result = $map['_translations']($data, $entity);
  1572. $this->assertNotEmpty($entity->getErrors(), 'Needs validation errors.');
  1573. $expected = [
  1574. 'title' => [
  1575. '_empty' => 'This field cannot be left empty'
  1576. ]
  1577. ];
  1578. $this->assertEquals($expected, $entity->getError('es'));
  1579. }
  1580. /**
  1581. * Test that the behavior uses associations' locator.
  1582. *
  1583. * @return void
  1584. */
  1585. public function testDefaultTableLocator()
  1586. {
  1587. $locator = new TableLocator();
  1588. $table = $locator->get('Articles');
  1589. $table->addBehavior('Translate', [
  1590. 'fields' => ['title', 'body'],
  1591. 'validator' => 'custom'
  1592. ]);
  1593. $behaviorLocator = $table->behaviors()->get('Translate')->getTableLocator();
  1594. $this->assertSame($locator, $behaviorLocator);
  1595. $this->assertSame($table->associations()->getTableLocator(), $behaviorLocator);
  1596. $this->assertNotSame($this->getTableLocator(), $behaviorLocator);
  1597. }
  1598. /**
  1599. * Test that the behavior uses a custom locator.
  1600. *
  1601. * @return void
  1602. */
  1603. public function testCustomTableLocator()
  1604. {
  1605. $locator = new TableLocator();
  1606. $table = $this->getTableLocator()->get('Articles');
  1607. $table->addBehavior('Translate', [
  1608. 'fields' => ['title', 'body'],
  1609. 'validator' => 'custom',
  1610. 'tableLocator' => $locator,
  1611. ]);
  1612. $behaviorLocator = $table->behaviors()->get('Translate')->getTableLocator();
  1613. $this->assertSame($locator, $behaviorLocator);
  1614. $this->assertNotSame($table->associations()->getTableLocator(), $behaviorLocator);
  1615. $this->assertNotSame($this->getTableLocator(), $behaviorLocator);
  1616. }
  1617. }