only imports conflicted
This commit is contained in:
Lesstat
2015-07-11 11:44:26 +02:00
95 changed files with 2041 additions and 794 deletions

View File

@@ -49,11 +49,6 @@ class Authentication extends Base
return false;
}
// We update each time the RememberMe cookie tokens
if ($this->backend('rememberMe')->hasCookie()) {
$this->backend('rememberMe')->refresh();
}
return true;
}

View File

@@ -85,8 +85,7 @@ class DateParser extends Base
*/
public function getTimestamp($value)
{
foreach ($this->getDateFormats() as $format) {
foreach ($this->getAllFormats() as $format) {
$timestamp = $this->getValidDate($value, $format);
if ($timestamp !== 0) {
@@ -97,6 +96,25 @@ class DateParser extends Base
return 0;
}
/**
* Get all combinations of date/time formats
*
* @access public
* @return []string
*/
public function getAllFormats()
{
$formats = array();
foreach ($this->getDateFormats() as $date) {
foreach ($this->getTimeFormats() as $time) {
$formats[] = $date.' '.$time;
}
}
return array_merge($formats, $this->getDateFormats());
}
/**
* Return the list of supported date formats (for the parser)
*
@@ -112,6 +130,21 @@ class DateParser extends Base
);
}
/**
* Return the list of supported time formats (for the parser)
*
* @access public
* @return string[]
*/
public function getTimeFormats()
{
return array(
'H:i',
'g:i A',
'g:iA',
);
}
/**
* Return the list of available date formats (for the config page)
*
@@ -143,7 +176,7 @@ class DateParser extends Base
* Get a timetstamp from an ISO date format
*
* @access public
* @param string $date Date format
* @param string $date
* @return integer
*/
public function getTimestampFromIsoFormat($date)
@@ -166,7 +199,6 @@ class DateParser extends Base
}
foreach ($fields as $field) {
if (! empty($values[$field])) {
$values[$field] = date($format, $values[$field]);
}
@@ -180,15 +212,16 @@ class DateParser extends Base
* Convert date (form input data)
*
* @access public
* @param array $values Database values
* @param string[] $fields Date fields
* @param array $values Database values
* @param string[] $fields Date fields
* @param boolean $keep_time Keep time or not
*/
public function convert(array &$values, array $fields)
public function convert(array &$values, array $fields, $keep_time = false)
{
foreach ($fields as $field) {
if (! empty($values[$field]) && ! is_numeric($values[$field])) {
$values[$field] = $this->removeTimeFromTimestamp($this->getTimestamp($values[$field]));
$timestamp = $this->getTimestamp($values[$field]);
$values[$field] = $keep_time ? $timestamp : $this->removeTimeFromTimestamp($timestamp);
}
}
}

View File

@@ -49,7 +49,7 @@ class ProjectAnalytic extends Base
* Get users repartition
*
* @access public
* @param integer $project_id Project id
* @param integer $project_id
* @return array
*/
public function getUserRepartition($project_id)
@@ -87,4 +87,96 @@ class ProjectAnalytic extends Base
return array_values($metrics);
}
/**
* Get the average lead and cycle time
*
* @access public
* @param integer $project_id
* @return array
*/
public function getAverageLeadAndCycleTime($project_id)
{
$stats = array(
'count' => 0,
'total_lead_time' => 0,
'total_cycle_time' => 0,
'avg_lead_time' => 0,
'avg_cycle_time' => 0,
);
$tasks = $this->db
->table(Task::TABLE)
->columns('date_completed', 'date_creation', 'date_started')
->eq('project_id', $project_id)
->desc('id')
->limit(1000)
->findAll();
foreach ($tasks as &$task) {
$stats['count']++;
$stats['total_lead_time'] += ($task['date_completed'] ?: time()) - $task['date_creation'];
$stats['total_cycle_time'] += empty($task['date_started']) ? 0 : ($task['date_completed'] ?: time()) - $task['date_started'];
}
$stats['avg_lead_time'] = (int) ($stats['total_lead_time'] / $stats['count']);
$stats['avg_cycle_time'] = (int) ($stats['total_cycle_time'] / $stats['count']);
return $stats;
}
/**
* Get the average time spent into each column
*
* @access public
* @param integer $project_id
* @return array
*/
public function getAverageTimeSpentByColumn($project_id)
{
$stats = array();
$columns = $this->board->getColumnsList($project_id);
// Get the time spent of the last move for each tasks
$tasks = $this->db
->table(Task::TABLE)
->columns('id', 'date_completed', 'date_moved', 'column_id')
->eq('project_id', $project_id)
->desc('id')
->limit(1000)
->findAll();
// Init values
foreach ($columns as $column_id => $column_title) {
$stats[$column_id] = array(
'count' => 0,
'time_spent' => 0,
'average' => 0,
'title' => $column_title,
);
}
// Get time spent foreach task/column and take into account the last move
foreach ($tasks as &$task) {
$sums = $this->transition->getTimeSpentByTask($task['id']);
if (! isset($sums[$task['column_id']])) {
$sums[$task['column_id']] = 0;
}
$sums[$task['column_id']] += ($task['date_completed'] ?: time()) - $task['date_moved'];
foreach ($sums as $column_id => $time_spent) {
$stats[$column_id]['count']++;
$stats[$column_id]['time_spent'] += $time_spent;
}
}
// Calculate average for each column
foreach ($columns as $column_id => $column_title) {
$stats[$column_id]['average'] = (int) ($stats[$column_id]['time_spent'] / $stats[$column_id]['count']);
}
return $stats;
}
}

View File

@@ -3,22 +3,22 @@
namespace Model;
/**
* Project daily summary
* Project Daily Column Stats
*
* @package model
* @author Frederic Guillot
*/
class ProjectDailySummary extends Base
class ProjectDailyColumnStats extends Base
{
/**
* SQL table name
*
* @var string
*/
const TABLE = 'project_daily_summaries';
const TABLE = 'project_daily_column_stats';
/**
* Update daily totals for the project
* Update daily totals for the project and foreach column
*
* "total" is the number open of tasks in the column
* "score" is the sum of tasks score in the column
@@ -38,7 +38,7 @@ class ProjectDailySummary extends Base
// This call will fail if the record already exists
// (cross database driver hack for INSERT..ON DUPLICATE KEY UPDATE)
$db->table(ProjectDailySummary::TABLE)->insert(array(
$db->table(ProjectDailyColumnStats::TABLE)->insert(array(
'day' => $date,
'project_id' => $project_id,
'column_id' => $column_id,
@@ -46,7 +46,7 @@ class ProjectDailySummary extends Base
'score' => 0,
));
$db->table(ProjectDailySummary::TABLE)
$db->table(ProjectDailyColumnStats::TABLE)
->eq('project_id', $project_id)
->eq('column_id', $column_id)
->eq('day', $date)
@@ -95,19 +95,19 @@ class ProjectDailySummary extends Base
*/
public function getRawMetrics($project_id, $from, $to)
{
return $this->db->table(ProjectDailySummary::TABLE)
return $this->db->table(ProjectDailyColumnStats::TABLE)
->columns(
ProjectDailySummary::TABLE.'.column_id',
ProjectDailySummary::TABLE.'.day',
ProjectDailySummary::TABLE.'.total',
ProjectDailySummary::TABLE.'.score',
ProjectDailyColumnStats::TABLE.'.column_id',
ProjectDailyColumnStats::TABLE.'.day',
ProjectDailyColumnStats::TABLE.'.total',
ProjectDailyColumnStats::TABLE.'.score',
Board::TABLE.'.title AS column_title'
)
->join(Board::TABLE, 'id', 'column_id')
->eq(ProjectDailySummary::TABLE.'.project_id', $project_id)
->eq(ProjectDailyColumnStats::TABLE.'.project_id', $project_id)
->gte('day', $from)
->lte('day', $to)
->asc(ProjectDailySummary::TABLE.'.day')
->asc(ProjectDailyColumnStats::TABLE.'.day')
->findAll();
}
@@ -122,17 +122,17 @@ class ProjectDailySummary extends Base
*/
public function getRawMetricsByDay($project_id, $from, $to)
{
return $this->db->table(ProjectDailySummary::TABLE)
return $this->db->table(ProjectDailyColumnStats::TABLE)
->columns(
ProjectDailySummary::TABLE.'.day',
'SUM('.ProjectDailySummary::TABLE.'.total) AS total',
'SUM('.ProjectDailySummary::TABLE.'.score) AS score'
ProjectDailyColumnStats::TABLE.'.day',
'SUM('.ProjectDailyColumnStats::TABLE.'.total) AS total',
'SUM('.ProjectDailyColumnStats::TABLE.'.score) AS score'
)
->eq(ProjectDailySummary::TABLE.'.project_id', $project_id)
->eq(ProjectDailyColumnStats::TABLE.'.project_id', $project_id)
->gte('day', $from)
->lte('day', $to)
->asc(ProjectDailySummary::TABLE.'.day')
->groupBy(ProjectDailySummary::TABLE.'.day')
->asc(ProjectDailyColumnStats::TABLE.'.day')
->groupBy(ProjectDailyColumnStats::TABLE.'.day')
->findAll();
}
@@ -160,7 +160,7 @@ class ProjectDailySummary extends Base
$aggregates = array();
// Fetch metrics for the project
$records = $this->db->table(ProjectDailySummary::TABLE)
$records = $this->db->table(ProjectDailyColumnStats::TABLE)
->eq('project_id', $project_id)
->gte('day', $from)
->lte('day', $to)

View File

@@ -0,0 +1,72 @@
<?php
namespace Model;
/**
* Project Daily Stats
*
* @package model
* @author Frederic Guillot
*/
class ProjectDailyStats extends Base
{
/**
* SQL table name
*
* @var string
*/
const TABLE = 'project_daily_stats';
/**
* Update daily totals for the project
*
* @access public
* @param integer $project_id Project id
* @param string $date Record date (YYYY-MM-DD)
* @return boolean
*/
public function updateTotals($project_id, $date)
{
$lead_cycle_time = $this->projectAnalytic->getAverageLeadAndCycleTime($project_id);
return $this->db->transaction(function($db) use ($project_id, $date, $lead_cycle_time) {
// This call will fail if the record already exists
// (cross database driver hack for INSERT..ON DUPLICATE KEY UPDATE)
$db->table(ProjectDailyStats::TABLE)->insert(array(
'day' => $date,
'project_id' => $project_id,
'avg_lead_time' => 0,
'avg_cycle_time' => 0,
));
$db->table(ProjectDailyStats::TABLE)
->eq('project_id', $project_id)
->eq('day', $date)
->update(array(
'avg_lead_time' => $lead_cycle_time['avg_lead_time'],
'avg_cycle_time' => $lead_cycle_time['avg_cycle_time'],
));
});
}
/**
* Get raw metrics for the project within a data range
*
* @access public
* @param integer $project_id Project id
* @param string $from Start date (ISO format YYYY-MM-DD)
* @param string $to End date
* @return array
*/
public function getRawMetrics($project_id, $from, $to)
{
return $this->db->table(self::TABLE)
->columns('day', 'avg_lead_time', 'avg_cycle_time')
->eq(self::TABLE.'.project_id', $project_id)
->gte('day', $from)
->lte('day', $to)
->asc(self::TABLE.'.day')
->findAll();
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace Model;
/**
* Task Analytic
*
* @package model
* @author Frederic Guillot
*/
class TaskAnalytic extends Base
{
/**
* Get the time between date_creation and date_completed or now if empty
*
* @access public
* @param array $task
* @return integer
*/
public function getLeadTime(array $task)
{
return ($task['date_completed'] ?: time()) - $task['date_creation'];
}
/**
* Get the time between date_started and date_completed or now if empty
*
* @access public
* @param array $task
* @return integer
*/
public function getCycleTime(array $task)
{
if (empty($task['date_started'])) {
return 0;
}
return ($task['date_completed'] ?: time()) - $task['date_started'];
}
/**
* Get the average time spent in each column
*
* @access public
* @param array $task
* @return array
*/
public function getTimeSpentByColumn(array $task)
{
$result = array();
$columns = $this->board->getColumnsList($task['project_id']);
$sums = $this->transition->getTimeSpentByTask($task['id']);
foreach ($columns as $column_id => $column_title) {
$time_spent = isset($sums[$column_id]) ? $sums[$column_id] : 0;
if ($task['column_id'] == $column_id) {
$time_spent += ($task['date_completed'] ?: time()) - $task['date_moved'];
}
$result[] = array(
'id' => $column_id,
'title' => $column_title,
'time_spent' => $time_spent,
);
}
return $result;
}
}

View File

@@ -43,9 +43,10 @@ class TaskCreation extends Base
*/
public function prepare(array &$values)
{
$this->dateParser->convert($values, array('date_due', 'date_started'));
$this->dateParser->convert($values, array('date_due'));
$this->dateParser->convert($values, array('date_started'), true);
$this->removeFields($values, array('another_task'));
$this->resetFields($values, array('owner_id', 'swimlane_id', 'date_due', 'score', 'category_id', 'time_estimated'));
$this->resetFields($values, array('creator_id', 'owner_id', 'swimlane_id', 'date_due', 'score', 'category_id', 'time_estimated'));
if (empty($values['column_id'])) {
$values['column_id'] = $this->board->getFirstColumn($values['project_id']);
@@ -59,6 +60,10 @@ class TaskCreation extends Base
$values['title'] = t('Untitled');
}
if ($this->userSession->isLogged()) {
$values['creator_id'] = $this->userSession->getId();
}
$values['swimlane_id'] = empty($values['swimlane_id']) ? 0 : $values['swimlane_id'];
$values['date_creation'] = time();
$values['date_modification'] = $values['date_creation'];

View File

@@ -118,7 +118,7 @@ class TaskFilter extends Base
* Exclude a list of task_id
*
* @access public
* @param array $task_ids
* @param integer[] $task_ids
* @return TaskFilter
*/
public function excludeTasks(array $task_ids)
@@ -641,10 +641,10 @@ class TaskFilter extends Base
* Transform results to ical events
*
* @access public
* @param string $start_column Column name for the start date
* @param string $end_column Column name for the end date
* @param Eluceo\iCal\Component\Calendar $vCalendar Calendar object
* @return Eluceo\iCal\Component\Calendar
* @param string $start_column Column name for the start date
* @param string $end_column Column name for the end date
* @param Calendar $vCalendar Calendar object
* @return Calendar
*/
public function addDateTimeIcalEvents($start_column, $end_column, Calendar $vCalendar = null)
{
@@ -674,9 +674,9 @@ class TaskFilter extends Base
* Transform results to all day ical events
*
* @access public
* @param string $column Column name for the date
* @param Eluceo\iCal\Component\Calendar $vCalendar Calendar object
* @return Eluceo\iCal\Component\Calendar
* @param string $column Column name for the date
* @param Calendar $vCalendar Calendar object
* @return Calendar
*/
public function addAllDayIcalEvents($column = 'date_due', Calendar $vCalendar = null)
{
@@ -706,7 +706,7 @@ class TaskFilter extends Base
* @access protected
* @param array $task
* @param string $uid
* @return Eluceo\iCal\Component\Event
* @return Event
*/
protected function getTaskIcalEvent(array &$task, $uid)
{
@@ -723,11 +723,11 @@ class TaskFilter extends Base
$vEvent->setSummary(t('#%d', $task['id']).' '.$task['title']);
$vEvent->setUrl($this->helper->url->base().$this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
if (! empty($task['creator_id'])) {
$vEvent->setOrganizer('MAILTO:'.($task['creator_email'] ?: $task['creator_username'].'@kanboard.local'));
if (! empty($task['owner_id'])) {
$vEvent->setOrganizer('MAILTO:'.($task['assignee_email'] ?: $task['assignee_username'].'@kanboard.local'));
}
if (! empty($task['owner_id'])) {
if (! empty($task['creator_id'])) {
$attendees = new Attendees;
$attendees->add('MAILTO:'.($task['creator_email'] ?: $task['creator_username'].'@kanboard.local'));
$vEvent->setAttendees($attendees);

View File

@@ -4,7 +4,6 @@ namespace Model;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
use PicoDb\Table;
/**
* TaskLink model

View File

@@ -83,7 +83,8 @@ class TaskModification extends Base
*/
public function prepare(array &$values)
{
$this->dateParser->convert($values, array('date_due', 'date_started'));
$this->dateParser->convert($values, array('date_due'));
$this->dateParser->convert($values, array('date_started'), true);
$this->removeFields($values, array('another_task', 'id'));
$this->resetFields($values, array('date_due', 'date_started', 'score', 'category_id', 'time_estimated', 'time_spent'));
$this->convertIntegerFields($values, array('is_active', 'recurrence_status', 'recurrence_trigger', 'recurrence_factor', 'recurrence_timeframe', 'recurrence_basedate'));

View File

@@ -26,109 +26,176 @@ class TaskPosition extends Base
*/
public function movePosition($project_id, $task_id, $column_id, $position, $swimlane_id = 0, $fire_events = true)
{
$original_task = $this->taskFinder->getById($task_id);
if ($position < 1) {
return false;
}
$task = $this->taskFinder->getById($task_id);
// Ignore closed tasks
if ($original_task['is_active'] == Task::STATUS_CLOSED) {
if ($task['is_active'] == Task::STATUS_CLOSED) {
return true;
}
$result = $this->calculateAndSave($project_id, $task_id, $column_id, $position, $swimlane_id);
$result = false;
if ($result) {
if ($task['swimlane_id'] != $swimlane_id) {
$result = $this->saveSwimlaneChange($project_id, $task_id, $position, $task['column_id'], $column_id, $task['swimlane_id'], $swimlane_id);
}
else if ($task['column_id'] != $column_id) {
$result = $this->saveColumnChange($project_id, $task_id, $position, $swimlane_id, $task['column_id'], $column_id);
}
else if ($task['position'] != $position) {
$result = $this->savePositionChange($project_id, $task_id, $position, $column_id, $swimlane_id);
}
if ($original_task['swimlane_id'] != $swimlane_id) {
$this->calculateAndSave($project_id, 0, $column_id, 1, $original_task['swimlane_id']);
}
if ($fire_events) {
$this->fireEvents($original_task, $column_id, $position, $swimlane_id);
}
if ($result && $fire_events) {
$this->fireEvents($task, $column_id, $position, $swimlane_id);
}
return $result;
}
/**
* Calculate the new position of all tasks
* Move a task to another swimlane
*
* @access public
* @param integer $project_id Project id
* @param integer $task_id Task id
* @param integer $column_id Column id
* @param integer $position Position (must be >= 1)
* @param integer $swimlane_id Swimlane id
* @return array|boolean
* @access private
* @param integer $project_id
* @param integer $task_id
* @param integer $position
* @param integer $original_column_id
* @param integer $new_column_id
* @param integer $original_swimlane_id
* @param integer $new_swimlane_id
* @return boolean
*/
public function calculatePositions($project_id, $task_id, $column_id, $position, $swimlane_id = 0)
private function saveSwimlaneChange($project_id, $task_id, $position, $original_column_id, $new_column_id, $original_swimlane_id, $new_swimlane_id)
{
// The position can't be lower than 1
if ($position < 1) {
return false;
}
$this->db->startTransaction();
$r1 = $this->saveTaskPositions($project_id, $task_id, 0, $original_column_id, $original_swimlane_id);
$r2 = $this->saveTaskPositions($project_id, $task_id, $position, $new_column_id, $new_swimlane_id);
$this->db->closeTransaction();
$board = $this->db->table(Board::TABLE)->eq('project_id', $project_id)->asc('position')->findAllByColumn('id');
$columns = array();
// For each column fetch all tasks ordered by position
foreach ($board as $board_column_id) {
$columns[$board_column_id] = $this->db->table(Task::TABLE)
->eq('is_active', 1)
->eq('swimlane_id', $swimlane_id)
->eq('project_id', $project_id)
->eq('column_id', $board_column_id)
->neq('id', $task_id)
->asc('position')
->asc('id') // Fix Postgresql unit test
->findAllByColumn('id');
}
// The column must exists
if (! isset($columns[$column_id])) {
return false;
}
// We put our task to the new position
if ($task_id) {
array_splice($columns[$column_id], $position - 1, 0, $task_id);
}
return $columns;
return $r1 && $r2;
}
/**
* Save task positions
* Move a task to another column
*
* @access private
* @param array $columns Sorted tasks
* @param integer $swimlane_id Swimlane id
* @param integer $project_id
* @param integer $task_id
* @param integer $position
* @param integer $swimlane_id
* @param integer $original_column_id
* @param integer $new_column_id
* @return boolean
*/
private function savePositions(array $columns, $swimlane_id)
private function saveColumnChange($project_id, $task_id, $position, $swimlane_id, $original_column_id, $new_column_id)
{
return $this->db->transaction(function ($db) use ($columns, $swimlane_id) {
$this->db->startTransaction();
$r1 = $this->saveTaskPositions($project_id, $task_id, 0, $original_column_id, $swimlane_id);
$r2 = $this->saveTaskPositions($project_id, $task_id, $position, $new_column_id, $swimlane_id);
$this->db->closeTransaction();
foreach ($columns as $column_id => $column) {
return $r1 && $r2;
}
$position = 1;
/**
* Move a task to another position in the same column
*
* @access private
* @param integer $project_id
* @param integer $task_id
* @param integer $position
* @param integer $column_id
* @param integer $swimlane_id
* @return boolean
*/
private function savePositionChange($project_id, $task_id, $position, $column_id, $swimlane_id)
{
$this->db->startTransaction();
$result = $this->saveTaskPositions($project_id, $task_id, $position, $column_id, $swimlane_id);
$this->db->closeTransaction();
foreach ($column as $task_id) {
return $result;
}
$result = $db->table(Task::TABLE)->eq('id', $task_id)->update(array(
'position' => $position,
'column_id' => $column_id,
'swimlane_id' => $swimlane_id,
));
/**
* Save all task positions for one column
*
* @access private
* @param integer $project_id
* @param integer $task_id
* @param integer $position
* @param integer $column_id
* @param integer $swimlane_id
* @return boolean
*/
private function saveTaskPositions($project_id, $task_id, $position, $column_id, $swimlane_id)
{
$tasks_ids = $this->db->table(Task::TABLE)
->eq('is_active', 1)
->eq('swimlane_id', $swimlane_id)
->eq('project_id', $project_id)
->eq('column_id', $column_id)
->neq('id', $task_id)
->asc('position')
->asc('id')
->findAllByColumn('id');
if (! $result) {
return false;
}
$offset = 1;
$position++;
foreach ($tasks_ids as $current_task_id) {
// Insert the new task
if ($position == $offset) {
if (! $this->saveTaskPosition($task_id, $offset, $column_id, $swimlane_id)) {
return false;
}
$offset++;
}
});
// Rewrite other tasks position
if (! $this->saveTaskPosition($current_task_id, $offset, $column_id, $swimlane_id)) {
return false;
}
$offset++;
}
// Insert the new task at the bottom and normalize bad position
if ($position >= $offset && ! $this->saveTaskPosition($task_id, $offset, $column_id, $swimlane_id)) {
return false;
}
return true;
}
/**
* Save new task position
*
* @access private
* @param integer $task_id
* @param integer $position
* @param integer $column_id
* @param integer $swimlane_id
* @return boolean
*/
private function saveTaskPosition($task_id, $position, $column_id, $swimlane_id)
{
$result = $this->db->table(Task::TABLE)->eq('id', $task_id)->update(array(
'position' => $position,
'column_id' => $column_id,
'swimlane_id' => $swimlane_id,
));
if (! $result) {
$this->db->cancelTransaction();
return false;
}
return true;
}
/**
@@ -165,26 +232,4 @@ class TaskPosition extends Base
$this->container['dispatcher']->dispatch(Task::EVENT_MOVE_POSITION, new TaskEvent($event_data));
}
}
/**
* Calculate the new position of all tasks
*
* @access private
* @param integer $project_id Project id
* @param integer $task_id Task id
* @param integer $column_id Column id
* @param integer $position Position (must be >= 1)
* @param integer $swimlane_id Swimlane id
* @return boolean
*/
private function calculateAndSave($project_id, $task_id, $column_id, $position, $swimlane_id)
{
$positions = $this->calculatePositions($project_id, $task_id, $column_id, $position, $swimlane_id);
if ($positions === false || ! $this->savePositions($positions, $swimlane_id)) {
return false;
}
return true;
}
}

View File

@@ -39,7 +39,7 @@ class TaskValidator extends Base
new Validators\Integer('recurrence_status', t('This value must be an integer')),
new Validators\MaxLength('title', t('The maximum length is %d characters', 200), 200),
new Validators\Date('date_due', t('Invalid date'), $this->dateParser->getDateFormats()),
new Validators\Date('date_started', t('Invalid date'), $this->dateParser->getDateFormats()),
new Validators\Date('date_started', t('Invalid date'), $this->dateParser->getAllFormats()),
new Validators\Numeric('time_spent', t('This value must be numeric')),
new Validators\Numeric('time_estimated', t('This value must be numeric')),
);

View File

@@ -38,6 +38,22 @@ class Transition extends Base
));
}
/**
* Get time spent by task for each column
*
* @access public
* @param integer $task_id
* @return array
*/
public function getTimeSpentByTask($task_id)
{
return $this->db
->hashtable(self::TABLE)
->groupBy('src_column_id')
->eq('task_id', $task_id)
->getAll('src_column_id', 'SUM(time_spent) AS time_spent');
}
/**
* Get all transitions by task
*

View File

@@ -118,4 +118,28 @@ class UserSession extends Base
{
$_SESSION['filters'][$project_id] = $filters;
}
/**
* Is board collapsed or expanded
*
* @access public
* @param integer $project_id
* @return boolean
*/
public function isBoardCollapsed($project_id)
{
return ! empty($_SESSION['board_collapsed'][$project_id]) ? $_SESSION['board_collapsed'][$project_id] : false;
}
/**
* Set board display mode
*
* @access public
* @param integer $project_id
* @param boolean $collapsed
*/
public function setBoardDisplayMode($project_id, $collapsed)
{
$_SESSION['board_collapsed'][$project_id] = $collapsed;
}
}