├── Module.php ├── README.md ├── codeception.yml ├── commands └── NotificationController.php ├── components ├── JobEvent.php ├── Notification.php ├── Provider.php └── Push.php ├── composer.json ├── helpers └── ErrorHelper.php ├── migrations ├── m170419_203853_create_table_notification.php ├── m170625_123108_add_column_event_into_notification.php ├── m171222_202322_create_table_notification_status.php └── m180307_122753_alter_table_notification_status.php ├── models ├── Message.php └── NotificationStatus.php ├── providers ├── email.php ├── notify.php ├── push.php ├── smsc.php └── web.php ├── tests ├── _bootstrap.php ├── _data │ ├── .gitkeep │ └── db.sq3 ├── _output │ └── .gitignore ├── _support │ ├── AcceptanceTester.php │ ├── FunctionalTester.php │ ├── Helper │ │ ├── Acceptance.php │ │ ├── Functional.php │ │ └── Unit.php │ ├── UnitTester.php │ └── _generated │ │ └── .gitignore ├── app │ ├── config │ │ └── main.php │ └── mail │ │ ├── layouts │ │ ├── html.php │ │ └── text.php │ │ ├── simple-html.php │ │ └── simple-text.php ├── functional.suite.yml ├── unit.suite.yml └── unit │ ├── ComponentsTest.php │ ├── Providers │ ├── EmailTest.php │ ├── NotifyTest.php │ └── WebTest.php │ └── _bootstrap.php └── tpl ├── email-base.html.tpl.php ├── email-base.text.tpl.php └── layouts ├── html.php └── text.php /Module.php: -------------------------------------------------------------------------------- 1 | providers as $providerName => $provider){ 35 | if(empty($provider['events'])) continue; 36 | $this->attachEvents($providerName, $provider); 37 | } 38 | } 39 | 40 | /** 41 | * @param Notification $notification 42 | * 43 | * @return array|string|void 44 | * @throws Exception 45 | * @throws \yii\base\InvalidConfigException 46 | */ 47 | public function sendEvent(Notification $notification) 48 | { 49 | if(isset($notification->data['provider'])) { 50 | $provider = Yii::createObject($notification->data[ 'provider' ]); 51 | } elseif(isset($notification->data['providerName'])) { 52 | $provider = $this->provider($notification->data['providerName']); 53 | } else { 54 | throw new Exception(Yii::t('app', 'Provider not found')); 55 | } 56 | 57 | /** @var Provider $provider */ 58 | if(!$provider || !$provider->enabled){ 59 | return; 60 | } 61 | 62 | $reflect = new \ReflectionClass($provider); 63 | $event = new JobEvent([ 64 | 'provider' => $reflect->getShortName(), 65 | 'event' => $notification->name, 66 | 'params' => $notification, 67 | ]); 68 | $this->trigger(self::EVENT_BEFORE_SEND, $event); 69 | 70 | // Skip if is not valid 71 | if(!$event->isValid){ 72 | return; 73 | } 74 | 75 | $provider->send($notification); 76 | $this->setProviderStatus($notification, $provider); 77 | 78 | $event->status = $provider->status; 79 | $event->errors = $provider->errors; 80 | $this->trigger(self::EVENT_AFTER_SEND, $event); 81 | 82 | if($provider->errors){ 83 | throw new Exception(Yii::t('app', "Rised exception in the provider {provider}", ['provider' => $reflect->getShortName()])); 84 | } 85 | } 86 | 87 | /** 88 | * @param $name 89 | * @return mixed 90 | */ 91 | public function provider($name) 92 | { 93 | if (!isset($this->_providers[$name])) { 94 | if (isset($this->providers[$name])) { 95 | $this->_providers[$name] = Yii::createObject($this->providers[$name]); 96 | } 97 | } 98 | return $this->_providers[$name]; 99 | } 100 | 101 | /** 102 | * @param $provider 103 | */ 104 | public function attachEvents($providerName, $provider) 105 | { 106 | foreach ($provider['events'] as $className => $events) { 107 | foreach ($events as $eventName) { 108 | Notification::on($className, $eventName, [$this, 'sendEvent'], ['providerName' => $providerName, 'provider' => $provider]); 109 | } 110 | } 111 | } 112 | 113 | /** 114 | * @param $class 115 | * @return string 116 | */ 117 | function class_basename($class) 118 | { 119 | $class = is_object($class) ? get_class($class) : $class; 120 | 121 | return basename(str_replace('\\', '/', $class)); 122 | } 123 | 124 | /** 125 | * @param Notification $notification 126 | * @param Provider $provider 127 | * @return int|void 128 | */ 129 | private function setProviderStatus(Notification &$notification, $provider = null) 130 | { 131 | if(!$this->storeNotificationStatus){ 132 | return; 133 | } 134 | 135 | $providerName = $notification->data['providerName']; 136 | $status = new NotificationStatus; 137 | $status->provider = $providerName; 138 | $status->event = $notification->name; 139 | $status->params = Json::encode($notification->getAttributes()); 140 | $status->update_at = new Expression('CURRENT_TIMESTAMP'); 141 | $status->status = $provider->getStatus(); 142 | $status->save(); 143 | 144 | return $status->id; 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Yii 2.0 Notification 2 | 3 | [](https://packagist.org/packages/mirocow/yii2-notification) 4 | [](https://packagist.org/packages/mirocow/yii2-notification) 5 | [](https://packagist.org/packages/mirocow/yii2-notification) [](https://packagist.org/packages/mirocow/yii2-notification) 6 | 7 | ## Install 8 | 9 | ```sh 10 | $ composer require --prefer-dist "mirocow/yii2-notification" 11 | $ php ./yii migrate/up -p=@mirocow/notification/migrations 12 | ``` 13 | 14 | ## Configurate 15 | 16 | ```php 17 | 'modules' => [ 18 | // Notification by providers 19 | 'notification' => [ 20 | 'class' => 'mirocow\notification\Module', 21 | 'providers' => [ 22 | 23 | // SMS prostor-sms.ru 24 | 'sms' => [ 25 | 'class' => 'mirocow\notification\providers\sms', 26 | 'config' => [ 27 | 'gate' => '', 28 | 'port' => 80, 29 | 'login' => '', 30 | 'password' => '', 31 | 'signature' => '', 32 | ] 33 | ], 34 | 35 | // E-mail 36 | 'email' => [ 37 | 'class' => 'mirocow\notification\providers\email', 38 | 'emailViewPath' => '@common/mail', 39 | 'events' => [ 40 | 'frontend\controllers\SiteController' => [ 41 | 'Request', 42 | 'Signup', 43 | ], 44 | 'backend\controllers\deal\SiteController' => [ 45 | 'Login', 46 | 'Confirm', 47 | ] 48 | ] 49 | ] 50 | ], 51 | ] 52 | ], 53 | ``` 54 | 55 | ## Using 56 | 57 | ### By method send 58 | 59 | ```php 60 | use mirocow\notification\components\Notification; 61 | 62 | /* @var \mirocow\notification\Module $sender */ 63 | $sender = Yii::$app->getModule('notification'); 64 | 65 | $notification = new Notification([ 66 | 'from' => [\Yii::$app->params['supportEmail'] => \Yii::$app->name . ' robot'], 67 | 'to' => $deal['userSeller']['email'], // строка или массив 68 | 'toId' => $deal['userSeller']['id'], // строка или массив 69 | 'phone' => $deal['userSeller']['phone_number'], // строка или массив 70 | 'subject' => "\"{$deal['userBuyer']['nameForOut']}\" предлагает вам сделку для \"{$deal['ads']['product']->getName()}\"", 71 | 'token' => 'TOKEN', 72 | 'content' => "", 73 | 'params' => [ 74 | 'productName' => $deal['ads']['product']->getName(), 75 | 'avatar' => $deal['userBuyer']->avatarFile, 76 | 'fromUserName' => $deal['userBuyer']['nameForOut'], 77 | ], 78 | 'view' => ['html' => 'Request-html', 'text' => 'Request-text'], 79 | 'path' => '@common/mail/deal', 80 | 'notify' => ['growl', 'На Ваш email отправлено письмо для подтверждения'], 81 | 'callback' => function(Provider $provider, $status){ 82 | // Тут можно обработать ответ от провайдеров нотификаций 83 | } 84 | ]); 85 | 86 | $sender->sendEvent($notification); 87 | ``` 88 | 89 | ### By Event 90 | 91 | ```php 92 | use yii\base\Event; 93 | use mirocow\notification\components\Notification; 94 | 95 | $event = new Notification(['params' => [ 96 | 'from' => [\Yii::$app->params['supportEmail'] => \Yii::$app->name . ' robot'], 97 | 'to' => $user->email, 98 | 'subject' => 'Регистрация на сайте ' . \Yii::$app->name, 99 | 'emailView' => ['html' => 'signUp-html', 'text' => 'signUp-text'], 100 | 'user' => $user, 101 | 'phone' => $user->phone_number, 102 | 'notify' => ['growl', 'На Ваш email отправлено письмо для подтверждения'], 103 | ]]); 104 | Notification::trigger(self::className(),'Signup', $event); 105 | ``` 106 | 107 | or full 108 | 109 | ```php 110 | $notification = new Notification([ 111 | 'from' => [\Yii::$app->params['supportEmail'] => \Yii::$app->name . ' robot'], 112 | 'to' => $deal['userSeller']['email'], // строка или массив 113 | 'toId' => $deal['userSeller']['id'], // строка или массив 114 | 'phone' => $deal['userSeller']['phone_number'], // строка или массив 115 | 'subject' => "\"{$deal['userBuyer']['nameForOut']}\" предлагает вам сделку для \"{$deal['ads']['product']->getName()}\"", 116 | 'token' => 'TOKEN', 117 | 'content' => "", 118 | 'params' => [ 119 | 'productName' => $deal['ads']['product']->getName(), 120 | 'avatar' => $deal['userBuyer']->avatarFile, 121 | 'fromUserName' => $deal['userBuyer']['nameForOut'], 122 | ], 123 | 'view' => ['html' => 'Request-html', 'text' => 'Request-text'], 124 | 'path' => '@common/mail/deal', 125 | 'notify' => ['growl', 'На Ваш email отправлено письмо для подтверждения'], 126 | 'callback' => function(Provider $provider, $status){ 127 | // Тут можно обработать ответ от провайдеров нотификаций 128 | } 129 | ]); 130 | Notification::trigger(self::className(),'Request', $notification); 131 | ``` 132 | 133 | ### With mirocow/yii2-queue 134 | 135 | ```php 136 | \Yii::$app->queue->getChannel()->push(new MessageModel([ 137 | 'worker' => 'notification', 138 | 'method' => 'action', 139 | 'arguments' => [ 140 | 'triggerClass' => self::class, 141 | 'methodName' => 'Subscribe', 142 | 'arguments' => [ 143 | 'param' => 'value' 144 | ], 145 | ], 146 | ]), 30); 147 | ``` 148 | 149 | ## Tests 150 | 151 | ```bash 152 | $ ./vendor/bin/codecept -c ./vendor/mirocow/yii2-notification run unit 153 | ``` 154 | -------------------------------------------------------------------------------- /codeception.yml: -------------------------------------------------------------------------------- 1 | actor: Tester 2 | paths: 3 | tests: tests 4 | log: tests/_output 5 | data: tests/_data 6 | helpers: tests/_support 7 | settings: 8 | bootstrap: _bootstrap.php 9 | memory_limit: 1024M 10 | colors: true 11 | -------------------------------------------------------------------------------- /commands/NotificationController.php: -------------------------------------------------------------------------------- 1 | db->close(); 21 | $notification = new Notification($data); 22 | Notification::trigger($triggerClass, $name, $notification); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /components/JobEvent.php: -------------------------------------------------------------------------------- 1 | '@common/mail/layouts/text', 51 | 'html' => '@common/mail/layouts/html', 52 | ]; 53 | 54 | /** @var array */ 55 | public $view = []; 56 | 57 | /** @var string */ 58 | public $TextBody = ''; 59 | 60 | /** @var string */ 61 | public $HtmlBody = ''; 62 | 63 | /** @var array */ 64 | public $attaches = []; 65 | 66 | /** @var array */ 67 | public $params = []; 68 | 69 | /** @var array */ 70 | public $push = [ 71 | 'aps' => [ 72 | 'alert' => 'Hi', 73 | 'badge' => 1, 74 | 'sound' => 'default', 75 | "link_url" => "https://google.com" 76 | ], 77 | ]; 78 | 79 | public function __construct($config = []) 80 | { 81 | if (!empty($config)) { 82 | foreach ($config as $name => $value) { 83 | if(property_exists($this, $name)) { 84 | $this->{$name} = $value; 85 | } 86 | } 87 | } 88 | $this->init(); 89 | } 90 | 91 | /** 92 | * @throws Exception 93 | */ 94 | public function init() 95 | { 96 | if (empty($this->from)) { 97 | if (!empty(\Yii::$app->params['supportEmail'])) { 98 | $this->from = [\Yii::$app->params['supportEmail'] => \Yii::$app->name]; 99 | $this->fromId = 0; 100 | } else { 101 | throw new Exception("Sender email not found"); 102 | } 103 | } 104 | 105 | if(!$this->notify){ 106 | $this->notify = ['growl', $this->subject]; 107 | } 108 | } 109 | 110 | /** 111 | * @return \ReflectionProperty[] 112 | */ 113 | public function getAttributes() 114 | { 115 | $properties = []; 116 | 117 | $reflect = new \ReflectionClass($this); 118 | $props = $reflect->getProperties(\ReflectionProperty::IS_PUBLIC); 119 | 120 | foreach ($props as $prop) { 121 | $properties[$prop->name] = $this->{$prop->name}; 122 | } 123 | 124 | return $properties; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /components/Provider.php: -------------------------------------------------------------------------------- 1 | errors)? $this->errors: $this->status; 41 | return Json::encode($status); 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /components/Push.php: -------------------------------------------------------------------------------- 1 | apnsConfig) && !empty($this->apnsConfig)) { 62 | $this->validateApns(); 63 | $this->apnsEnabled = true; 64 | } 65 | 66 | if (is_array($this->gcmConfig) && !empty($this->gcmConfig)) { 67 | $this->validateGcm(); 68 | $this->gcmEnabled = true; 69 | } 70 | } 71 | 72 | /** 73 | * @throws InvalidConfigException 74 | */ 75 | private function validateApns() 76 | { 77 | if (!ArrayHelper::keyExists('environment', $this->apnsConfig) || !ArrayHelper::isIn(ArrayHelper::getValue($this->apnsConfig, 'environment'), [ 78 | self::APNS_ENVIRONMENT_SANDBOX, self::APNS_ENVIRONMENT_PRODUCTION]) 79 | ) { 80 | throw new InvalidConfigException('Apns environment is invalid.'); 81 | } 82 | 83 | if (ArrayHelper::keyExists('pem', $this->apnsConfig)) { 84 | if (0 === strpos(ArrayHelper::getValue($this->apnsConfig, 'pem'), '@')) { 85 | $path = Yii::getAlias(ArrayHelper::getValue($this->apnsConfig, 'pem')); 86 | } else { 87 | $path = ArrayHelper::getValue($this->apnsConfig, 'pem'); 88 | } 89 | 90 | if (!is_file($path)) { 91 | throw new InvalidConfigException('Apns pem is invalid.'); 92 | } 93 | 94 | $this->apnsConfig['pem'] = $path; 95 | } else { 96 | throw new InvalidConfigException('Apns pem is required.'); 97 | } 98 | } 99 | 100 | /** 101 | * @throws InvalidConfigException 102 | */ 103 | private function validateGcm() 104 | { 105 | if (!ArrayHelper::keyExists('apiAccessKey', $this->gcmConfig)) { 106 | throw new InvalidConfigException('Gcm api access key is invalid.'); 107 | } 108 | } 109 | 110 | /** 111 | * @param $tokens 112 | * @return array 113 | */ 114 | private function splitDeviceTokens($tokens) 115 | { 116 | $apnsTokens = []; 117 | $gcmTokens = []; 118 | $invalidTokens = []; 119 | 120 | foreach ($tokens as $token) { 121 | if (strlen($token) == 64) { 122 | $apnsTokens[] = $token; 123 | } elseif (strlen($token) == 152) { 124 | $gcmTokens[] = $token; 125 | } else { 126 | $invalidTokens[] = $token; 127 | } 128 | } 129 | 130 | return [ 131 | 'apns' => $apnsTokens, 132 | 'gcm' => $gcmTokens, 133 | 'invalid' => $invalidTokens 134 | ]; 135 | } 136 | 137 | /** 138 | * @return Push 139 | */ 140 | public function android() 141 | { 142 | $this->type = self::TYPE_GCM; 143 | return $this; 144 | } 145 | 146 | /** 147 | * @return Push 148 | */ 149 | public function ios() 150 | { 151 | $this->type = self::TYPE_APNS; 152 | return $this; 153 | } 154 | 155 | /** 156 | * @param $id 157 | * @param $payload 158 | * @return mixed 159 | */ 160 | public function send($token, $payload) 161 | { 162 | if ($this->type) { 163 | switch ($this->type) { 164 | case self::TYPE_GCM: 165 | return self::sendGcm($token, $payload); 166 | break; 167 | case self::TYPE_APNS: 168 | return self::sendApns($token, $payload); 169 | break; 170 | } 171 | } else { 172 | $tokens = self::splitDeviceTokens($token); 173 | 174 | if (!empty(ArrayHelper::getValue($tokens, 'apns'))) { 175 | self::sendApns(ArrayHelper::getValue($tokens, 'apns'), $payload); 176 | } 177 | 178 | if (!empty(ArrayHelper::getValue($tokens, 'gcm'))) { 179 | self::sendGcm(ArrayHelper::getValue($tokens, 'gcm'), $payload); 180 | } 181 | 182 | if (is_array($this->options) && ArrayHelper::getValue($this->options, 'returnInvalidTokens', false)) { 183 | return ArrayHelper::getValue($tokens, 'invalid'); 184 | } 185 | } 186 | } 187 | 188 | /** 189 | * @param $id 190 | * @param $data 191 | * @throws Exception 192 | */ 193 | private function sendGcm($id, $data) 194 | { 195 | if (!$this->gcmEnabled) { 196 | throw new InvalidConfigException('Gcm in not enabled.'); 197 | } 198 | 199 | if (!empty($id)) { 200 | $fields = [ 201 | 'registration_ids' => $id, 202 | 'data' => $data 203 | ]; 204 | 205 | $curl = curl_init(); 206 | curl_setopt($curl, CURLOPT_URL, self::GCM_URL); 207 | curl_setopt($curl, CURLOPT_POST, true); 208 | curl_setopt($curl, CURLOPT_HTTPHEADER, [ 209 | sprintf('Authorization: key=%s', ArrayHelper::getValue($this->gcmConfig, 'apiAccessKey')), 210 | 'Content-Type: application/json' 211 | ]); 212 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); 213 | curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); 214 | curl_setopt($curl, CURLOPT_POSTFIELDS, Json::encode($fields)); 215 | 216 | $result = curl_exec($curl); 217 | $err = curl_error($curl); 218 | 219 | curl_close($curl); 220 | 221 | if ($err) { 222 | Yii::error($err); 223 | return false; 224 | } 225 | 226 | Yii::info($result); 227 | 228 | return true; 229 | } 230 | } 231 | 232 | /** 233 | * @param $token 234 | * @param $body 235 | * @throws Exception 236 | */ 237 | private function sendApns($token, $body) 238 | { 239 | if (!$this->apnsEnabled) { 240 | throw new InvalidConfigException('Apns in not enabled.'); 241 | } 242 | 243 | if (is_array($body)) { 244 | $body = Json::encode($body); 245 | } 246 | 247 | if(strlen($body) > 256){ 248 | Yii::error("Send message is to long"); 249 | return false; 250 | } 251 | 252 | $path = sprintf('tls://gateway%s.push.apple.com:2195', ArrayHelper::getValue($this->apnsConfig, 'environment')); 253 | $this->ctx = stream_context_create(); 254 | stream_context_set_option($this->ctx, 'ssl', 'local_cert', ArrayHelper::getValue($this->apnsConfig, 'pem')); 255 | 256 | if (ArrayHelper::keyExists('passphrase', $this->apnsConfig)) { 257 | stream_context_set_option($this->ctx, 'ssl', 'passphrase', ArrayHelper::getValue($this->apnsConfig, 'passphrase')); 258 | } 259 | 260 | $fp = stream_socket_client($path, $err, $message, 60, STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT, $this->ctx); 261 | 262 | if (!$fp) { 263 | Yii::error(['error' => $err , 'message' => $message]); 264 | return false; 265 | } 266 | 267 | stream_set_blocking($fp, 0); 268 | 269 | $ret = false; 270 | 271 | try { 272 | $msg = chr(0) . pack('n', 32) . pack('H*', $token) . pack('n', strlen($body)) . $body; 273 | $writtenBytes = fwrite($fp, $msg, strlen($msg)); 274 | 275 | if($writtenBytes === false){ 276 | Yii::error("An error occurred while the data on the server"); 277 | $ret = false; 278 | } elseif ($writtenBytes > 0) { 279 | Yii::info("Copied $writtenBytes bytes to server."); 280 | $ret = true; 281 | } 282 | 283 | } catch (Exception $e) { 284 | Yii::error($e); 285 | $ret = false; 286 | } 287 | 288 | fclose($fp); 289 | 290 | return $ret; 291 | } 292 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mirocow/yii2-notification", 3 | "description": "Yii2 notification", 4 | "license": "MIT", 5 | "keywords": ["yii2", "eav", "attribute", "notification", "email", "sms"], 6 | "type": "yii2-extension", 7 | "authors": [ 8 | { 9 | "name": "Mirocow", 10 | "email": "mr.mirocow@gmail.com", 11 | "homepage": "http://mirocow.com", 12 | "role": "Developer" 13 | } 14 | ], 15 | "minimum-stability": "dev", 16 | "require": { 17 | "php":">=5.4.0", 18 | "yiisoft/yii2": ">=2.0", 19 | "yiisoft/yii2-redis": "~2.0.0", 20 | "ladamalina/yii2-smsc": "@dev", 21 | "develandoo/yii2-push-notification": "^1.0" 22 | }, 23 | "require-dev": { 24 | "yiisoft/yii2-codeception": "*" 25 | }, 26 | "suggest": { 27 | "mirocow/yii2-queue": "to use Queue server" 28 | }, 29 | "autoload": { 30 | "psr-4": { 31 | "mirocow\\notification\\": "./" 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /helpers/ErrorHelper.php: -------------------------------------------------------------------------------- 1 | $e->getMessage(), 11 | 'Code' => $e->getCode(), 12 | 'File' => $e->getFile() . ': ' . $e->getLine(), 13 | 'Trace' => $e->getTraceAsString(), 14 | ]; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /migrations/m170419_203853_create_table_notification.php: -------------------------------------------------------------------------------- 1 | createTable('notification', [ 12 | 'id' => $this->primaryKey(), 13 | 'from_id' => $this->integer(11), 14 | 'to_id' => $this->integer(11), 15 | 'title' => $this->string(255), 16 | 'message' => $this->text(), 17 | 'params' => $this->text(), 18 | 'update_at' => $this->timestamp()->defaultValue(new Expression('CURRENT_TIMESTAMP')), 19 | 'create_at' => $this->timestamp()->defaultValue(new Expression('CURRENT_TIMESTAMP')), 20 | ]); 21 | 22 | } 23 | 24 | public function down() 25 | { 26 | $this->dropTable('notification'); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /migrations/m170625_123108_add_column_event_into_notification.php: -------------------------------------------------------------------------------- 1 | addColumn('notification', 'event', $this->string(100)->after('to_id')); 11 | } 12 | 13 | public function down() 14 | { 15 | $this->dropColumn('notification', 'event'); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /migrations/m171222_202322_create_table_notification_status.php: -------------------------------------------------------------------------------- 1 | createTable('notification_status', [ 15 | 'id' => $this->primaryKey(), 16 | 'provider' => $this->string(), 17 | 'event' => $this->string(), 18 | 'params' => $this->text(), 19 | 'status' => $this->string()->null(), 20 | 'update_at' => $this->timestamp()->null(), 21 | 'create_at' => $this->timestamp()->defaultValue(new Expression('CURRENT_TIMESTAMP')), 22 | ]); 23 | 24 | } 25 | 26 | public function down() 27 | { 28 | $this->dropTable('notification_status'); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /migrations/m180307_122753_alter_table_notification_status.php: -------------------------------------------------------------------------------- 1 | alterColumn('notification_status', 'status', $this->text()); 16 | } 17 | 18 | /** 19 | * @inheritdoc 20 | */ 21 | public function safeDown() 22 | { 23 | echo "m180307_122753_alter_table_notification_status cannot be reverted.\n"; 24 | 25 | return false; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /models/Message.php: -------------------------------------------------------------------------------- 1 | 255], 43 | [['event'], 'string', 'max' => 100], 44 | ]; 45 | } 46 | 47 | /** 48 | * @inheritdoc 49 | */ 50 | public function attributeLabels() 51 | { 52 | return [ 53 | 'id' => 'ID', 54 | 'from_id' => 'From Id', 55 | 'to_id' => 'To Id', 56 | 'event' => 'Event name', 57 | 'title' => 'Title', 58 | 'message' => 'Message', 59 | 'params' => 'Json params', 60 | 'update_at' => 'Update At', 61 | 'create_at' => 'Create At', 62 | ]; 63 | } 64 | 65 | /** 66 | * @param array $where 67 | * @return array|\yii\db\ActiveRecord[] 68 | */ 69 | public static function messages($where = []) 70 | { 71 | if(!$where) { 72 | $where = ['or', 'to_id' => Yii::$app->user->identity->id, 'from_id' => Yii::$app->user->identity->id]; 73 | } 74 | 75 | return self::find()->where($where)->all(); 76 | } 77 | 78 | /** 79 | * @param array $params 80 | */ 81 | public function setParams($params = []){ 82 | $params = ArrayHelper::merge($this->attributes, $params); 83 | $this->params = Json::encode($params); 84 | } 85 | 86 | /** 87 | * @return array|mixed 88 | */ 89 | public function getParams(){ 90 | $params = Json::decode($this->getAttribute('params')); 91 | if(!$params){ 92 | $params = []; 93 | } 94 | return $params; 95 | } 96 | 97 | /** 98 | * @param string $name 99 | * @return array|mixed 100 | * @throws Exception 101 | */ 102 | public function __get($name) { 103 | 104 | if($name == 'attributes'){ 105 | return $this->getAttributes(); 106 | } 107 | 108 | // If name is model attribute 109 | $attributes = $this->attributes(); 110 | if(in_array($name, $attributes)){ 111 | return parent::__get($name); 112 | } 113 | 114 | // If name is param of model`s attribute by name params 115 | $params = $this->getParams(); 116 | if (isset($params[$name])) { 117 | return $params[$name]; 118 | } 119 | 120 | throw new Exception(); 121 | 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /models/NotificationStatus.php: -------------------------------------------------------------------------------- 1 | 255], 37 | [['status'], 'string'], 38 | ]; 39 | } 40 | 41 | /** 42 | * @inheritdoc 43 | */ 44 | public function attributeLabels() 45 | { 46 | return [ 47 | 'id' => 'ID', 48 | 'provider' => 'Provider', 49 | 'event' => 'Event', 50 | 'params' => 'Params', 51 | 'status' => 'Status', 52 | 'update_at' => 'Update At', 53 | 'create_at' => 'Create At', 54 | ]; 55 | } 56 | } -------------------------------------------------------------------------------- /providers/email.php: -------------------------------------------------------------------------------- 1 | '@common/mail/layouts/text', 23 | 'html' => '@common/mail/layouts/html', 24 | ]; 25 | 26 | public $views = [ 27 | 'text' => 'email-base.text.tpl.php', 28 | 'html' => 'email-base.html.tpl.php', 29 | ]; 30 | 31 | public $config = [ 32 | 'mailer' => [], 33 | ]; 34 | 35 | /** 36 | * @param Notification $notification 37 | * 38 | * @return bool 39 | * @throws Exception 40 | */ 41 | public function send(Notification $notification) 42 | { 43 | if (empty($notification->to)) { 44 | return; 45 | } 46 | 47 | $provider = 'mailer'; 48 | 49 | if (!empty($this->config['mailer'])) { 50 | $provider = $this->config['mailer']; 51 | } 52 | 53 | /** @var \yii\swiftmailer\Mailer $mailer */ 54 | $mailer = Yii::$app->get($provider); 55 | 56 | if (!$mailer) { 57 | throw new InvalidConfigException(); 58 | } 59 | 60 | /** 61 | * Prepare 62 | */ 63 | 64 | if (!empty($this->config['view'])) { 65 | $mailer->setView($this->config['view']); 66 | $mailer->getView(); 67 | } 68 | 69 | $mailer->view->params['notification'] = $notification; 70 | 71 | $mailer->viewPath = isset($notification->path)? $notification->path: $this->emailViewPath; 72 | 73 | $params = array_merge($notification->params, [ 74 | 'subject' => $notification->subject, 75 | 'content' => $notification->content, 76 | ]); 77 | 78 | // Registered variable 79 | unset($params['message']); 80 | 81 | /** 82 | * Layouts 83 | */ 84 | 85 | if (isset($notification->layouts['text'])) { 86 | $mailer->textLayout = $notification->layouts['text']; 87 | } elseif (isset($this->layouts['text'])) { 88 | $mailer->textLayout = $this->layouts['text']; 89 | } 90 | 91 | if (isset($notification->layouts['html'])) { 92 | $mailer->htmlLayout = $notification->layouts['html']; 93 | } elseif (isset($this->layouts['html'])) { 94 | $mailer->htmlLayout = $this->layouts['html']; 95 | } 96 | 97 | /** 98 | * From 99 | */ 100 | 101 | if (!empty($notification->from)) { 102 | $from = $notification->from; 103 | } else { 104 | if (isset($this->config['from'])) { 105 | $from = $this->config['from']; 106 | } else { 107 | $from = isset(Yii::$app->params['adminEmail'])? Yii::$app->params['adminEmail']: 'admin@localhost'; 108 | } 109 | } 110 | 111 | /** 112 | * To 113 | */ 114 | 115 | if (is_array($notification->to)) { 116 | if (is_array(reset($notification->to))) { 117 | $emails = $notification->to; 118 | } else { 119 | // like [email => userName] 120 | $emails = [$notification->to]; 121 | } 122 | } else { 123 | $emails = [$notification->to]; 124 | } 125 | 126 | /** 127 | * Send emails 128 | */ 129 | 130 | $views = isset($notification->view)? $notification->view: $this->views; 131 | 132 | foreach ($emails as $email) { 133 | $status = false; 134 | try { 135 | 136 | /** @var MessageInterface $message */ 137 | $message = $mailer->compose($views, $params); 138 | 139 | /** 140 | * Reply-To 141 | */ 142 | 143 | if ($notification->replyTo) { 144 | $message->setReplyTo($notification->replyTo); 145 | } 146 | 147 | /** 148 | * Body 149 | */ 150 | 151 | if ($notification->TextBody) { 152 | $message->setTextBody($notification->TextBody); 153 | } 154 | 155 | if ($notification->HtmlBody) { 156 | $message->setHtmlBody($notification->HtmlBody); 157 | } 158 | 159 | /** 160 | * Attaches 161 | */ 162 | 163 | if ($notification->attaches) { 164 | foreach ($notification->attaches as $attach) { 165 | $message->attach($attach); 166 | } 167 | } 168 | 169 | /** 170 | * Send email 171 | */ 172 | 173 | $status = $message->setFrom($from)->setTo($email)->setSubject($notification->subject)->send(); 174 | 175 | } catch (\Exception $e) { 176 | $this->errors[] = ErrorHelper::message($e); 177 | } 178 | 179 | if (is_array($email)) { 180 | foreach ($email as $_email) { 181 | $this->status[$_email] = $status; 182 | } 183 | } else { 184 | $this->status[$email] = $status; 185 | } 186 | } 187 | 188 | unset($mailer); 189 | } 190 | 191 | } 192 | -------------------------------------------------------------------------------- /providers/notify.php: -------------------------------------------------------------------------------- 1 | notify)) { 23 | return; 24 | } 25 | 26 | Yii::$app->session->addFlash($notification->notify[0], $notification->notify[1]); 27 | 28 | $this->status = true; 29 | 30 | } 31 | } -------------------------------------------------------------------------------- /providers/push.php: -------------------------------------------------------------------------------- 1 | token)) { 24 | return; 25 | } 26 | 27 | /** @var \mirocow\notification\components\Push $push */ 28 | $push = Yii::createObject(array_merge(['class' => 'mirocow\notification\components\Push'], $this->config)); 29 | 30 | if (is_array($notification->token)) { 31 | $tokens = $notification->token; 32 | } else { 33 | $tokens = [$notification->token]; 34 | } 35 | 36 | foreach ($tokens as $token) { 37 | try { 38 | $status = $push->ios()->send($token, $notification->push); 39 | } catch (\Exception $e) { 40 | $this->errors[] = ErrorHelper::message($e); 41 | } 42 | 43 | $this->status[$token] = $status; 44 | } 45 | 46 | unset($push); 47 | } 48 | } -------------------------------------------------------------------------------- /providers/smsc.php: -------------------------------------------------------------------------------- 1 | '', 18 | 'password' => '', 19 | 'post' => true, 20 | // use http POST method 21 | 'https' => true, 22 | // use secure HTTPS connection 23 | 'charset' => 'utf-8', 24 | // charset: windows-1251, koi8-r or utf-8 (default) 25 | 'debug' => false, 26 | // debug mode 27 | ]; 28 | 29 | /** 30 | * @param Notification $notification 31 | * 32 | * @return bool 33 | */ 34 | public function send(Notification $notification) 35 | { 36 | if (empty($notification->phone)) { 37 | return; 38 | } 39 | 40 | /** @var \ladamalina\smsc\Smsc $sms */ 41 | $sms = Yii::createObject(array_merge(['class' => 'ladamalina\smsc\Smsc'], $this->config)); 42 | 43 | if (is_array($notification->phone)) { 44 | $phones = $notification->phone; 45 | } else { 46 | $phones = [$notification->phone]; 47 | } 48 | 49 | foreach ($phones as $phone) { 50 | try { 51 | $result = $sms->send_sms($phone, $notification->subject); 52 | $status = $sms->isSuccess($result); 53 | } catch (\Exception $e) { 54 | $this->errors[] = ErrorHelper::message($e); 55 | } 56 | 57 | $this->status[$phone] = $status; 58 | } 59 | 60 | unset($sms); 61 | 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /providers/web.php: -------------------------------------------------------------------------------- 1 | toId)) { 27 | return; 28 | } 29 | 30 | if (is_array($notification->toId)) { 31 | $toIds = $notification->toId; 32 | } else { 33 | $toIds = [$notification->toId]; 34 | } 35 | 36 | \Yii::$app->db->open(); 37 | foreach ($toIds as $toId) { 38 | try { 39 | $message = new Message(); 40 | $message->from_id = $notification->fromId; 41 | $message->to_id = $toId; 42 | $message->event = $notification->name; 43 | $message->title = $notification->subject; 44 | $message->message = $notification->content; 45 | $message->setParams(ArrayHelper::merge(['event' => $notification->name], $notification->params)); 46 | $status = $message->save(); 47 | unset($message); 48 | } catch (\Exception $e) { 49 | $this->errors[] = ErrorHelper::message($e); 50 | } 51 | 52 | $this->status[$toId] = $status; 53 | } 54 | \Yii::$app->db->close(); 55 | 56 | } 57 | } -------------------------------------------------------------------------------- /tests/_bootstrap.php: -------------------------------------------------------------------------------- 1 | 'yii2-notification-app', 4 | 'basePath' => dirname(__DIR__), 5 | 'vendorPath' => dirname(dirname(__DIR__)).'/vendor', 6 | 'runtimePath' => dirname(dirname(__DIR__)).'/_data/runtime', 7 | 'bootstrap' => [ 8 | ], 9 | 'components' => [ 10 | 11 | 'db' => [ 12 | 'class' => 'yii\db\Connection', 13 | 'dsn' => 'sqlite:@tests/_data/db.sq3', 14 | ], 15 | 16 | 'mailer' => [ 17 | 'class' => 'yii\swiftmailer\Mailer', 18 | 'viewPath' => '@app/mail', 19 | 'useFileTransport' => true, 20 | 'messageConfig' => [ 21 | 'charset' => 'UTF-8', 22 | ], 23 | ], 24 | 25 | 'session' => ['class' => 'yii\web\Session'], 26 | 27 | ], 28 | 'modules' => [ 29 | 30 | // Notification by providers (Система нотификаций: email, sms, push, web, итд) 31 | 'notification' => [ 32 | 'class' => 'mirocow\notification\Module', 33 | 'storeNotificationStatus' => true, 34 | 'providers' => [ 35 | 36 | // notify 37 | 'notify' => [ 38 | 'class' => 'mirocow\notification\providers\notify', 39 | 'enabled' => true, 40 | ], 41 | 42 | // Web notify 43 | 'web' => [ 44 | 'class' => 'mirocow\notification\providers\web', 45 | 'enabled' => true, 46 | ], 47 | 48 | // E-mail 49 | 'email' => [ 50 | 'class' => 'mirocow\notification\providers\email', 51 | 'enabled' => true, 52 | 'emailViewPath' => '@app/mail', 53 | ], 54 | 55 | ] 56 | ], 57 | 58 | ], 59 | 'params' => [ 60 | 'noreplyEmail' => 'noreply@localhost', 61 | ], 62 | ]; 63 | 64 | return $config; -------------------------------------------------------------------------------- /tests/app/mail/layouts/html.php: -------------------------------------------------------------------------------- 1 | = $content?> -------------------------------------------------------------------------------- /tests/app/mail/layouts/text.php: -------------------------------------------------------------------------------- 1 | = $content?> -------------------------------------------------------------------------------- /tests/app/mail/simple-html.php: -------------------------------------------------------------------------------- 1 |