Skip to content

Commit

Permalink
Merge pull request #23 from Julielesss/telegram
Browse files Browse the repository at this point in the history
Telegram
  • Loading branch information
tuyakhov committed Nov 21, 2019
2 parents 520379f + 3bc1bf2 commit c91c519
Show file tree
Hide file tree
Showing 10 changed files with 307 additions and 11 deletions.
33 changes: 30 additions & 3 deletions README.md
Expand Up @@ -71,8 +71,8 @@ $notification = new InvoicePaid($invoice);

Yii::$app->notifier->send($recipient, $nofitication);
```
Each notification class should implement NotificationInterface and contains a via method and a variable number of message building methods (such as `exportForMail`) that convert the notification to a message optimized for that particular channel.
Example of notification that covers the case when an invoice has been paid:
Each notification class should implement `NotificationInterface` and contain a `viaChannels` method and a variable number of message building methods (such as `exportForMail`) that convert the notification to a message optimized for that particular channel.
Example of a notification that covers the case when an invoice has been paid:

```php
use tuyakhov\notifications\NotificationInterface;
Expand All @@ -88,7 +88,10 @@ class InvoicePaid implements NotificationInterface
{
$this->invoice = $invoice;
}


/**
* Prepares notification for 'mail' channel
*/
public function exportForMail() {
return Yii::createObject([
'class' => '\tuyakhov\notifications\messages\MailMessage',
Expand All @@ -100,13 +103,31 @@ class InvoicePaid implements NotificationInterface
])
}

/**
* Prepares notification for 'sms' channel
*/
public function exportForSms()
{
return \Yii::createObject([
'class' => '\tuyakhov\notifications\messages\SmsMessage',
'text' => "Your invoice #{$this->invoice->id} has been paid"
]);
}

/**
* Prepares notification for 'database' channel
*/
public function exportForDatabase()
{
return \Yii::createObject([
'class' => '\tuyakhov\notifications\messages\DatabaseChannel',
'subject' => "Invoice has been paid",
'body' => "Your invoice #{$this->invoice->id} has been paid",
'data' => [
'actionUrl' => ['href' => '/invoice/123/view', 'label' => 'View Details']
]
]);
}
}
```

Expand Down Expand Up @@ -169,6 +190,12 @@ foreach($model->unreadNotifications as $notification) {
echo $notification->subject;
}
```
You can access custom JSON data that describes the notification and was added using `DatabaseMessage`:
```php
/** @var $notificatiion tuyakhov\notifications\models\Notificatios */
$actionUrl = $notification->data('actionUrl'); // ['href' => '/invoice/123/pay', 'label' => 'Pay Invoice']
```

