Completed
Push — master ( d83ba1...8a9b13 )
by Sergey
02:13
created

SlackTarget.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace sergeymakinen\log;
4
5
use yii\helpers\Json;
6
use yii\helpers\Url;
7
use yii\helpers\VarDumper;
8
use yii\log\Logger;
9
use yii\log\Target;
10
use yii\web\Request;
11
12
class SlackTarget extends Target
13
{
14
    /**
15
     * Incoming Webhook URL.
16
     *
17
     * @var string
18
     */
19
    public $webhookUrl;
20
21
    /**
22
     * Displayed username.
23
     *
24
     * @var string
25
     */
26
    public $username;
27
28
    /**
29
     * Icon URL.
30
     *
31
     * @var string
32
     */
33
    public $iconUrl;
34
35
    /**
36
     * Icon Emoji.
37
     *
38
     * @var string
39
     */
40
    public $iconEmoji;
41
42
    /**
43
     * Channel or Direct Message.
44
     *
45
     * @var string
46
     */
47
    public $channel;
48
49
    /**
50
     * Colors per a Logger level.
51
     *
52
     * @var array
53
     */
54
    public $colors = [
55
        Logger::LEVEL_ERROR => 'danger',
56
        Logger::LEVEL_WARNING => 'warning'
57
    ];
58
59
    /**
60
     * @inheritDoc
61
     */
62
    public function export()
63
    {
64
        if (!isset($this->webhookUrl)) {
65
            return;
66
        }
67
68
        $payload = [
69
            'parse' => 'none',
70
            'attachments' => array_map([$this, 'formatMessageAttachment'], $this->messages)
71
        ];
72
        if (isset($this->username)) {
73
            $payload['username'] = $this->username;
74
        }
75
        if (isset($this->iconUrl)) {
76
            $payload['icon_url'] = $this->iconUrl;
77
        }
78
        if (isset($this->iconEmoji)) {
79
            $payload['icon_emoji'] = $this->iconEmoji;
80
        }
81
        if (isset($this->channel)) {
82
            $payload['channel'] = $this->channel;
83
        }
84
        if (extension_loaded('curl')) {
85
            $curl = curl_init();
86
            curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
87
            curl_setopt($curl, CURLOPT_POST, true);
88
            curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type: application/json; charset=UTF-8']);
89
            curl_setopt($curl, CURLOPT_POSTFIELDS, Json::encode($payload));
90
            curl_setopt($curl, CURLOPT_URL, $this->webhookUrl);
91
            curl_exec($curl);
92
            curl_close($curl);
93
        } else {
94
            $context = stream_context_create([
95
                'http' => [
96
                    'method' => 'POST',
97
                    'header' => "Content-Type: application/json; charset=UTF-8\r\n",
98
                    'content' => Json::encode($payload)
99
                ]
100
            ]);
101
            @file_get_contents($this->webhookUrl, false, $context);
102
        }
103
    }
104
105
    /**
106
     * Encodes special chars in a message as HTML entities.
107
     *
108
     * @param string $message
109
     *
110
     * @return string
111
     */
112
    protected static function encodeMessage($message)
113
    {
114
        return htmlspecialchars($message, ENT_NOQUOTES, 'UTF-8');
115
    }
116
117
    /**
118
     * Returns a properly formatted message attachment for Slack API.
119
     *
120
     * @param array $message
121
     *
122
     * @return array
123
     */
124
    protected function formatMessageAttachment($message)
0 ignored issues
show
formatMessageAttachment uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
125
    {
126
        list($text, $level, $category, $timestamp) = $message;
127
        $attachment = [
128
            'fallback' => static::encodeMessage($this->formatMessage($message)),
129
            'title' => ucwords(Logger::getLevelName($level)),
130
            'fields' => [
131
                [
132
                    'title' => 'Level',
133
                    'value' => Logger::getLevelName($level),
134
                    'short' => true
135
                ],
136
                [
137
                    'title' => 'Category',
138
                    'value' => '`' . static::encodeMessage($category) . '`',
139
                    'short' => true
140
                ]
141
            ],
142
            'footer' => static::class,
143
            'ts' => (integer) round($timestamp),
144
            'mrkdwn_in' => [
145
                'fields',
146
                'text'
147
            ]
148
        ];
149
        if ($this->prefix !== null) {
150
            $attachment['fields'][] = [
151
                'title' => 'Prefix',
152
                'value' => '`' . static::encodeMessage(call_user_func($this->prefix, $message)) . '`',
153
                'short' => true,
154
            ];
155
        }
156
        if (isset(\Yii::$app)) {
157
            if (isset($_SERVER['argv'])) {
158
                $attachment['author_name'] = implode(' ', $_SERVER['argv']);
159
            } elseif (\Yii::$app->request instanceof Request) {
160
                $attachment['author_name'] = Url::current([], true);
1 ignored issue
show
The method current() does not seem to exist on object<yii\helpers\Url>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
161
                $attachment['author_link'] = $attachment['author_name'];
162
                $attachment['fields'][] = [
163
                    'title' => 'User IP',
164
                    'value' => \Yii::$app->request->userIP,
165
                    'short' => true,
166
                ];
167
            }
168
            if (\Yii::$app->has('user', true) && isset(\Yii::$app->user)) {
169
                $user = \Yii::$app->user->getIdentity(false);
170
                if (isset($user)) {
171
                    $attachment['fields'][] = [
172
                        'title' => 'User ID',
173
                        'value' => $user->getId(),
174
                        'short' => true,
175
                    ];
176
                }
177
            }
178
            if (\Yii::$app->has('session', true) && isset(\Yii::$app->session)) {
179
                if (\Yii::$app->session->isActive) {
180
                    $attachment['fields'][] = [
181
                        'title' => 'Session ID',
182
                        'value' => \Yii::$app->session->id,
183
                        'short' => true,
184
                    ];
185
                }
186
            }
187
        }
188
        if (isset($this->colors[$level])) {
189
            $attachment['color'] = $this->colors[$level];
190
        }
191
        if (!is_string($text)) {
192
            if ($text instanceof \Throwable || $text instanceof \Exception) {
193
                $text = (string) $text;
194
            } else {
195
                $text = VarDumper::export($text);
1 ignored issue
show
The method export() does not seem to exist on object<yii\helpers\VarDumper>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
196
            }
197
        }
198
        $attachment['text'] = "```\n" . static::encodeMessage($text) . "\n```";
199
        $traces = [];
200
        if (isset($message[4])) {
201
            foreach ($message[4] as $trace) {
202
                $traces[] = "in {$trace['file']}:{$trace['line']}";
203
            }
204
        }
205
        if (!empty($traces)) {
206
            $attachment['fields'][] = [
207
                'title' => 'Stack Trace',
208
                'value' => "```\n" . static::encodeMessage(implode("\n", $traces)) . "\n```",
209
                'short' => false,
210
            ];
211
        }
212
        return $attachment;
213
    }
214
}
215