Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Telegram #23

Merged
merged 5 commits into from
Nov 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 30 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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'));
}
}
Loading