**Marking Notifications As Read**
Typically, you will want to mark a notification as "read" when a user views it. The `ReadableBehavior` in `Notification` model provides a `markAsRead` method, which updates the read_at column on the notification's database record:
```php
Expand Down
2 changes: 2 additions & 0 deletions src/channels/ActiveRecordChannel.php
Expand Up @@ -12,6 +12,7 @@
use yii\base\Component;
use yii\base\InvalidConfigException;
use yii\db\BaseActiveRecord;
use yii\helpers\Json;

class ActiveRecordChannel extends Component implements ChannelInterface
{
Expand All @@ -37,6 +38,7 @@ public function send(NotifiableInterface $recipient, NotificationInterface $noti
'body' => $message->body,
'notifiable_type' => $notifiableType,
'notifiable_id' => $notifiableId,
'data' => Json::encode($message->data),
];

if ($model->load($data, '')) {
Expand Down
111 changes: 111 additions & 0 deletions src/channels/TelegramChannel.php
@@ -0,0 +1,111 @@
<?php

namespace tuyakhov\notifications\channels;

use tuyakhov\notifications\NotifiableInterface;
use tuyakhov\notifications\NotificationInterface;
use tuyakhov\notifications\messages\TelegramMessage;
use yii\base\Component;
use yii\di\Instance;
use yii\helpers\ArrayHelper;
use yii\httpclient\Client;

class TelegramChannel extends Component implements ChannelInterface
{
/**
* @var Client|array|string
*/
public $httpClient;

/**
* @var string
*/
public $apiUrl = "https://api.telegram.org/";

/**
* @var string
*/
public $bot_id;

/**
* @var string
*/
public $botToken;

/**
* @var string
*/
public $parseMode = null;

const PARSE_MODE_HTML = "HTML";

const PARSE_MODE_MARKDOWN = "Markdown";

/**
* @var bool
* If you need to change silentMode, you can use this code before calling telegram channel
*
* \Yii::$container->set('\app\additional\notification\TelegramChannel', [
* 'silentMode' => true,
* ]);
*/
public $silentMode = false;

/**
* @throws \yii\base\InvalidConfigException
*/
public function init()
{
parent::init();

if(!isset($this->bot_id) || !isset($this->botToken)){
throw new InvalidConfigException("Bot id or bot token is undefined");
}

if (!isset($this->httpClient)) {
$this->httpClient = [
'class' => Client::className(),
'baseUrl' => $this->apiUrl
];
}
$this->httpClient = Instance::ensure($this->httpClient, Client::className());
}


/**
* @param NotifiableInterface $recipient
* @param NotificationInterface $notification
* @return mixed
* @throws \Exception
*/
public function send(NotifiableInterface $recipient, NotificationInterface $notification)
{
/** @var TelegramMessage $message */
$message = $notification->exportFor('telegram');
$text = "*{$message->subject}*\n{$message->body}";
$chat_id = $recipient->routeNotificationFor('telegram');
if(!$chat_id){
throw new \Exception("User doesn't have telegram_id");
}

$data = [
"chat_id" => $chat_id,
"text" => $text,
'disable_notification' => $this->silentMode
];
if($this->parseMode != null){
$data["parse_mode"] = $this->parseMode;
}

return $this->httpClient->createRequest()
->setMethod('post')
->setUrl($this->createUrl())
->setData($data)
->send();
}

private function createUrl()
{
return "bot" . $this->bot_id . ":" . $this->botToken . "/sendmessage";
}
}
10 changes: 10 additions & 0 deletions src/messages/DatabaseMessage.php
Expand Up @@ -8,4 +8,14 @@

class DatabaseMessage extends AbstractMessage
{
/**
* @var array additional data
* Example:
* [
* 'data' => [
* 'actionUrl' => ['href' => '/invoice/123/pay', 'label' => 'Pay Invoice']
* ]
* ]
*/
public $data = [];
}
7 changes: 7 additions & 0 deletions src/messages/TelegramMessage.php
@@ -0,0 +1,7 @@
<?php

namespace tuyakhov\notifications\messages;

class TelegramMessage extends AbstractMessage
{
}
23 changes: 23 additions & 0 deletions src/migrations/m181112_171335_add_data_column.php
@@ -0,0 +1,23 @@
<?php
/**
* @copyright Anton Tuyakhov <atuyakhov@gmail.com>
*/

namespace tuyakhov\notifications\migrations;

use yii\db\Migration;

class m181112_171335_add_data_column extends Migration
{

public function up()
{
$this->addColumn('notification', 'data', $this->text());
}

public function down()
{
$this->dropColumn('notification', 'data');
}

}
39 changes: 31 additions & 8 deletions src/models/Notification.php
Expand Up @@ -5,18 +5,23 @@


use tuyakhov\notifications\behaviors\ReadableBehavior;
use tuyakhov\notifications\messages\DatabaseMessage;
use yii\behaviors\TimestampBehavior;
use yii\db\ActiveRecord;
use yii\db\Expression;
use yii\helpers\ArrayHelper;
use yii\helpers\Json;

/**
* Database notification model
* @property $level string
* @property $subject string
* @property $notifiable_type string
* @property $notifiable_id int
* @property $body string
* @property $read_at string
* @property string $level
* @property string $subject
* @property string $notifiable_type
* @property int $notifiable_id
* @property string $body
* @property string $data
* @property DatabaseMessage $message
* @property string $read_at
* @property $notifiable
* @method void markAsRead()
* @method void markAsUnread()
Expand All @@ -32,7 +37,7 @@ class Notification extends ActiveRecord
public function rules()
{
return [
[['level', 'notifiable_type', 'subject', 'body'], 'string'],
[['level', 'notifiable_type', 'subject', 'body', 'data'], 'string'],
['notifiable_id', 'integer'],
];
}
Expand All @@ -45,14 +50,32 @@ public function behaviors()
return [
[
'class' => TimestampBehavior::className(),
'value' => new Expression('NOW()'),
'value' => new Expression('CURRENT_TIMESTAMP'),
],
ReadableBehavior::className()
];
}

/**
* @return \yii\db\ActiveQuery
*/
public function getNotifiable()
{
return $this->hasOne($this->notifiable_type, ['id' => 'notifiable_id']);
}


/**
* @param null $key
* @return mixed
*/
public function data($key = null)
{
$data = Json::decode($this->data);
if ($key === null) {
return $data;
}
return ArrayHelper::getValue($data, $key);
}

}
53 changes: 53 additions & 0 deletions tests/ActiveRecordChannelTest.php
Expand Up @@ -7,6 +7,7 @@


use tuyakhov\notifications\messages\DatabaseMessage;
use tuyakhov\notifications\models\Notification;

class ActiveRecordChannelTest extends TestCase
{
Expand Down Expand Up @@ -38,6 +39,7 @@ public function testSend()
'body' => $message->body,
'notifiable_type' => 'yii\base\DynamicModel',
'notifiable_id' => 123,
'data' => '[]'
], '');
$notificationModel->method('insert')->willReturn(true);
$notificationModel->expects($this->once())
Expand All @@ -54,4 +56,55 @@ public function testSend()
$channel->send($recipient, $notification);

}

public function testWithDB()
{
$recipient = $this->createMock('tuyakhov\notifications\NotifiableInterface');
$recipient->expects($this->atLeastOnce())
->method('routeNotificationFor')
->with('database')
->willReturn(['yii\base\DynamicModel', 123]);

$message = \Yii::createObject([
'class' => DatabaseMessage::className(),
'level' => 'debug',
'subject' => 'It',
'body' => 'Works',
]);

$channel = \Yii::createObject([
'class' => 'tuyakhov\notifications\channels\ActiveRecordChannel',
]);

$notification = $this->createMock('tuyakhov\notifications\NotificationInterface');
$notification->expects($this->once())
->method('exportFor')
->with('database')
->willReturn($message);
$channel->send($recipient, $notification);

$this->assertEquals(1, Notification::find()->count());

$body = 'Does not work';
$actionUrl = ['href' => '/invoice/123/pay', 'label' => 'Pay Invoice'];
$differentMessage = \Yii::createObject([
'class' => DatabaseMessage::className(),
'level' => 'error',
'subject' => 'It',
'body' => $body,
'data' => ['actionUrl' => $actionUrl]
]);
$anotherNotification = $this->createMock('tuyakhov\notifications\NotificationInterface');
$anotherNotification->expects($this->once())
->method('exportFor')
->with('database')
->willReturn($differentMessage);
$channel->send($recipient, $anotherNotification);
$this->assertEquals(2, Notification::find()->count());
/** @var $savedError Notification */
$this->assertNotEmpty($savedError = Notification::find()->where(['level' => 'error'])->one());
$this->assertEquals($body, $savedError->body);
$this->assertEquals($actionUrl, $savedError->data('actionUrl'));
$this->assertNull($savedError->data('invalid'));
}
}

0 comments on commit c91c519

Please sign in to comment.