Commits

Hisateru Tanaka committed db4786e Merge

Merge

Comments (0)

Files changed (18)

app/protected/config/common.php

             'gamificationObserver' => array(
                 'class' => 'application.modules.gamification.observers.GamificationObserver',
             ),
+            /* Will be enabled later
+            'messages' => array(
+                'class' => 'application.core.components.ZurmoMessageSource',
+            ),
+            */
             'minScript' => array(
                 'class' => 'application.core.components.ZurmoExtMinScript',
                 'groupMap' => array(

app/protected/core/components/ZurmoGettextPoFile.php

+<?php
+    /*********************************************************************************
+     * Zurmo is a customer relationship management program developed by
+     * Zurmo, Inc. Copyright (C) 2012 Zurmo Inc.
+     *
+     * Zurmo is free software; you can redistribute it and/or modify it under
+     * the terms of the GNU General Public License version 3 as published by the
+     * Free Software Foundation with the addition of the following permission added
+     * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
+     * IN WHICH THE COPYRIGHT IS OWNED BY ZURMO, ZURMO DISCLAIMS THE WARRANTY
+     * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
+     *
+     * Zurmo is distributed in the hope that it will be useful, but WITHOUT
+     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+     * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+     * details.
+     *
+     * You should have received a copy of the GNU General Public License along with
+     * this program; if not, see http://www.gnu.org/licenses or write to the Free
+     * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+     * 02110-1301 USA.
+     *
+     * You can contact Zurmo, Inc. with a mailing address at 113 McHenry Road Suite 207,
+     * Buffalo Grove, IL 60089, USA. or at email address contact@zurmo.com.
+     ********************************************************************************/
+
+    /**
+     * Represents a PO Gettext message file
+     */
+    class ZurmoGettextPoFile extends CGettextPoFile
+    {
+        /**
+         * Loads messages from a PO file.
+         * @param string $file file path
+         * @param bool $skipEmptyContext If set to true, then all messages without
+         *                               or wirh empty context will be skiped
+         * @return array message translations with context:
+         *              context => array(source message => translated message)
+         */
+        public function loadWithContext($file, $skipEmptyContext=true)
+        {
+            assert('is_string($file)');
+            if (!is_string($file))
+            {
+                throw new NotSupportedException();
+            }
+
+            assert('is_readable($file)');
+            if (!is_readable($file))
+            {
+                throw new FileNotReadableException();
+            }
+
+            $pattern='/(msgctxt\s+"(.*?(?<!\\\\))")?'
+                    . '\s+msgid\s+"(.*?(?<!\\\\))"'
+                    . '\s+msgstr\s+"(.*?(?<!\\\\))"/';
+
+            $content=file_get_contents($file);
+
+            $n=preg_match_all($pattern,$content,$matches);
+
+            $messages=array();
+            for($i=0;$i<$n;++$i)
+            {
+                $context = $matches[2][$i];
+                if ($skipEmptyContext && empty($context)) continue;
+
+                $id=$this->decode($matches[3][$i]);
+                if (empty($id)) continue;
+
+                $message=$this->decode($matches[4][$i]);
+
+                if (!isset($messages[$context]))
+                {
+                    $messages[$context] = array();
+                }
+
+                $messages[$context][$id] = $message;
+            }
+
+            return $messages;
+        }
+    }
+?>

app/protected/core/components/ZurmoMessageSource.php

+<?php
+    /*********************************************************************************
+     * Zurmo is a customer relationship management program developed by
+     * Zurmo, Inc. Copyright (C) 2012 Zurmo Inc.
+     *
+     * Zurmo is free software; you can redistribute it and/or modify it under
+     * the terms of the GNU General Public License version 3 as published by the
+     * Free Software Foundation with the addition of the following permission added
+     * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
+     * IN WHICH THE COPYRIGHT IS OWNED BY ZURMO, ZURMO DISCLAIMS THE WARRANTY
+     * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
+     *
+     * Zurmo is distributed in the hope that it will be useful, but WITHOUT
+     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+     * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+     * details.
+     *
+     * You should have received a copy of the GNU General Public License along with
+     * this program; if not, see http://www.gnu.org/licenses or write to the Free
+     * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+     * 02110-1301 USA.
+     *
+     * You can contact Zurmo, Inc. with a mailing address at 113 McHenry Road Suite 207,
+     * Buffalo Grove, IL 60089, USA. or at email address contact@zurmo.com.
+     ********************************************************************************/
+
+    /**
+     * Represents a message source that stores translated messages in database.
+     *
+     * The ZurmoMessageSource::installSchema() method must be called to create
+     * the tables with required indexes
+     */
+    class ZurmoMessageSource extends CDbMessageSource
+    {
+        const CACHE_KEY_PREFIX='ZurmoMessageSource';
+
+        protected function loadMessagesFromDb($category,$languageCode)
+        {
+            $sourceTableName   = RedBeanModel::getTableName('MessageSource');
+            $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter('MessageTranslation');
+            $joinTablesAdapter->addFromTableAndGetAliasName($sourceTableName, "{$sourceTableName}_id");
+
+            $where             =  " messagesource.`category` = '$category' AND"
+                                . " messagetranslation.`language` = '$languageCode' ";
+
+            $beans = MessageTranslation::getSubset($joinTablesAdapter, null, null, $where);
+
+            $messages = array();
+            foreach ($beans as $bean) {
+                $messages[$bean->messagesource->source] = $bean->translation;
+            }
+
+            return $messages;
+        }
+    }
+?>

app/protected/core/exceptions/FileNotReadableException.php

+<?php
+    /*********************************************************************************
+     * Zurmo is a customer relationship management program developed by
+     * Zurmo, Inc. Copyright (C) 2012 Zurmo Inc.
+     *
+     * Zurmo is free software; you can redistribute it and/or modify it under
+     * the terms of the GNU General Public License version 3 as published by the
+     * Free Software Foundation with the addition of the following permission added
+     * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
+     * IN WHICH THE COPYRIGHT IS OWNED BY ZURMO, ZURMO DISCLAIMS THE WARRANTY
+     * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
+     *
+     * Zurmo is distributed in the hope that it will be useful, but WITHOUT
+     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+     * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+     * details.
+     *
+     * You should have received a copy of the GNU General Public License along with
+     * this program; if not, see http://www.gnu.org/licenses or write to the Free
+     * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+     * 02110-1301 USA.
+     *
+     * You can contact Zurmo, Inc. with a mailing address at 113 McHenry Road Suite 207,
+     * Buffalo Grove, IL 60089, USA. or at email address contact@zurmo.com.
+     ********************************************************************************/
+
+    /**
+     * Exception thrown when file is not readable.
+     */
+    class FileNotReadableException extends CException
+    {
+    }
+?>

app/protected/core/models/MessageSource.php

+<?php
+    /*********************************************************************************
+     * Zurmo is a customer relationship management program developed by
+     * Zurmo, Inc. Copyright (C) 2012 Zurmo Inc.
+     *
+     * Zurmo is free software; you can redistribute it and/or modify it under
+     * the terms of the GNU General Public License version 3 as published by the
+     * Free Software Foundation with the addition of the following permission added
+     * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
+     * IN WHICH THE COPYRIGHT IS OWNED BY ZURMO, ZURMO DISCLAIMS THE WARRANTY
+     * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
+     *
+     * Zurmo is distributed in the hope that it will be useful, but WITHOUT
+     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+     * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+     * details.
+     *
+     * You should have received a copy of the GNU General Public License along with
+     * this program; if not, see http://www.gnu.org/licenses or write to the Free
+     * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+     * 02110-1301 USA.
+     *
+     * You can contact Zurmo, Inc. with a mailing address at 113 McHenry Road Suite 207,
+     * Buffalo Grove, IL 60089, USA. or at email address contact@zurmo.com.
+     ********************************************************************************/
+
+    class MessageSource extends RedBeanModel
+    {
+        public static function getDefaultMetadata()
+        {
+            $metadata = parent::getDefaultMetadata();
+            $metadata[__CLASS__] = array(
+                'members' => array(
+                    'category',
+                    'source',
+                ),
+                'rules' => array(
+                    array('category',           'required'),
+                    array('category',           'type', 'type' => 'string'),
+                    array('category',           'length',  'min'  => 1, 'max' => 255),
+                    array('source',             'required'),
+                    array('source',             'type', 'type' => 'blob')
+                )
+            );
+            return $metadata;
+        }
+
+        /**
+         * Gets a model from the database by category and source message
+         * @param $category String Category fo the source
+         * @param $source String The source message
+         * @param $modelClassName Pass only when getting it at runtime
+         *                        gets the wrong name.
+         * @return A model of the type of the extending model.
+         */
+        public static function getByCategoryAndSource($category, $source, $modelClassName = null)
+        {
+            assert('!empty($category)');
+            assert('!empty($source)');
+            assert('$modelClassName === null || is_string($modelClassName) && $modelClassName != ""');
+            if ($modelClassName === null)
+            {
+                $modelClassName = get_called_class();
+            }
+            $tableName = self::getTableName($modelClassName);
+            $bean = R::findOne(
+                               $tableName,
+                               ' category = :category AND source = :source',
+                               array(
+                                     ':category'=>$category,
+                                     ':source'=>$source
+                                     )
+                               );
+            assert('$bean === false || $bean instanceof RedBean_OODBBean');
+            if (!is_object($bean))
+            {
+                throw new NotFoundException();
+            }
+            return self::makeModel($bean, $modelClassName);
+        }
+
+        /**
+         * Adds new message source to the database
+         *
+         * @param String $category Category of the source message
+         * @param String $source The source message
+         */
+        public static function addNewSource($category, $source)
+        {
+            assert('is_string($category) && !empty($category)');
+            assert('is_string($source) && !empty($source)');
+            $model = new MessageSource();
+            $model->category = $category;
+            $model->source   = $source;
+            if (!$model->save())
+            {
+                throw new FailedToSaveModelException();
+            }
+
+            return $model;
+        }
+    }
+?>

app/protected/core/models/MessageTranslation.php

+<?php
+    /*********************************************************************************
+     * Zurmo is a customer relationship management program developed by
+     * Zurmo, Inc. Copyright (C) 2012 Zurmo Inc.
+     *
+     * Zurmo is free software; you can redistribute it and/or modify it under
+     * the terms of the GNU General Public License version 3 as published by the
+     * Free Software Foundation with the addition of the following permission added
+     * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
+     * IN WHICH THE COPYRIGHT IS OWNED BY ZURMO, ZURMO DISCLAIMS THE WARRANTY
+     * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
+     *
+     * Zurmo is distributed in the hope that it will be useful, but WITHOUT
+     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+     * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+     * details.
+     *
+     * You should have received a copy of the GNU General Public License along with
+     * this program; if not, see http://www.gnu.org/licenses or write to the Free
+     * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+     * 02110-1301 USA.
+     *
+     * You can contact Zurmo, Inc. with a mailing address at 113 McHenry Road Suite 207,
+     * Buffalo Grove, IL 60089, USA. or at email address contact@zurmo.com.
+     ********************************************************************************/
+
+    class MessageTranslation extends RedBeanModel
+    {
+        public static function getDefaultMetadata()
+        {
+            $metadata = parent::getDefaultMetadata();
+            $metadata[__CLASS__] = array(
+                'members' => array(
+                    'translation',
+                    'language',
+                ),
+                'relations' => array(
+                    'messagesource'   => array(
+                                               RedBeanModel::HAS_ONE,
+                                               'MessageSource',
+                                               RedBeanModel::OWNED
+                                               ),
+                ),
+                'rules' => array(
+                    array('translation',        'required'),
+                    array('translation',        'type', 'type' => 'blob'),
+                    array('language',           'required'),
+                    array('language',           'type', 'type' => 'string'),
+                    array('language',           'length',  'min'  => 1, 'max' => 255),
+                ),
+                'elements' => array(
+                    'messagesource' => 'MessageSource',
+                )
+            );
+            return $metadata;
+        }
+
+        /**
+         * Gets a model from the database by source message id and langcode
+         * @param $sourceId Integer Id of the source message
+         * @param $languageCode String Language code of the translation
+         * @param $modelClassName Pass only when getting it at runtime
+         *                        gets the wrong name.
+         * @return A model of the type of the extending model.
+         */
+        public static function getBySourceIdAndLangCode($sourceId, $languageCode, $modelClassName = null)
+        {
+            assert('intval($sourceId) && $sourceId > 0');
+            assert('!empty($languageCode)');
+            assert('$modelClassName === null || is_string($modelClassName) && $modelClassName != ""');
+            if ($modelClassName === null)
+            {
+                $modelClassName = get_called_class();
+            }
+            $tableName = self::getTableName($modelClassName);
+            $bean = R::findOne(
+                               $tableName,
+                               ' messagesource_id = :sourceId AND language = :languageCode',
+                               array(
+                                     ':sourceId'=>$sourceId,
+                                     ':languageCode'=>$languageCode
+                                     )
+                               );
+            assert('$bean === false || $bean instanceof RedBean_OODBBean');
+            if (!is_object($bean))
+            {
+                throw new NotFoundException();
+            }
+            return self::makeModel($bean, $modelClassName);
+        }
+
+        /**
+         * Adds new message translation to the database
+         *
+         * @param String $languageCode Languagecode of the translation
+         * @param MessageSource $sourceModel MessageSource model for the relation
+         * @param String $translation The translation
+         *
+         * @return Instance of the MessageTranslation model for created translation
+         */
+        public static function addNewTranslation($languageCode, $sourceModel, $translation)
+        {
+            assert('is_string($languageCode) && !empty($languageCode)');
+            assert('$sourceModel instanceof MessageSource');
+            assert('is_string($translation) && !empty($translation)');
+            $model = new MessageTranslation();
+            $model->language      = $languageCode;
+            $model->messagesource = $sourceModel;
+            $model->translation   = $translation;
+            if (!$model->save())
+            {
+                throw new FailedToSaveModelException();
+            }
+
+            return $model;
+        }
+
+        /**
+         * Updates the translation of the current model
+         *
+         * @param String $translation The translation
+         *
+         * @return The updated model
+         */
+        public function updateTranslation($translation)
+        {
+            assert('!empty($translation)');
+            $this->translation = $translation;
+            if (!$this->save())
+            {
+                throw new FailedToSaveModelException();
+            }
+
+            return $this;
+        }
+    }
+?>

app/protected/core/tests/unit/StringUtilTest.php

         public function testGetChoppedStringContentFromString()
         {
             $testString   = 'This is a test string to test the getchoppedstringcontent method for stringutil class.';
-            $compairSting = 'This is a test string to test the getchoppedstringcontent...';
+            $compareSting = 'This is a test string to test the getchoppedstringcontent...';
             $newSting     = StringUtil::getChoppedStringContent($testString, 60);
-            $this->assertEquals($compairSting, $newSting);
+            $this->assertEquals($compareSting, $newSting);
 
             $testString   = 'This is a test string to test the getchoppedstringcontent method for stringutil class.';
-            $compairSting = 'This is a test string to test the getchoppedstringcontent method for stringutil class.';
+            $compareSting = 'This is a test string to test the getchoppedstringcontent method for stringutil class.';
             $newSting     = StringUtil::getChoppedStringContent($testString, 100);
-            $this->assertEquals($compairSting, $newSting);
+            $this->assertEquals($compareSting, $newSting);
 
             $testString   = 'This is a test string to test the getchoppedstringcontent method for stringutil class. This is a test string to test the getchoppedstringcontent method for stringutil class.';
-            $compairSting = 'This is a test string to test the getchoppedstringcontent method for stringutil class. This is a test string to test...';
+            $compareSting = 'This is a test string to test the getchoppedstringcontent method for stringutil class. This is a test string to test...';
             $newSting     = StringUtil::getChoppedStringContent($testString, 119);
-            $this->assertEquals($compairSting, $newSting);
+            $this->assertEquals($compareSting, $newSting);
         }
     }
 ?>

app/protected/core/tests/unit/ZurmoGettextPoFileTest.php

+<?php
+    /*********************************************************************************
+     * Zurmo is a customer relationship management program developed by
+     * Zurmo, Inc. Copyright (C) 2012 Zurmo Inc.
+     *
+     * Zurmo is free software; you can redistribute it and/or modify it under
+     * the terms of the GNU General Public License version 3 as published by the
+     * Free Software Foundation with the addition of the following permission added
+     * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
+     * IN WHICH THE COPYRIGHT IS OWNED BY ZURMO, ZURMO DISCLAIMS THE WARRANTY
+     * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
+     *
+     * Zurmo is distributed in the hope that it will be useful, but WITHOUT
+     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+     * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+     * details.
+     *
+     * You should have received a copy of the GNU General Public License along with
+     * this program; if not, see http://www.gnu.org/licenses or write to the Free
+     * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+     * 02110-1301 USA.
+     *
+     * You can contact Zurmo, Inc. with a mailing address at 113 McHenry Road Suite 207,
+     * Buffalo Grove, IL 60089, USA. or at email address contact@zurmo.com.
+     ********************************************************************************/
+
+    /**
+     * Test class to test the ZurmoGettextPoFile class.
+     */
+    class ZurmoGettextPoFileTest extends BaseTest
+    {
+        protected static $poFileName = 'messages-test.po';
+
+        protected static $compareArray = array (
+            '' => array (
+                'PO file Testmessage. Category: NONE. Type: source 1'
+                    => 'PO file Testmessage. Category: NONE. Type: translation 1',
+                'PO file Testmessage. Category: NONE. Type: source 2'
+                    => 'PO file Testmessage. Category: NONE. Type: translation 2',
+                'PO file Testmessage. Category: NONE. Type: source 3'
+                    => 'PO file Testmessage. Category: NONE. Type: translation 3',
+                'PO file Testmessage. Category: NONE. Type: source 4'
+                    => 'PO file Testmessage. Category: NONE. Type: translation 4',
+                'PO file Testmessage. Category: NONE. Type: source 5'
+                    => 'PO file Testmessage. Category: NONE. Type: translation 5',
+
+                'PO file Testmessage. Category: EMPTY. Type: source 1'
+                    => 'PO file Testmessage. Category: EMPTY. Type: translation 1',
+                'PO file Testmessage. Category: EMPTY. Type: source 2'
+                    => 'PO file Testmessage. Category: EMPTY. Type: translation 2',
+                'PO file Testmessage. Category: EMPTY. Type: source 3'
+                    => 'PO file Testmessage. Category: EMPTY. Type: translation 3',
+                'PO file Testmessage. Category: EMPTY. Type: source 4'
+                    => 'PO file Testmessage. Category: EMPTY. Type: translation 4',
+                'PO file Testmessage. Category: EMPTY. Type: source 5'
+                    => 'PO file Testmessage. Category: EMPTY. Type: translation 5',
+            ),
+            'TestCategory1' => array (
+                'PO file Testmessage. Category: 1. Type: source 1'
+                    => 'PO file Testmessage. Category: 1. Type: translation 1',
+                'PO file Testmessage. Category: 1. Type: source 2'
+                    => 'PO file Testmessage. Category: 1. Type: translation 2',
+                'PO file Testmessage. Category: 1. Type: source 3'
+                    => 'PO file Testmessage. Category: 1. Type: translation 3',
+                'PO file Testmessage. Category: 1. Type: source 4'
+                    => 'PO file Testmessage. Category: 1. Type: translation 4',
+                'PO file Testmessage. Category: 1. Type: source 5'
+                    => 'PO file Testmessage. Category: 1. Type: translation 5',
+            ),
+            'TestCategory2' => array (
+                'PO file Testmessage. Category: 2. Type: source 1'
+                    => 'PO file Testmessage. Category: 2. Type: translation 1',
+                'PO file Testmessage. Category: 2. Type: source 2'
+                    => 'PO file Testmessage. Category: 2. Type: translation 2',
+                'PO file Testmessage. Category: 2. Type: source 3'
+                    => 'PO file Testmessage. Category: 2. Type: translation 3',
+                'PO file Testmessage. Category: 2. Type: source 4'
+                    => 'PO file Testmessage. Category: 2. Type: translation 4',
+                'PO file Testmessage. Category: 2. Type: source 5'
+                    => 'PO file Testmessage. Category: 2. Type: translation 5',
+            ),
+            'TestCategory3' => array (
+                'PO file Testmessage. Category: 3. Type: source 1'
+                    => 'PO file Testmessage. Category: 3. Type: translation 1',
+                'PO file Testmessage. Category: 3. Type: source 2'
+                    => 'PO file Testmessage. Category: 3. Type: translation 2',
+                'PO file Testmessage. Category: 3. Type: source 3'
+                    => 'PO file Testmessage. Category: 3. Type: translation 3',
+                'PO file Testmessage. Category: 3. Type: source 4'
+                    => 'PO file Testmessage. Category: 3. Type: translation 4',
+                'PO file Testmessage. Category: 3. Type: source 5'
+                    => 'PO file Testmessage. Category: 3. Type: translation 5',
+            )
+        );
+
+        protected static function getFilePath($fileName)
+        {
+            $pathToFiles = Yii::getPathOfAlias('application.tests.unit.files');
+
+            return $pathToFiles . DIRECTORY_SEPARATOR . $fileName;
+        }
+
+        protected static function getCompareArray($withoutEmptyContext=true)
+        {
+            $array =  self::$compareArray;
+            if ($withoutEmptyContext)
+            {
+                unset($array['']);
+            }
+
+            return $array;
+        }
+
+        public function testLoadWithContextWithoutEmpty() {
+            $filePath = self::getFilePath(self::$poFileName);
+            $compareArray = self::getCompareArray();
+
+            $poFile = new ZurmoGettextPoFile();
+            $contextArray = $poFile->loadWithContext($filePath);
+            $this->assertEquals(
+                                md5(json_encode($compareArray)),
+                                md5(json_encode($contextArray))
+                                );
+        }
+
+        public function testLoadWithContextWithEmpty() {
+            $filePath = self::getFilePath(self::$poFileName);
+            $compareArray = self::getCompareArray(false);
+
+            $poFile = new ZurmoGettextPoFile();
+            $contextArray = $poFile->loadWithContext($filePath, false);
+            $this->assertEquals(
+                                md5(json_encode($compareArray)),
+                                md5(json_encode($contextArray))
+                                );
+        }
+    }
+?>

app/protected/core/tests/unit/ZurmoMessageSourceTest.php

+<?php
+    /*********************************************************************************
+     * Zurmo is a customer relationship management program developed by
+     * Zurmo, Inc. Copyright (C) 2012 Zurmo Inc.
+     *
+     * Zurmo is free software; you can redistribute it and/or modify it under
+     * the terms of the GNU General Public License version 3 as published by the
+     * Free Software Foundation with the addition of the following permission added
+     * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
+     * IN WHICH THE COPYRIGHT IS OWNED BY ZURMO, ZURMO DISCLAIMS THE WARRANTY
+     * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
+     *
+     * Zurmo is distributed in the hope that it will be useful, but WITHOUT
+     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+     * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+     * details.
+     *
+     * You should have received a copy of the GNU General Public License along with
+     * this program; if not, see http://www.gnu.org/licenses or write to the Free
+     * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+     * 02110-1301 USA.
+     *
+     * You can contact Zurmo, Inc. with a mailing address at 113 McHenry Road Suite 207,
+     * Buffalo Grove, IL 60089, USA. or at email address contact@zurmo.com.
+     ********************************************************************************/
+
+    /**
+     * Test class to test the ZurmoMessageSource class.
+     */
+    class ZurmoMessageSourceTest extends BaseTest
+    {
+        protected static $testLanguageCode = 'de';
+
+        protected static $testCategory = 'test';
+
+        protected static $testMessages = array(
+                                    'message1-source'=>'message1-translation',
+                                    'message2-source'=>'message2-translation',
+                                    'message3-source'=>'message3-translation',
+                                    'message4-source'=>'message4-translation',
+                                    'message5-source'=>'message5-translation',
+                                    'message6-source'=>'message6-translation'
+        );
+
+        public static function setupBeforeClass()
+        {
+            parent::setUpBeforeClass();
+
+            foreach (self::$testMessages as $source=>$translation)
+            {
+                $sourceModel = MessageSource::addNewSource(
+                                                           self::$testCategory,
+                                                           $source
+                                                           );
+                MessageTranslation::addNewTranslation(
+                                                      self::$testLanguageCode,
+                                                      $sourceModel,
+                                                      $translation
+                                                      );
+            }
+        }
+
+        public function testLoadMessagesFromDb()
+        {
+            $messageSource = new ZurmoMessageSource();
+
+            foreach (self::$testMessages as $source=>$compareTranslation)
+            {
+                $translation = $messageSource->translate(
+                                                         self::$testCategory,
+                                                         $source,
+                                                         self::$testLanguageCode
+                                                         );
+                $this->assertEquals($translation, $compareTranslation);
+            }
+        }
+    }
+?>

app/protected/core/tests/unit/ZurmoMessageSourceUtilTest.php

+<?php
+    /*********************************************************************************
+     * Zurmo is a customer relationship management program developed by
+     * Zurmo, Inc. Copyright (C) 2012 Zurmo Inc.
+     *
+     * Zurmo is free software; you can redistribute it and/or modify it under
+     * the terms of the GNU General Public License version 3 as published by the
+     * Free Software Foundation with the addition of the following permission added
+     * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
+     * IN WHICH THE COPYRIGHT IS OWNED BY ZURMO, ZURMO DISCLAIMS THE WARRANTY
+     * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
+     *
+     * Zurmo is distributed in the hope that it will be useful, but WITHOUT
+     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+     * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+     * details.
+     *
+     * You should have received a copy of the GNU General Public License along with
+     * this program; if not, see http://www.gnu.org/licenses or write to the Free
+     * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+     * 02110-1301 USA.
+     *
+     * You can contact Zurmo, Inc. with a mailing address at 113 McHenry Road Suite 207,
+     * Buffalo Grove, IL 60089, USA. or at email address contact@zurmo.com.
+     ********************************************************************************/
+
+    /**
+     * Test class to test the ZurmoMessageSourceUtil class.
+     */
+    class ZurmoMessageSourceUtilTest extends BaseTest
+    {
+        protected static $testLanguageCode = 'de';
+
+        protected static $testCategory = 'UtilTest';
+
+        protected static $testMessageSource = 'messageUtilOne-source';
+
+        protected static $testMessageNewTranslation = 'messageUtilOne-translation';
+
+        protected static $testMessageUpdatedTranslation = 'messageUtilOne-updatedTranslation';
+
+        protected static $testMessagesNew = array(
+                    'messageUtil1-source'=>'messageUtil1-translation',
+                    'messageUtil2-source'=>'messageUtil2-translation',
+                    'messageUtil3-source'=>'messageUtil3-translation',
+                    'messageUtil4-source'=>'messageUtil4-translation',
+                    'messageUtil5-source'=>'messageUtil5-translation',
+                    'messageUtil6-source'=>'messageUtil6-translation'
+        );
+
+        protected static $testMessagesUpdated = array(
+                    'messageUtil1-source'=>'messageUtil1-updatedTranslation',
+                    'messageUtil2-source'=>'messageUtil2-updatedTranslation',
+                    'messageUtil3-source'=>'messageUtil3-updatedTranslation',
+                    'messageUtil4-source'=>'messageUtil4-updatedTranslation',
+                    'messageUtil5-source'=>'messageUtil5-updatedTranslation',
+                    'messageUtil6-source'=>'messageUtil6-updatedTranslation'
+        );
+
+        public function testImportOneMessageNew() {
+            ZurmoMessageSourceUtil::importOneMessage(
+                                                self::$testLanguageCode,
+                                                self::$testCategory,
+                                                self::$testMessageSource,
+                                                self::$testMessageNewTranslation
+            );
+
+            $messageSource = new ZurmoMessageSource();
+
+            $translation = $messageSource->translate(
+                                                     self::$testCategory,
+                                                     self::$testMessageSource,
+                                                     self::$testLanguageCode
+                                                     );
+
+            $this->assertEquals($translation, self::$testMessageNewTranslation);
+        }
+
+        /**
+         * @depends testImportOneMessageNew
+         */
+        public function testImportOneMessageUpdated() {
+            ZurmoMessageSourceUtil::importOneMessage(
+                                            self::$testLanguageCode,
+                                            self::$testCategory,
+                                            self::$testMessageSource,
+                                            self::$testMessageUpdatedTranslation
+            );
+
+            $messageSource = new ZurmoMessageSource();
+
+            $translation = $messageSource->translate(
+                                                     self::$testCategory,
+                                                     self::$testMessageSource,
+                                                     self::$testLanguageCode
+                                                     );
+
+            $this->assertEquals($translation, self::$testMessageUpdatedTranslation);
+        }
+
+        /**
+         * @depends testImportOneMessageUpdated
+         */
+        public function testImportMessagesArrayNew()
+        {
+            ZurmoMessageSourceUtil::importMessagesArray(
+                                                        self::$testLanguageCode,
+                                                        self::$testCategory,
+                                                        self::$testMessagesNew
+                                                        );
+            
+            $messageSource = new ZurmoMessageSource();
+
+            foreach (self::$testMessagesNew as $source=>$compareTranslation)
+            {
+                $translation = $messageSource->translate(
+                                                         self::$testCategory,
+                                                         $source,
+                                                         self::$testLanguageCode
+                                                         );
+                $this->assertEquals($translation, $compareTranslation);
+            }
+        }
+
+        /**
+         * @depends testImportMessagesArrayNew
+         */
+        public function testImportMessagesArrayUpdated()
+        {
+            ZurmoMessageSourceUtil::importMessagesArray(
+                                                        self::$testLanguageCode,
+                                                        self::$testCategory,
+                                                        self::$testMessagesUpdated
+                                                        );
+            
+            $messageSource = new ZurmoMessageSource();
+
+            foreach (self::$testMessagesUpdated as $source=>$compareTranslation)
+            {
+                $translation = $messageSource->translate(
+                                                         self::$testCategory,
+                                                         $source,
+                                                         self::$testLanguageCode
+                                                         );
+                $this->assertEquals($translation, $compareTranslation);
+            }
+        }
+
+        public function testImportPoFile()
+        {
+            $testLanguageCode = 'po';
+
+            $pathToFiles = Yii::getPathOfAlias('application.tests.unit.files');
+            $filePath = $pathToFiles . DIRECTORY_SEPARATOR . 'messages-test.po';
+
+            ZurmoMessageSourceUtil::importPoFile($testLanguageCode, $filePath);
+
+            $file = new ZurmoGettextPoFile();
+            $contexts = $file->loadWithContext($filePath);
+
+            $messageSource = new ZurmoMessageSource();
+
+            foreach ($contexts as $category=>$messages) {
+                foreach ($messages as $source=>$compareTranslation) {
+                    $translation = $messageSource->translate(
+                                                  $category,
+                                                  $source,
+                                                  $testLanguageCode
+                                                  );
+
+                    $this->assertEquals($translation, $compareTranslation);
+                }
+            }
+        }
+    }
+?>

app/protected/core/utils/ZurmoMessageSourceUtil.php

+<?php
+    /*********************************************************************************
+     * Zurmo is a customer relationship management program developed by
+     * Zurmo, Inc. Copyright (C) 2012 Zurmo Inc.
+     *
+     * Zurmo is free software; you can redistribute it and/or modify it under
+     * the terms of the GNU General Public License version 3 as published by the
+     * Free Software Foundation with the addition of the following permission added
+     * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
+     * IN WHICH THE COPYRIGHT IS OWNED BY ZURMO, ZURMO DISCLAIMS THE WARRANTY
+     * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
+     *
+     * Zurmo is distributed in the hope that it will be useful, but WITHOUT
+     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+     * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+     * details.
+     *
+     * You should have received a copy of the GNU General Public License along with
+     * this program; if not, see http://www.gnu.org/licenses or write to the Free
+     * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+     * 02110-1301 USA.
+     *
+     * You can contact Zurmo, Inc. with a mailing address at 113 McHenry Road Suite 207,
+     * Buffalo Grove, IL 60089, USA. or at email address contact@zurmo.com.
+     ********************************************************************************/
+
+    /**
+     * Utility class for importing messages to the database
+     */
+    class ZurmoMessageSourceUtil
+    {
+        /**
+         * Imports one message string to the database
+         *
+         * @param String $languageCode The language code
+         * @param String $category The category of the translation
+         * @param String $source Message source
+         * @param String $translation Message translation
+         *
+         * @return Integer Id of the added translation or false
+         */
+        public static function importOneMessage($languageCode, $category, $source, $translation)
+        {
+            assert('is_string($languageCode) && !empty($languageCode)');
+            assert('is_string($category) && !empty($category)');
+            assert('is_string($source) && !empty($source)');
+            assert('is_string($translation) && !empty($translation)');
+            if (
+                !is_string($languageCode) || empty($languageCode) ||
+                !is_string($category) || empty($category) ||
+                !is_string($source) || empty($source) ||
+                !is_string($translation) || empty($translation)
+                )
+            {
+                return false;
+            }
+
+            try {
+                $sourceModel = MessageSource::getByCategoryAndSource(
+                                                                     $category,
+                                                                     $source
+                                                                    );
+            } catch (NotFoundException $e) {
+                $sourceModel = MessageSource::addNewSource($category, $source);
+            }
+
+            try {
+                $translationModel = MessageTranslation::getBySourceIdAndLangCode(
+                                        $sourceModel->id,
+                                        $languageCode
+                                    );
+                $translationModel->updateTranslation($translation);
+            } catch (NotFoundException $e) {
+                $translationModel = MessageTranslation::addNewTranslation(
+                                        $languageCode,
+                                        $sourceModel,
+                                        $translation
+                                    );
+            }
+
+            return $translationModel->id;
+        }
+
+        /**
+         * Imports messages array to the database
+         *
+         * @param $languageCode String The language code
+         * @param $category String The category of the translation
+         * @param Array $messages Array with the messages
+         *
+         * @return Boolean Status of the import process
+         */
+        public static function importMessagesArray($languageCode, $category, $messages)
+        {
+            
+            assert('is_string($languageCode) && !empty($languageCode)');
+            assert('is_string($category) && !empty($category)');
+            assert('is_array($messages) && !empty($messages)');
+            if (
+                !is_string($languageCode) || empty($languageCode) ||
+                !is_string($category) || empty($category) ||
+                !is_array($messages) || empty($messages)
+                )
+            {
+                return false;
+            }
+
+            foreach ($messages as $source=>$translation)
+            {
+                self::importOneMessage(
+                                       $languageCode,
+                                       $category,
+                                       $source,
+                                       $translation
+                                       );
+            }
+
+            return true;
+        }
+
+        /**
+         * Loads all messages with context from PO file and impots them to the database
+         *
+         * @param String $languageCode The language code
+         * @param String $messageFile Path to the PO file to import.
+         *
+         * @return Boolean Status of the import
+         */
+        public static function importPoFile($languageCode, $messageFile) {
+            assert('is_string($languageCode) && !empty($languageCode)');
+            if (!is_string($languageCode) || empty($languageCode))
+            {
+                return false;
+            }
+
+            $file = new ZurmoGettextPoFile();
+            $contexts = $file->loadWithContext($messageFile);
+
+            foreach ($contexts as $context=>$messages)
+            {
+                self::importMessagesArray($languageCode, $context, $messages);
+            }
+        }
+    }
+?>

app/protected/modules/designer/adapters/DropDownDependencyToMappingLayoutAdapter.php

             $parentAttributeName            = null;
             foreach ($mappingData as $dependencyData)
             {
+                if($dependencyData['attributeName'] == null)
+                {
+                     break;
+                }
                 self::resolveAvailableCustomFieldAttributes($availableCustomFieldAttributes, $parentAttributeName);
                 $valuesToParentValues = self::resolveValuesToParentValues($dependencyData);
                 $dependencyMapping   = new DropDownDependencyCustomFieldMapping(

app/protected/modules/designer/views/attributetypes/DropDownDependencyAttributeEditView.php

                 'global' => array(
                     'toolbar' => array(
                         'elements' => array(
+                            array('type'  => 'CancelLink'),
                             array('type' => 'SaveButton'),
                         ),
                     ),

app/protected/modules/install/utils/InstallUtil.php

             }
             RedBeanDatabaseBuilderUtil::autoBuildModels($rootModels, $messageLogger);
             ZurmoDatabaseCompatibilityUtil::createStoredFunctionsAndProcedures();
+            ZurmoDatabaseCompatibilityUtil::createIndexes();
         }
 
         /**

app/protected/modules/zurmo/ZurmoModule.php

             // dependence hierarchy it needed concern itself, other than
             // with the models that are specific to itself.
             return array('AuditEvent', 'NamedSecurableItem', 'GlobalMetadata', 'PerUserMetadata', 'Portlet', 'CustomFieldData',
-                         'CalculatedDerivedAttributeMetadata', 'DropDownDependencyDerivedAttributeMetadata', 'SavedSearch');
+                         'CalculatedDerivedAttributeMetadata', 'DropDownDependencyDerivedAttributeMetadata', 'SavedSearch',
+                         'MessageSource', 'MessageTranslation');
         }
 
         public static function getDefaultMetadata()

app/protected/modules/zurmo/utils/MixedTermSearchUtil.php

             assert('is_string($partialTerm)');
             assert('$module::getGlobalSearchFormClassName() != null');
             $metadata                   = $module::getMetadata();
-            $globalSearchAttributeNames = $metadata['global']['globalSearchAttributeNames'];
+            if(null == $globalSearchAttributeNames = $metadata['global']['globalSearchAttributeNames'])
+            {
+                return array();
+            }
             $searchAttributes           = array();
             foreach ($globalSearchAttributeNames as $realOrDerivedAttributeName)
             {

app/protected/modules/zurmo/utils/ZurmoDatabaseCompatibilityUtil.php

                 throw new NotSupportedException();
             }
         }
+
+        public static function createIndexes()
+        {
+            self::createUniqueIndex(
+                                    'messagesource',
+                                    'source_category_Index',
+                                    array(
+                                          'category',
+                                          'source(767)'
+                                          )
+                                    );
+            self::createUniqueIndex(
+                                    'messagetranslation',
+                                    'source_language_translation_Index',
+                                    array(
+                                          'messagesource_id',
+                                          'language',
+                                          'translation(767)'
+                                          )
+                                    );
+        }
+
+        protected static function createUniqueIndex($tableName, $indexName, $columns = array())
+        {
+            assert('RedBeanDatabase::isSetup()');
+            assert('$tableName != ""');
+            assert('$indexName != ""');
+            assert('!empty($columns)');
+            if (RedBeanDatabase::getDatabaseType() == 'mysql')
+            {
+                try
+                {
+                    $rows = R::getAll("SHOW INDEX FROM $tableName");
+                    if (!empty($rows))
+                    {
+                        foreach ($rows as $row)
+                        {
+                            // Delete only first index in sequence
+                            if ($row['Key_name'] == $indexName && $row['Seq_in_index'] == '1')
+                            {
+                                R::exec("DROP INDEX $indexName ON $tableName");
+                            }
+                        }
+                    }
+                    $columnsString = implode(",", $columns);
+                    R::exec("ALTER TABLE $tableName  ADD  UNIQUE INDEX $indexName ($columnsString);");
+                }
+                catch (Exception $e)
+                {
+                    echo "Failed to add $indexName on  $tableName.\n";
+                    throw $e;
+                }
+            }
+            else
+            {
+                throw new NotSupportedException();
+            }
+        }
     }
 ?>

app/protected/tests/unit/files/messages-test.po

+# PO file with messages used for unit testing
+# Copyright (C) 2012 Zurmo Inc.
+# This file is distributed under the same license as the Zurmo package.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Zurmo\n"
+"POT-Creation-Date: 2012-12-02 02:01+0100\n"
+"PO-Revision-Date: 2012-12-02 02:01+0100\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+  
+msgid "PO file Testmessage. Category: NONE. Type: source 1"
+msgstr "PO file Testmessage. Category: NONE. Type: translation 1"
+
+msgid "PO file Testmessage. Category: NONE. Type: source 2"
+msgstr "PO file Testmessage. Category: NONE. Type: translation 2"
+
+msgid "PO file Testmessage. Category: NONE. Type: source 3"
+msgstr "PO file Testmessage. Category: NONE. Type: translation 3"
+
+msgid "PO file Testmessage. Category: NONE. Type: source 4"
+msgstr "PO file Testmessage. Category: NONE. Type: translation 4"
+
+msgid "PO file Testmessage. Category: NONE. Type: source 5"
+msgstr "PO file Testmessage. Category: NONE. Type: translation 5"
+
+msgctxt ""
+msgid "PO file Testmessage. Category: EMPTY. Type: source 1"
+msgstr "PO file Testmessage. Category: EMPTY. Type: translation 1"
+
+msgctxt ""
+msgid "PO file Testmessage. Category: EMPTY. Type: source 2"
+msgstr "PO file Testmessage. Category: EMPTY. Type: translation 2"
+
+msgctxt ""
+msgid "PO file Testmessage. Category: EMPTY. Type: source 3"
+msgstr "PO file Testmessage. Category: EMPTY. Type: translation 3"
+
+msgctxt ""
+msgid "PO file Testmessage. Category: EMPTY. Type: source 4"
+msgstr "PO file Testmessage. Category: EMPTY. Type: translation 4"
+
+msgctxt ""
+msgid "PO file Testmessage. Category: EMPTY. Type: source 5"
+msgstr "PO file Testmessage. Category: EMPTY. Type: translation 5"
+
+msgctxt "TestCategory1"
+msgid "PO file Testmessage. Category: 1. Type: source 1"
+msgstr "PO file Testmessage. Category: 1. Type: translation 1"
+
+msgctxt "TestCategory1"
+msgid "PO file Testmessage. Category: 1. Type: source 2"
+msgstr "PO file Testmessage. Category: 1. Type: translation 2"
+
+msgctxt "TestCategory1"
+msgid "PO file Testmessage. Category: 1. Type: source 3"
+msgstr "PO file Testmessage. Category: 1. Type: translation 3"
+
+msgctxt "TestCategory1"
+msgid "PO file Testmessage. Category: 1. Type: source 4"
+msgstr "PO file Testmessage. Category: 1. Type: translation 4"
+
+msgctxt "TestCategory1"
+msgid "PO file Testmessage. Category: 1. Type: source 5"
+msgstr "PO file Testmessage. Category: 1. Type: translation 5"
+
+msgctxt "TestCategory2"
+msgid "PO file Testmessage. Category: 2. Type: source 1"
+msgstr "PO file Testmessage. Category: 2. Type: translation 1"
+
+msgctxt "TestCategory2"
+msgid "PO file Testmessage. Category: 2. Type: source 2"
+msgstr "PO file Testmessage. Category: 2. Type: translation 2"
+
+msgctxt "TestCategory2"
+msgid "PO file Testmessage. Category: 2. Type: source 3"
+msgstr "PO file Testmessage. Category: 2. Type: translation 3"
+
+msgctxt "TestCategory2"
+msgid "PO file Testmessage. Category: 2. Type: source 4"
+msgstr "PO file Testmessage. Category: 2. Type: translation 4"
+
+msgctxt "TestCategory2"
+msgid "PO file Testmessage. Category: 2. Type: source 5"
+msgstr "PO file Testmessage. Category: 2. Type: translation 5"
+
+msgctxt "TestCategory3"
+msgid "PO file Testmessage. Category: 3. Type: source 1"
+msgstr "PO file Testmessage. Category: 3. Type: translation 1"
+
+msgctxt "TestCategory3"
+msgid "PO file Testmessage. Category: 3. Type: source 2"
+msgstr "PO file Testmessage. Category: 3. Type: translation 2"
+
+msgctxt "TestCategory3"
+msgid "PO file Testmessage. Category: 3. Type: source 3"
+msgstr "PO file Testmessage. Category: 3. Type: translation 3"
+
+msgctxt "TestCategory3"
+msgid "PO file Testmessage. Category: 3. Type: source 4"
+msgstr "PO file Testmessage. Category: 3. Type: translation 4"
+
+msgctxt "TestCategory3"
+msgid "PO file Testmessage. Category: 3. Type: source 5"
+msgstr "PO file Testmessage. Category: 3. Type: translation 5"
+