GemsTracker/gemstracker-library

View on GitHub
classes/Gems/Snippets/Tracker/InsertSurveySnippet.php

Summary

Maintainability
F
3 days
Test Coverage
F
0%
<?php

/**
 *
 * @package    Gems
 * @subpackage Snippets_Tracker
 * @author     Matijs de Jong <mjong@magnafacta.nl>
 * @copyright  Copyright (c) 2015 Erasmus MC
 * @license    New BSD License
 */

namespace Gems\Snippets\Tracker;

/**
 *
 *
 * @package    Gems
 * @subpackage Snippets_Tracker
 * @copyright  Copyright (c) 2015 Erasmus MC
 * @license    New BSD License
 * @since      Class available since version 1.7.1 23-apr-2015 12:34:48
 */
class InsertSurveySnippet extends \Gems_Snippets_ModelFormSnippetAbstract
{
    /**
     *
     * @var array The fields from formData to copy to the token when creating it
     */
    protected $copyFields = array(
        'gto_id_round',
        'gto_valid_from',
        'gto_valid_from_manual',
        'gto_valid_until',
        'gto_valid_until_manual',
        'gto_comment',
        'gto_id_relationfield',
        );

    /**
     * True when the form should edit a new model item.
     *
     * @var boolean
     */
    protected $createData = true;

    /**
     *
     * @var \Gems_User_User
     */
    protected $currentUser;

    /**
     *
     * @var \Zend_Db_Adapter_Abstract
     */
    protected $db;

    /**
     *
     * @var int
     */
    protected $defaultRound = 10;

    /**
     * The items on the form - in the order of display
     *
     * @var array
     */
    protected $formItems = array(
        'gto_id_respondent',
        'gr2o_patient_nr',
        'respondent_name',
        'gto_id_organization',
        'gto_id_survey',
        'ggp_name',
        'gto_id_relationfield',
        'gto_id_track',
        'gto_round_order',
        'gto_valid_from_manual',
        'gto_valid_from',
        'gto_valid_until_manual',
        'gto_valid_until',
        'gto_comment',
        );

    /**
     * @var bool Allow selection of more than one survey
     */
    protected $insertMultipleSurveys = true;
    
    /**
     * Required
     *
     * @var \Gems_Loader
     */
    protected $loader;

    /**
     * @var \Gems_Tracker_Respondent
     */
    protected $respondent;
    
    /**
     * Required
     *
     * @var \Gems_Tracker_RespondentTrack The currently selected respondent track, required for save
     */
    protected $respondentTrack;

    /**
     * Required
     *
     * @var array of \Gems_Tracker_RespondentTrack Respondent Track
     */
    protected $respondentTracks = [];
    
    /**
     * The name of the action to forward to after form completion
     *
     * @var string
     */
    protected $routeAction = 'show';

    /**
     *
     * @var array of surveyId => \Gems_Tracker_Survey
     */
    protected $surveys = [];

    /**
     *
     * @var array id => label
     */
    protected $surveyList;

    /**
     * The newly create token
     *
     * @var array of \Gems_Tracker_Token
     */
    protected $tokens = [];

    /**
     *
     * @var \Gems_Tracker
     */
    protected $tracker;

    /**
     *
     * @var array of respondent track id => description
     */
    protected $tracksList;
    
    /**
     *
     * @var \Gems_Util
     */
    protected $util;

    /**
     * Adds one or more messages to the session based message store.
     *
     * @param mixed $message_args Can be an array or multiple argemuents. Each sub element is a single message string
     * @return self (continuation pattern)
     */
    public function addMessageInvalid($message_args)
    {
        $this->saveButtonId = null;
        $this->saveLabel    = null;

        parent::addMessage(func_get_args());
    }

    /**
     * Called after the check that all required registry values
     * have been set correctly has run.
     *
     * @return void
     */
    public function afterRegistry()
    {
        parent::afterRegistry();

        $this->respondent = $this->loader->getRespondent(
            $this->request->getParam(\MUtil_Model::REQUEST_ID1), 
            $this->request->getParam(\MUtil_Model::REQUEST_ID2)
        );

        if ($this->insertMultipleSurveys) {
            $this->saveLabel = $this->_('Insert survey(s)');
        } else {
            $this->saveLabel = $this->_('Insert survey');
        }
        $this->tracker   = $this->loader->getTracker();
        
    }

