/
Token.php
305 lines (283 loc) · 12 KB
/
Token.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
<?php
/**
*
* For code completion we add the available scenario's here
* Attributes
* @property int $tid
* @property string $firstname
* @property string $lastname
* @property string $email
* @property string $emailstatus
* @property string $token
* @property string $language
* @property string $blacklisted
* @property string $sent
* @property string $remindersent
* @property int $remindercount
* @property string $completed
* @property int $usesleft
* @property DateTime $validfrom
* @property DateTime $validuntil
*
* Relations
* @property Survey $survey The survey this token belongs to.
*
* Scopes
* @method Token incomplete() incomplete() Select only uncompleted tokens
* @method Token usable() usable() Select usable tokens: valid daterange and userleft > 0
*
*/
abstract class Token extends Dynamic
{
public function attributeLabels() {
$labels = array(
'tid' => gT('Token ID'),
'partcipant' => gt('Participant ID'),
'firstname' => gT('First name'),
'lastname' => gT('Last name'),
'email' => gT('Email address'),
'emailstatus' => gT('Email status'),
'token' => gT('Token'),
'language' => gT('Language code'),
'blacklisted' => gT('Blacklisted'),
'sent' => gT('Invitation sent date'),
'remindersent' => gT('Last reminder sent date'),
'remindercount' =>gT('Total numbers of sent reminders'),
'completed' => gT('Completed'),
'usesleft' => gT('Uses left'),
'validfrom' => gT('Valid from'),
'validuntil' => gT('Valid until'),
);
// Check if we have custom attributes.
if ($this->hasAttribute('attribute_1'))
{
foreach (decodeTokenAttributes($this->survey->attributedescriptions) as $key => $info)
{
$labels[$key] = $info['description'];
}
}
return $labels;
}
public function beforeDelete() {
$result = parent::beforeDelete();
if ($result && isset($this->surveylink))
{
if (!$this->surveylink->delete())
{
throw new CException('Could not delete survey link. Token was not deleted.');
}
return true;
}
return $result;
}
public static function createTable($surveyId, array $extraFields = array())
{
$surveyId=intval($surveyId);
// Specify case sensitive collations for the token
$sCollation='';
if (Yii::app()->db->driverName=='mysqli' | Yii::app()->db->driverName=='mysqli'){
$sCollation="COLLATE 'utf8_bin'";
}
if (Yii::app()->db->driverName=='sqlsrv' | Yii::app()->db->driverName=='dblib' | Yii::app()->db->driverName=='mssql'){
$sCollation="COLLATE SQL_Latin1_General_CP1_CS_AS";
}
$fields = array(
'tid' => 'pk',
'participant_id' => 'string(50)',
'firstname' => 'string(40)',
'lastname' => 'string(40)',
'email' => 'text',
'emailstatus' => 'text',
'token' => "string(35) {$sCollation}",
'language' => 'string(25)',
'blacklisted' => 'string(17)',
'sent' => "string(17) DEFAULT 'N'",
'remindersent' => "string(17) DEFAULT 'N'",
'remindercount' => 'integer DEFAULT 0',
'completed' => "string(17) DEFAULT 'N'",
'usesleft' => 'integer DEFAULT 1',
'validfrom' => 'datetime',
'validuntil' => 'datetime',
'mpid' => 'integer'
);
foreach ($extraFields as $extraField) {
$fields[$extraField] = 'text';
}
// create fields for the custom token attributes associated with this survey
$tokenattributefieldnames = Survey::model()->findByPk($surveyId)->tokenAttributes;
foreach($tokenattributefieldnames as $attrname=>$attrdetails)
{
if (!isset($fields[$attrname])) {
$fields[$attrname] = 'string(255)';
}
}
$db = \Yii::app()->db;
$sTableName="{{tokens_{$surveyId}}}";
$db->createCommand()->createTable($sTableName, $fields);
/**
* @todo Check if this random component in the index name is needed.
* As far as I (sam) know index names need only be unique per table.
*/
$db->createCommand()->createIndex("idx_token_token_{$surveyId}_".rand(1,50000), $sTableName,'token');
// Refresh schema cache just in case the table existed in the past, and return if table exist
return $db->schema->getTable($sTableName, true);
}
public function findByToken($token)
{
return $this->findByAttributes(array(
'token' => $token
));
}
/**
* Generates a token for this object.
* @throws CHttpException
*/
public function generateToken()
{
$length = $this->survey->tokenlength;
$this->token = \Yii::app()->securityManager->generateRandomString($length);
$counter = 0;
while (!$this->validate('token'))
{
$this->token = \Yii::app()->securityManager->generateRandomString($length);
$counter++;
// This is extremely unlikely.
if ($counter > 10)
{
throw new CHttpException(500, 'Failed to create unique token in 10 attempts.');
}
}
}
/**
* Generates a token for all token objects in this survey.
* Syntax: Token::model(12345)->generateTokens();
*/
public function generateTokens() {
if ($this->scenario != '') {
throw new \Exception("This function should only be called like: Token::model(12345)->generateTokens");
}
$surveyId = $this->dynamicId;
$tokenLength = isset($this->survey) && is_numeric($this->survey->tokenlength) ? $this->survey->tokenlength : 15;
$tkresult = Yii::app()->db->createCommand("SELECT tid FROM {{tokens_{$surveyId}}} WHERE token IS NULL OR token=''")->queryAll();
//Exit early if there are not empty tokens
if (count($tkresult)===0) return array(0,0);
//get token length from survey settings
$tlrow = Survey::model()->findByAttributes(array("sid"=>$surveyId));
//Add some criteria to select only the token field
$criteria = $this->getDbCriteria();
$criteria->select = 'token';
$ntresult = $this->findAllAsArray($criteria); //Use AsArray to skip active record creation
// select all existing tokens
foreach ($ntresult as $tkrow)
{
$existingtokens[$tkrow['token']] = true;
}
$newtokencount = 0;
$invalidtokencount=0;
foreach ($tkresult as $tkrow)
{
$bIsValidToken = false;
while ($bIsValidToken == false && $invalidtokencount<50)
{
$newtoken = Yii::app()->securityManager->generateRandomString($tokenLength);
if (!isset($existingtokens[$newtoken]))
{
$existingtokens[$newtoken] = true;
$bIsValidToken = true;
$invalidtokencount=0;
}
else
{
$invalidtokencount ++;
}
}
if($bIsValidToken)
{
$itresult = $this->updateByPk($tkrow['tid'], array('token' => $newtoken));
$newtokencount++;
}
else
{
break;
}
}
return array($newtokencount,count($tkresult));
}
/**
*
* @param mixed $className Either the classname or the survey id.
* @return Token
*/
public static function model($className = null) {
return parent::model($className);
}
/**
*
* @param int $surveyId
* @param string $scenario
* @return Token Description
*/
public static function create($surveyId, $scenario = 'insert') {
return parent::create($surveyId, $scenario);
}
public function relations()
{
$result = array(
'responses' => array(self::HAS_MANY, 'Response_' . $this->dynamicId, array('token' => 'token')),
'survey' => array(self::BELONGS_TO, 'Survey', '', 'on' => "sid = {$this->dynamicId}"),
'surveylink' => array(self::BELONGS_TO, 'SurveyLink', array('participant_id' => 'participant_id'), 'on' => "survey_id = {$this->dynamicId}")
);
return $result;
}
public function rules()
{
return array(
array('token', 'unique', 'allowEmpty' => true),
array(implode(',', $this->tableSchema->columnNames), 'safe'),
array('remindercount','numerical', 'integerOnly'=>true,'allowEmpty'=>true),
array('email','filter','filter'=>'trim'),
array('email','LSYii_EmailIDNAValidator', 'allowEmpty'=>true, 'allowMultiple'=>true,'except'=>'allowinvalidemail'),
array('usesleft','numerical', 'integerOnly'=>true,'allowEmpty'=>true),
array('mpid','numerical', 'integerOnly'=>true,'allowEmpty'=>true),
array('blacklisted', 'in','range'=>array('Y','N'), 'allowEmpty'=>true),
array('emailstatus', 'default', 'value' => 'OK'),
);
}
public function scopes()
{
$now = dateShift(date("Y-m-d H:i:s"), "Y-m-d H:i:s", Yii::app()->getConfig("timeadjust"));
return array(
'incomplete' => array(
'condition' => "completed = 'N'"
),
'usable' => array(
'condition' => "COALESCE(validuntil, '$now') >= '$now' AND COALESCE(validfrom, '$now') <= '$now'"
),
'editable' => array(
'condition' => "COALESCE(validuntil, '$now') >= '$now' AND COALESCE(validfrom, '$now') <= '$now'"
),
'empty' => array(
'condition' => 'token is null or token = ""'
)
);
}
public function summary()
{
$criteria = $this->getDbCriteria();
$criteria->select = array(
"COUNT(*) as count",
"COUNT(CASE WHEN (token IS NULL OR token='') THEN 1 ELSE NULL END) as invalid",
"COUNT(CASE WHEN (sent!='N' AND sent<>'') THEN 1 ELSE NULL END) as sent",
"COUNT(CASE WHEN (emailstatus LIKE 'OptOut%') THEN 1 ELSE NULL END) as optout",
"COUNT(CASE WHEN (completed!='N' and completed<>'') THEN 1 ELSE NULL END) as completed",
"COUNT(CASE WHEN (completed='Q') THEN 1 ELSE NULL END) as screenout",
);
$command = $this->getCommandBuilder()->createFindCommand($this->getTableSchema(),$criteria);
return $command->queryRow();
}
public function tableName()
{
return '{{tokens_' . $this->dynamicId . '}}';
}
}
?>