GemsTracker/gemstracker-library

View on GitHub
classes/Gems/Selector/DateSelectorAbstract.php

Summary

Maintainability
D
2 days
Test Coverage
F
0%
<?php

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

/**
 *
 * @package    Gems
 * @subpackage Selector
 * @copyright  Copyright (c) 2011 Erasmus MC
 * @license    New BSD License
 * @since      Class available since version 1.2
 */
abstract class Gems_Selector_DateSelectorAbstract extends \MUtil_Translate_TranslateableAbstract
{
    const DATE_FACTOR = 'df';
    const DATE_GROUP  = 'dg';
    const DATE_TYPE   = 'dt';

    private $_actionKey;

    /**
     * @var array $_fields
     */
    private $_fields;

    private $_model;

    /**
     *
     * @var string
     */
    protected $dataCellClass = 'centerAlign timeResult';

    /**
     * The name of the database table to use as the main table.
     *
     * @var string
     */
    protected $dataTableName;

    /**
     * The date the current period ends
     *
     * @var \MUtil_Date
     */
    protected $dateCurrentEnd;

    /**
     * The date the current period starts
     *
     * @var \MUtil_Date
     */
    protected $dateCurrentStart;

    /**
     * The number of dateTypes in the future or (when negative) before now.
     *
     * @var integer
     */
    protected $dateFactor;

    /**
     * Stores the dateFactor's to use so that the current period will be roughly the same
     * as the current period.
     *
     * Assigned by createModel() SHOULD BECOME PRIVATE
     *
     * @var array
     */
    protected $dateFactorChanges;

    /**
     * The name of the field where the date is calculated from
     *
     * @var string
     */
    protected $dateFrom;

    /**
     * The group (row) of data selected.
     *
     * @var integer
     */
    protected $dateGroup;

    /**
     * The number of periods shown before and after the current period.
     *
     * @var integer
     */
    protected $dateRange = 3;

    /**
     * Character D W M Y for one of the date types.
     *
     * @var string
     */
    protected $dateType = 'W';

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

    /**
     *
     * @var \Gems_Util
     */
    protected $util;

    /**
     *
     * @param string $name
     * @return \Gems_Selector_SelectorField
     */
    protected function addField($name)
    {
        $field = new \Gems_Selector_SelectorField($name);

        $this->_fields[$name] = $field;

        return $field;
    }

