Passed
Push — master ( a04c07...bfd5dc )
by Tõnis
03:46
created

Respondent::validateMultiplePhoneNumbers()   D

Complexity

Conditions 9
Paths 22

Size

Total Lines 32
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 21
c 0
b 0
f 0
nc 22
nop 1
dl 0
loc 32
rs 4.909
1
<?php
2
3
namespace andmemasin\surveybasemodels;
4
5
use andmemasin\myabstract\MyActiveRecord;
6
use PascalDeVink\ShortUuid\ShortUuid;
7
use Ramsey\Uuid\Uuid;
8
use yii;
9
10
/**
11
 * This is the model class for a generic Respondent.
12
 *
13
 * @property int $respondent_id
14
 * @property int $survey_id
15
 * @property string $token
16
 * @property Survey $survey
17
 * @property string $email_address
18
 * @property string $alternative_email_addresses Inserted as CSV, stored as JSON
19
 * @property string $phone_number
20
 * @property string $alternative_phone_numbers Inserted as CSV, stored as JSON
21
 *
22
 * @property boolean $isRejected
23
 * @property string $shortToken If the token is uuid, then short-uuid will be returned
24
 */
25
class Respondent extends MyActiveRecord
26
{
27
    const MAX_ALTERNATIVE_CONTACTS = 20;
28
    /** @var bool $checkDSNForEmails whether email validation also used DSN records to check if domain exists */
29
    public static $checkDSNForEmails = true;
30
31
    /**
32
     * @var array $surveyIdentifyingColumns names of the columns that identify a respondent as unique inside a survey
33
     */
34
    public static $surveyIdentifyingColumns = ['phone_number','email_address'];
35
36
    public function init()
37
    {
38
        parent::init();
39
    }
40
41
42
    /**
43
     * {@inheritdoc}
44
     */
45
    public function rules()
46
    {
47
        return array_merge([
48
            [['survey_id','token'], 'required'],
49
            [['survey_id'], 'integer'],
50
            [['email_address'], 'validateEmail'],
51
            // email addresses always lowercase
52
            [['email_address','email_address'],'trim'],
53
            ['email_address','filter', 'filter' => 'strtolower'],
54
            [['alternative_email_addresses'], 'string'],
55
            [['alternative_email_addresses'], 'validateMultipleEmails'],
56
            [['phone_number','email_address'],'trim'],
57
            ['phone_number','filter', 'filter' => 'strtolower'],
58
            [['phone_number'],'string'],
59
            [['phone_number'],'validatePhoneNumber'],
60
            [['alternative_phone_numbers'], 'string'],
61
            [['alternative_phone_numbers'],'validateMultiplePhoneNumbers'],
62
            [['token'], 'unique'],
63
        ], parent::rules());
64
    }
65
66
67
    public function validateEmail($attribute,$address = null){
68
        if(!$address or empty($address)){
69
            $address = $this->email_address;
70
            $isSameAsMainAddress = false;
71
        }else{
72
            $isSameAsMainAddress = ($attribute=='email_address' ? false : $address == $this->email_address);
73
        }
74
75
        $validator = new yii\validators\EmailValidator();
76
        $validator->checkDNS = static::$checkDSNForEmails;
77
        $isValidFormat = $validator->validate($address);
78
79
        $isDuplicate = $this->isEmailSurveyDuplicate($address);
80
81
        if($isValidFormat && !$isSameAsMainAddress && !$isDuplicate){
82
            return true;
83
        }else{
84
            $reason = '';
85
            if(!$isValidFormat){
86
                $reason = Yii::t('app','Invalid email format');
87
            } else if($isSameAsMainAddress){
88
                $reason = Yii::t('app',$attribute. ' duplicates main address');
89
            } else if($isDuplicate) {
90
                $reason = Yii::t('app','Duplicates some other address');
91
            }
92
93
            $this->addError($attribute,
94
                Yii::t('app',
95
                    'Invalid email address "{0}"',[$address]
96
                ).' '.Yii::t('app','Reason: {0}',[$reason])
97
            );
98
        }
99
        return false;
100
    }
101
102
103
    public function validateMultipleEmails($attribute){
104
        $addresses = yii\helpers\Json::decode($this->alternative_email_addresses);
105
        if($this->alternative_email_addresses && !empty($addresses)){
106
            $cleanAddresses = [];
107
            if(!empty($addresses)){
108
                $i=0;
109
                foreach ($addresses as $key => $address){
110
                    if($address <> ""){
111
                        // check the alternative numbers of that model for duplicates
112
                        $checkItems = $addresses;
113
                        unset($checkItems[$key]);
114
                        if(in_array($address,$checkItems)){
115
                            $this->addError($attribute,Yii::t('app','Duplicate email in alternative email addresses'));
116
                        }
117
118
                        $i++;
119
                        if($i>=static::MAX_ALTERNATIVE_CONTACTS){
120
                            $this->addError($attribute,Yii::t('app','Maximum alternative addresses limit ({0}) reached for {1}',[static::MAX_ALTERNATIVE_CONTACTS,$this->email_address]));
121
                        }
122
                        $address = strtolower(trim($address));
123
                        if($this->validateEmail($attribute,$address)){
124
                            $cleanAddresses[]=$address;
125
                        }
126
                    }
127
128
                }
129
                if(!empty($cleanAddresses)){
130
                    $this->alternative_email_addresses = yii\helpers\Json::encode($cleanAddresses);
131
                } else {
132
                    $this->alternative_email_addresses = null;
133
                }
134
            }
135
        }
136
    }
137
138
139
    public function validatePhoneNumber($attribute, $phone_number = null){
140
        if(!$phone_number or empty($phone_number)){
141
            $phone_number = $this->phone_number;
142
            $isSameAsMain = false;
143
        }else{
144
            $isSameAsMain = ($attribute=='phone_number' ? false : $phone_number == $this->phone_number);
145
        }
146
        // TODO
147
        $isValidFormat = true;
148
149
        $isDuplicate = $this->isPhoneSurveyDuplicate($phone_number);
150
151
        if($isValidFormat && !$isSameAsMain && !$isDuplicate){
152
            return true;
153
        }else{
154
            $reason = '';
155
            if(!$isValidFormat){
156
                $reason = Yii::t('app','Invalid phone number format');
157
            } else if($isSameAsMain){
158
                $reason = Yii::t('app',$attribute. ' duplicates main phone number');
159
            } else if($isDuplicate) {
160
                $reason = Yii::t('app','Duplicate phone number');
161
            }
162
163
            $this->addError($attribute,
164
                Yii::t('app',
165
                    'Invalid phone number "{0}"',[$phone_number]
166
                ).' '.Yii::t('app','Reason: {0}',[$reason])
167
            );
168
        }
169
        return false;
170
    }
171
172
    public function validateMultiplePhoneNumbers($attribute){
173
        if($this->alternative_phone_numbers){
174
            $cleanItems = [];
175
            $items = yii\helpers\Json::decode($this->alternative_phone_numbers);
176
177
            if(!empty($items)){
178
                $i=0;
179
                foreach ($items as $key=> $item){
180
                    $item = strtolower(trim($item));
181
                    if($item <> ''){
182
                        $i++;
183
                        // check the alternative numbers of that model for duplicates
184
                        $checkItems = $items;
185
                        unset($checkItems[$key]);
186
                        if(in_array($item,$checkItems)){
187
                            $this->addError($attribute,Yii::t('app','Duplicate number in alternative phone numbers'));
188
                        }
189
190
191
                        if($i>=static::MAX_ALTERNATIVE_CONTACTS){
192
                            $this->addError($attribute,Yii::t('app','Maximum alternative phone numbers limit ({0}) reached for {1}',[static::MAX_ALTERNATIVE_CONTACTS,$this->phone_number]));
193
                        }
194
                        if($this->validatePhoneNumber($attribute,$item)){
195
                            $cleanItems[]=$item;
196
                        }
197
198
                    }
199
                }
200
                if(!empty($cleanItems)){
201
                    $this->alternative_phone_numbers = yii\helpers\Json::encode($cleanItems);
202
                } else {
203
                    $this->alternative_phone_numbers = null;
204
                }
205
            }
206
        }
207
    }
208
209
210
211
    /**
212
     * @param string $phone_number Phone number to check duplicates for
213
     * @return bool
214
     */
215
    public function isPhoneSurveyDuplicate($phone_number){
216
        $query = static::find();
217
        // check only this survey
218
        $query->andWhere(['survey_id'=>$this->survey_id]);
219
220
        if($this->respondent_id){
221
            // not itself
222
            $query->andWhere(['!=','respondent_id',$this->respondent_id]);
223
        }
224
225
        $condition = ['or',
226
            '`phone_number`=:phone_number',
227
            '`alternative_phone_numbers` LIKE :phone_number2',
228
        ];
229
        $query->andWhere($condition,[':phone_number'=>$phone_number,':phone_number2'=>'%\"'.$phone_number.'\"%']);
230
        if($query->count() > 0){
231
            return true;
232
        }
233
        return false;
234
    }
235
236
237
    /**
238
     * @param string $email_address Email address to check duplicates for
239
     * @return bool
240
     */
241
    public function isEmailSurveyDuplicate($email_address){
242
        $query = static::find();
243
        // check only this survey
244
        $query->andWhere(['survey_id'=>$this->survey_id]);
245
        // not itself
246
        if($this->respondent_id){
247
            // not itself
248
            $query->andWhere(['!=','respondent_id',$this->respondent_id]);
249
        }
250
251
        $email_condition = ['or',
252
            '`email_address`=:email_address',
253
            '`alternative_email_addresses` LIKE :email_address2',
254
        ];
255
        $query->andWhere($email_condition,[':email_address'=>$email_address,':email_address2'=>'%\"'.$email_address.'\"%']);
256
        if($query->count() > 0){
257
            return true;
258
        }
259
        return false;
260
    }
261
262
    /**
263
     * {@inheritdoc}
264
     */
265
    public function attributeLabels()
266
    {
267
        return [
268
            'token' => Yii::t('app', 'Unique Token'),
269
        ];
270
    }
271
272
    /**
273
     * @param string $token Respondents token
274
     * @return static
275
     */
276
    public static function findByToken($token = null){
277
        if($token){
278
            $models = self::findByTokens([$token]);
279
            if (!empty($models)){
280
                return $models[0];
281
            }
282
        }
283
        return null;
284
    }
285
286
    /**
287
     * @param string[] $tokens Respondents tokens
288
     * @return static[]
289
     */
290
    public static function findByTokens($tokens){
291
        /** @var static[] $model */
292
        $models = static::find()
293
            ->andWhere(['in','token', $tokens])
294
            ->all();
295
        return $models;
296
    }
297
298
    /**
299
     * Check whether respondent has rejected this specific survey or has a hard bounce on e_mail address
300
     * @return bool
301
     */
302
    public function getIsRejected(){
303
        if(Rejection::rejectedByCode($this->token)){
304
            return true;
305
        }
306
        if(Rejection::bouncedByEmailAddress($this->email_address)){
307
            return true;
308
        }
309
310
        return false;
311
    }
312
313
    /**
314
     * Get the last respondent based on Email-address
315
     * @param string $email_address
316
     * @return static
317
     */
318
    public static function getLatestByEmail($email_address){
319
        /** @var static $model */
320
        $model = static::find()
321
            ->andWhere(['email_address'=>$email_address])
322
            ->orderBy([static::primaryKey()[0]=>SORT_DESC])
323
            ->one();
324
        return $model;
325
    }
326
327
    /**
328
     * @return string
329
     */
330
    public function getShortToken(){
331
        if(Uuid::isValid($this->token)){
332
            $uuid = Uuid::fromString($this->token);
333
            $shotUuid = new ShortUuid();
334
            return $shotUuid->encode($uuid);
335
        }
336
        return $this->token;
337
    }
338
339
340
}