diff --git a/src/Model/Multilingual.php b/src/Model/Multilingual.php index 165a156..267e851 100644 --- a/src/Model/Multilingual.php +++ b/src/Model/Multilingual.php @@ -11,357 +11,9 @@ namespace Terminal42\DcMultilingualBundle\Model; -use Contao\Database; -use Contao\Database\Result; use Contao\Model; -use Contao\Model\Collection; -use Doctrine\DBAL\Query\QueryBuilder; -use Terminal42\DcMultilingualBundle\QueryBuilder\MultilingualQueryBuilderFactoryInterface; class Multilingual extends Model { - /** - * Returns the ID of the fallback language. - */ - public function getLanguageId() - { - $pidColumn = static::getPidColumn(); - - if ($this->{$pidColumn} > 0) { - return $this->{$pidColumn}; - } - - return $this->id; - } - - /** - * Get the alias of a multilingual model. - * - * @param string $language - * @param string $aliasColumnName - * - * @return mixed - */ - public function getAlias($language, $aliasColumnName = 'alias') - { - // Do not load any translation if already target language - $langColumn = static::getLangColumn(); - $fallbackLang = static::getFallbackLanguage(); - - if ($language === $fallbackLang && !$this->{$langColumn}) { - return $this->{$aliasColumnName}; - } - - if ($language === $this->{$langColumn}) { - return $this->{$aliasColumnName}; - } - - // Try to load the translated model - $translatedModel = static::findByPk($this->getLanguageId(), ['language' => $language]); - - if (null === $translatedModel) { - // Get fallback - if ($language === $fallbackLang) { - return $this->{$aliasColumnName}; - } - - $fallbackModel = static::findByPk($this->getLanguageId(), ['language' => $fallbackLang]); - - return $fallbackModel->{$aliasColumnName}; - } - - return $translatedModel->{$aliasColumnName}; - } - - /** - * Find a model by its alias. - * - * @param $alias - * @param string $aliasColumnName - * @param array $options - * - * @return mixed - */ - public static function findByAlias($alias, $aliasColumnName = 'alias', $options = []) - { - $table = static::getTable(); - $options = array_merge([ - 'limit' => 1, - 'column' => ["$table.$aliasColumnName=?"], - 'value' => [$alias], - 'return' => 'Model', - ], - $options - ); - - return static::find($options); - } - - /** - * Find a model by its alias when using multilingal aliases. - * - * @param $alias - * @param string $aliasColumnName - * @param array $options - * - * @return mixed - */ - public static function findByMultilingualAlias($alias, $aliasColumnName = 'alias', $options = []) - { - $table = static::getTable(); - $options = array_merge([ - 'limit' => 1, - 'column' => ["($table.$aliasColumnName=? OR translation.$aliasColumnName=?)"], - 'value' => [$alias, $alias], - 'return' => 'Model', - ], - $options - ); - - return static::find($options); - } - - /** - * Get the language column. - * - * @return string - */ - public static function getLangColumn() - { - static::ensureDataContainerIsLoaded(); - - if ($GLOBALS['TL_DCA'][static::getTable()]['config']['langColumnName']) { - return $GLOBALS['TL_DCA'][static::getTable()]['config']['langColumnName']; - } - - return 'language'; - } - - /** - * Get the fallback language if available. - * - * @return string|null - */ - public static function getFallbackLanguage() - { - static::ensureDataContainerIsLoaded(); - - if ($GLOBALS['TL_DCA'][static::getTable()]['config']['fallbackLang']) { - return $GLOBALS['TL_DCA'][static::getTable()]['config']['fallbackLang']; - } - - return null; - } - - /** - * Build a query based on the given options. - * The method returns a QueryBuilder instance so you can easily modify - * the query in your child class. We can just return the instance as the - * QueryBuilder implements the __toString() method so we don't have to call - * ->getSql() manually. - * - * @param array $options The options array - * - * @return QueryBuilder - */ - protected static function buildFindQuery(array $options) - { - $mlqb = static::getMultilingualQueryBuilder(); - - // Use the current language if none provided - if (!isset($options['language'])) { - $options['language'] = str_replace('-', '_', $GLOBALS['TL_LANGUAGE']); - } - - // Consider the fallback language - $fallbackLang = static::getFallbackLanguage(); - if (null !== $fallbackLang && $fallbackLang === $options['language']) { - $options['language'] = ''; - } - - $mlqb->buildQueryBuilderForFind($options['language']); - - static::applyOptionsToQueryBuilder($mlqb->getQueryBuilder(), $options); - - return $mlqb->getQueryBuilder(); - } - - /** - * Build a query based on the given options to count the number of records. - * The method returns a QueryBuilder instance so you can easily modify - * the query in your child class. We can just return the instance as the - * QueryBuilder implements the __toString() method so we don't have to call - * ->getSql() manually. - * - * @param array $options The options array - * - * @return QueryBuilder - */ - protected static function buildCountQuery(array $options) - { - $mlqb = static::getMultilingualQueryBuilder(); - - if (isset($options['having'])) { - $mlqb->buildQueryBuilderForCountWithSubQuery(static::buildFindQuery($options)); - } else { - $mlqb->buildQueryBuilderForCount(); - static::applyOptionsToQueryBuilder($mlqb->getQueryBuilder(), $options); - } - - return $mlqb->getQueryBuilder(); - } - - /** - * Prevent model from saving when creating a model from a database result. See #51 - * - * @param Result $objResult The database result object - * - * @return static The model - */ - protected static function createModelFromDbResult(Result $objResult) - { - $model = new static($objResult); - $model->preventSaving(false); - - return $model; - } - - /** - * Prevent new models from saving when creating a new collection from a database result. See #51 - * - * @param Result $objResult The database result object - * @param string $strTable The table name - * - * @return Collection The model collection - */ - protected static function createCollectionFromDbResult(Result $objResult, $strTable) - { - $collection = Collection::createFromDbResult($objResult, $strTable); - - /** @var self $model */ - foreach ($collection as $model) { - $model->preventSaving(false); - } - - return $collection->reset(); - } - - /** - * Apply the model options to the query builder. - * - * @param QueryBuilder $qb - * @param array $options - */ - protected static function applyOptionsToQueryBuilder(QueryBuilder $qb, array $options) - { - // Columns - if (null !== $options['column']) { - if (is_array($options['column'])) { - foreach ($options['column'] as $column) { - $qb->andWhere($column); - } - } else { - // Default is likely fallback table - $table = static::getTable(); - $qb->andWhere("$table.{$options['column']}=?"); - } - } - - // Group by - if (null !== $options['group']) { - $qb->groupBy($options['group']); - } - - // Having - if (null !== $options['having']) { - $qb->having($options['having']); - } - - // Order by - if (null !== $options['order']) { - $qb->add('orderBy', $options['order']); - } - } - - /** - * Get the MultilingualQueryBuilder. - * - * @return \Terminal42\DcMultilingualBundle\QueryBuilder\MultilingualQueryBuilderInterface - */ - protected static function getMultilingualQueryBuilder() - { - /** @var MultilingualQueryBuilderFactoryInterface $factory */ - $factory = \System::getContainer()->get('terminal42.dc_multilingual.querybuilder_factory'); - - return $factory->build( - static::getTable(), - static::getPidColumn(), - static::getLangColumn(), - static::getRegularFields(), - static::getTranslatableFields() - ); - } - - /** - * Get the regular fields. - * - * @return array - */ - protected static function getRegularFields() - { - $extractor = \DcaExtractor::getInstance(static::getTable()); - $tableColumns = Database::getInstance()->getFieldNames(static::getTable()); - - return array_intersect($tableColumns, array_keys($extractor->getFields())); - } - - /** - * Get the fields that are translatable. - * - * @return array - */ - protected static function getTranslatableFields() - { - static::ensureDataContainerIsLoaded(); - - $fields = []; - $tableColumns = Database::getInstance()->getFieldNames(static::getTable()); - - foreach ($GLOBALS['TL_DCA'][static::getTable()]['fields'] as $field => $data) { - if (!isset($data['eval']['translatableFor']) || !in_array($field, $tableColumns, true)) { - continue; - } - - $fields[] = $field; - } - - return $fields; - } - - /** - * Get the PID column. - * - * @return string - */ - protected static function getPidColumn() - { - static::ensureDataContainerIsLoaded(); - - if ($GLOBALS['TL_DCA'][static::getTable()]['config']['langPid']) { - return $GLOBALS['TL_DCA'][static::getTable()]['config']['langPid']; - } - - return 'langPid'; - } - - /** - * Ensure the data container is loaded. - */ - protected static function ensureDataContainerIsLoaded() - { - if (!isset($GLOBALS['TL_DCA'][static::getTable()])) { - $loader = new \DcaLoader(static::getTable()); - $loader->load(); - } - } + use MultilingualTrait; } diff --git a/src/Model/MultilingualTrait.php b/src/Model/MultilingualTrait.php new file mode 100644 index 0000000..1e37c0a --- /dev/null +++ b/src/Model/MultilingualTrait.php @@ -0,0 +1,366 @@ +<?php + +/* + * dc_multilingual Extension for Contao Open Source CMS + * + * @copyright Copyright (c) 2011-2017, terminal42 gmbh + * @author terminal42 gmbh <info@terminal42.ch> + * @license http://opensource.org/licenses/lgpl-3.0.html LGPL + * @link http://github.com/terminal42/contao-dc_multilingual + */ + +namespace Terminal42\DcMultilingualBundle\Model; + +use Contao\Database; +use Contao\Database\Result; +use Contao\Model\Collection; +use Doctrine\DBAL\Query\QueryBuilder; +use Terminal42\DcMultilingualBundle\QueryBuilder\MultilingualQueryBuilderFactoryInterface; + +trait MultilingualTrait +{ + /** + * Returns the ID of the fallback language. + */ + public function getLanguageId() + { + $pidColumn = static::getPidColumn(); + + if ($this->{$pidColumn} > 0) { + return $this->{$pidColumn}; + } + + return $this->id; + } + + /** + * Get the alias of a multilingual model. + * + * @param string $language + * @param string $aliasColumnName + * + * @return mixed + */ + public function getAlias($language, $aliasColumnName = 'alias') + { + // Do not load any translation if already target language + $langColumn = static::getLangColumn(); + $fallbackLang = static::getFallbackLanguage(); + + if ($language === $fallbackLang && !$this->{$langColumn}) { + return $this->{$aliasColumnName}; + } + + if ($language === $this->{$langColumn}) { + return $this->{$aliasColumnName}; + } + + // Try to load the translated model + $translatedModel = static::findByPk($this->getLanguageId(), ['language' => $language]); + + if (null === $translatedModel) { + // Get fallback + if ($language === $fallbackLang) { + return $this->{$aliasColumnName}; + } + + $fallbackModel = static::findByPk($this->getLanguageId(), ['language' => $fallbackLang]); + + return $fallbackModel->{$aliasColumnName}; + } + + return $translatedModel->{$aliasColumnName}; + } + + /** + * Find a model by its alias. + * + * @param $alias + * @param string $aliasColumnName + * @param array $options + * + * @return mixed + */ + public static function findByAlias($alias, $aliasColumnName = 'alias', $options = []) + { + $table = static::getTable(); + $options = array_merge([ + 'limit' => 1, + 'column' => ["$table.$aliasColumnName=?"], + 'value' => [$alias], + 'return' => 'Model', + ], + $options + ); + + return static::find($options); + } + + /** + * Find a model by its alias when using multilingal aliases. + * + * @param $alias + * @param string $aliasColumnName + * @param array $options + * + * @return mixed + */ + public static function findByMultilingualAlias($alias, $aliasColumnName = 'alias', $options = []) + { + $table = static::getTable(); + $options = array_merge([ + 'limit' => 1, + 'column' => ["($table.$aliasColumnName=? OR translation.$aliasColumnName=?)"], + 'value' => [$alias, $alias], + 'return' => 'Model', + ], + $options + ); + + return static::find($options); + } + + /** + * Get the language column. + * + * @return string + */ + public static function getLangColumn() + { + static::ensureDataContainerIsLoaded(); + + if ($GLOBALS['TL_DCA'][static::getTable()]['config']['langColumnName']) { + return $GLOBALS['TL_DCA'][static::getTable()]['config']['langColumnName']; + } + + return 'language'; + } + + /** + * Get the fallback language if available. + * + * @return string|null + */ + public static function getFallbackLanguage() + { + static::ensureDataContainerIsLoaded(); + + if ($GLOBALS['TL_DCA'][static::getTable()]['config']['fallbackLang']) { + return $GLOBALS['TL_DCA'][static::getTable()]['config']['fallbackLang']; + } + + return null; + } + + /** + * Build a query based on the given options. + * The method returns a QueryBuilder instance so you can easily modify + * the query in your child class. We can just return the instance as the + * QueryBuilder implements the __toString() method so we don't have to call + * ->getSql() manually. + * + * @param array $options The options array + * + * @return QueryBuilder + */ + protected static function buildFindQuery(array $options) + { + $mlqb = static::getMultilingualQueryBuilder(); + + // Use the current language if none provided + if (!isset($options['language'])) { + $options['language'] = str_replace('-', '_', $GLOBALS['TL_LANGUAGE']); + } + + // Consider the fallback language + $fallbackLang = static::getFallbackLanguage(); + if (null !== $fallbackLang && $fallbackLang === $options['language']) { + $options['language'] = ''; + } + + $mlqb->buildQueryBuilderForFind($options['language']); + + static::applyOptionsToQueryBuilder($mlqb->getQueryBuilder(), $options); + + return $mlqb->getQueryBuilder(); + } + + /** + * Build a query based on the given options to count the number of records. + * The method returns a QueryBuilder instance so you can easily modify + * the query in your child class. We can just return the instance as the + * QueryBuilder implements the __toString() method so we don't have to call + * ->getSql() manually. + * + * @param array $options The options array + * + * @return QueryBuilder + */ + protected static function buildCountQuery(array $options) + { + $mlqb = static::getMultilingualQueryBuilder(); + + if (isset($options['having'])) { + $mlqb->buildQueryBuilderForCountWithSubQuery(static::buildFindQuery($options)); + } else { + $mlqb->buildQueryBuilderForCount(); + static::applyOptionsToQueryBuilder($mlqb->getQueryBuilder(), $options); + } + + return $mlqb->getQueryBuilder(); + } + + /** + * Prevent model from saving when creating a model from a database result. See #51 + * + * @param Result $objResult The database result object + * + * @return static The model + */ + protected static function createModelFromDbResult(Result $objResult) + { + $model = new static($objResult); + $model->preventSaving(false); + + return $model; + } + + /** + * Prevent new models from saving when creating a new collection from a database result. See #51 + * + * @param Result $objResult The database result object + * @param string $strTable The table name + * + * @return Collection The model collection + */ + protected static function createCollectionFromDbResult(Result $objResult, $strTable) + { + $collection = Collection::createFromDbResult($objResult, $strTable); + + /** @var self $model */ + foreach ($collection as $model) { + $model->preventSaving(false); + } + + return $collection->reset(); + } + + /** + * Apply the model options to the query builder. + * + * @param QueryBuilder $qb + * @param array $options + */ + protected static function applyOptionsToQueryBuilder(QueryBuilder $qb, array $options) + { + // Columns + if (null !== $options['column']) { + if (is_array($options['column'])) { + foreach ($options['column'] as $column) { + $qb->andWhere($column); + } + } else { + // Default is likely fallback table + $table = static::getTable(); + $qb->andWhere("$table.{$options['column']}=?"); + } + } + + // Group by + if (null !== $options['group']) { + $qb->groupBy($options['group']); + } + + // Having + if (null !== $options['having']) { + $qb->having($options['having']); + } + + // Order by + if (null !== $options['order']) { + $qb->add('orderBy', $options['order']); + } + } + + /** + * Get the MultilingualQueryBuilder. + * + * @return \Terminal42\DcMultilingualBundle\QueryBuilder\MultilingualQueryBuilderInterface + */ + protected static function getMultilingualQueryBuilder() + { + /** @var MultilingualQueryBuilderFactoryInterface $factory */ + $factory = \System::getContainer()->get('terminal42.dc_multilingual.querybuilder_factory'); + + return $factory->build( + static::getTable(), + static::getPidColumn(), + static::getLangColumn(), + static::getRegularFields(), + static::getTranslatableFields() + ); + } + + /** + * Get the regular fields. + * + * @return array + */ + protected static function getRegularFields() + { + $extractor = \DcaExtractor::getInstance(static::getTable()); + $tableColumns = Database::getInstance()->getFieldNames(static::getTable()); + + return array_intersect($tableColumns, array_keys($extractor->getFields())); + } + + /** + * Get the fields that are translatable. + * + * @return array + */ + protected static function getTranslatableFields() + { + static::ensureDataContainerIsLoaded(); + + $fields = []; + $tableColumns = Database::getInstance()->getFieldNames(static::getTable()); + + foreach ($GLOBALS['TL_DCA'][static::getTable()]['fields'] as $field => $data) { + if (!isset($data['eval']['translatableFor']) || !in_array($field, $tableColumns, true)) { + continue; + } + + $fields[] = $field; + } + + return $fields; + } + + /** + * Get the PID column. + * + * @return string + */ + protected static function getPidColumn() + { + static::ensureDataContainerIsLoaded(); + + if ($GLOBALS['TL_DCA'][static::getTable()]['config']['langPid']) { + return $GLOBALS['TL_DCA'][static::getTable()]['config']['langPid']; + } + + return 'langPid'; + } + + /** + * Ensure the data container is loaded. + */ + protected static function ensureDataContainerIsLoaded() + { + if (!isset($GLOBALS['TL_DCA'][static::getTable()])) { + $loader = new \DcaLoader(static::getTable()); + $loader->load(); + } + } +}