Passed
Push — master ( 6a76e0...a126cd )
by Sergey
06:56
created

SlackTarget.php (2 issues)

Labels
Severity

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
 * Slack log target for Yii 2
4
 *
5
 * @see       https://github.com/sergeymakinen/yii2-slack-log
6
 * @copyright Copyright (c) 2016 Sergey Makinen (https://makinen.ru)
7
 * @license   https://github.com/sergeymakinen/yii2-slack-log/blob/master/LICENSE The MIT License
8
 */
9
10
namespace sergeymakinen\log;
11
12
use yii\helpers\Json;
13
use yii\helpers\Url;
14
use yii\helpers\VarDumper;
15
use yii\log\Logger;
16
use yii\log\Target;
17
use yii\web\Request;
18
19
class SlackTarget extends Target
20
{
21
    /**
22
     * Incoming Webhook URL.
23
     *
24
     * @var string
25
     */
26
    public $webhookUrl;
27
28
    /**
29
     * Displayed username.
30
     *
31
     * @var string
32
     */
33
    public $username;
34
35
    /**
36
     * Icon URL.
37
     *
38
     * @var string
39
     */
40
    public $iconUrl;
41
42
    /**
43
     * Icon Emoji.
44
     *
45
     * @var string
46
     */
47
    public $iconEmoji;
48
49
    /**
50
     * Channel or Direct Message.
51
     *
52
     * @var string
53
     */
54
    public $channel;
55
56
    /**
57
     * Colors per a Logger level.
58
     *
59
     * @var array
60
     */
61
    public $colors = [
62
        Logger::LEVEL_ERROR => 'danger',
63
        Logger::LEVEL_WARNING => 'warning'
64
    ];
65
66
    /**
67
     * @inheritDoc
68
     */
69
    public function export()
70
    {
71
        if (!isset($this->webhookUrl)) {
72
            return;
73
        }
74
75
        $payload = [
76
            'parse' => 'none',
77
            'attachments' => array_map([$this, 'formatMessageAttachment'], $this->messages)
78
        ];
79
        if (isset($this->username)) {
80
            $payload['username'] = $this->username;
81
        }
82
        if (isset($this->iconUrl)) {
83
            $payload['icon_url'] = $this->iconUrl;
84
        }
85
        if (isset($this->iconEmoji)) {
86
            $payload['icon_emoji'] = $this->iconEmoji;
87
        }
88
        if (isset($this->channel)) {
89
            $payload['channel'] = $this->channel;
90
        }
91
        if (extension_loaded('curl')) {
92
            $curl = curl_init();
93
            curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
94
            curl_setopt($curl, CURLOPT_POST, true);
95
            curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type: application/json; charset=UTF-8']);
96
            curl_setopt($curl, CURLOPT_POSTFIELDS, Json::encode($payload));
97
            curl_setopt($curl, CURLOPT_URL, $this->webhookUrl);
98
            curl_exec($curl);
99
            curl_close($curl);
100
        } else {
101
            $context = stream_context_create([
102
                'http' => [
103
                    'method' => 'POST',
104
                    'header' => "Content-Type: application/json; charset=UTF-8\r\n",
105
                    'content' => Json::encode($payload)
106
                ]
107
            ]);
108
            @file_get_contents($this->webhookUrl, false, $context);
109
        }
110
    }
111
112
    /**
113
     * Encodes special chars in a message as HTML entities.
114
     *
115
     * @param string $message
116
     *
117
     * @return string
118
     */
119
    protected static function encodeMessage($message)
120
    {
121
        return htmlspecialchars($message, ENT_NOQUOTES, 'UTF-8');
122
    }
123
124
    /**
125
     * Returns a properly formatted message attachment for Slack API.
126
     *
127
     * @param array $message
128
     *
129
     * @return array
130
     */
131
    protected function formatMessageAttachment($message)
132
    {
133
        list($text, $level, $category, $timestamp) = $message;
134
        $attachment = [
135
            'fallback' => static::encodeMessage($this->formatMessage($message)),
136
            'title' => ucwords(Logger::getLevelName($level)),
137
            'fields' => [
138
                [
139
                    'title' => 'Level',
140
                    'value' => Logger::getLevelName($level),
141
                    'short' => true
142
                ],
143
                [
144
                    'title' => 'Category',
145
                    'value' => '`' . static::encodeMessage($category) . '`',
146
                    'short' => true
147
                ]
148
            ],
149
            'footer' => static::class,
150
            'ts' => (integer) round($timestamp),
151
            'mrkdwn_in' => [
152
                'fields',
153
                'text'
154
            ]
155
        ];
156
        if ($this->prefix !== null) {
157
            $attachment['fields'][] = [
158
                'title' => 'Prefix',
159
                'value' => '`' . static::encodeMessage(call_user_func($this->prefix, $message)) . '`',
160
                'short' => true,
161
            ];
162
        }
163
        if (isset(\Yii::$app)) {
164
            if (isset($_SERVER['argv'])) {
165
                $attachment['author_name'] = implode(' ', $_SERVER['argv']);
166
            } elseif (\Yii::$app->getRequest() instanceof Request) {
167
                $attachment['author_name'] = Url::current([], true);
168
                $attachment['author_link'] = $attachment['author_name'];
169
                $attachment['fields'][] = [
170
                    'title' => 'User IP',
171
                    'value' => \Yii::$app->getRequest()->getUserIP(),
172
                    'short' => true,
173
                ];
174
            }
175
            if (\Yii::$app->has('user', true) && !is_null(\Yii::$app->getUser())) {
176
                $user = \Yii::$app->getUser()->getIdentity(false);
1 ignored issue
show
The method getUser does only exist in yii\web\Application, but not in yii\console\Application.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
177
                if (isset($user)) {
178
                    $attachment['fields'][] = [
179
                        'title' => 'User ID',
180
                        'value' => $user->getId(),
181
                        'short' => true,
182
                    ];
183
                }
184
            }
185
            if (\Yii::$app->has('session', true) && !is_null(\Yii::$app->getSession())) {
186
                if (\Yii::$app->getSession()->getIsActive()) {
187
                    $attachment['fields'][] = [
188
                        'title' => 'Session ID',
189
                        'value' => \Yii::$app->getSession()->getId(),
1 ignored issue
show
The method getSession does only exist in yii\web\Application, but not in yii\console\Application.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
190
                        'short' => true,
191
                    ];
192
                }
193
            }
194
        }
195
        if (isset($this->colors[$level])) {
196
            $attachment['color'] = $this->colors[$level];
197
        }
198
        if (!is_string($text)) {
199
            if ($text instanceof \Throwable || $text instanceof \Exception) {
200
                $text = (string) $text;
201
            } else {
202
                $text = VarDumper::export($text);
203
            }
204
        }
205
        $attachment['text'] = "```\n" . static::encodeMessage($text) . "\n```";
206
        $traces = [];
207
        if (isset($message[4])) {
208
            foreach ($message[4] as $trace) {
209
                $traces[] = "in {$trace['file']}:{$trace['line']}";
210
            }
211
        }
212
        if (!empty($traces)) {
213
            $attachment['fields'][] = [
214
                'title' => 'Stack Trace',
215
                'value' => "```\n" . static::encodeMessage(implode("\n", $traces)) . "\n```",
216
                'short' => false,
217
            ];
218
        }
219
        return $attachment;
220
    }
221
}
222