    /**
     * Creates the base model.
     *
     * @return \MUtil_Model_SelectModel
     */
    protected function createModel()
    {
        $groupby['period_1'] = new \Zend_Db_Expr("YEAR($this->dateFrom)");

        $date = new \MUtil_Date();

        switch ($this->dateType) {
            case 'D':
                $keyCount = 1;
                $groupby['period_1'] = new \Zend_Db_Expr("CONVERT($this->dateFrom, DATE)");

                $date->setTime(0);
                $date->addDay($this->dateFactor - $this->dateRange);

                $start = $date->getIso();
                for ($i = -$this->dateRange; $i <= $this->dateRange; $i++) {
                    if (0 == $i) {
                        $this->dateCurrentStart = clone $date;
                        $this->dateCurrentEnd   = clone $date;
                        $this->dateCurrentEnd->setTimeToDayEnd();
                    }

                    $values = array();
                    $values['period_1'] = $date->get('yyyy-MM-dd');
                    $values['range']    = $i;
                    $requiredRows[$i]   = $values;
                    $date->addDay(1);
                }
                $date->subSecond(1);
                $end = $date->getIso();
                break;

            case 'W':
                $keyCount = 2;

                // Use MONDAY as start of week
                $groupby['period_1'] = new \Zend_Db_Expr("substr(YEARWEEK(gto_valid_from, 3),1,4)");
                //$groupby['period_1'] = new \Zend_Db_Expr("YEAR($this->dateFrom) - CASE WHEN WEEK($this->dateFrom, 1) = 0 THEN 1 ELSE 0 END");
                $groupby['period_2'] = new \Zend_Db_Expr("WEEK($this->dateFrom, 3)");

                $date->setWeekday(1);
                $date->setTime(0);
                $date->addWeek($this->dateFactor - $this->dateRange);

                $start = $date->getIso();
                for ($i = -$this->dateRange; $i <= $this->dateRange; $i++) {
                    if (0 == $i) {
                        $this->dateCurrentStart = clone $date;
                        $this->dateCurrentEnd   = clone $date;
                        $this->dateCurrentEnd->addWeek(1)->subSecond(1);
                    }

                    $values = array();
                    $values['period_1'] = $date->get(\Zend_Date::YEAR);
                    $values['period_2'] = (int) $date->get(\Zend_Date::WEEK);   // Use constant but drop leading zero
                    // When monday is in the previous year, add one to the year
                    if ($date->get(\Zend_Date::DAY_OF_YEAR)>14 && $date->get(\Zend_Date::WEEK) == 1) {
                        $values['period_1'] =  $values['period_1'] + 1;
                    }
                    $values['range']    = $i;
                    $requiredRows[$i]   = $values;
                    $date->addWeek(1);
                }
                $date->subSecond(1);
                $end = $date->getIso();
                break;

            case 'M':
                $keyCount = 2;
                $groupby['period_2'] = new \Zend_Db_Expr("MONTH($this->dateFrom)");

                $date->setDay(1);
                $date->setTime(0);
                $date->addMonth($this->dateFactor - $this->dateRange);

                $start = $date->getIso();
                for ($i = -$this->dateRange; $i <= $this->dateRange; $i++) {
                    if (0 == $i) {
                        $this->dateCurrentStart = clone $date;
                        $this->dateCurrentEnd   = clone $date;
                        $this->dateCurrentEnd->addMonth(1)->subSecond(1);
                    }

                    $values = array();
                    $values['period_1'] = $date->get(\Zend_Date::YEAR);
                    $values['period_2'] = $date->get(\Zend_Date::MONTH);
                    $values['range']    = $i;
                    $requiredRows[$i]   = $values;
                    $date->addMonth(1);
                }
                $date->subSecond(1);
                $end = $date->getIso();
                break;

            case 'Y':
                $keyCount = 1;
                $date->setDay(1);
                $date->setMonth(1);
                $date->setTime(0);
                $date->addYear($this->dateFactor - $this->dateRange);
                $start = $date->getIso();
                for ($i = -$this->dateRange; $i <= $this->dateRange; $i++) {
                    if (0 == $i) {
                        $this->dateCurrentStart = clone $date;
                        $this->dateCurrentEnd   = clone $date;
                        $this->dateCurrentEnd->addYear(1)->subSecond(1);
                    }

                    $values = array();
                    $values['period_1'] = $date->get(\Zend_Date::YEAR);
                    $values['range']    = $i;
                    $requiredRows[$i]   = $values;
                    $date->addYear(1);
                }
                $date->subSecond(1);
                $end = $date->getIso();
                break;

            default:
                throw new \Gems_Exception_Coding('Incorrect date_type value: ' . $this->dateType);
        }
        $where = "$this->dateFrom BETWEEN '$start' AND '$end'";

        for ($i = -$this->dateRange; $i <= $this->dateRange; $i++) {
            $requiredRows[$i]['date_factor'] = $this->dateFactor + $i;
            $requiredRows[$i]['df_link']     = null;
            $requiredRows[$i]['df_label']    = null;
        }
        if ($this->dateRange > 0) {
            $requiredRows[-$this->dateRange]['df_link']  = $this->dateFactor - ($this->dateRange * 2);
            $requiredRows[-$this->dateRange]['df_label'] = $this->_('<<');
            $requiredRows[ $this->dateRange]['df_link']  = $this->dateFactor + ($this->dateRange * 2);
            $requiredRows[ $this->dateRange]['df_label'] = $this->_('>>');
            if ($this->dateRange > 1) {
                $i = intval($this->dateRange / 2);
                $requiredRows[-$i]['df_link']  = $this->dateFactor - 1;
                $requiredRows[-$i]['df_label'] = $this->_('<');
                $requiredRows[ $i]['df_link']  = $this->dateFactor + 1;
                $requiredRows[ $i]['df_label'] = $this->_('>');
            }
            $requiredRows[ 0]['df_link']  = $this->dateFactor ? '0' : null;
            $requiredRows[ 0]['df_label'] = $this->_('Now!');
        }

        if ($this->dateFactor) {
            $today = new \MUtil_Date();
            $this->dateFactorChanges['D'] = $this->dateCurrentStart->diffDays($today);
            $this->dateFactorChanges['W'] = $this->dateCurrentStart->diffWeeks($today);
            $this->dateFactorChanges['M'] = $this->dateCurrentStart->diffMonths($today);
            $this->dateFactorChanges['Y'] = $this->dateCurrentStart->diffYears($today);
        } else {
            $this->dateFactorChanges = array_fill_keys(array('D', 'W', 'M', 'Y'), 0);
        }
        // \MUtil_Echo::track($requiredRows);
        // \MUtil_Echo::rs($start, $end, $where);

        $select = new \Zend_Db_Select($this->db);
        $select->from($this->dataTableName, $groupby + $this->getDbFields());
        $select->where($where);
        $select->group($groupby);
        $select->order($groupby);

        $this->processSelect($select);

        // \MUtil_Echo::r((string) $select);

        $model = new \MUtil_Model_SelectModel($select, $this->dataTableName);

        $this->processModel($model);
        
        // Display by column cannot use formatFunction as it is a simple repeater
        // $model->set('duration_avg', 'formatFunction', $this->util->getLocalized()->formatNumber);

        $transformer = new \MUtil_Model_Transform_RequiredRowsTransformer();
        $transformer->setDefaultRow($this->getDefaultRow());
        $transformer->setRequiredRows($requiredRows);
        $transformer->setKeyItemCount($keyCount);
        $model->addTransformer($transformer);

        return $model;
    }

