diff --git a/README.md b/README.md index 1670954..4873876 100644 --- a/README.md +++ b/README.md @@ -225,13 +225,17 @@ The `Bakkerij/Notifier.Notifier` component can be used in Controllers: The component has the following methods available: -- `getNotifications` +- `getNotifications` (deprecated) +- `getAllNotificationsBy` +- `getReadNotificationsBy` +- `getUnReadNotificationsBy` - `countNotifications` - `markAsRead` - `notify` -## Keep in touch +## Credits -If you need some help or got ideas for this plugin, feel free to chat at [Gitter](https://gitter.im/bakkerij/notifier). +This plugin have been forked from [Norifier](https://github.com/bakkerij/notifier) originally developed by [bakkerij](https://github.com/bakkerij). +Thank you for their work and contributions to the open-source community. Pull Requests are always more than welcome! diff --git a/composer.json b/composer.json index 4cb8ac2..62e4470 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,7 @@ "type": "cakephp-plugin", "require": { "php": ">=5.4.16", - "cakephp/cakephp": "^3.5" + "cakephp/cakephp": "^4.0" }, "require-dev": { "phpunit/phpunit": "*", diff --git a/config/Migrations/schema-dump-default.lock b/config/Migrations/schema-dump-default.lock new file mode 100644 index 0000000..5807e8d Binary files /dev/null and b/config/Migrations/schema-dump-default.lock differ diff --git a/src/Controller/Component/NotifierComponent.php b/src/Controller/Component/NotifierComponent.php index 8a070b9..b811ae2 100644 --- a/src/Controller/Component/NotifierComponent.php +++ b/src/Controller/Component/NotifierComponent.php @@ -12,11 +12,13 @@ * @since 1.0 * @license http://www.opensource.org/licenses/mit-license.php MIT License */ + namespace Bakkerij\Notifier\Controller\Component; +use Bakkerij\Notifier\Model\Entity\Notification; use Bakkerij\Notifier\Utility\NotificationManager; use Cake\Controller\Component; -use Cake\Core\Configure; +use Cake\Database\Expression\QueryExpression; use Cake\ORM\TableRegistry; /** @@ -40,17 +42,25 @@ class NotifierComponent extends Component */ private $Controller = null; + /** + * The controller. + * + * @var \Cake\ORM\Table + */ + private $table = null; + /** * initialize * * @param array $config Config. * @return void */ - public function initialize(array $config) + public function initialize(array $config): void { parent::initialize($config); $this->Controller = $this->_registry->getController(); + $this->table = TableRegistry::getTableLocator()->get('Bakkerij/Notifier.Notifications'); } /** @@ -61,7 +71,7 @@ public function initialize(array $config) * @param \Cake\Controller\Controller $controller Controller. * @return void */ - public function setController($controller) + public function setController($controller): void { $this->Controller = $controller; } @@ -90,22 +100,148 @@ public function setController($controller) * @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 + * + * @deprecated 1.3 use getReadNotifications or getUnreadNotifications instead. */ - public function getNotifications($userId = null, $state = null) + public function getNotifications($userId = null, $state = null): array { - if (!$userId) { + $stateCondition = []; + if (isset($state)) { + $stateCondition = ['whereConditions' => ['state' => $state]]; + } + + return $this->getNotificationsFactory($userId, $stateCondition); + } + + + /** + * getNotifications + * + * Returns a list of notifications. + * + * ### Examples + * ``` + * // if the user is logged in, this is the way to get all notifications + * $this->Notifier->getNotifications(); + * + * // for a specific user, use the first parameter for the user_id + * $this->Notifier->getNotifications(1); + * + * + * ``` + * @param int $userId + * @param array $options { + * @type array $whereConditions Conditions used for filtering. + * @type string $order The order in which results should be returned. + * } + * * @return array + */ + public function getAllNotificationsBy($userId, $options = []): array + { + if (array_key_exists('state', $options)) { + unset($options['state']); + } + + return $this->getNotificationsFactory($userId, $options); + } + + /** + * getNotifications + * + * Returns a list of notifications. + * + * ### Examples + * ``` + * // if the user is logged in, this is the way to get all notifications + * $this->Notifier->getNotifications(); + * + * // for a specific user, use the first parameter for the user_id + * $this->Notifier->getNotifications(1); + * + * // for and specific user you can add also ORM conditions for the where and order + * + * $this->Notifier->getNotifications(1, [whereConditions => ['']]); + * + * ``` + * @param int $userId + * @param array $options { + * @type array $whereConditions Conditions used for filtering. + * @type string $order The order in which results should be returned. + * } + * * @return array + */ + public function getReadNotificationsBy($userId, $options = []): array + { + $readCondition = ['whereConditions' => ['state' => Notification::READ_STATUS]]; + $conditions = array_merge($options, $readCondition); + + return $this->getNotificationsFactory($userId, $conditions); + } + + /** + * getNotifications + * + * Returns a list of notifications. + * + * ### Examples + * ``` + * // if the user is logged in, this is the way to get all notifications + * $this->Notifier->getNotifications(); + * + * // for a specific user, use the first parameter for the user_id + * $this->Notifier->getNotifications(1); + * + * + * ``` + * @param int $userId + * @param array $options { + * @type array $whereConditions Conditions used for filtering. + * @type string $order The order in which results should be returned. + * } + * @return array + */ + public function getUnReadNotificationsBy($userId, $options = []): array + { + $unreadCondition = ['whereConditions' => ['state' => Notification::UNREAD_STATUS]]; + $conditions = array_merge($options, $unreadCondition); + + return $this->getNotificationsFactory($userId, $conditions); + } + + /** + * @param int $userId + * @param array $options { + * @type array $whereConditions Conditions used for filtering. + * @type string $order The order in which results should be returned. + * @type string $state The state of the items to be processed. + * } + * @return array + */ + private function getNotificationsFactory($userId, $options = []): array + { + if (!isset($userId)) { $userId = $this->Controller->Auth->user('id'); } - $model = TableRegistry::get('Bakkerij/Notifier.Notifications'); + $whereConditions = [ + 'Notifications.user_id' => $userId, + ]; - $query = $model->find()->where(['Notifications.user_id' => $userId])->order(['created' => 'desc']); + $order = ['created' => 'desc']; - if (!is_null($state)) { - $query->where(['Notifications.state' => $state]); + if (array_key_exists('whereConditions', $options)) { + $whereConditions = array_merge($whereConditions, $options['whereConditions']); } - return $query->toArray(); + if (array_key_exists('order', $options)) { + $order = array_merge($whereConditions, $options['order']); + } + + return $this->table + ->find() + ->where($whereConditions) + ->order($order) + ->toArray(); } /** @@ -133,15 +269,13 @@ public function getNotifications($userId = null, $state = null) * @param bool|null $state The state of notifications: `true` for unread, `false` for read, `null` for all. * @return int */ - public function countNotifications($userId = null, $state = null) + public function countNotifications(?int $userId = null, ?int $state = null): int { if (!$userId) { $userId = $this->Controller->Auth->user('id'); } - $model = TableRegistry::get('Bakkerij/Notifier.Notifications'); - - $query = $model->find()->where(['Notifications.user_id' => $userId]); + $query = $this->table->find()->where(['Notifications.user_id' => $userId]); if (!is_null($state)) { $query->where(['Notifications.state' => $state]); @@ -158,33 +292,40 @@ public function countNotifications($userId = null, $state = null) * * @param int $notificationId Id of the notification. * @param int|null $user Id of the user. Else the id of the session will be taken. - * @return void + * @return void|false */ - public function markAsRead($notificationId = null, $user = null) + public function markAsRead($notificationId = null, $user = null): bool { if (!$user) { $user = $this->Controller->Auth->user('id'); } - $model = TableRegistry::get('Bakkerij/Notifier.Notifications'); - if (!$notificationId) { - $query = $model->find('all')->where([ + $query = $this->table->find()->where([ 'user_id' => $user, - 'state' => 1 + 'state' => Notification::UNREAD_STATUS ]); } else { - $query = $model->find('all')->where([ + $query = $this->table->find()->where([ 'user_id' => $user, 'id' => $notificationId ]); } - foreach ($query as $item) { - $item->set('state', 0); - $model->save($item); + $notifications = []; + foreach ($query as $notification) { + $notification->set('state', Notification::READ_STATUS); + $notifications[] = $notification; } + + $savedNotifications = $this->table->saveMany($notifications); + + if (!$savedNotifications) { + return false; + } + + return true; } /** @@ -214,8 +355,13 @@ public function markAsRead($notificationId = null, $user = null) * @param array $data Data with options. * @return string */ - public function notify($data) + public function notify($data): string { - return NotificationManager::instance()->notify($data); + $notification = NotificationManager::instance()->notify($data); + if (!$notification) { + $this->getController()->Flash->error(__d('bakkerij/notifier', 'An error occurred sending the notifications')); + } + + return $notification; } } diff --git a/src/Model/Entity/Notification.php b/src/Model/Entity/Notification.php index 5da078e..42f4889 100644 --- a/src/Model/Entity/Notification.php +++ b/src/Model/Entity/Notification.php @@ -12,6 +12,7 @@ * @since 1.0 * @license http://www.opensource.org/licenses/mit-license.php MIT License */ + namespace Bakkerij\Notifier\Model\Entity; use Cake\Core\Configure; @@ -24,6 +25,8 @@ class Notification extends Entity { + const UNREAD_STATUS = 1; + const READ_STATUS = 0; /** * Fields that can be mass assigned using newEntity() or patchEntity(). * @@ -46,7 +49,7 @@ class Notification extends Entity * @param string $vars Data. * @return mixed */ - protected function _getVars($vars) + protected function _getVars($vars): array|string|null { $array = json_decode($vars, true); @@ -65,7 +68,7 @@ protected function _getVars($vars) * @param array $vars Data. * @return string */ - protected function _setVars($vars) + protected function _setVars($vars): string|null { if (is_array($vars)) { return json_encode($vars); @@ -83,14 +86,14 @@ protected function _setVars($vars) * * @return string */ - protected function _getTitle() + protected function _getTitle(): string { $templates = Configure::read('Notifier.templates'); - if (array_key_exists($this->_properties['template'], $templates)) { - $template = $templates[$this->_properties['template']]; + if (array_key_exists($this->get('template'), $templates)) { + $template = $templates[$this->get('template')]; - $vars = json_decode($this->_properties['vars'], true); + $vars = json_decode($this->get('vars'), true); return Text::insert($template['title'], $vars); } @@ -106,14 +109,14 @@ protected function _getTitle() * * @return string */ - protected function _getBody() + protected function _getBody(): string { $templates = Configure::read('Notifier.templates'); - if (array_key_exists($this->_properties['template'], $templates)) { - $template = $templates[$this->_properties['template']]; + if (array_key_exists($this->get('template'), $templates)) { + $template = $templates[$this->get('template')]; - $vars = json_decode($this->_properties['vars'], true); + $vars = json_decode($this->get('vars'), true); return Text::insert($template['body'], $vars); } @@ -127,12 +130,9 @@ protected function _getBody() * * @return bool */ - protected function _getUnread() + protected function _getUnread(): bool { - if ($this->_properties['state'] === 1) { - return true; - } - return false; + return $this->get('state') == self::UNREAD_STATUS; } /** @@ -142,12 +142,9 @@ protected function _getUnread() * * @return bool */ - protected function _getRead() + protected function _getRead(): bool { - if ($this->_properties['state'] === 0) { - return true; - } - return false; + return $this->get('state') == self::READ_STATUS; } /** diff --git a/src/Model/Table/NotificationsTable.php b/src/Model/Table/NotificationsTable.php index 4620429..b552358 100644 --- a/src/Model/Table/NotificationsTable.php +++ b/src/Model/Table/NotificationsTable.php @@ -12,11 +12,10 @@ * @since 1.0 * @license http://www.opensource.org/licenses/mit-license.php MIT License */ + namespace Bakkerij\Notifier\Model\Table; -use Cake\ORM\RulesChecker; use Cake\ORM\Table; -use Cake\ORM\TableRegistry; use Cake\Validation\Validator; /** @@ -37,11 +36,11 @@ class NotificationsTable extends Table * @param array $config The configuration for the Table. * @return void */ - public function initialize(array $config) + public function initialize(array $config): void { - $this->table('notifications'); - $this->displayField('title'); - $this->primaryKey('id'); + $this->setTable('notifications'); + $this->setDisplayField('title'); + $this->setPrimaryKey('id'); $this->addBehavior('Timestamp'); } @@ -51,15 +50,15 @@ public function initialize(array $config) * @param \Cake\Validation\Validator $validator Validator instance. * @return \Cake\Validation\Validator */ - public function validationDefault(Validator $validator) + public function validationDefault(Validator $validator): Validator { $validator ->add('id', 'valid', ['rule' => 'numeric']) - ->allowEmpty('id', 'create') - ->allowEmpty('title') - ->allowEmpty('body') + ->allowEmptyString('id', 'create') + ->allowEmptyString('title') + ->allowEmptyString('body') ->add('state', 'valid', ['rule' => 'numeric']) - ->allowEmpty('state'); + ->allowEmptyString('state'); return $validator; } diff --git a/src/Utility/NotificationManager.php b/src/Utility/NotificationManager.php index ecdea55..fb56180 100644 --- a/src/Utility/NotificationManager.php +++ b/src/Utility/NotificationManager.php @@ -12,12 +12,13 @@ * @since 1.0 * @license http://www.opensource.org/licenses/mit-license.php MIT License */ + namespace Bakkerij\Notifier\Utility; +use Bakkerij\Notifier\Model\Entity\Notification; use Cake\Core\Configure; -use Cake\Core\Plugin; +use Cake\ORM\Exception\PersistenceFailedException; use Cake\ORM\TableRegistry; -use Cake\Utility\Text; /** * Notifier component @@ -35,7 +36,7 @@ class NotificationManager * @param null $manager Possible different manager. (Helpfull for testing). * @return NotificationManager */ - public static function instance($manager = null) + public static function instance($manager = null): NotificationManager { if ($manager instanceof NotificationManager) { static::$_generalManager = $manager; @@ -43,6 +44,7 @@ public static function instance($manager = null) if (empty(static::$_generalManager)) { static::$_generalManager = new NotificationManager(); } + return static::$_generalManager; } @@ -71,42 +73,57 @@ public static function instance($manager = null) * ``` * * @param array $data Data with options. - * @return string The tracking_id to follow the notification. + * @return string|false The tracking_id to follow the notification. */ - public function notify($data) + public function notify($data): string { - $model = TableRegistry::get('Bakkerij/Notifier.Notifications'); + $model = TableRegistry::getTableLocator()->get('Bakkerij/Notifier.Notifications'); $_data = [ 'users' => [], 'recipientLists' => [], 'template' => 'default', 'vars' => [], - 'tracking_id' => $this->getTrackingId() + 'tracking_id' => $this->getTrackingId(), + 'state' => Notification::UNREAD_STATUS, ]; $data = array_merge($_data, $data); - foreach ((array)$data['recipientLists'] as $recipientList) { - $list = (array)$this->getRecipientList($recipientList); - $data['users'] = array_merge($data['users'], $list); - } + $data['users'] = $this->mergeRecipientList($data); - foreach ((array)$data['users'] as $user) { - $entity = $model->newEntity(); - - $entity->set('template', $data['template']); - $entity->set('tracking_id', $data['tracking_id']); + $entities = []; + foreach ((array)$data['users'] as $userId) { + $finalData = array_merge($data, ['user_id' => $userId]); + $entity = $model->newEntity($finalData); $entity->set('vars', $data['vars']); - $entity->set('state', 1); - $entity->set('user_id', $user); + $entities[] = $entity; + } - $model->save($entity); + $notificationsSaved = $model->saveMany($entities); + + if (!$notificationsSaved) { + return $notificationsSaved; } return $data['tracking_id']; } + /** + * @param array $data + * @return array + */ + private function mergeRecipientList($data): ?array + { + $users = $data['users']; + foreach ((array)$data['recipientLists'] as $recipientList) { + $list = (array)$this->getRecipientList($recipientList); + $users = array_merge($users, $list); + } + + return $users; + } + /** * addRecipientList * @@ -124,7 +141,7 @@ public function notify($data) * @param array $userIds Array with id's of users. * @return void */ - public function addRecipientList($name, $userIds) + public function addRecipientList($name, $userIds): void { Configure::write('Notifier.recipientLists.' . $name, $userIds); } @@ -138,7 +155,7 @@ public function addRecipientList($name, $userIds) * @param string $name The name of the list. * @return array|null */ - public function getRecipientList($name) + public function getRecipientList($name): ?array { return Configure::read('Notifier.recipientLists.' . $name); } @@ -170,7 +187,7 @@ public function getRecipientList($name) * @param array $options Options. * @return void */ - public function addTemplate($name, $options = []) + public function addTemplate($name, $options = []): void { $_options = [ 'title' => 'Notification', @@ -192,7 +209,7 @@ public function addTemplate($name, $options = []) * @param string|null $type The type like `title` or `body`. Leave empty to get the whole template. * @return array|string|bool */ - public function getTemplate($name, $type = null) + public function getTemplate($name, $type = null): array|string|bool { $templates = Configure::read('Notifier.templates'); diff --git a/tests/TestCase/Controller/Component/NotifierComponentTest.php b/tests/TestCase/Controller/Component/NotifierComponentTest.php index ea9f9c2..3b8986b 100644 --- a/tests/TestCase/Controller/Component/NotifierComponentTest.php +++ b/tests/TestCase/Controller/Component/NotifierComponentTest.php @@ -32,7 +32,7 @@ class NotifierComponentTest extends TestCase 'plugin.bakkerij\Notifier.notifications' ]; - public function setUp() + public function setUp(): void { parent::setUp(); @@ -58,7 +58,7 @@ public function setUp() $this->Notifier = new NotifierComponent($registry); } - public function tearDown() + public function tearDown(): void { unset($this->Notifier); unset($this->Manager); diff --git a/tests/TestCase/Model/Table/NotificationsTableTest.php b/tests/TestCase/Model/Table/NotificationsTableTest.php index 7ac241c..a7e6ee2 100644 --- a/tests/TestCase/Model/Table/NotificationsTableTest.php +++ b/tests/TestCase/Model/Table/NotificationsTableTest.php @@ -28,13 +28,13 @@ class NotificationsTableTest extends TestCase 'plugin.bakkerij\Notifier.notifications', ]; - public function setUp() + public function setUp(): void { parent::setUp(); $this->Notifications = TableRegistry::get('Bakkerij/Notifier.Notifications'); } - public function tearDown() + public function tearDown(): void { unset($this->Notifications); diff --git a/tests/TestCase/Utility/NotificationManagerTest.php b/tests/TestCase/Utility/NotificationManagerTest.php index b3e208d..471ceaf 100644 --- a/tests/TestCase/Utility/NotificationManagerTest.php +++ b/tests/TestCase/Utility/NotificationManagerTest.php @@ -26,7 +26,7 @@ class NotificationManagerTest extends TestCase 'plugin.bakkerij\Notifier.notifications' ]; - public function setUp() + public function setUp(): void { parent::setUp(); $this->Manager = NotificationManager::instance(); @@ -34,7 +34,7 @@ public function setUp() $this->Model = TableRegistry::get('Bakkerij/Notifier.Notifications'); } - public function tearDown() + public function tearDown(): void { unset($this->Manager); unset($this->Model);