Passed
Pull Request — master (#11)
by Simon
01:34
created

SlackInvite   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 287
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 287
rs 10
c 0
b 0
f 0
wmc 29

13 Methods

Rating   Name   Duplication   Size   Complexity  
A onBeforeWrite() 0 7 2
A getBetterButtonsActions() 0 16 2
A getCMSFields() 0 20 2
A canCreate() 0 3 1
A canEdit() 0 3 1
A resendInvite() 0 5 1
A canDelete() 0 3 1
B providePermissions() 0 25 1
A handleResult() 0 12 4
A canView() 0 3 1
B inviteUser() 0 27 6
B doRequestEmail() 0 26 4
A deleteDuplicates() 0 10 3
1
<?php
2
3
namespace Firesphere\StripeSlack\Model;
4
5
use GuzzleHttp\Client;
6
use SilverStripe\Admin\ModelAdmin;
7
use SilverStripe\Control\Controller;
8
use SilverStripe\Core\Convert;
9
use SilverStripe\Forms\LiteralField;
10
use SilverStripe\Forms\ReadonlyField;
11
use SilverStripe\ORM\DataList;
12
use SilverStripe\ORM\DataObject;
13
use SilverStripe\ORM\ValidationException;
14
use SilverStripe\Security\Permission;
15
use SilverStripe\Security\PermissionProvider;
16
use SilverStripe\SiteConfig\SiteConfig;
17
18
/**
19
 * Class SlackInvite
20
 *
21
 * @property string $Name
22
 * @property string $Email
23
 * @property boolean $Invited
24
 * @property string $Message
25
 */
26
class SlackInvite extends DataObject implements PermissionProvider
27
{
28
    private static $db = [
0 ignored issues
show
introduced by
The private property $db is not used, and could be removed.
Loading history...
29
        'Name'    => 'Varchar(255)',
30
        'Email'   => 'Varchar(255)',
31
        'Invited' => 'Boolean(false)',
32
        'Message' => 'Varchar(255)',
33
    ];
34
35
    private static $summary_fields = [
0 ignored issues
show
introduced by
The private property $summary_fields is not used, and could be removed.
Loading history...
36
        'Created',
37
        'Name',
38
        'Email',
39
        'Invited.Nice',
40
        'Message'
41
    ];
42
43
    private static $field_labels = [
0 ignored issues
show
introduced by
The private property $field_labels is not used, and could be removed.
Loading history...
44
        'Created'      => 'Invite requested on',
45
        'Name'         => 'Name',
46
        'Email'        => 'Email address',
47
        'Invited.Nice' => 'Invite successful'
48
    ];
49
50
    private static $table_name = 'SlackInvite';
0 ignored issues
show
introduced by
The private property $table_name is not used, and could be removed.
Loading history...
51
52
    private static $messages = [
53
        'not_authed'        => 'No valid Slack Token provided, please check your settings',
54
        'already_invited'   => 'User has already received an email invitation',
55
        'already_in_team'   => 'User is already part of the team',
56
        'channel_not_found' => 'Provided channel ID does not match an existing channel in your workspace',
57
        'sent_recently'     => 'When using resend=true, the email has been sent recently already',
58
        'user_disabled'     => 'User account has been deactivated',
59
        'missing_scope'     => 'Using an access_token not authorized for "client" scope',
60
        'invalid_email'     => 'Invalid email address (e.g. "qwe"). Note that Slack does not recognize some email addresses even though they are technically valid. This is a known issue.',
61
        'not_allowed'       => 'When SSO is enabeld this method can not be used to invite new users except guests. The SCIM API needs to be used instead to invite new users. '
62
    ];
63
64
    private static $better_buttons_actions = [
0 ignored issues
show
introduced by
The private property $better_buttons_actions is not used, and could be removed.
Loading history...
65
        'resendInvite'
66
    ];
67
68
    public function getCMSFields()
69
    {
70
        $fields = parent::getCMSFields();
71
        $fields->removeByName(['Invited']);
72
        if (!$this->Invited) {
73
            $fields->addFieldToTab(
74
                'Root.Main',
75
                LiteralField::create(
76
                    'resend',
0 ignored issues
show
Bug introduced by
'resend' of type string is incompatible with the type array expected by parameter $args of SilverStripe\View\ViewableData::create(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

76
                    /** @scrutinizer ignore-type */ 'resend',
Loading history...
77
                    _t('SlackInvite.Resend', '<p>To resend an invite, click the resend button in the overview</p>')
78
                )
79
            );
80
        }
81
        $fields->addFieldToTab('Root.Main', ReadonlyField::create('Message', 'API Message'));
82
        $fields->addFieldToTab(
83
            'Root.Main',
84
            ReadonlyField::create('InvitedStatus', 'Invite successful', $this->dbObject('Invited')->Nice())
85
        );
86
87
        return $fields;
88
    }
89
90
    /**
91
     * If BetterButtons is installed, add a button to resend or retry
92
     * @return mixed
93
     */
94
    public function getBetterButtonsActions()
95
    {
96
        $fields = parent::getBetterButtonsActions();
0 ignored issues
show
introduced by
The method getBetterButtonsActions() does not exist on SilverStripe\ORM\DataObject. Are you sure you never get this type here, but always one of the subclasses? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

96
        /** @scrutinizer ignore-call */ 
97
        $fields = parent::getBetterButtonsActions();
Loading history...
97
        if ($this->Invited) {
98
            $fields->push(
99
                BetterButtonCustomAction::create('resendInvite', 'Resend')
0 ignored issues
show
Bug introduced by
The type Firesphere\StripeSlack\M...etterButtonCustomAction was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
100
                    ->setRedirectType(BetterButtonCustomAction::REFRESH)
101
            );
102
        } else {
103
            $fields->push(
104
                BetterButtonCustomAction::create('resendInvite', 'Retry')
105
                    ->setRedirectType(BetterButtonCustomAction::REFRESH)
106
            );
107
        }
108
109
        return $fields;
110
    }
111
112
    /**
113
     * If the user isn't invited yet, send out the invite
114
     * @throws ValidationException
115
     */
116
    public function onBeforeWrite()
117
    {
118
        parent::onBeforeWrite();
119
        // Only attempt to send when there is no ID
120
        // This prevents retrying from the CMS from ending up in an endless loop
121
        if (!$this->ID) {
122
            $this->inviteUser();
123
        }
124
    }
125
126
127
    /**
128
     * This method is public, so it can be addressed from the CMS.
129
     *
130
     * @param bool $resend
131
     * @return bool|SlackInvite|string
132
     * @throws ValidationException
133
     */
134
    public function inviteUser($resend = false)
135
    {
136
        /** @var SiteConfig $config */
137
        $config = SiteConfig::current_site_config();
138
        // Break if there is a configuration error
139
        if (!$config->SlackURL || !$config->SlackToken || !$config->SlackChannel) {
140
            $this->Invited = false;
141
        } else {
142
            $params = [
143
                'form_params' => [
144
                    'token'      => $config->SlackToken,
145
                    'type'       => 'post',
146
                    'email'      => $this->Email,
147
                    'set_active' => true,
148
                    'channel'    => $config->SlackChannel,
149
                    'scope'      => 'identify,read,post,client',
150
                ]
151
            ];
152
153
            if ($resend) {
154
                $params['form_params']['resend'] = true;
155
            }
156
            if ($this->Name) {
157
                $params['form_params']['first_name'] = $this->Name;
158
            }
159
160
            return $this->doRequestEmail($config, $params);
161
        }
162
    }
163
164
165
    /**
166
     * @param SiteConfig $config
167
     * @param array $params
168
     * @return bool|$this|string
169
     * @throws ValidationException
170
     */
171
    private function doRequestEmail($config, $params)
172
    {
173
        $now = time();
174
        $service = new Client(['base_uri' => $config->SlackURL]);
175
176
        $response = $service->request('POST', '/api/users.admin.invite?t=' . $now, $params);
177
        $result = Convert::json2array($response->getBody());
178
179
        $this->handleResult($result);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type boolean; however, parameter $result of Firesphere\StripeSlack\M...kInvite::handleResult() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

179
        $this->handleResult(/** @scrutinizer ignore-type */ $result);
Loading history...
180
181
        if ($this->Invited) {
182
            $this->deleteDuplicates();
183
        }
184
185
        $isModelAdmin = Controller::curr() instanceof ModelAdmin;
186
        /*
187
         * Only write here if we're in the CMS, don't write if the invite failed
188
         * As that will cause a possible infinite loop
189
         */
190
        if ((bool)$this->Invited === true && $isModelAdmin) {
191
            $this->write();
192
193
            return true;
194
        }
195
196
        return false;
197
    }
198
199
    /**
200
     * @param array $result
201
     */
202
    public function handleResult($result)
203
    {
204
        if (isset($result['error'])) {
205
            $this->Message = static::$messages[$result['error']];
0 ignored issues
show
Bug introduced by
Since $messages is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $messages to at least protected.
Loading history...
206
207
            if ($result['error'] === 'already_invited' || $result['error'] === 'already_in_team') {
208
                $this->Message .= '; Invite successful';
209
                $this->Invited = true;
210
            }
211
        } else {
212
            $this->Message = 'Invite successful';
213
            $this->Invited = (bool)$result['ok'];
214
        }
215
    }
216
217
218
    /**
219
     * Remove duplicates after a successful invite
220
     */
221
    public function deleteDuplicates()
222
    {
223
        /** @var DataList|SlackInvite[] $thisDuplicates */
224
        $thisDuplicates = static::get()
225
            ->filter(['Email' => $this->Email, 'Invited' => false])
226
            ->exclude(['ID' => $this->ID]);
227
228
        if ($this->Invited === true && $thisDuplicates->count() > 0) {
229
            // This user tried multiple times, now that Invited is true, let's delete the others
230
            $thisDuplicates->removeAll();
231
        }
232
    }
233
234
    /**
235
     * Re-send this invite
236
     * @throws ValidationException
237
     */
238
    public function resendInvite()
239
    {
240
        // Resend the invite. If the user has been invited before it should re-send
241
        // The instance is needed so we know if we should write inside the `inviteUser` method
242
        return $this->inviteUser((bool)$this->Invited);
243
    }
244
245
    /**
246
     * Permissions
247
     *
248
     * @return array
249
     */
250
    public function providePermissions()
251
    {
252
        return [
253
            'EDIT_SLACKINVITE'   => [
254
                'name'     => _t('SlackInvite.PERMISSION_EDIT_DESCRIPTION', 'Edit Slack invites'),
255
                'category' => _t('Permissions.SLACK_SLACKINVITE', 'Slack permissions'),
256
                'help'     => _t(
257
                    'SlackInvite.PERMISSION_EDIT_HELP',
258
                    'Permission required to edit existing Slack invites.'
259
                )
260
            ],
261
            'DELETE_SLACKINVITE' => [
262
                'name'     => _t('SlackInvite.PERMISSION_DELETE_DESCRIPTION', 'Delete Slack invites'),
263
                'category' => _t('Permissions.SLACK_SLACKINVITE', 'Slack permissions'),
264
                'help'     => _t(
265
                    'SlackInvite.PERMISSION_DELETE_HELP',
266
                    'Permission required to delete existing Slack invites.'
267
                )
268
            ],
269
            'VIEW_SLACKINVITE'   => [
270
                'name'     => _t('SlackInvite.PERMISSION_VIEW_DESCRIPTION', 'View Slack invites'),
271
                'category' => _t('Permissions.SLACK_SLACKINVITE', 'Slack permissions'),
272
                'help'     => _t(
273
                    'SlackInvite.PERMISSION_VIEW_HELP',
274
                    'Permission required to view existing Slack invites.'
275
                )
276
            ],
277
        ];
278
    }
279
280
281
    /**
282
     * Don't create them from the CMS
283
     * {@inheritdoc}
284
     */
285
    public function canCreate($member = null, $context = [])
286
    {
287
        return false;
288
    }
289
290
    /**
291
     * Edit is useful for if someone mis-typed it's email address
292
     * {@inheritdoc}
293
     */
294
    public function canEdit($member = null)
295
    {
296
        return Permission::checkMember($member, array('EDIT_SLACKINVITE', 'CMS_ACCESS_SlackInviteAdmin'));
297
    }
298
299
    /**
300
     * {@inheritdoc}
301
     */
302
    public function canDelete($member = null)
303
    {
304
        return Permission::checkMember($member, array('DELETE_SLACKINVITE', 'CMS_ACCESS_SlackInviteAdmin'));
305
    }
306
307
    /**
308
     * {@inheritdoc}
309
     */
310
    public function canView($member = null)
311
    {
312
        return Permission::checkMember($member, array('VIEW_SLACKINVITE', 'CMS_ACCESS_SlackInviteAdmin'));
313
    }
314
}
315