    protected function getDateDescriptions()
    {
        return array(
            'D' => $this->_('Show by day'),
            'W' => $this->_('Show by week'),
            'M' => $this->_('Show by month'),
            'Y' => $this->_('Show by year'),
            );
    }

    protected function getDateLabels()
    {
        return array_map('strtolower', array(
            'D' => $this->_('D'),
            'W' => $this->_('W'),
            'M' => $this->_('M'),
            'Y' => $this->_('Y'),
            ));
    }

    protected function getDbFields()
    {
        $results = array();
        foreach ($this->getFields() as $name => $field) {
            $results[$name] = $field->getSQL();
        }
        return $results;
    }

    /**
     * Returns defaults for all field values. Can be overruled.
     *
     * @return array An array with appropriate default values for use in \MUtil_Model_Transform_RequiredRowsTransformer
     */
    protected function getDefaultRow()
    {
        $results = array();
        foreach ($this->getFields() as $name => $field) {
            $results[$name] = $field->getDefault();
        }
        return $results;
    }

    /**
     * Returns defaults for this filter. Can be overruled.
     *
     * @return array An array with appropriate default values for filtering
     */
    public function getDefaultSearchData()
    {
        return array(
            self::DATE_FACTOR => 0,
            self::DATE_GROUP => null,
            self::DATE_TYPE => 'W');
    }

    protected function getFields()
    {
        if (! $this->_fields) {
            $this->loadFields();
        }

        return $this->_fields;
    }

    /**
     * Returns the base model.
     *
     * @return \MUtil_Model_Transform_RequiredRowsTransformer
     */
    public function getModel()
    {
        if (! $this->_model) {
            $this->_model = $this->createModel();
        }

        return $this->_model;
    }

