Browse Source

Merge pull request #2810 from cakephp/3.0-translation-save

3.0 translation save
José Lorenzo Rodríguez 12 years ago
parent
commit
635757a04e

+ 2 - 1
src/Database/Expression/TupleComparison.php

@@ -88,7 +88,8 @@ class TupleComparison extends Comparison {
 			$type = $this->_type;
 			$multiType = is_array($type);
 			$isMulti = $this->isMulti($i, $type);
-			$type = $isMulti ? str_replace('[]', '', $type) : $type;
+			$type = $multiType ? $type : str_replace('[]', '', $type);
+			$type = $type ?: null;
 
 			if ($isMulti) {
 				$bound = [];

+ 53 - 0
src/Model/Behavior/TranslateBehavior.php

@@ -14,6 +14,7 @@
  */
 namespace Cake\Model\Behavior;
 
+use ArrayObject;
 use Cake\Collection\Collection;
 use Cake\Event\Event;
 use Cake\ORM\Behavior;
@@ -149,6 +150,58 @@ class TranslateBehavior extends Behavior {
 	}
 
 /**
+ * Modifies the entity before it is saved so that translated fields are persisted
+ * in the database too.
+ *
+ * @param \Cake\Event\Event the beforeSave event that was fired
+ * @param \Cake\ORM\Entity the entity that is going to be saved
+ * @param \ArrayObject $options the options passed to the save method
+ * @return void
+ */
+	public function beforeSave(Event $event, Entity $entity, ArrayObject $options) {
+		$locale = $entity->get('_locale') ?: $this->locale();
+
+		if (!$locale) {
+			return;
+		}
+
+		$newOptions = ['I18n' => ['validate' => false]];
+		$options['associated'] = $newOptions + $options['associated'];
+		$values = $entity->extract($this->config()['fields'], true);
+		$fields = array_keys($values);
+		$key = $entity->get(current((array)$this->_table->primaryKey()));
+
+		$preexistent = TableRegistry::get('I18n')->find()
+			->select(['id', 'field'])
+			->where(['field IN' => $fields, 'locale' => $locale, 'foreign_key' => $key])
+			->bufferResults(false)
+			->indexBy('field');
+
+		$modified = [];
+		foreach ($preexistent as $field => $translation) {
+			$translation->set('content', $values[$field]);
+			$modified[$field] = $translation;
+		}
+
+		$new = array_diff_key($values, $modified);
+		$model = $this->_table->alias();
+		foreach ($new as $field => $content) {
+			$new[$field] = new Entity(compact('locale', 'field', 'content', 'model'), [
+				'useSetters' => false,
+				'markNew' => true
+			]);
+		}
+
+		$entity->set('_i18n', array_values($modified + $new));
+		$entity->set('_locale', $locale, ['setter' => false]);
+		$entity->dirty('_locale', false);
+
+		foreach ($fields as $field) {
+			$entity->dirty($field, false);
+		}
+	}
+
+/**
  * Sets all future finds for the bound table to also fetch translated fields for
  * the passed locale. If no value is passed, it returns the currently configured
  * locale

+ 1 - 0
src/ORM/Association/ExternalAssociationTrait.php

@@ -313,6 +313,7 @@ trait ExternalAssociationTrait {
  */
 	protected function _buildSubquery($query, $foreignKey) {
 		$filterQuery = clone $query;
+		$filterQuery->limit(null);
 		$filterQuery->contain([], true);
 		$joins = $filterQuery->join();
 		foreach ($joins as $i => $join) {

+ 1 - 2
src/ORM/Query.php

@@ -780,8 +780,7 @@ class Query extends DatabaseQuery {
 		if ($this->_dirty) {
 			$this->limit(1);
 		}
-		$this->_results = $this->all();
-		return $this->_results->first();
+		return $this->all()->first();
 	}
 
 /**

+ 18 - 3
src/ORM/ResultSet.php

@@ -114,6 +114,13 @@ class ResultSet implements Countable, Iterator, Serializable, JsonSerializable {
 	protected $_useBuffering = true;
 
 /**
+ * Holds the count of records in this result set
+ *
+ * @var integer
+ */
+	protected $_count;
+
+/**
  * Constructor
  *
  * @param Query from where results come
@@ -128,6 +135,10 @@ class ResultSet implements Countable, Iterator, Serializable, JsonSerializable {
 		$this->_hydrate = $this->_query->hydrate();
 		$this->_entityClass = $query->repository()->entityClass();
 		$this->_useBuffering = $query->bufferResults();
+
+		if ($statement) {
+			$this->count();
+		}
 	}
 
 /**
@@ -199,8 +210,9 @@ class ResultSet implements Countable, Iterator, Serializable, JsonSerializable {
 
 		$this->_current = $this->_fetchResult();
 		$valid = $this->_current !== false;
+		$hasNext = $this->_index < $this->_count;
 
-		if (!$valid && $this->_statement) {
+		if ($this->_statement && !($valid && $hasNext)) {
 			$this->_statement->closeCursor();
 		}
 
@@ -271,10 +283,13 @@ class ResultSet implements Countable, Iterator, Serializable, JsonSerializable {
  * @return integer
  */
 	public function count() {
+		if ($this->_count !== null) {
+			return $this->_count;
+		}
 		if ($this->_statement) {
-			return $this->_statement->rowCount();
+			return $this->_count = $this->_statement->rowCount();
 		}
-		return count($this->_results);
+		return $this->_count = count($this->_results);
 	}
 
 /**

+ 114 - 0
tests/TestCase/Model/Behavior/TranslateBehaviorTest.php

@@ -426,6 +426,7 @@ class TranslateBehaviorTest extends TestCase {
 
 		$results = $table->find()
 			->select(['title', 'body'])
+			->order(['title' => 'asc'])
 			->contain(['Authors' => function($q) {
 				return $q->select(['id', 'name']);
 			}]);
@@ -456,4 +457,117 @@ class TranslateBehaviorTest extends TestCase {
 		$this->assertEquals($expected, $results);
 	}
 
+/**
+ * Tests that updating an existing record translations work
+ *
+ * @return void
+ */
+	public function testUpdateSingleLocale() {
+		$table = TableRegistry::get('Articles');
+		$table->addBehavior('Translate', ['fields' => ['title', 'body']]);
+		$table->locale('eng');
+		$article = $table->find()->first();
+		$this->assertEquals(1, $article->get('id'));
+		$article->set('title', 'New translated article');
+		$table->save($article);
+
+		$article = $table->find()->first();
+		$this->assertEquals(1, $article->get('id'));
+		$this->assertEquals('New translated article', $article->get('title'));
+		$this->assertEquals('Content #1', $article->get('body'));
+
+		$table->locale(false);
+		$article = $table->find()->first();
+		$this->assertEquals(1, $article->get('id'));
+		$this->assertEquals('First Article', $article->get('title'));
+
+		$table->locale('eng');
+		$article->set('title', 'Wow, such translated article');
+		$article->set('body', 'A translated body');
+		$table->save($article);
+
+		$article = $table->find()->first();
+		$this->assertEquals(1, $article->get('id'));
+		$this->assertEquals('Wow, such translated article', $article->get('title'));
+		$this->assertEquals('A translated body', $article->get('body'));
+	}
+
+/**
+ * Tests adding new translation to a record
+ *
+ * @return void
+ */
+	public function testInsertNewTranslations() {
+		$table = TableRegistry::get('Articles');
+		$table->addBehavior('Translate', ['fields' => ['title', 'body']]);
+		$table->locale('fra');
+
+		$article = $table->find()->first();
+		$this->assertEquals(1, $article->get('id'));
+		$article->set('title', 'Le titre');
+		$table->save($article);
+		$this->assertEquals('fra', $article->get('_locale'));
+
+		$article = $table->find()->first();
+		$this->assertEquals(1, $article->get('id'));
+		$this->assertEquals('Le titre', $article->get('title'));
+		$this->assertEquals('First Article Body', $article->get('body'));
+
+		$article->set('title', 'Un autre titre');
+		$article->set('body', 'Le contenu');
+		$table->save($article);
+
+		$article = $table->find()->first();
+		$this->assertEquals('Un autre titre', $article->get('title'));
+		$this->assertEquals('Le contenu', $article->get('body'));
+	}
+
+/**
+ * Tests that it is possible to use the _locale property to specify the language
+ * to use for saving an entity
+ *
+ * @return void
+ */
+	public function testUpdateTranslationWithLocaleInEntity() {
+		$table = TableRegistry::get('Articles');
+		$table->addBehavior('Translate', ['fields' => ['title', 'body']]);
+		$article = $table->find()->first();
+		$this->assertEquals(1, $article->get('id'));
+		$article->set('_locale', 'fra');
+		$article->set('title', 'Le titre');
+		$table->save($article);
+
+		$article = $table->find()->first();
+		$this->assertEquals(1, $article->get('id'));
+		$this->assertEquals('First Article', $article->get('title'));
+		$this->assertEquals('First Article Body', $article->get('body'));
+
+		$table->locale('fra');
+		$article = $table->find()->first();
+		$this->assertEquals(1, $article->get('id'));
+		$this->assertEquals('Le titre', $article->get('title'));
+		$this->assertEquals('First Article Body', $article->get('body'));
+	}
+
+/**
+ * Tests that translations are added to the whitelist of associations to be
+ * saved
+ *
+ * @return void
+ */
+	public function testSaveTranslationWithAssociationWhitelist() {
+		$table = TableRegistry::get('Articles');
+		$table->hasMany('Comments');
+		$table->addBehavior('Translate', ['fields' => ['title', 'body']]);
+		$table->locale('fra');
+
+		$article = $table->find()->first();
+		$this->assertEquals(1, $article->get('id'));
+		$article->set('title', 'Le titre');
+		$table->save($article, ['associated' => ['Comments']]);
+
+		$article = $table->find()->first();
+		$this->assertEquals('Le titre', $article->get('title'));
+	}
+
 }

+ 4 - 1
tests/TestCase/ORM/QueryTest.php

@@ -885,7 +885,10 @@ class QueryTest extends TestCase {
 		$params = [$this->connection, $this->table];
 		$query = $this->getMock('\Cake\ORM\Query', ['execute'], $params);
 
-		$statement = $this->getMock('\Database\StatementInterface', ['fetch', 'closeCursor']);
+		$statement = $this->getMock(
+			'\Database\StatementInterface',
+			['fetch', 'closeCursor', 'rowCount']
+		);
 		$statement->expects($this->exactly(3))
 			->method('fetch')
 			->will($this->onConsecutiveCalls(['a' => 1], ['a' => 2], false));