From 813d01f8f411de384ef4c74a12eb2b72cb9cf426 Mon Sep 17 00:00:00 2001 From: Colas Ruamps Date: Wed, 9 Mar 2016 14:21:31 +0100 Subject: [PATCH 1/3] Add localization for templates and vars, with CakePHP i18n table. --- .../Component/NotifierComponent.php | 26 ++++ src/Model/Entity/Notification.php | 33 +++++ src/Model/Table/NotificationsTable.php | 4 + src/Utility/NotificationManager.php | 135 ++++++++++++++++++ .../Component/NotifierComponentTest.php | 26 +++- .../Model/Table/NotificationsTableTest.php | 46 ++++++ .../Utility/NotificationManagerTest.php | 3 +- 7 files changed, 271 insertions(+), 2 deletions(-) diff --git a/src/Controller/Component/NotifierComponent.php b/src/Controller/Component/NotifierComponent.php index f0c4a8a..9b25dc8 100644 --- a/src/Controller/Component/NotifierComponent.php +++ b/src/Controller/Component/NotifierComponent.php @@ -109,6 +109,32 @@ public function getNotifications($userId = null, $state = null) return $query->toArray(); } + + /** + * getI18nNotifications + * + * Returns a list of translated notifications. + * + * @param int|null $userId Id of the user. + * @param bool|null $state The state of notifications: `true` for unread, `false` for read, `null` for all. + * @return array + */ + public function getI18nNotifications($userId = null, $state = null) + { + if (!$userId) { + $userId = $this->Controller->Auth->user('id'); + } + + $model = TableRegistry::get('Notifier.Notifications'); + + $query = $model->find('translations')->where(['Notifications.user_id' => $userId])->order(['created' => 'desc']); + + if (!is_null($state)) { + $query->where(['Notifications.state' => $state]); + } + + return $query->toArray(); + } /** * countNotifications diff --git a/src/Model/Entity/Notification.php b/src/Model/Entity/Notification.php index c36299e..6e1ebc2 100644 --- a/src/Model/Entity/Notification.php +++ b/src/Model/Entity/Notification.php @@ -17,6 +17,7 @@ use Cake\Core\Configure; use Cake\ORM\Entity; use Cake\Utility\Text; +use Cake\ORM\Behavior\Translate\TranslateTrait; /** * Notification Entity. @@ -24,6 +25,8 @@ class Notification extends Entity { + use TranslateTrait; + /** * Fields that can be mass assigned using newEntity() or patchEntity(). * @@ -149,6 +152,36 @@ protected function _getRead() } return false; } + + /** + * getI18n + * + * Get localized property + * + * @param string $property : `title` or `body` + * @param string|null $lang + * @return type + */ + public function getI18n($property, $lang = null) + { + $templates = Configure::read('Notifier.templates.i18n'); + + if (array_key_exists($this->_properties['template'], $templates) && array_key_exists($lang, $templates[$this->_properties['template']])) { + $template = $templates[$this->_properties['template']][$lang]; + + if (isset($this->_translations[$lang]['vars'])) { + $vars = json_decode($this->_translations[$lang]['vars'], true); + } else { + $vars = json_decode($this->_properties['vars'], true); + } + + if (isset($template[$property])) { + return Text::insert($template[$property], $vars); + } + } + + return ''; + } /** * Virtual fields diff --git a/src/Model/Table/NotificationsTable.php b/src/Model/Table/NotificationsTable.php index b45659d..fa47bd8 100644 --- a/src/Model/Table/NotificationsTable.php +++ b/src/Model/Table/NotificationsTable.php @@ -43,6 +43,10 @@ public function initialize(array $config) $this->displayField('title'); $this->primaryKey('id'); $this->addBehavior('Timestamp'); + $this->addBehavior('Translate', [ + 'fields' => ['vars'], + 'allowEmptyTranslations' => false + ]); } /** diff --git a/src/Utility/NotificationManager.php b/src/Utility/NotificationManager.php index 28a90a8..3fd4811 100644 --- a/src/Utility/NotificationManager.php +++ b/src/Utility/NotificationManager.php @@ -106,6 +106,77 @@ public function notify($data) return $data['tracking_id']; } + + + /** + * notifyI18n + * + * Sends notifications to specific users. + * The first parameter `$data` is an array with multiple options. + * + * ### Options + * - `users` - An array or int with id's of users who will receive a notification. + * - `roles` - An array or int with id's of roles which all users ill receive a notification. + * - `template` - The template wich will be used. + * - `vars` - The localized variables used in the template. + * + * ### Example + * ``` + * NotificationManager::instance()->notify([ + * 'users' => 1, + * 'template' => 'newOrder', + * 'vars' => [ + * 'en' => [ + * 'receiver' => $receiver->name + * 'link' => '/en/order/], + * 'fr' => [ + * 'receiver' => $receiver->name + * 'link' => '/fr/order/], + * ], + * ]); + * ``` + * + * @param array $data Data with options. + * @return string The tracking_id to follow the notification. + */ + public function notifyI18n($data) + { + $model = TableRegistry::get('Notifier.Notifications'); + + $_data = [ + 'users' => [], + 'recipientLists' => [], + 'template' => 'default', + 'vars' => [], + 'tracking_id' => $this->getTrackingId() + ]; + + $data = array_merge($_data, $data); + + foreach ((array) $data['recipientLists'] as $recipientList) { + $list = (array) $this->getRecipientList($recipientList); + $data['users'] = array_merge($data['users'], $list); + } + + foreach ((array) $data['users'] as $user) { + $entity = $model->newEntity(); + + $entity->set('template', $data['template']); + $entity->set('tracking_id', $data['tracking_id']); + + $entity->set('vars', current($data['vars'])); + foreach ($data['vars'] as $lang => $vars) { + $entity->translation($lang)->set(['vars' => $vars], ['guard' => false]); + } + + $entity->set('state', 1); + $entity->set('user_id', $user); + + $model->save($entity); + } + + return $data['tracking_id']; + } /** * addRecipientList @@ -182,6 +253,42 @@ public function addTemplate($name, $options = []) Configure::write('Notifier.templates.' . $name, $options); } + /** + * addI18nTemplate + * + * Adds localized template. + * + * ### Example + * + * $this->Notifier->addTemplate('newUser', [ + * 'en' => [ + * 'title' => 'New User: :name', + * 'body' => 'The user :email has been registered'], + * 'fr' => [ + * 'title' => 'Nouvel utilisateur : :name', + * 'body' => 'L\'utilisateur :email vient de s\'inscrire'], + * ]); + * + * This code contains the variables `title` and `body`. + * + * @param string $name Unique name. + * @param array $options Options. + * @return void + */ + public function addI18nTemplate($name, $options = []) + { + $_options = [ + 'en' => [ + 'title' => 'Notification', + 'body' => '', + ] + ]; + + $options = array_merge($_options, $options); + + Configure::write('Notifier.templates.i18n.' . $name, $options); + } + /** * getTemplate * @@ -209,6 +316,34 @@ public function getTemplate($name, $type = null) return false; } + /** + * getI18nTemplate + * + * Returns the requested localized template. + * If the template or type does not exists, `false` will be returned. + * + * @param string $lang Language code. + * @param string $name Name of the template. + * @param string|null $type The type like `title` or `body`. Leave empty to get the whole template. + * @return array|string|bool + */ + public function getI18nTemplate($lang, $name, $type = null) + { + $templates = Configure::read('Notifier.templates.i18n'); + + if (array_key_exists($name, $templates)) { + if ($type == 'title') { + return $templates[$name]['title']; + } + if ($type == 'body') { + return $templates[$name]['body']; + } + return $templates[$name]; + } + + return false; + } + /** * getTrackingId * diff --git a/tests/TestCase/Controller/Component/NotifierComponentTest.php b/tests/TestCase/Controller/Component/NotifierComponentTest.php index d8b9e89..23e5069 100644 --- a/tests/TestCase/Controller/Component/NotifierComponentTest.php +++ b/tests/TestCase/Controller/Component/NotifierComponentTest.php @@ -29,7 +29,8 @@ class NotifierComponentTest extends TestCase { public $fixtures = [ - 'plugin.notifier.notifications' + 'plugin.notifier.notifications', + 'core.translates' ]; public function setUp() @@ -108,6 +109,29 @@ public function testGetNotifications() $this->assertEquals(2, count($this->Notifier->getNotifications(2, false))); } + public function testGetI18nNotifications() + { + $this->Manager->notify(['users' => [1, 1, 1, 2, 2]]); + + $this->assertEquals(3, count($this->Notifier->getI18nNotifications(1))); + $this->assertEquals(3, count($this->Notifier->getI18nNotifications(1, true))); + $this->assertEquals(0, count($this->Notifier->getI18nNotifications(1, false))); + $this->assertEquals(2, count($this->Notifier->getI18nNotifications(2))); + $this->assertEquals(2, count($this->Notifier->getI18nNotifications(2, true))); + $this->assertEquals(0, count($this->Notifier->getI18nNotifications(2, false))); + + $this->Notifier->markAsRead(2, 1); + $this->Notifier->markAsRead(5, 2); + $this->Notifier->markAsRead(6, 2); + + $this->assertEquals(3, count($this->Notifier->getI18nNotifications(1))); + $this->assertEquals(2, count($this->Notifier->getI18nNotifications(1, true))); + $this->assertEquals(1, count($this->Notifier->getI18nNotifications(1, false))); + $this->assertEquals(2, count($this->Notifier->getI18nNotifications(2))); + $this->assertEquals(0, count($this->Notifier->getI18nNotifications(2, true))); + $this->assertEquals(2, count($this->Notifier->getI18nNotifications(2, false))); + } + public function testMarkAsReadWithAuth() { $this->Manager->notify(['users' => [1, 1, 1, 1, 1]]); diff --git a/tests/TestCase/Model/Table/NotificationsTableTest.php b/tests/TestCase/Model/Table/NotificationsTableTest.php index 98f19f1..c70a294 100644 --- a/tests/TestCase/Model/Table/NotificationsTableTest.php +++ b/tests/TestCase/Model/Table/NotificationsTableTest.php @@ -18,6 +18,7 @@ use Cake\TestSuite\TestCase; use Notifier\Model\Table\NotificationsTable; use Notifier\Utility\NotificationManager; +use Cake\I18n\I18n; /** * Notifier\Model\Table\NotificationsTable Test Case @@ -27,6 +28,7 @@ class NotificationsTableTest extends TestCase public $fixtures = [ 'plugin.notifier.notifications', + 'core.translates' ]; public function setUp() @@ -65,4 +67,48 @@ public function testEntity() $this->assertEquals('New Notification', $entity->title); $this->assertEquals('Bob has sent Leonardo a notification about Programming Stuff', $entity->body); } + + public function testI18nEntity() + { + NotificationManager::instance()->addI18nTemplate('newOrder', [ + 'en' => [ + 'title' => 'New order', + 'body' => ':username bought :product' + ], + 'fr' => [ + 'title' => 'Nouvelle commande', + 'body' => ':username a acheté :product' + ] + ]); + + $notify = NotificationManager::instance()->notifyI18n([ + 'users' => 1, + 'template' => 'newOrder', + 'vars' => [ + 'en' => [ + 'username' => 'Bob', + 'product' => 'a car' + ], + 'fr' => [ + 'username' => 'Bob', + 'product' => 'une voiture' + ] + ] + ]); + + I18n::locale('en'); + $entity = $this->Notifications->get(2); + + $this->assertEquals('newOrder', $entity->template); + $this->assertEquals('New order', $entity->getI18n('title', 'en')); + $this->assertEquals('Bob bought a car', $entity->getI18n('body', 'en')); + + I18n::locale('fr'); + $entity = $this->Notifications->get(2); + + $this->assertEquals('newOrder', $entity->template); + $this->assertEquals('Nouvelle commande', $entity->getI18n('title', 'fr')); + $this->assertEquals('Bob a acheté une voiture', $entity->getI18n('body', 'fr')); + + } } diff --git a/tests/TestCase/Utility/NotificationManagerTest.php b/tests/TestCase/Utility/NotificationManagerTest.php index 7176be1..8c59876 100644 --- a/tests/TestCase/Utility/NotificationManagerTest.php +++ b/tests/TestCase/Utility/NotificationManagerTest.php @@ -23,7 +23,8 @@ class NotificationManagerTest extends TestCase { public $fixtures = [ - 'plugin.notifier.notifications' + 'plugin.notifier.notifications', + 'core.translates' ]; public function setUp() From d2da3830ff491e47479beec5b18d8e31307cfa73 Mon Sep 17 00:00:00 2001 From: Colas Ruamps Date: Thu, 24 Mar 2016 10:38:58 +0100 Subject: [PATCH 2/3] CakePHP codestandards fix --- src/Model/Entity/Notification.php | 10 +++++----- src/Utility/NotificationManager.php | 18 ++++++++++-------- .../Model/Table/NotificationsTableTest.php | 18 ++++++++++-------- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/Model/Entity/Notification.php b/src/Model/Entity/Notification.php index 6e1ebc2..026af76 100644 --- a/src/Model/Entity/Notification.php +++ b/src/Model/Entity/Notification.php @@ -15,9 +15,9 @@ namespace Notifier\Model\Entity; use Cake\Core\Configure; +use Cake\ORM\Behavior\Translate\TranslateTrait; use Cake\ORM\Entity; use Cake\Utility\Text; -use Cake\ORM\Behavior\Translate\TranslateTrait; /** * Notification Entity. @@ -155,14 +155,14 @@ protected function _getRead() /** * getI18n - * + * * Get localized property - * + * * @param string $property : `title` or `body` - * @param string|null $lang + * @param string|null $lang : language code * @return type */ - public function getI18n($property, $lang = null) + public function getI18n($property, $lang = null) { $templates = Configure::read('Notifier.templates.i18n'); diff --git a/src/Utility/NotificationManager.php b/src/Utility/NotificationManager.php index 3fd4811..7089ee1 100644 --- a/src/Utility/NotificationManager.php +++ b/src/Utility/NotificationManager.php @@ -1,4 +1,5 @@ getRecipientList($recipientList); + foreach ((array)$data['recipientLists'] as $recipientList) { + $list = (array)$this->getRecipientList($recipientList); $data['users'] = array_merge($data['users'], $list); } - foreach ((array) $data['users'] as $user) { + foreach ((array)$data['users'] as $user) { $entity = $model->newEntity(); - + $entity->set('template', $data['template']); $entity->set('tracking_id', $data['tracking_id']); - + $entity->set('vars', current($data['vars'])); foreach ($data['vars'] as $lang => $vars) { $entity->translation($lang)->set(['vars' => $vars], ['guard' => false]); } - + $entity->set('state', 1); $entity->set('user_id', $user); - + $model->save($entity); } @@ -361,4 +362,5 @@ public function getTrackingId() } return $trackingId; } + } diff --git a/tests/TestCase/Model/Table/NotificationsTableTest.php b/tests/TestCase/Model/Table/NotificationsTableTest.php index c70a294..48aa6ad 100644 --- a/tests/TestCase/Model/Table/NotificationsTableTest.php +++ b/tests/TestCase/Model/Table/NotificationsTableTest.php @@ -1,4 +1,5 @@ assertEquals('New Notification', $entity->title); $this->assertEquals('Bob has sent Leonardo a notification about Programming Stuff', $entity->body); } - + public function testI18nEntity() { NotificationManager::instance()->addI18nTemplate('newOrder', [ @@ -95,20 +97,20 @@ public function testI18nEntity() ] ] ]); - + I18n::locale('en'); $entity = $this->Notifications->get(2); - + $this->assertEquals('newOrder', $entity->template); $this->assertEquals('New order', $entity->getI18n('title', 'en')); $this->assertEquals('Bob bought a car', $entity->getI18n('body', 'en')); - + I18n::locale('fr'); $entity = $this->Notifications->get(2); - + $this->assertEquals('newOrder', $entity->template); $this->assertEquals('Nouvelle commande', $entity->getI18n('title', 'fr')); $this->assertEquals('Bob a acheté une voiture', $entity->getI18n('body', 'fr')); - } + } From bd9797d7bdbe4272c155bd75e3a05651bb08b16d Mon Sep 17 00:00:00 2001 From: Colas Ruamps Date: Thu, 24 Mar 2016 11:07:08 +0100 Subject: [PATCH 3/3] CakePHP codestandards fix using PHP CBF --- src/Model/Entity/Notification.php | 4 +++- src/Utility/NotificationManager.php | 1 - tests/TestCase/Model/Table/NotificationsTableTest.php | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Model/Entity/Notification.php b/src/Model/Entity/Notification.php index 026af76..b5e7a1c 100644 --- a/src/Model/Entity/Notification.php +++ b/src/Model/Entity/Notification.php @@ -1,4 +1,5 @@ assertEquals('Nouvelle commande', $entity->getI18n('title', 'fr')); $this->assertEquals('Bob a acheté une voiture', $entity->getI18n('body', 'fr')); } - }