    /**
     * Creates the model
     *
     * @return \MUtil_Model_ModelAbstract
     */
    protected function createModel()
    {
        $model = $this->tracker->getTokenModel();

        if ($model instanceof \Gems_Tracker_Model_StandardTokenModel) {
            $model->addEditTracking();

            if ($this->createData) {
                $model->applyInsertionFormatting();
            }
        }

        // Valid from can not be calculated for inserted rounds, and should always be manual
        $model->set('gto_valid_from_manual', 'elementClass', 'Hidden', 'default', '1');

        $trackData = $this->util->getTrackData();

        if (! $this->surveyList) {
            $this->surveyList = $trackData->getInsertableSurveys($this->respondent->getOrganizationId());
        }

        $model->set('gto_id_survey',   'label', $this->_('Suvey to insert'),
                // 'elementClass' set in loadSurvey
                'multiOptions', $this->surveyList,
                'onchange', 'this.form.submit();'
                );
        $model->set('gto_id_track',    'label', $this->_('Existing track'),
                'elementClass', 'Select',
                //'multiOptions' set in loadTrackSettings
                'onchange', 'this.form.submit();'
                );
        $model->set('gto_round_order', 'label', $this->_('In round'),
                'elementClass', 'Select',
                //'multiOptions' set in loadRoundSettings
                'required', true
                );
        $model->set('gto_valid_from',
                'required', true
                );

        return $model;
    }
    