    /**
     * Prcesses the filter for the date selector and return the filter to use instead
     *
     * @param string $dateField
     * @return array The new complete filter to use
     */
    public function getSelectorFilterPart($dateField = null)
    {
        // \MUtil_Echo::track($filter);
        $newfilter = [];

        if ($this->dateCurrentStart && $this->dateCurrentEnd) {
            if (null === $dateField) {
                $dateField = $this->dateFrom;
            }
            $start = $this->dateCurrentStart->getIso();
            $end   = $this->dateCurrentEnd->getIso();
            $newfilter[] = "$dateField BETWEEN '$start' AND '$end'";
        }

        if ($this->dateGroup) {
            $fields = $this->getFields();
            if (isset($fields[$this->dateGroup])) {
                if ($groupfilter = $fields[$this->dateGroup]->getFilter()) {
                    $newfilter[] = $groupfilter;
                }
            }
        }

        return $newfilter;
    }

    public function getTable($baseurl)
    {
        $model    = $this->getModel();
        $bridge   = $model->getBridgeFor('table', array('class' => 'timeTable table table-condensed table-bordered'));
        $repeater = $bridge->getRepeater();

        $bridge->setBaseUrl(array($this->_actionKey => 'index', 'reset' => null) + $baseurl); // + $model->getFilter();

        $columnClass = \MUtil_Lazy::iff($repeater->range, null, 'selectedColumn');

        $this->setTableHeader($bridge, $repeater, $columnClass);
        $this->setTableBody(  $bridge, $repeater, $columnClass);
        $this->setTableFooter($bridge, $repeater, $columnClass);

        return $bridge->getTable();
    }

    /**
     * Loads the fields for this instance.
     */
    abstract protected function loadFields();

    /**
     * Stub function to allow extension of model with extra columns
     *
     * @param \MUtil_Model_ModelAbstract $model
     */
    protected function processModel(\MUtil_Model_ModelAbstract $model)
    {  }

    /**
     * Stub function to allow extension of standard one table select.
     *
     * @param \Zend_Db_Select $select
     */
    protected function processSelect(\Zend_Db_Select $select)
    {  }
    
    /**
     * Processing of filter, sets the selected position in the overview table.
     * Can be overriden.
     *
     * @param \Zend_Controller_Request_Abstract $request
     * @param array $filter
     * @return array The filter minus the Selector fields
     */
    public function processSelectorFilter(\Zend_Controller_Request_Abstract $request, array $filter)
    {
        $this->_actionKey = $request->getActionKey();

        $defaults = $this->getDefaultSearchData();

        $this->dateFactor = $this->processSelectorFilterName(self::DATE_FACTOR, $request, $filter, $defaults);
        $this->dateGroup  = $this->processSelectorFilterName(self::DATE_GROUP, $request, $filter, $defaults);
        $this->dateType   = $this->processSelectorFilterName(self::DATE_TYPE, $request, $filter, $defaults);

        unset($filter[self::DATE_FACTOR], $filter[self::DATE_GROUP], $filter[self::DATE_TYPE]);

        return $filter;
    }

    protected function processSelectorFilterName($name, \Zend_Controller_Request_Abstract $request, array $filter, array $defaults = null)
    {
        if (isset($filter[$name])) {
            return $filter[$name];
        }
        if ($val = $request->getParam($name)) {
            return $val;
        }
        if (is_array($defaults)) {
            if (isset($defaults[$name])) {
                return $defaults[$name];
            }
        } else {
            return $defaults;
        }
    }
    
    /**
     * Set the filter for the whole table
     * @param array $filter
     */
    public function setFilter(array $filter)
    {
        $model = $this->getModel();
        $model->setFilter($filter);
    }

    protected function setTableBody(\MUtil_Model_Bridge_TableBridge $bridge, \MUtil_Lazy_RepeatableInterface $repeater, $columnClass)
    {
        $baseurl = $bridge->getBaseUrl();
        $onEmpty = $this->_('-');

        foreach ($this->getFields() as $name => $field) {
            $bridge->tr(array('class' => ($this->dateGroup == $name) ? 'selectedRow' : null));

            // Left cell
            $td = $bridge->td($field->getLabel());
            $td->class = $field->getLabelClass();

            // Repeating column
            $href = $field->getHRef($repeater, $baseurl);
            $td = $bridge->td();
            $td->a($href, $repeater->$name);
            $td->class = array($this->dataCellClass, $field->getClass(), $columnClass);
            $td->onclick = array('location.href=\'', $href, '\';');
            $td->setOnEmpty($onEmpty);
            $td->setRepeater($repeater);
            $td->setRepeatTags(true);
        }
    }

