/**
  * saveAnswer
  * save the answer data
  *
  * @param array $questionnaire questionnaire data
  * @param int $userId user id
  * @param string $sessionId session id
  * @param array $data Postされた回答データ
  * @param array &$errors error messages
  * @throws $ex
  * @return bool
  */
 public function saveAnswer($questionnaire, $userId, $sessionId, $data, &$errors)
 {
     $errors = array();
     $this->loadModels(['QuestionnaireAnswerSummary' => 'Questionnaires.QuestionnaireAnswerSummary']);
     //トランザクションBegin
     $this->setDataSource('master');
     //$dataSource = $this->getDataSource();
     //$dataSource->begin();
     try {
         // 初回回答か再回答かを確認している
         // 初めは「ID」がPOSTに入っているかどうかで判断しようと思っていたが
         // Cakeは簡単にブラウザの「戻る」で前画面を表示させたりするので、
         // POSTのIDは再回答であるにもかかわらず空ってことがありうる
         // なので毎回DBチェックするしかないかと思う I think so.
         // サマリレコード取得
         $summary = $this->QuestionnaireAnswerSummary->forceGetAnswerSummary($questionnaire, $userId, $sessionId, array('questionnaire_origin_id' => $questionnaire['Questionnaire']['origin_id'], 'answer_status' => QuestionnairesComponent::ACTION_NOT_ACT, 'session_value' => $sessionId, 'user_id' => $userId));
         $summaryId = $summary['QuestionnaireAnswerSummary']['id'];
         //
         foreach ($data as $answer) {
             // 質問によってバリデーション動作が変わるので
             $targetQuestion = Hash::extract($questionnaire['QuestionnairePage'], '{n}.QuestionnaireQuestion.{n}[origin_id=' . $answer['questionnaire_question_origin_id'] . ']');
             $this->validator()->getField('answer_value')->setRule('answerValidation', array('rule' => array('checkAnswerValue', $targetQuestion[0], $answer, 'message' => '')));
             // データ保存
             if (QuestionnairesComponent::isMatrixInputType($targetQuestion[0]['question_type'])) {
                 foreach ($answer as $ans) {
                     if (!$this->__saveAnswer($ans, $summaryId, $summary)) {
                         $errors[$ans['questionnaire_question_origin_id']] = $this->validationErrors;
                     }
                 }
             } else {
                 if (!$this->__saveAnswer($answer, $summaryId, $summary)) {
                     $errors[$answer['questionnaire_question_origin_id']] = $this->validationErrors;
                 }
             }
         }
         //$dataSource->commit();
     } catch (Exception $ex) {
         //$dataSource->rollback();
         CakeLog::error($ex);
         throw $ex;
     }
     if (count($errors) == 0) {
         return true;
     } else {
         return false;
     }
 }
 /**
  * __aggrigateAnswer
  * 集計処理の実施
  *
  * @param array $questionnaire アンケート情報
  * @param bool $contentEditable 編集可能フラグ
  * @param array &$questions アンケート質問(集計結果を配列追加して返します)
  * @return void
  */
 private function __aggrigateAnswer($questionnaire, $contentEditable, &$questions)
 {
     // 集計データを集める際の基本条件
     $baseConditions = $this->QuestionnaireAnswerSummary->getResultCondition($questionnaire);
     //質問毎に、まとめあげる.
     //$questionsは、questionnaire_question_origin_idをキーとし、questionnaire_question配下が代入されている。
     //
     foreach ($questions as &$question) {
         if ($question['is_result_display'] != QuestionnairesComponent::EXPRESSION_SHOW) {
             //集計表示をしない、なので飛ばす
             continue;
         }
         // 戻り値の、この質問の合計回答数を記録しておく。
         // skip ロジックがあるため、単純にsummaryのcountじゃない..
         $questionConditions = $baseConditions + array('QuestionnaireAnswer.questionnaire_question_origin_id' => $question['origin_id']);
         $question['answer_total_cnt'] = $this->QuestionnaireAnswer->getAnswerCount($questionConditions);
         if (QuestionnairesComponent::isMatrixInputType($question['question_type'])) {
             $this->__aggrigateAnswerForMatrix($question, $questionConditions);
         } else {
             $this->__aggrigateAnswerForNotMatrix($question, $questionConditions);
         }
     }
 }
 /**
  * __getRow 
  *
  * @param array $questionnaire questionnaire data
  * @param array $summary answer summary
  * @param array $answers answer data
  * @return array
  */
 private function __getRows($questionnaire, $summary, $answers)
 {
     // ページ、質問のループから、取り出すべき質問のIDを順番に取り出す
     // question loop
     // 返却用配列にquestionのIDにマッチするAnswerを配列要素として追加、Answerがないときは空文字
     // なお選択肢系のものはchoice_idが回答にくっついているのでそれを削除する
     // MatrixのものはMatrixの行数分返却行の列を加える
     // その他の選択肢の場合は、入力されたその他のテキストを入れる
     $cols = array();
     $cols[] = $questionnaire['Questionnaire']['is_anonymity'] ? __d('questionnaires', 'Anonymity') : $summary['TrackableCreator']['username'];
     $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 array_map(array($this, 'convertCode'), $cols);
 }