    /**
     * Get a select with the fields:
     *  - round_order: The gto_round_order to use for this round
     *  - has_group: True when has surveys for same group as current survey
     *  - group_answered: True when has answers for same group as current survey
     *  - any_answered: True when has answers for any survey
     *  - round_description: The gto_round_description for the round
     *
     * @return \Zend_Db_Select or you can return a nested array containing said output/
     */
    protected function getRoundSelect()
    {
        $select = $this->db->select();

        $select->from('gems__tokens', array('gto_round_description AS round_description'))
                ->joinInner('gems__reception_codes', 'gto_reception_code = grc_id_reception_code', array())
                ->joinInner('gems__surveys', 'gto_id_survey = gsu_id_survey', array())
                // ->where('grc_success = 1')
                ->group('gto_round_description');

        $groupIds = implode(', ', $this->getGroupIds());
        if ($groupIds) {
            $select->columns(array(
                // Round order is maximum for the survey's group unless this round had no surveys of the same group
                'round_order'    => new \Zend_Db_Expr(
                        "COALESCE(
                            MAX(CASE WHEN gsu_id_primary_group IN ($groupIds) THEN gto_round_order ELSE NULL END),
                            MAX(gto_round_order)
                            ) + 1"
                        ),
                'has_group'      => new \Zend_Db_Expr(
                        "SUM(CASE WHEN gsu_id_primary_group IN ($groupIds) THEN 1 ELSE 0 END)"
                        ),
                'group_answered' => new \Zend_Db_Expr(
                        "SUM(CASE WHEN gto_completion_time IS NOT NULL AND gsu_id_primary_group IN ($groupIds)
                            THEN 1
                            ELSE 0
                            END)"
                        ),
                'any_answered'   => new \Zend_Db_Expr(
                        "SUM(CASE WHEN gto_completion_time IS NOT NULL THEN 1 ELSE 0 END)"
                        ),
            ));
        } else {
            $select->columns(array(
                'round_order'    => new \Zend_Db_Expr("MAX(gto_round_order) + 1"),
                'has_group'      => new \Zend_Db_Expr("0"),
                'group_answered' => new \Zend_Db_Expr("0"),
                'any_answered'   => new \Zend_Db_Expr(
                        "SUM(CASE WHEN gto_completion_time IS NOT NULL THEN 1 ELSE 0 END)"
                        ),
            ));
        }

        if (isset($this->formData['gto_id_track'])) {
            $select->where('gto_id_respondent_track = ?', $this->formData['gto_id_track']);
        } else {
            $select->where('1=0');
        }

        $select->order('round_order');

        // \MUtil_Echo::track((string) $select);
        
        return $select;
    }

    /**
     * @param int $changed
     * @return string
     */
    public function getChangedMessage($changed)
    {
        return sprintf($this->plural('%d survey inserted', '%d surveys inserted', $changed), $changed);
    }

    /**
     * @return array groupId => groupId for all selected surveys
     */
    public function getGroupIds()
    {
        $groupIds = [];
        if ($this->surveys) {
            foreach ($this->surveys as $survey) {
                if ($survey instanceof \Gems_Tracker_Survey) {
                    $groupIds[$survey->getGroupId()] = $survey->getGroupId();
                }
            }
        }
        return $groupIds;
    }

    /**
     * Get the list of rounds and set the default
     *
     * @return array [roundInsertNr => RoundDescription
     */
    protected function getRoundsListAndSetDefault()
    {
        $model  = $this->getModel();
        $output = array();
        $select = $this->getRoundSelect();

        if ($select instanceof \Zend_Db_Select) {
            $rows = $this->db->fetchAll($select);
        } else {
            $rows = $select;
        }

        if ($rows) {

            // Initial values
            $maxAnswered      = 0;
            $maxGroupAnswered = 0;
            $minGroup         = -1;

            foreach ($rows as $row) {
                $output[$row['round_order']] = $row['round_description'];

                if ($row['has_group']) {
                    if (-1 === $minGroup) {
                        $minGroup = $row['round_order'];
                    }
                    if ($row['group_answered']) {
                        $maxGroupAnswered = $row['round_order'];
                    }
                }
                if ($row['any_answered']) {
                    $maxAnswered = $row['round_order'];
                }
            }
            if ($maxGroupAnswered) {
                $this->defaultRound = $maxGroupAnswered;
                $model->set('gto_round_order', 'description', sprintf(
                        $this->_('The last round containing answers for surveys in the same user group is "%s".'),
                        $output[$this->defaultRound]
                        ));

            } elseif ($maxAnswered) {
                $this->defaultRound = $maxAnswered;
                $model->set('gto_round_order', 'description', sprintf(
                        $this->_('The last round containing answers is "%s".'),
                        $output[$this->defaultRound]
                        ));

            } elseif (-1 !== $minGroup) {
                $this->defaultRound = $minGroup;
                $model->set('gto_round_order', 'description', sprintf(
                        $this->_('No survey has been answered, the first round with surveys in the same user group is "%s".'),
                        $output[$this->defaultRound]
                        ));

            } else {
                reset($output);
                $this->defaultRound = key($output);
                $model->set('gto_round_order',
                        'description', $this->_('No surveys have answers, nor are any in the same user group.')
                        );
            }

        } else {
            $output[10] = $this->_('Added survey');
            $this->defaultRound = 10;
            $model->set('gto_round_order',
                    'description', $this->_('No current rounds available.')
                    );
        }

        return $output;
    }
    
    /**
     * When there is no track to add to we return false
     * 
     * @return boolean
     */
    public function hasHtmlOutput() {
        $this->initTracks();
        $canDo = count($this->tracksList) > 0;
        if ($canDo === false) { 
            $this->afterSaveRouteUrl = ['controller'=>'respondent', 'action'=>'show'];             
        }
        return $canDo && parent::hasHtmlOutput();
    }

    /**
     * @param \Gems_Tracker_RespondentTrack $respondentTrack
     * @return bool
     */
    protected function includeTrack(\Gems_Tracker_RespondentTrack $respondentTrack)
    {
        return $respondentTrack->getReceptionCode()->isSuccess(); 
    }
    
    /**
     * Initialize the _items variable to hold all items from the model
     */
    protected function initItems()
    {
        if (is_null($this->_items)) {
            $this->_items = array_merge(
                    $this->formItems,
                    $this->getModel()->getMeta(\MUtil_Model_Type_ChangeTracker::HIDDEN_FIELDS, array())
                    );
            if (! $this->createData) {
                array_unshift($this->_items, 'gto_id_token');
            }
        }
    }

    /**
     * @throws \Gems_Exception
     */
    protected function initTracks()
    {
        $this->respondentTracks = $this->tracker->getRespondentTracks(
            $this->respondent->getId(), 
            $this->respondent->getOrganizationId(), 
            'gr2t_start_date DESC'  // Descending order, last added track comes first
        );
        $this->tracksList       = [];
        
        foreach ($this->respondentTracks as $respTrack) {
            if ($respTrack instanceof \Gems_Tracker_RespondentTrack) {
                if ($this->includeTrack($respTrack)) {
                    $this->tracksList[$respTrack->getRespondentTrackId()] = substr(sprintf(
                                    $this->_('%s - %s'),
                                    $respTrack->getTrackEngine()->getTrackName(),
                                    $respTrack->getFieldsInfo()
                            ), 0, 100);
                }
            }
        }
        if (! $this->tracksList) {
            $this->addMessageInvalid($this->_('Survey insertion impossible: respondent has no track!'));
        }
    }

    /**
     * @return bool True when any selected survey is not taken by staff
     */
    protected function isAnySurveyTakenByRespondents()
    {
        foreach ($this->surveys as $survey) {
            if ($survey instanceof \Gems_Tracker_Survey) {
                if (! $survey->isTakenByStaff()) {
                    return true;
                }
            }
        } 
        return false;
    }    

    /**
     * Hook that loads the form data from $_POST or the model
     *
     * Or from whatever other source you specify here.
     */
    protected function loadFormData()
    {
        if ($this->createData && (! $this->request->isPost())) {
            $surveyId = $this->request->getParam(\Gems_Model::SURVEY_ID);
            if ($surveyId) {
                if ($this->insertMultipleSurveys) {
                    $surveyIds = [$surveyId];
                } else {
                    $surveyIds = $surveyId;
                }
            } else {
                if ($this->insertMultipleSurveys) {
                    $surveyIds = [];
                } else {
                    $surveyIds = null;
                }
            }
            
            $this->formData = array(
                'gr2o_patient_nr'        => $this->respondent->getPatientNumber(),
                'gto_id_organization'    => $this->respondent->getOrganizationId(),
                'gto_id_respondent'      => $this->respondent->getId(),
                'respondent_name'        => $this->respondent->getName(),
                'gto_id_survey'          => $surveyIds,
                'gto_id_track'           => $this->request->getParam(\Gems_Model::TRACK_ID),
                'gto_valid_from_manual'  => 1,
                'gto_valid_from'         => new \MUtil_Date(),
                'gto_valid_until_manual' => 0,
                'gto_valid_until'        => null, // Set in loadSurvey
                );

            $output = $this->getModel()->processAfterLoad(array($this->formData), $this->createData, false);
            $this->formData = reset($output);
        } else {
            parent::loadFormData();
        }

        $this->loadSurvey();
        $this->loadTrackSettings();
        $this->loadRoundSettings();

        // \MUtil_Echo::track($this->formData);
    }

    /**
     * Load the settings for the round
     */
    protected function loadRoundSettings()
    {
        $rounds = $this->getRoundsListAndSetDefault();
        $model  = $this->getModel();
        $model->set('gto_round_order', 'multiOptions', $rounds, 'size', count($rounds));

        if (count($rounds) === 1) {
            $model->set('gto_round_order', 'elementClass', 'Exhibitor');
        }

        if (! isset($this->formData['gto_round_order'], $rounds[$this->formData['gto_round_order']])) {
            $this->formData['gto_round_order'] = $this->defaultRound;
        }
        if (! isset($rounds[$this->formData['gto_round_order']])) {
            reset($rounds);
            $this->formData['gto_round_order'] = key($rounds);
        }
    }

    /**
     * Load the survey object and use it
     */
    protected function loadSurvey()
    {
        if (! $this->surveyList) {
            $this->addMessageInvalid($this->_('Survey insertion impossible: no insertable survey exists!'));
        }

        $model = $this->getModel();
        if (count($this->surveyList) === 1) {
            $model->set('gto_id_survey', 'elementClass', 'Exhibitor');

            reset($this->surveyList);
            $this->formData['gto_id_survey'] = key($this->surveyList);
        } elseif ($this->insertMultipleSurveys) {
            $model->set('gto_id_survey', 'elementClass', 'MultiCheckbox', 'required', 'required');
        }

        if (isset($this->formData['gto_id_survey'])) {
            foreach ((array) $this->formData['gto_id_survey'] as $surveyId) {
                $this->surveys[$surveyId] = $this->tracker->getSurvey($surveyId);
            }
            
            $groups = array_intersect_key($this->util->getDbLookup()->getGroups(), $this->getGroupIds());
            if ($groups) {
                $this->formData['ggp_name'] = implode($this->_(', '), $groups);
            }            

            if (!(isset($this->formData['gto_valid_until_manual']) && $this->formData['gto_valid_until_manual'])) {
                foreach ($this->surveys as $survey) {
                    if ($survey instanceof \Gems_Tracker_Survey) {
                        // Just use the date of the first select survey
                        // No theory about this, will usually be 6 months
                        $this->formData['gto_valid_until'] = $survey->getInsertDateUntil($this->formData['gto_valid_from']);
                        break;
                    }
                }
            }
        }
    }

    /**
     * Load the settings for the survey
     */
    protected function loadTrackSettings()
    {
        $respTracks = $this->respondentTracks;
        
        if (! isset($this->formData['gto_id_track'])) {
            reset($this->tracksList);
            $this->formData['gto_id_track'] = key($this->tracksList);
        }
        
        asort($this->tracksList);
        
        $model = $this->getModel();
        $model->set('gto_id_track', 'multiOptions', $this->tracksList);
        if (count($this->tracksList) === 1) {
            $model->set('gto_id_track', 'elementClass', 'Exhibitor');
        }

        if (isset($this->formData['gto_id_track'], $respTracks[$this->formData['gto_id_track']]) && (int) $this->formData['gto_id_track'] > 0) {
            $this->respondentTrack = $respTracks[$this->formData['gto_id_track']];

            // Add relation field when survey is not for staff
            if ($this->isAnySurveyTakenByRespondents()) {
                
                $engine = $this->respondentTrack->getTrackEngine();
                $empty  = array('-1' => $this->_('Patient'));
                $relations = $empty + $engine->getRespondentRelationFields();
                $model->set('gto_id_relationfield', 'label', $this->_('Fill out by'),
                    'elementClass', (1 == count($relations) ? 'Exhibitor' : 'Select'),
                    'multiOptions', $relations,
                    'required', true
                );
                
                if (! isset($this->formData['gto_id_relationfield'])) {
                    reset($relations);
                    $this->formData['gto_id_relationfield'] = key($relations);
                }
            }
        }
    }

    /**
     * Hook containing the actual save code.
     *
     * Calls afterSave() for user interaction.
     *
     * @see afterSave()
     */
    protected function saveData()
    {
        $model = $this->getModel();

        $userId     = $this->currentUser->getUserId();
        $tokenData  = array();

        foreach ($this->copyFields as $name) {
            if (array_key_exists($name, $this->formData)) {
                if ($model->hasOnSave($name)) {
                    $tokenData[$name] = $model->getOnSave($this->formData[$name], $this->createData, $name, $this->formData);
                } elseif ('' === $this->formData[$name]) {
                    $tokenData[$name] = null;
                } else {
                    $tokenData[$name] = $this->formData[$name];
                }
            } else {
                $tokenData[$name] = null;
            }
        }
        $changed  = 0;
        $relation = isset($tokenData['gto_id_relationfield']) ? $tokenData['gto_id_relationfield'] : null;
        $rounds   = $model->get('gto_round_order', 'multiOptions');
        $tokenData['gto_id_round']          = '0';
        $tokenData['gto_round_order']       = $this->formData['gto_round_order'];
        $tokenData['gto_round_description'] = $rounds[$this->formData['gto_round_order']];

        foreach ((array) $this->formData['gto_id_survey'] as $surveyId) {
            $survey = $this->tracker->getSurvey($surveyId);
            
            if ($survey instanceof \Gems_Tracker_Survey) {
                // We may have a mix of relation and non/relation fields
                if ($survey->isTakenByStaff()) {
                    unset($tokenData['gto_id_relationfield']);
                } else {
                    $tokenData['gto_id_relationfield'] = $relation;
                }
                $this->tokens[] = $this->respondentTrack->addSurveyToTrack($surveyId, $tokenData, $userId);
                $tokenData['gto_round_order']++;
                
                $changed++;
            }
        }


        // Communicate with the user
        $this->afterSave($changed);
    }

    /**
     * Set what to do when the form is 'finished'.
     *
     * @return self
     */
    protected function setAfterSaveRoute()
    {
        // Default is just go to the index
        if ($this->routeAction && ($this->request->getActionName() !== $this->routeAction)) {
            if (1 == count($this->tokens)) {
                $token = reset($this->tokens);
                $this->afterSaveRouteUrl = [
                    $this->request->getControllerKey() => 'track',
                    $this->request->getActionKey()     => $this->routeAction,
                    \MUtil_Model::REQUEST_ID           => $token->getTokenId(),
                    ];
            } else {
                // For multiple surveys
                $this->afterSaveRouteUrl = [
                    $this->request->getControllerKey() => 'respondent',
                    $this->request->getActionKey()     => 'show',
                    \MUtil_Model::REQUEST_ID1          => $this->respondent->getPatientNumber(),
                    \MUtil_Model::REQUEST_ID2          => $this->respondent->getOrganizationId(),
                ];
            }
        }

        return $this;
    }
}