    protected function setTableFooter(\MUtil_Model_Bridge_TableBridge $bridge, \MUtil_Lazy_RepeatableInterface $repeater, $columnClass)
    {
        $baseurl = $bridge->getBaseUrl();

        // Empty cell for left column
        $bridge->tf();

        $href = array(
            self::DATE_FACTOR => $repeater->df_link,
            \MUtil_Model::AUTOSEARCH_RESET => null,
            ) + $baseurl;

        // Repeating column
        $tf = $bridge->tf();
        $tf->class = array($this->dataCellClass, $columnClass);
        $tf->iflink($repeater->df_link->strlen(),
            array('href' => $href, $repeater->df_label, 'class' => 'browselink btn btn-xs'),
            array($repeater->df_label, 'class' => 'browselink btn btn-xs disabled'));
        $tf->setRepeater($repeater);
        $tf->setRepeatTags(true);
    }

    protected function setTableHeader(\MUtil_Model_Bridge_TableBridge $bridge, \MUtil_Lazy_RepeatableInterface $repeater, $columnClass)
    {
        $baseurl = $bridge->getBaseUrl();

        // Left cell with period types
        $th = $bridge->th($this->_('Period'), ' ');
        $th->class = 'middleAlign';
        $thdiv = $th->span()->spaced(); // array('class' => 'rightFloat'));
        $contents = $this->getDateLabels();
        foreach ($this->getDateDescriptions() as $letter => $title) {
            if (isset($contents[$letter])) {
                $content = $contents[$letter];
            } else {
                $content = strtolower($this->_($letter));
            }
            if ($letter == $this->dateType) {
                $thdiv->span($content, array('class' => 'browselink btn btn-primary btn-xs disabled'));
            } else {
                $thdiv->a(array(self::DATE_TYPE => $letter, self::DATE_FACTOR => $this->dateFactorChanges[$letter]) + $baseurl,
                        $content,
                        array('class' => 'browselink btn btn-default btn-xs', 'title' => $title));
            }
        }

        // Repeating column
        switch ($this->dateType) {
            case 'D':
                // $header = $repeater->period_1;
                $header = $repeater->period_1->call($this->util->getTranslated()->formatDate);
                break;

            case 'W':
                $header = array($repeater->period_1, \MUtil_Html::create()->br(),
                    \MUtil_Lazy::call('sprintf', $this->_('week %s'), $repeater->period_2));
                break;

            case 'M':
                $header = array($repeater->period_1, \MUtil_Html::create()->br(),
                    $repeater->period_2->call($this->util->getLocalized()->getMonthName));
                break;

            case 'Y':
                $header = $repeater->period_1;
                break;

            default:
                throw new \Gems_Exception_Coding('Incorrect date_type value: ' . $this->dateType); //  $this->_getParam('date_type', 'W'));
        }
        $th = $bridge->th();
        $th->class = array($this->dataCellClass, $columnClass);
        $th->a(array(self::DATE_FACTOR => $repeater->date_factor, \MUtil_Model::AUTOSEARCH_RESET => null) + $baseurl,
                $header
                );
        $th->setRepeater($repeater);
        $th->setRepeatTags(true);

        $baseurl[\Gems_Selector_DateSelectorAbstract::DATE_FACTOR] = $repeater->date_factor;
        $baseurl[\Gems_Selector_DateSelectorAbstract::DATE_GROUP]  = null;
        $th->onclick = array('location.href=\'', new \MUtil_Html_HrefArrayAttribute($baseurl), '\';');
    }
}