-
Notifications
You must be signed in to change notification settings - Fork 3
/
QuestionnaireAnswerSummaryCsv.php
395 lines (365 loc) · 12.5 KB
/
QuestionnaireAnswerSummaryCsv.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
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
<?php
/**
* QuestionnaireAnswerSummary Model
*
* @property Questionnaire $Questionnaire
* @property User $User
* @property QuestionnaireAnswer $QuestionnaireAnswer
*
* @author Noriko Arai <arai@nii.ac.jp>
* @author AllCreator <info@allcreator.net>
* @link http://www.netcommons.org NetCommons Project
* @license http://www.netcommons.org/license.txt NetCommons License
*/
App::uses('QuestionnairesAppModel', 'Questionnaires.Model');
/**
* Summary for QuestionnaireAnswerSummary Model
*/
class QuestionnaireAnswerSummaryCsv extends QuestionnairesAppModel {
/**
* use table
*
* @var array
*/
public $useTable = 'questionnaire_answer_summaries';
/**
* use behaviors
*
* @var array
*/
public $actsAs = array(
'NetCommons.Trackable',
);
/**
* Validation rules
*
* @var array
*/
public $validate = array(
);
//The Associations below have been created with all possible keys, those that are not needed can be removed
/**
* belongsTo associations
*
* @var array
*/
public $belongsTo = array(
);
/**
* hasMany associations
*
* @var array
*/
public $hasMany = array(
);
/**
* Constructor. Binds the model's database table to the object.
*
* @param bool|int|string|array $id Set this ID for this model on startup,
* can also be an array of options, see above.
* @param string $table Name of database table to use.
* @param string $ds DataSource connection name.
* @see Model::__construct()
* @SuppressWarnings(PHPMD.BooleanArgumentFlag)
*/
public function __construct($id = false, $table = null, $ds = null) {
parent::__construct($id, $table, $ds);
$this->loadModels([
'Questionnaire' => 'Questionnaires.Questionnaire',
'QuestionnaireAnswer' => 'Questionnaires.QuestionnaireAnswer',
]);
}
/**
* getQuestionnaireForAnswerCsv
*
* @param int $questionnaireKey questionnaire key
* @return array questionnaire data
*/
public function getQuestionnaireForAnswerCsv($questionnaireKey) {
// 指定のアンケートデータを取得
// CSVの取得は公開してちゃんとした回答を得ているアンケートに限定である
$questionnaire = $this->Questionnaire->find('first', array(
'conditions' => array(
'Questionnaire.block_id' => Current::read('Block.id'),
'Questionnaire.key' => $questionnaireKey,
'Questionnaire.is_active' => true,
'Questionnaire.language_id' => Current::read('Language.id'),
),
'recursive' => -1
));
return $questionnaire;
}
/**
* getAnswerSummaryCsv
*
* @param array $questionnaire questionnaire data
* @param int $limit record limit
* @param int $offset offset
* @return array
*/
public function getAnswerSummaryCsv($questionnaire, $limit, $offset) {
// 指定されたアンケートの回答データをCsvに出力しやすい行形式で返す
$retArray = array();
// $offset == 0 のときのみヘッダ行を出す
if ($offset == 0) {
$retArray[] = $this->_putHeader($questionnaire);
}
// $questionnaireにはページデータ、質問データが入っていることを前提とする
// アンケートのkeyを取得
$key = $questionnaire['Questionnaire']['key'];
// keyに一致するsummaryを取得(テストじゃない、完了している)
$summaries = $this->find('all', array(
'fields' => array('QuestionnaireAnswerSummaryCsv.*', 'User.handlename'),
'conditions' => array(
'answer_status' => QuestionnairesComponent::ACTION_ACT,
'test_status' => QuestionnairesComponent::TEST_ANSWER_STATUS_PEFORM,
'questionnaire_key' => $key,
),
'recursive' => -1,
'joins' => array(
array(
'table' => 'users',
'alias' => 'User',
'type' => 'LEFT',
'conditions' => array(
'QuestionnaireAnswerSummaryCsv.user_id = User.id',
)
)
),
'limit' => $limit,
'offset' => $offset,
'order' => array('QuestionnaireAnswerSummaryCsv.created ASC'),
));
if (empty($summaries)) {
return $retArray;
}
// 質問のIDを取得
$questionIds = [];
foreach ($questionnaire['QuestionnairePage'] as $QuestionnairePage) {
foreach ($QuestionnairePage['QuestionnaireQuestion'] as $question) {
$questionIds[] = $question['id'];
}
}
// summary loop
foreach ($summaries as $summary) {
//$answers = $summary['QuestionnaireAnswer'];
// 何回もSQLを発行するのは無駄かなと思いつつも
// QuestionnaireAnswerに回答データの取り扱いしやすい形への整備機能を組み込んであるので、それを利用したかった
// このクラスからでも利用できないかと試みたが
// AnswerとQuestionがJOINされた形でFindしないと整備機能が発動しない
// そうするためにはrecursive=2でないといけないわけだが、recursive=2にするとRoleのFindでSQLエラーになる
// 仕方ないのでこの形式で処理を行う
$answers = $this->QuestionnaireAnswer->find('all', array(
'fields' => array('QuestionnaireAnswer.*', 'QuestionnaireQuestion.*'),
'conditions' => array(
'questionnaire_answer_summary_id' => $summary[$this->alias]['id'],
'QuestionnaireQuestion.id' => $questionIds
),
'recursive' => -1,
'joins' => array(
array(
'table' => 'questionnaire_questions',
'alias' => 'QuestionnaireQuestion',
'type' => 'LEFT',
'conditions' => array(
'QuestionnaireAnswer.questionnaire_question_key = QuestionnaireQuestion.key',
)
)
)
));
$retArray[] = $this->_getRows($questionnaire, $summary, $answers);
}
return $retArray;
}
/**
* _putHeader
*
* @param array $questionnaire questionnaire data
* @return array
*/
protected function _putHeader($questionnaire) {
$cols = array();
// "回答者","回答日","回数"
$cols[] = __d('questionnaires', 'Respondent');
$cols[] = __d('questionnaires', 'Answer Date');
$cols[] = __d('questionnaires', 'Number');
foreach ($questionnaire['QuestionnairePage'] as $page) {
foreach ($page['QuestionnaireQuestion'] as $question) {
$pageNumber = $page['page_sequence'] + 1;
$questionNumber = $question['question_sequence'] + 1;
if (QuestionnairesComponent::isMatrixInputType($question['question_type'])) {
$choiceSeq = 1;
foreach ($question['QuestionnaireChoice'] as $choice) {
if ($choice['matrix_type'] == QuestionnairesComponent::MATRIX_TYPE_ROW_OR_NO_MATRIX) {
$cols[] = sprintf('%d-%d-%d. %s:%s',
$pageNumber,
$questionNumber,
$choiceSeq++,
$question['question_value'],
$choice['choice_label']);
}
}
} else {
$cols[] = sprintf('%d-%d. %s',
$pageNumber,
$questionNumber,
$question['question_value']);
}
}
}
return $cols;
}
/**
* _getRow
*
* @param array $questionnaire questionnaire data
* @param array $summary answer summary
* @param array $answers answer data
* @return array
*/
protected function _getRows($questionnaire, $summary, $answers) {
// ページ、質問のループから、取り出すべき質問のIDを順番に取り出す
// question loop
// 返却用配列にquestionのIDにマッチするAnswerを配列要素として追加、Answerがないときは空文字
// なお選択肢系のものはchoice_idが回答にくっついているのでそれを削除する
// MatrixのものはMatrixの行数分返却行の列を加える
// その他の選択肢の場合は、入力されたその他のテキストを入れる
$cols = array();
$cols[] = $this->_getUserName($questionnaire, $summary);
$cols[] = $summary['QuestionnaireAnswerSummaryCsv']['modified'];
$cols[] = $summary['QuestionnaireAnswerSummaryCsv']['answer_number'];
foreach ($questionnaire['QuestionnairePage'] as $page) {
foreach ($page['QuestionnaireQuestion'] as $question) {
if (QuestionnairesComponent::isMatrixInputType($question['question_type'])) {
foreach ($question['QuestionnaireChoice'] as $choice) {
if ($choice['matrix_type'] == QuestionnairesComponent::MATRIX_TYPE_ROW_OR_NO_MATRIX) {
$cols[] = $this->_getMatrixAns($question, $choice, $answers);
}
}
} else {
$cols[] = $this->_getAns($question, $answers);
}
}
}
return $cols;
}
/**
* _getUserName
*
* @param array $questionnaire questionnaire data
* @param array $summary answer summary
* @return string
*/
protected function _getUserName($questionnaire, $summary) {
if ($questionnaire['Questionnaire']['is_anonymity']) {
return __d('questionnaires', 'Anonymity');
}
if (empty($summary['User']['handlename'])) {
return __d('questionnaires', 'Guest');
}
return $summary['User']['handlename'];
}
/**
* _getAns
*
* @param array $question question data
* @param array $answers answer data
* @return string
*
* 速度改善の修正に伴って発生したため抑制
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
protected function _getAns($question, $answers) {
$retAns = '';
// 回答配列データの中から、現在指定された質問に該当するものを取り出す
$ans = [];
foreach ($answers as $answer) {
if ($answer['QuestionnaireAnswer']['questionnaire_question_key'] === $question['key']) {
$ans = $answer['QuestionnaireAnswer'];
break;
}
}
// 回答が存在するとき処理
if (! $ans) {
// 通常の処理ではこのような場面はありえない
// アンケートは空回答であっても回答レコードを作成するからです
// データレコード異常があった場合のみです
// ただ、この回答を異常データだからといってオミットすると、サマリの合計数と
// 合わなくなって集計データが狂ってしまうので空回答だったように装って処理します
return $retAns;
}
// 単純入力タイプのときは回答の値をそのまま返す
if (QuestionnairesComponent::isOnlyInputType($question['question_type'])) {
$retAns = $ans['answer_value'];
} elseif (QuestionnairesComponent::isSelectionInputType($question['question_type'])) {
// choice_id と choice_valueに分けられた回答選択肢配列を得る
// 選択されていた数分処理
foreach ($ans['answer_values'] as $choiceKey => $dividedAns) {
// idから判断して、その他が選ばれていた場合、other_answer_valueを入れる
$choice = [];
foreach ($question['QuestionnaireChoice'] as $item) {
if ($item['key'] === $choiceKey) {
$choice = $item;
break;
}
}
if ($choice) {
if ($choice['other_choice_type'] !=
QuestionnairesComponent::OTHER_CHOICE_TYPE_NO_OTHER_FILED) {
$retAns .= $ans['other_answer_value'];
} else {
$retAns .= $dividedAns;
}
$retAns .= QuestionnairesComponent::ANSWER_DELIMITER;
}
}
$retAns = trim($retAns, QuestionnairesComponent::ANSWER_DELIMITER);
}
return $retAns;
}
/**
* _getMatrixAns
*
* @param array $question question data
* @param array $choice question choice data
* @param array $answers answer data
* @return string
*/
protected function _getMatrixAns($question, $choice, $answers) {
$retAns = '';
// 回答配列データの中から、現在指定された質問に該当するものを取り出す
// マトリクスタイプのときは複数存在する(行数分)
$answerArr = [];
foreach ($answers as $answer) {
if ($answer['QuestionnaireAnswer']['questionnaire_question_key']
=== $question['key']) {
$answerArr[] = $answer['QuestionnaireAnswer'];
}
}
if (empty($answerArr)) {
// 通常の処理ではこのような場面はありえない
// アンケートは空回答であっても回答レコードを作成するからです
// データレコード異常があった場合のみです
// ただ、この回答を異常データだからといってオミットすると、サマリの合計数と
// 合わなくなって集計データが狂ってしまうので空回答だったように装って処理します
return $retAns;
}
// その中から現在指定された選択肢行に該当するものを取り出す
$ans = [];
foreach ($answerArr as $item) {
if ($item['matrix_choice_key'] === $choice['key']) {
$ans = $item;
break;
}
}
// 回答が存在するとき処理
if ($ans) {
// idから判断して、その他が選ばれていた場合、other_answer_valueを入れる
if ($choice['other_choice_type'] != QuestionnairesComponent::OTHER_CHOICE_TYPE_NO_OTHER_FILED) {
$retAns = $ans['other_answer_value'] . QuestionnairesComponent::ANSWER_VALUE_DELIMITER;
}
$retAns .= implode(QuestionnairesComponent::ANSWER_DELIMITER, $ans['answer_values']);
}
return $retAns;
}
}