Files
2024-04-10 05:04:20 +00:00

3176 lines
113 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* The model file of pivot module of ZenTaoPMS.
*
* @copyright Copyright 2009-2015 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.cnezsoft.com)
* @license ZPL(http://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
* @author Chunsheng Wang <chunsheng@cnezsoft.com>
* @package pivot
* @version $Id: model.php 4726 2013-05-03 05:51:27Z chencongzhi520@gmail.com $
* @link http://www.zentao.net
*/
class pivotModel extends model
{
/**
* Construct.
*
* @access public
* @return void
*/
public function __construct()
{
parent::__construct();
$this->loadBIDAO();
}
/**
* Get pivot.
*
* @param int $pivotID
* @access public
* @return object
*/
public function getByID($pivotID, $processDateVar = false)
{
$pivot = $this->dao->select('*')->from(TABLE_PIVOT)->where('id')->eq($pivotID)->fetch();
if(!$pivot) return false;
if(!empty($pivot->fields) and $pivot->fields != 'null')
{
$pivot->fieldSettings = json_decode($pivot->fields);
$pivot->fields = array();
foreach($pivot->fieldSettings as $field => $settings) $pivot->fields[] = $field;
}
else
{
$pivot->fieldSettings = array();
}
if(!empty($pivot->filters))
{
$filters = json_decode($pivot->filters, true);
foreach($filters as $key => $filter)
{
if(empty($filter['default'])) continue;
if($processDateVar) $filters[$key]['default'] = $this->processDateVar($filter['default']);
}
$pivot->filters = $filters;
}
return $this->processPivot($pivot);
}
/**
* Get pivots.
*
* @param int $dimensionID
* @param int $groupID
* @param string $orderBy
* @param object $pager
* @access public
* @return array
*/
public function getList($dimensionID = 0, $groupID = 0, $orderBy = 'id_desc', $pager = null)
{
$this->loadModel('screen');
if($groupID)
{
$groups = $this->dao->select('id')->from(TABLE_MODULE)
->where('deleted')->eq(0)
->andWhere('type')->eq('pivot')
->andWhere('path')->like("%,$groupID,%")
->fetchPairs('id');
$conditions = '';
foreach($groups as $groupID)
{
$conditions .= " FIND_IN_SET($groupID, `group`) or";
}
$conditions = trim($conditions, 'or');
$pivots = $this->dao->select('*')->from(TABLE_PIVOT)
->where('deleted')->eq(0)
->beginIF($conditions)->andWhere("({$conditions})")->fi()
->beginIF(!empty($dimensionID))->andWhere('dimension')->eq($dimensionID)->fi()
->orderBy($orderBy)
->fetchAll();
$charts = $this->dao->select('*')->from(TABLE_CHART)
->where('deleted')->eq(0)
->andWhere('type')->eq('table')
->beginIF($conditions)->andWhere("({$conditions})")->fi()
->beginIF(!empty($dimensionID))->andWhere('dimension')->eq($dimensionID)->fi()
->orderBy($orderBy)
->fetchAll();
$pivots = array_merge($pivots, $charts);
}
else
{
$pivots = $this->dao->select('*')->from(TABLE_PIVOT)
->where('deleted')->eq(0)
->beginIF(!empty($dimensionID))->andWhere('dimension')->eq($dimensionID)->fi()
->orderBy($orderBy)
->fetchAll();
$charts = $this->dao->select('*')->from(TABLE_CHART)
->where('deleted')->eq(0)
->andWhere('type')->eq('table')
->beginIF(!empty($dimensionID))->andWhere('dimension')->eq($dimensionID)->fi()
->orderBy($orderBy)
->fetchAll();
$pivots = array_merge($pivots, $charts);
}
if(!empty($pager))
{
$pager->setRecTotal(count($pivots));
$pager->setPageTotal();
if($pager->pageID > $pager->pageTotal) $pager->setPageID($pager->pageTotal);
if($pivots)
{
$pivots = array_chunk($pivots, $pager->recPerPage);
$pivots = $pivots[$pager->pageID - 1];
}
}
return $this->processPivot($pivots, false);
}
/**
* Process date vars in sql.
*
* @param string $var
* @param string $type
* @access public
* @return string
*/
public function processDateVar($var, $type = 'date')
{
if(empty($var)) return NULL;
$format = $type == 'datetime' ? 'Y-m-d H:i:s' : 'Y-m-d';
switch($var)
{
case '$MONDAY': $var = date($format, time() - (date('N') - 1) * 24 * 3600); break;
case '$SUNDAY': $var = date($format, time() + (7 - date('N')) * 24 * 3600); break;
case '$MONTHBEGIN': $var = date($format, time() - (date('j') - 1) * 24 * 3600); break;
case '$MONTHEND': $var = date($format, time() + (date('t') - date('j')) * 24 * 3600); break;
}
return $var;
}
/**
* Process sql and correct type.
*
* @param object|array $pivots
* @param bool $isObject
* @access public
* @return object|array
*/
public function processPivot($pivots, $isObject = true, $processFieldSettings = true)
{
if($isObject) $pivots = array($pivots);
$this->loadModel('screen');
$screenList = $this->dao->select('scheme')->from(TABLE_SCREEN)->where('deleted')->eq(0)->andWhere('status')->eq('published')->fetchAll();
foreach($pivots as $index => $pivot)
{
if(!empty($pivot->sql)) $pivots[$index]->sql = trim(str_replace(';', '', $pivot->sql));
if(!empty($pivot->settings)) $pivots[$index]->settings = json_decode($pivot->settings, true);
if(empty($pivot->type))
{
$pivot->names = '';
$pivot->descs = '';
if(!empty($pivot->name))
{
$pivotNames = json_decode($pivot->name, true);
$pivot->name = zget($pivotNames, $this->app->getClientLang(), '');
$pivot->names = $pivotNames;
if(!$pivot->name)
{
$pivotNames = array_filter($pivotNames);
$pivot->name = reset($pivotNames);
}
}
if(!empty($pivot->desc))
{
$pivotDescs = json_decode($pivot->desc, true);
$pivot->desc = zget($pivotDescs, $this->app->getClientLang(), '');
$pivot->descs = $pivotDescs;
}
$pivots[$index]->used = $this->screen->checkIFChartInUse($pivot->id, 'pivot', $screenList);
}
if($isObject and $processFieldSettings and $pivots[$index]->stage == 'published') $pivots[$index] = $this->processFieldSettings($pivots[$index]);
}
return $isObject ? reset($pivots) : $pivots;
}
/**
* Process pivot field settings, function like dataview/js/basequery.js getFieldSettings().
*
* @param object $pivot
* @access public
* @return object
*/
public function processFieldSettings($pivot)
{
if(isset($pivot->fieldSettings))
{
$fieldSettings = $pivot->fieldSettings;
}
else
{
$fieldSettings = (!empty($pivot->fields) and $pivot->fields != 'null') ? json_decode($pivot->fields) : array();
}
if(empty($fieldSettings)) return $pivot;
$this->loadModel('chart');
$this->loadModel('dataview');
$sql = isset($pivot->sql) ? $pivot->sql : '';
$filters = isset($pivot->filters) ? (is_array($pivot->filters) ? $pivot->filters : json_decode($pivot->filters, true)) : array();
$recPerPage = 20;
$pageID = 1;
if(!empty($filters))
{
foreach($filters as $index => $filter)
{
if(empty($filter['default'])) continue;
$filters[$index]['default'] = $this->processDateVar($filter['default']);
}
}
$querySQL = $this->chart->parseSqlVars($sql, $filters);
$this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
$stmt = $this->dbh->query($querySQL);
if(!$stmt) return $pivot;
$columns = $this->dataview->getColumns($querySQL);
$columnFields = array();
foreach($columns as $column => $type) $columnFields[$column] = $column;
$tableAndFields = $this->chart->getTables($querySQL);
$tables = $tableAndFields['tables'];
$fields = $tableAndFields['fields'];
$querySQL = $tableAndFields['sql'];
$moduleNames = array();
if($tables) $moduleNames = $this->dataview->getModuleNames($tables);
list($fieldPairs, $relatedObject) = $this->dataview->mergeFields($columnFields, $fields, $moduleNames);
/* Use fieldPairs, columns, relatedObject, objectFields refresh pivot fieldSettings .*/
$objectFields = array();
foreach($this->lang->dataview->objects as $object => $objectName) $objectFields[$object] = $this->dataview->getTypeOptions($object);
$fieldSettingsNew = new stdclass();
foreach($fieldPairs as $index => $field)
{
$defaultType = $columns->$index;
$defaultObject = $relatedObject[$index];
if(!empty($objectFields) and isset($objectFields[$defaultObject]) and isset($objectFields[$defaultObject][$index])) $defaultType = $objectFields[$defaultObject][$index]['type'] == 'object' ? 'string' : $objectFields[$defaultObject][$index]['type'];
if(!isset($fieldSettings->$index))
{
$fieldItem = new stdclass();
$fieldItem->name = $field;
$fieldItem->object = $defaultObject;
$fieldItem->field = $index;
$fieldItem->type = $defaultType;
$fieldSettingsNew->$index = $fieldItem;
}
else
{
if(!isset($fieldSettings->$index->object) or strlen($fieldSettings->$index->object) == 0) $fieldSettings->$index->object = $defaultObject;
if(!isset($fieldSettings->$index->field) or strlen($fieldSettings->$index->field) == 0)
{
$fieldSettings->$index->field = $index;
$fieldSettings->$index->object = $defaultObject;
$fieldSettings->$index->type = 'string';
}
$object = $fieldSettings->$index->object;
$type = $fieldSettings->$index->type;
if($object == $defaultObject && $type != $defaultType) $fieldSettings->$index->type = $defaultType;
$fieldSettingsNew->$index = $fieldSettings->$index;
}
}
$pivot->fieldSettings = $fieldSettingsNew;
return $pivot;
}
/**
* Compute percent of every item.
*
* @param array $datas
* @access public
* @return array
*/
public function computePercent($datas)
{
$sum = 0;
foreach($datas as $data) $sum += $data->value;
$totalPercent = 0;
foreach($datas as $i => $data)
{
$data->percent = round($data->value / $sum, 4);
$totalPercent += $data->percent;
}
if(isset($i)) $datas[$i]->percent = round(1 - $totalPercent + $datas[$i]->percent, 4);
return $datas;
}
/**
* Create json data of single charts
* @param array $sets
* @param array $dateList
* @return string the json string
*/
public function createSingleJSON($sets, $dateList)
{
$data = '[';
$now = date('Y-m-d');
$preValue = 0;
$setsDate = array_keys($sets);
foreach($dateList as $date)
{
$date = date('Y-m-d', strtotime($date));
if($date > $now) break;
if(!isset($sets[$date]) and $sets)
{
$tmpDate = $setsDate;
$tmpDate[] = $date;
sort($tmpDate);
$tmpDateStr = ',' . implode(',', $tmpDate);
$preDate = rtrim(substr($tmpDateStr, 0, strpos($tmpDateStr, $date)), ',');
$preDate = substr($preDate, strrpos($preDate, ',') + 1);
if($preDate)
{
$preValue = $sets[$preDate];
$preValue = $preValue->value;
}
}
$data .= isset($sets[$date]) ? "{$sets[$date]->value}," : "{$preValue},";
}
$data = rtrim($data, ',');
$data .= ']';
return $data;
}
/**
* Convert date format.
*
* @param array $dateList
* @param string $format
* @access public
* @return array
*/
public function convertFormat($dateList, $format = 'Y-m-d')
{
foreach($dateList as $i => $date) $dateList[$i] = date($format, strtotime($date));
return $dateList;
}
/**
* Get executions.
*
* @param string $begin
* @param string $end
* @access public
* @return array
*/
public function getExecutions($begin = 0, $end = 0)
{
$permission = common::hasPriv('pivot', 'showProject') or $this->app->user->admin;
$tasks = $this->dao->select("t1.*, IF(t3.multiple = '1', t2.name, '') as executionName, t3.name as projectName, t2.multiple")->from(TABLE_TASK)->alias('t1')
->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.execution = t2.id')
->leftJoin(TABLE_PROJECT)->alias('t3')->on('t1.project = t3.id')
->where('t1.status')->ne('cancel')
->andWhere('t1.deleted')->eq(0)
->beginIF(!$permission)->andWhere('t2.id')->in($this->app->user->view->sprints)->fi()
->andWhere('t2.deleted')->eq(0)
->andWhere('t1.parent')->lt(1)
->andWhere('t2.status')->eq('closed')
->beginIF($begin)->andWhere('t2.begin')->ge($begin)->fi()
->beginIF($end)->andWhere('t2.end')->le($end)->fi()
->orderBy('t2.end_desc')
->fetchAll();
$executions = array();
foreach($tasks as $task)
{
$executionID = $task->execution;
if(!isset($executions[$executionID]))
{
$executions[$executionID] = new stdclass();
$executions[$executionID]->estimate = 0;
$executions[$executionID]->consumed = 0;
}
$executions[$executionID]->projectID = $task->project;
$executions[$executionID]->projectName = $task->projectName;
$executions[$executionID]->multiple = $task->multiple;
$executions[$executionID]->name = $task->executionName;
$executions[$executionID]->estimate += $task->estimate;
$executions[$executionID]->consumed += $task->consumed;
}
return $executions;
}
/**
* Get products.
*
* @access public
* @return array
*/
public function getProducts($conditions, $storyType = 'story')
{
$permission = common::hasPriv('pivot', 'showProduct') or $this->app->user->admin;
$products = $this->dao->select('t1.id as id, t1.code, t1.name, t1.PO')->from(TABLE_PRODUCT)->alias('t1')
->leftJoin(TABLE_PROGRAM)->alias('t2')->on('t1.program = t2.id')
->where('t1.deleted')->eq(0)
->andWhere('t1.shadow')->eq(0)
->beginIF(strpos($conditions, 'closedProduct') === false)->andWhere('t1.status')->ne('closed')->fi()
->beginIF(!$permission)->andWhere('t1.id')->in($this->app->user->view->products)->fi()
->orderBy('t2.order_asc, t1.line_desc, t1.order_asc')
->fetchAll('id');
$plans = $this->dao->select('id, product, branch, parent, title, begin, end')->from(TABLE_PRODUCTPLAN)->where('deleted')->eq(0)->andWhere('product')->in(array_keys($products))
->beginIF(strpos($conditions, 'overduePlan') === false)->andWhere('end')->gt(date('Y-m-d'))->fi()
->orderBy('product,parent_desc,begin')
->fetchAll('id');
foreach($plans as $plan)
{
if($plan->parent > 0)
{
$parentPlan = zget($plans, $plan->parent, null);
if($parentPlan)
{
$products[$plan->product]->plans[$parentPlan->id] = $parentPlan;
unset($plans[$parentPlan->id]);
}
$plan->title = '>>' . $plan->title;
}
$products[$plan->product]->plans[$plan->id] = $plan;
}
$planStories = array();
$unplannedStories = array();
$stmt = $this->dao->select('id,plan,product,status')
->from(TABLE_STORY)
->where('deleted')->eq(0)
->andWhere('parent')->ge(0)
->beginIF($storyType)->andWhere('type')->eq($storyType)->fi()
->query();
while($story = $stmt->fetch())
{
if(empty($story->plan))
{
$unplannedStories[$story->id] = $story;
continue;
}
$storyPlans = array();
$storyPlans[] = $story->plan;
if(strpos($story->plan, ',') !== false) $storyPlans = explode(',', trim($story->plan, ','));
foreach($storyPlans as $planID)
{
if(isset($plans[$planID]))
{
$planStories[$story->id] = $story;
break;
}
}
}
foreach($planStories as $story)
{
$storyPlans = array();
$storyPlans[] = $story->plan;
if(strpos($story->plan, ',') !== false) $storyPlans = explode(',', trim($story->plan, ','));
foreach($storyPlans as $planID)
{
if(!isset($plans[$planID])) continue;
$plan = $plans[$planID];
$products[$plan->product]->plans[$planID]->status[$story->status] = isset($products[$plan->product]->plans[$planID]->status[$story->status]) ? $products[$plan->product]->plans[$planID]->status[$story->status] + 1 : 1;
}
}
foreach($unplannedStories as $story)
{
$product = $story->product;
if(isset($products[$product]))
{
if(!isset($products[$product]->plans[0]))
{
$products[$product]->plans[0] = new stdClass();
$products[$product]->plans[0]->title = $this->lang->pivot->unplanned;
$products[$product]->plans[0]->begin = '';
$products[$product]->plans[0]->end = '';
}
$products[$product]->plans[0]->status[$story->status] = isset($products[$product]->plans[0]->status[$story->status]) ? $products[$product]->plans[0]->status[$story->status] + 1 : 1;
}
}
unset($products['']);
return $products;
}
/**
* Get bugs
*
* @param int $begin
* @param int $end
* @param int $product
* @param int $execution
* @access public
* @return array
*/
public function getBugs($begin, $end, $product, $execution)
{
$end = date('Ymd', strtotime("$end +1 day"));
$bugs = $this->dao->select('id, resolution, openedBy, status')->from(TABLE_BUG)
->where('deleted')->eq(0)
->andWhere('openedDate')->ge($begin)
->andWhere('openedDate')->le($end)
->beginIF($product)->andWhere('product')->eq($product)->fi()
->beginIF($execution)->andWhere('execution')->eq($execution)->fi()
->fetchAll();
$bugCreate = array();
foreach($bugs as $bug)
{
$bugCreate[$bug->openedBy][$bug->resolution] = empty($bugCreate[$bug->openedBy][$bug->resolution]) ? 1 : $bugCreate[$bug->openedBy][$bug->resolution] + 1;
$bugCreate[$bug->openedBy]['all'] = empty($bugCreate[$bug->openedBy]['all']) ? 1 : $bugCreate[$bug->openedBy]['all'] + 1;
if($bug->status == 'resolved' or $bug->status == 'closed')
{
$bugCreate[$bug->openedBy]['resolved'] = empty($bugCreate[$bug->openedBy]['resolved']) ? 1 : $bugCreate[$bug->openedBy]['resolved'] + 1;
}
}
foreach($bugCreate as $account => $bug)
{
$validRate = 0;
if(isset($bug['fixed'])) $validRate += $bug['fixed'];
if(isset($bug['postponed'])) $validRate += $bug['postponed'];
$bugCreate[$account]['validRate'] = (isset($bug['resolved']) and $bug['resolved']) ? ($validRate / $bug['resolved']) : "0";
}
uasort($bugCreate, 'sortSummary');
return $bugCreate;
}
/**
* Get workload.
*
* @param int $dept
* @param string $assign
*
* @access public
* @return array
*/
public function getWorkload($dept = 0, $assign = 'assign')
{
$deptUsers = array();
if($dept) $deptUsers = $this->loadModel('dept')->getDeptUserPairs($dept);
if($assign == 'noassign')
{
$members = $this->dao->select('t1.account,t2.name,t2.multiple,t1.root,t3.id as project,t3.name as projectname')->from(TABLE_TEAM)->alias('t1')
->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t2.id = t1.root')
->leftJoin(TABLE_PROJECT)->alias('t3')->on('t3.id = t2.project')
->where('t2.status')->notin('cancel, closed, done, suspended')
->beginIF($dept)->andWhere('t1.account')->in(array_keys($deptUsers))->fi()
->andWhere('t1.type')->eq('execution')
->andWhere("t1.account NOT IN(SELECT `assignedTo` FROM " . TABLE_TASK . " WHERE `execution` = t1.`root` AND `status` NOT IN('cancel, closed, done, pause') AND assignedTo != '' GROUP BY assignedTo)")
->andWhere('t2.deleted')->eq('0')
->fetchGroup('account', 'name');
$workload = array();
if(!empty($members))
{
foreach($members as $member => $executions)
{
$project = array();
if(!empty($executions))
{
foreach($executions as $name => $execution)
{
$project[$execution->projectname]['projectID'] = $execution->project;
$project[$execution->projectname]['execution'][$name]['executionID'] = $execution->root;
$project[$execution->projectname]['execution'][$name]['multiple'] = $execution->multiple;
$project[$execution->projectname]['execution'][$name]['count'] = 0;
$project[$execution->projectname]['execution'][$name]['manhour'] = 0;
$workload[$member]['total']['count'] = 0;
$workload[$member]['total']['manhour'] = 0;
}
}
$workload[$member]['task']['project'] = $project;
}
}
return $workload;
}
$stmt = $this->dao->select('t1.*, t2.name as executionName, t3.name as projectname, t2.multiple')->from(TABLE_TASK)->alias('t1')
->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.execution = t2.id')
->leftJoin(TABLE_PROJECT)->alias('t3')->on('t3.id = t2.project')
->where('t1.deleted')->eq(0)
->andWhere('t1.status')->in('wait,pause,doing')
->andWhere('t2.deleted')->eq(0)
->andWhere('t2.status')->in('wait,suspended,doing')
->andWhere('assignedTo')->ne('');
$allTasks = $stmt->fetchAll('id');
if(empty($allTasks)) return array();
$tasks = array();
if(empty($dept))
{
$tasks = $allTasks;
}
else
{
foreach($allTasks as $taskID => $task)
{
if(isset($deptUsers[$task->assignedTo])) $tasks[$taskID] = $task;
}
}
/* Fix bug for children. */
$parents = array();
$taskGroups = array();
foreach($tasks as $task)
{
if($task->parent > 0) $parents[$task->parent] = $task->parent;
$taskGroups[$task->assignedTo][$task->id] = $task;
}
$stmt = $this->dao->select('*')->from(TABLE_TASKTEAM)->where('task')->in(array_keys($allTasks))
->beginIF($dept)->andWhere('account')->in(array_keys($deptUsers))->fi()
->query();
$multiTaskTeams = array();
while($taskTeam = $stmt->fetch())
{
$account = $taskTeam->account;
if(!isset($multiTaskTeams[$account][$taskTeam->task]))
{
$multiTaskTeams[$account][$taskTeam->task] = $taskTeam;
}
else
{
$multiTaskTeams[$account][$taskTeam->task]->estimate += $taskTeam->estimate;
$multiTaskTeams[$account][$taskTeam->task]->consumed += $taskTeam->consumed;
$multiTaskTeams[$account][$taskTeam->task]->left += $taskTeam->left;
}
}
foreach($multiTaskTeams as $assignedTo => $taskTeams)
{
foreach($taskTeams as $taskTeam)
{
$userTask = clone $allTasks[$taskTeam->task];
$userTask->estimate = $taskTeam->estimate;
$userTask->consumed = $taskTeam->consumed;
$userTask->left = $taskTeam->left;
$taskGroups[$assignedTo][$taskTeam->task] = $userTask;
}
}
$workload = array();
foreach($taskGroups as $user => $userTasks)
{
if($user)
{
$project = array();
foreach($userTasks as $task)
{
if(isset($parents[$task->id])) continue;
$project[$task->projectname]['projectID'] = isset($project[$task->projectname]['projectID']) ? $project[$task->projectname]['projectID'] : $task->project;
$project[$task->projectname]['execution'][$task->executionName]['executionID'] = isset($project[$task->projectname]['execution'][$task->executionName]['executionID']) ? $project[$task->projectname]['execution'][$task->executionName]['executionID'] : $task->execution;
$project[$task->projectname]['execution'][$task->executionName]['multiple'] = isset($project[$task->projectname]['execution'][$task->executionName]['multiple']) ? $project[$task->projectname]['execution'][$task->executionName]['multiple'] : $task->multiple;
$project[$task->projectname]['execution'][$task->executionName]['count'] = isset($project[$task->projectname]['execution'][$task->executionName]['count']) ? $project[$task->projectname]['execution'][$task->executionName]['count'] + 1 : 1;
$project[$task->projectname]['execution'][$task->executionName]['manhour'] = isset($project[$task->projectname]['execution'][$task->executionName]['manhour']) ? $project[$task->projectname]['execution'][$task->executionName]['manhour'] + $task->left : $task->left;
$workload[$user]['total']['count'] = isset($workload[$user]['total']['count']) ? $workload[$user]['total']['count'] + 1 : 1;
$workload[$user]['total']['manhour'] = isset($workload[$user]['total']['manhour']) ? $workload[$user]['total']['manhour'] + $task->left : $task->left;
}
if(empty($project)) continue;
$workload[$user]['task']['project'] = $project;
}
}
unset($workload['closed']);
return $workload;
}
/**
* Get bug assign.
*
* @access public
* @return array
*/
public function getBugAssign()
{
$bugs = $this->dao->select('t1.*, t2.name AS productName')->from(TABLE_BUG)->alias('t1')
->leftJoin(TABLE_PRODUCT)->alias('t2')->on('t1.product = t2.id')
->where('t1.deleted')->eq(0)
->andWhere('t1.status')->eq('active')
->andWhere('t2.deleted')->eq(0)
->fetchGroup('assignedTo');
$productProjects = $this->dao->select('t2.product, t2.project')->from(TABLE_PROJECT)->alias('t1')
->leftJoin(TABLE_PROJECTPRODUCT)->alias('t2')->on('t1.id = t2.project')
->where('t1.type')->eq('project')
->andWhere('t1.hasProduct')->eq(0)
->fetchPairs();
$assign = array();
foreach($bugs as $user => $userBugs)
{
if($user)
{
foreach($userBugs as $bug)
{
$assign[$user]['bug'][$bug->productName]['count'] = isset($assign[$user]['bug'][$bug->productName]['count']) ? $assign[$user]['bug'][$bug->productName]['count'] + 1 : 1;
$assign[$user]['bug'][$bug->productName]['productID'] = $bug->product;
$assign[$user]['bug'][$bug->productName]['projectID'] = zget($productProjects, $bug->product, 0);
$assign[$user]['total']['count'] = isset($assign[$user]['total']['count']) ? $assign[$user]['total']['count'] + 1 : 1;
}
}
}
unset($assign['closed']);
return $assign;
}
/**
* Get System URL.
*
* @access public
* @return void
*/
public function getSysURL()
{
if(isset($this->config->mail->domain)) return $this->config->mail->domain;
/* Ger URL when run in shell. */
if(PHP_SAPI == 'cli')
{
$url = parse_url(trim($this->server->argv[1]));
$port = (empty($url['port']) or $url['port'] == 80) ? '' : $url['port'];
$host = empty($port) ? $url['host'] : $url['host'] . ':' . $port;
return $url['scheme'] . '://' . $host;
}
else
{
return common::getSysURL();
}
}
/**
* Get user bugs.
*
* @access public
* @return void
*/
public function getUserBugs()
{
return $this->dao->select('t1.id, t1.title, t2.account as user, t1.deadline')
->from(TABLE_BUG)->alias('t1')
->leftJoin(TABLE_USER)->alias('t2')
->on('t1.assignedTo = t2.account')
->where('t1.assignedTo')->ne('')
->andWhere('t1.assignedTo')->ne('closed')
->andWhere('t1.deleted')->eq(0)
->andWhere('t2.deleted')->eq(0)
->andWhere('t1.deadline', true)->eq('0000-00-00')
->orWhere('t1.deadline')->lt(date(DT_DATE1, strtotime('+4 day')))
->markRight(1)
->fetchGroup('user');
}
/**
* Get user tasks.
*
* @access public
* @return void
*/
public function getUserTasks()
{
return $this->dao->select('t1.id, t1.name, t2.account as user, t1.deadline')->from(TABLE_TASK)->alias('t1')
->leftJoin(TABLE_USER)->alias('t2')->on('t1.assignedTo = t2.account')
->leftJoin(TABLE_EXECUTION)->alias('t3')->on('t1.execution = t3.id')
->leftJoin(TABLE_PROJECT)->alias('t4')->on('t1.project = t4.id')
->where('t1.assignedTo')->ne('')
->andWhere('t1.deleted')->eq(0)
->andWhere('t2.deleted')->eq(0)
->andWhere('t3.deleted')->eq(0)
->andWhere('t4.deleted')->eq(0)
->andWhere('t1.status')->in('wait,doing')
->andWhere('t3.status')->ne('suspended')
->andWhere('t1.deadline', true)->eq('0000-00-00')
->orWhere('t1.deadline')->lt(date(DT_DATE1, strtotime('+4 day')))
->markRight(1)
->fetchGroup('user');
}
/**
* Get user todos.
*
* @access public
* @return array
*/
public function getUserTodos()
{
$stmt = $this->dao->select('t1.*, t2.account as user')
->from(TABLE_TODO)->alias('t1')
->leftJoin(TABLE_USER)->alias('t2')
->on('t1.account = t2.account')
->where('t1.cycle')->eq(0)
->andWhere('t1.deleted')->eq(0)
->andWhere('t1.status')->in('wait,doing')
->query();
$todos = array();
while($todo = $stmt->fetch())
{
if($todo->type == 'task') $todo->name = $this->dao->findById($todo->idvalue)->from(TABLE_TASK)->fetch('name');
if($todo->type == 'bug') $todo->name = $this->dao->findById($todo->idvalue)->from(TABLE_BUG)->fetch('title');
$todos[$todo->user][] = $todo;
}
return $todos;
}
/**
* Get user testTasks.
*
* @access public
* @return array
*/
public function getUserTestTasks()
{
return $this->dao->select('t1.*, t2.account as user')->from(TABLE_TESTTASK)->alias('t1')
->leftJoin(TABLE_USER)->alias('t2')->on('t1.owner = t2.account')
->where('t1.deleted')->eq('0')
->andWhere('t2.deleted')->eq('0')
->andWhere("(t1.status='wait' OR t1.status='doing')")
->fetchGroup('user');
}
/**
* Get user login count in this year.
*
* @param array $accounts
* @param int $year
* @access public
* @return int
*/
public function getUserYearLogins($accounts, $year)
{
return $this->dao->select('count(*) as count')->from(TABLE_ACTION)->where('actor')->in($accounts)->andWhere('LEFT(date, 4)')->eq($year)->andWhere('action')->eq('login')->fetch('count');
}
/**
* Get user action count in this year.
*
* @param array $accounts
* @param int $year
* @access public
* @return int
*/
public function getUserYearActions($accounts, $year)
{
return $this->dao->select('count(*) as count')->from(TABLE_ACTION)
->where('LEFT(date, 4)')->eq($year)
->beginIF($accounts)->andWhere('actor')->in($accounts)->fi()
->fetch('count');
}
/**
* Get user contributions in this year.
*
* @param array $accounts
* @param int $year
* @access public
* @return array
*/
public function getUserYearContributions($accounts, $year)
{
$stmt = $this->dao->select('*')->from(TABLE_ACTION)
->where('LEFT(date, 4)')->eq($year)
->andWhere('objectType')->in(array_keys($this->config->pivot->annualData['contributions']))
->beginIF($accounts)->andWhere('actor')->in($accounts)->fi()
->orderBy('objectType,objectID,id')
->query();
$filterActions = array();
$objectIdList = array();
while($action = $stmt->fetch())
{
$objectType = $action->objectType;
$objectID = $action->objectID;
$lowerAction = strtolower($action->action);
if(!isset($this->config->pivot->annualData['contributions'][$objectType][$lowerAction])) continue;
$objectIdList[$objectType][$objectID] = $objectID;
$filterActions[$objectType][$objectID][$action->id] = $action;
}
foreach($objectIdList as $objectType => $idList)
{
$deletedIdList = $this->dao->select('id')->from($this->config->objectTables[$objectType])->where('deleted')->eq(1)->andWhere('id')->in($idList)->fetchPairs('id', 'id');
foreach($deletedIdList as $id) unset($filterActions[$objectType][$id]);
}
$actionGroups = array();
foreach($filterActions as $objectType => $objectActions)
{
foreach($objectActions as $objectID => $actions)
{
foreach($actions as $action) $actionGroups[$objectType][$action->id] = $action;
}
}
$contributions = array();
foreach($actionGroups as $objectType => $actions)
{
foreach($actions as $action)
{
$lowerAction = strtolower($action->action);
$actionName = $this->config->pivot->annualData['contributions'][$objectType][$lowerAction];
$type = ($actionName == 'svnCommit' or $actionName == 'gitCommit') ? 'repo' : $objectType;
if(!isset($contributions[$type][$actionName])) $contributions[$type][$actionName] = 0;
$contributions[$type][$actionName] += 1;
}
}
$contributions['case']['run'] = $this->dao->select('count(*) as count')->from(TABLE_TESTRESULT)->alias('t1')
->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case=t2.id')
->where('LEFT(t1.date, 4)')->eq($year)
->andWhere('t2.deleted')->eq(0)
->beginIF($accounts)->andWhere('t1.lastRunner')->in($accounts)->fi()
->fetch('count');
return $contributions;
}
/**
* Get user todo stat in this year.
*
* @param array $accounts
* @param int $year
* @access public
* @return object
*/
public function getUserYearTodos($accounts, $year)
{
return $this->dao->select("count(*) as count, sum(if((`status` != 'done'), 1, 0)) AS `undone`, sum(if((`status` = 'done'), 1, 0)) AS `done`")->from(TABLE_TODO)
->where('LEFT(date, 4)')->eq($year)
->andWhere('deleted')->eq('0')
->andWhere('vision')->eq($this->config->vision)
->beginIF($accounts)->andWhere('account')->in($accounts)->fi()
->fetch();
}
/**
* Get user effort stat in this error.
*
* @param array $accounts
* @param int $year
* @access public
* @return object
*/
public function getUserYearEfforts($accounts, $year)
{
$effort = $this->dao->select('count(*) as count, sum(consumed) as consumed')->from(TABLE_EFFORT)
->where('LEFT(date, 4)')->eq($year)
->andWhere('deleted')->eq(0)
->beginIF($accounts)->andWhere('account')->in($accounts)->fi()
->fetch();
$effort->consumed = !empty($effort->consumed) ? round($effort->consumed, 2) : 0;
return $effort;
}
/**
* Get count of created story,plan and closed story by accounts every product in this year.
*
* @param array $accounts
* @param int $year
* @access public
* @return array
*/
public function getUserYearProducts($accounts, $year)
{
/* Get changed products in this year. */
$products = $this->dao->select('id,name')->from(TABLE_PRODUCT)
->where('deleted')->eq(0)
->andWhere('LEFT(createdDate, 4)')->eq($year)
->beginIF($accounts)
->andWhere('createdBy', true)->in($accounts)
->orWhere('PO')->in($accounts)
->orWhere('QD')->in($accounts)
->orWhere('RD')->in($accounts)
->markRight(1)
->fi()
->andWhere('shadow')->eq(0)
->fetchAll('id');
/* Get created plans in this year. */
$plans = $this->dao->select('t1.id,t1.product')->from(TABLE_PRODUCTPLAN)->alias('t1')
->leftJoin(TABLE_ACTION)->alias('t2')->on("t1.id=t2.objectID and t2.objectType='productplan'")
->where('LEFT(t2.date, 4)')->eq($year)
->andWhere('t1.deleted')->eq(0)
->andWhere('t1.product')->in(array_keys($products))
->beginIF($accounts)
->andWhere('t2.actor')->in($accounts)
->fi()
->andWhere('t2.action')->eq('opened')
->fetchAll();
$planProducts = array();
$planGroups = array();
foreach($plans as $plan)
{
$planProducts[$plan->product] = $plan->product;
$planGroups[$plan->product][$plan->id] = $plan->id;
}
$createStoryProducts = $this->dao->select('DISTINCT product')->from(TABLE_STORY)
->where('LEFT(openedDate, 4)')->eq($year)
->andWhere('deleted')->eq(0)
->andWhere('product')->in(array_keys($products))
->beginIF($accounts)->andWhere('openedBy')->in($accounts)->fi()
->fetchPairs('product', 'product');
$closeStoryProducts = $this->dao->select('DISTINCT product')->from(TABLE_STORY)
->where('LEFT(closedDate, 4)')->eq($year)
->andWhere('deleted')->eq(0)
->andWhere('product')->in(array_keys($products))
->beginIF($accounts)->andWhere('closedBy')->in($accounts)->fi()
->fetchPairs('product', 'product');
if($createStoryProducts or $closeStoryProducts)
{
$products += $this->dao->select('id,name')->from(TABLE_PRODUCT)
->where('id')->in($createStoryProducts + $closeStoryProducts + $planProducts)
->andWhere('deleted')->eq(0)
->fetchAll('id');
}
$createdStoryStats = $this->dao->select("product,sum(if((type = 'requirement'), 1, 0)) as requirement, sum(if((type = 'story'), 1, 0)) as story")->from(TABLE_STORY)
->where('product')->in(array_keys($products))
->andWhere('deleted')->eq(0)
->andWhere('LEFT(openedDate, 4)')->eq($year)
->beginIF($accounts)->andWhere('openedBy')->in($accounts)->fi()
->groupBy('product')
->fetchAll('product');
$closedStoryStats = $this->dao->select("product,sum(if((status = 'closed'), 1, 0)) as closed")->from(TABLE_STORY)
->where('product')->in(array_keys($products))
->andWhere('deleted')->eq(0)
->andWhere('LEFT(closedDate, 4)')->eq($year)
->beginIF($accounts)->andWhere('closedBy')->in($accounts)->fi()
->groupBy('product')
->fetchAll('product');
/* Merge created plan, created story and closed story in every product. */
foreach($products as $productID => $product)
{
$product->plan = 0;
$product->requirement = 0;
$product->story = 0;
$product->closed = 0;
$plans = zget($planGroups, $productID, array());
if($plans) $product->plan = count($plans);
$createdStoryStat = zget($createdStoryStats, $productID, '');
if($createdStoryStat)
{
$product->requirement = $createdStoryStat->requirement;
$product->story = $createdStoryStat->story;
}
$closedStoryStat = zget($closedStoryStats, $productID, '');
if($closedStoryStat) $product->closed = $closedStoryStat->closed;
}
return $products;
}
/**
* Get count of finished task, story and resolved bug by accounts every executions in this year.
*
* @param array $accounts
* @param int $year
* @access public
* @return array
*/
public function getUserYearExecutions($accounts, $year)
{
/* Get changed executions in this year. */
$executions = $this->dao->select('id,name')->from(TABLE_EXECUTION)->where('deleted')->eq(0)
->andwhere('type')->eq('sprint')
->andwhere('multiple')->eq('1')
->andWhere('LEFT(begin, 4)', true)->eq($year)
->orWhere('LEFT(end, 4)')->eq($year)
->markRight(1)
->beginIF($accounts)
->andWhere('openedBy', true)->in($accounts)
->orWhere('PO')->in($accounts)
->orWhere('PM')->in($accounts)
->orWhere('QD')->in($accounts)
->orWhere('RD')->in($accounts)
->markRight(1)
->fi()
->orderBy('`order` desc')
->fetchAll('id');
$teamExecutions = $this->dao->select('t1.root')->from(TABLE_TEAM)->alias('t1')
->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.root=t2.id')
->where('t1.type')->eq('execution')
->beginIF($accounts)->andWhere('account')->in($accounts)->fi()
->andWhere('t2.multiple')->eq('1')
->andWhere('LEFT(`join`, 4)')->eq($year)
->fetchPairs('root', 'root');
$taskExecutions = $this->dao->select('t1.execution')->from(TABLE_TASK)->alias('t1')
->leftJoin(TABLE_EXECUTION)->alias('t2')->on('t1.execution=t2.id')
->where('LEFT(t1.finishedDate, 4)')->eq($year)
->beginIF($accounts)->andWhere('finishedBy')->in($accounts)->fi()
->andWhere('t2.multiple')->eq('1')
->andWhere('t1.deleted')->eq(0)
->fetchPairs('execution', 'execution');
if($teamExecutions or $taskExecutions)
{
$executions += $this->dao->select('id,name')->from(TABLE_EXECUTION)
->where('id')->in($teamExecutions + $taskExecutions)
->andWhere('deleted')->eq(0)
->andWhere('type')->eq('sprint')
->orderBy('`order` desc')
->fetchAll('id');
}
/* Get count of finished task, story and resolved bug in this year. */
$taskStats = $this->dao->select('execution, count(*) as finishedTask, sum(if((story != 0), 1, 0)) as finishedStory')->from(TABLE_TASK)
->where('execution')->in(array_keys($executions))
->andWhere('finishedBy')->ne('')
->andWhere('LEFT(finishedDate, 4)')->eq($year)
->andWhere('deleted')->eq(0)
->beginIF($accounts)->andWhere('finishedBy')->in($accounts)->fi()
->groupBy('execution')
->fetchAll('execution');
$resolvedBugs = $this->dao->select('t2.execution, count(*) as count')->from(TABLE_BUG)->alias('t1')
->leftJoin(TABLE_BUILD)->alias('t2')->on('t1.resolvedBuild=t2.id')
->where('t2.execution')->in(array_keys($executions))
->andWhere('t1.resolvedBy')->ne('')
->andWhere('t1.deleted')->eq(0)
->andWhere('LEFT(t1.resolvedDate, 4)')->eq($year)
->beginIF($accounts)->andWhere('t1.resolvedBy')->in($accounts)->fi()
->groupBy('t2.execution')
->fetchAll('execution');
foreach($executions as $executionID => $execution)
{
$execution->task = 0;
$execution->story = 0;
$execution->bug = 0;
$taskStat = zget($taskStats, $executionID, '');
if($taskStat)
{
$execution->task = $taskStat->finishedTask;
$execution->story = $taskStat->finishedStory;
}
$resolvedBug = zget($resolvedBugs, $executionID, '');
if($resolvedBug) $execution->bug = $resolvedBug->count;
}
return $executions;
}
/**
* Get status stat that is all time, include story, task and bug.
*
* @access public
* @return array
*/
public function getAllTimeStatusStat()
{
$statusStat = array();
$statusStat['story'] = $this->dao->select('status, count(status) as count')->from(TABLE_STORY)->where('deleted')->eq(0)->andWhere('type')->eq('story')->groupBy('status')->fetchPairs('status', 'count');
$statusStat['task'] = $this->dao->select('status, count(status) as count')->from(TABLE_TASK)->where('deleted')->eq(0)->groupBy('status')->fetchPairs('status', 'count');
$statusStat['bug'] = $this->dao->select('status, count(status) as count')->from(TABLE_BUG)->where('deleted')->eq(0)->groupBy('status')->fetchPairs('status', 'count');
return $statusStat;
}
/**
* Get year object stat, include status and action stat
*
* @param array $accounts
* @param string $year
* @param string $objectType story|task|bug
* @access public
* @return array
*/
public function getYearObjectStat($accounts, $year, $objectType)
{
$table = '';
if($objectType == 'story') $table = TABLE_STORY;
if($objectType == 'task') $table = TABLE_TASK;
if($objectType == 'bug') $table = TABLE_BUG;
if(empty($table)) return array();
$months = $this->getYearMonths($year);
$stmt = $this->dao->select('t1.*, t2.status')->from(TABLE_ACTION)->alias('t1')
->leftJoin($table)->alias('t2')->on('t1.objectID=t2.id')
->where('t1.objectType')->eq($objectType)
->andWhere('t2.deleted')->eq(0)
->andWhere('LEFT(t1.date, 4)')->eq($year)
->andWhere('t1.action')->in($this->config->pivot->annualData['monthAction'][$objectType])
->beginIF($accounts)->andWhere('t1.actor')->in($accounts)->fi()
->query();
/* Build object action stat and get status group. */
$statuses = array();
$actionStat = array();
while($action = $stmt->fetch())
{
$statuses[$action->objectID] = $action->status;
$lowerAction = strtolower($action->action);
/* Story,bug can from feedback and ticket, task can from feedback, boil this action down to opened. */
if(in_array($lowerAction, array('fromfeedback', 'fromticket'))) $lowerAction = 'opened';
if(!isset($actionStat[$lowerAction]))
{
foreach($months as $month) $actionStat[$lowerAction][$month] = 0;
}
$month = substr($action->date, 0, 7);
$actionStat[$lowerAction][$month] += 1;
}
/* Build status stat. */
$statusStat = array();
foreach($statuses as $status)
{
if(!isset($statusStat[$status])) $statusStat[$status] = 0;
$statusStat[$status] += 1;
}
return array('statusStat' => $statusStat, 'actionStat' => $actionStat);
}
/**
* Get year case stat, include result and action stat.
*
* @param array $accounts
* @param string $year
* @access public
* @return array
*/
public function getYearCaseStat($accounts, $year)
{
$months = $this->getYearMonths($year);
$stmt = $this->dao->select('t1.*')->from(TABLE_ACTION)->alias('t1')
->leftJoin(TABLE_CASE)->alias('t2')->on('t1.objectID=t2.id')
->where('t1.objectType')->eq('case')
->andWhere('t2.deleted')->eq(0)
->andWhere('t1.action')->eq('opened')
->andWhere('LEFT(t1.date, 4)')->eq($year)
->beginIF($accounts)->andWhere('t1.actor')->in($accounts)->fi()
->query();
/* Build create case stat. */
$resultStat = array();
$actionStat = array();
foreach($months as $month)
{
$actionStat['opened'][$month] = 0;
$actionStat['run'][$month] = 0;
$actionStat['createBug'][$month] = 0;
}
while($action = $stmt->fetch())
{
$month = substr($action->date, 0, 7);
$actionStat['opened'][$month] += 1;
}
/* Build testcase result stat and run case stat. */
$stmt = $this->dao->select('t1.*')->from(TABLE_TESTRESULT)->alias('t1')
->leftJoin(TABLE_CASE)->alias('t2')->on('t1.case=t2.id')
->where('LEFT(t1.date, 4)')->eq($year)
->andWhere('t2.deleted')->eq(0)
->beginIF($accounts)->andWhere('t1.lastRunner')->in($accounts)->fi()
->query();
while($testResult = $stmt->fetch())
{
if(!isset($resultStat[$testResult->caseResult])) $resultStat[$testResult->caseResult] = 0;
$resultStat[$testResult->caseResult] += 1;
$month = substr($testResult->date, 0, 7);
$actionStat['run'][$month] += 1;
}
/* Build testcase create bug stat. */
$stmt = $this->dao->select('t1.*')->from(TABLE_ACTION)->alias('t1')
->leftJoin(TABLE_BUG)->alias('t2')->on('t1.objectID=t2.id')
->where('t1.objectType')->eq('bug')
->andWhere('t2.deleted')->eq(0)
->andWhere('LEFT(t1.date, 4)')->eq($year)
->andWhere('t1.action')->eq('opened')
->andWhere('t2.case')->ne('0')
->beginIF($accounts)->andWhere('t1.actor')->in($accounts)->fi()
->query();
while($action = $stmt->fetch())
{
$month = substr($action->date, 0, 7);
$actionStat['createBug'][$month] += 1;
}
return array('resultStat' => $resultStat, 'actionStat' => $actionStat);
}
/**
* Get year months.
*
* @param string $year
* @access public
* @return array
*/
public function getYearMonths($year)
{
$months = array();
for($i = 1; $i <= 12; $i ++) $months[] = $year . '-' . sprintf('%02d', $i);
return $months;
}
/**
* Get project and execution name.
*
* @access public
* @return array
*/
public function getProjectExecutions()
{
$executions = $this->dao->select('t1.id, t1.name, t2.name as projectname, t1.status, t1.multiple')
->from(TABLE_EXECUTION)->alias('t1')
->leftJoin(TABLE_PROJECT)->alias('t2')->on('t1.project=t2.id')
->where('t1.deleted')->eq(0)
->andWhere('t1.type')->in('stage,sprint')
->fetchAll();
$pairs = array();
foreach($executions as $execution)
{
if($execution->multiple) $pairs[$execution->id] = $execution->projectname . '/' . $execution->name;
if(!$execution->multiple) $pairs[$execution->id] = $execution->projectname;
}
return $pairs;
}
/**
* Get filter format.
*
* @param string $sql
* @param array $filters
* @access public
* @return string
*/
public function getFilterFormat($sql, $filters)
{
$filterFormat = array();
if(empty($filters)) return array($sql, $filterFormat);
foreach($filters as $filter)
{
$field = $filter['field'];
if(isset($filter['from']) and $filter['from'] == 'query')
{
$queryDefault = isset($filter['default']) ? $this->processDateVar($filter['default']) : '';
$sql = str_replace('$' . $filter['field'], "'{$queryDefault}'", $sql);
}
else
{
if(!isset($filter['default'])) continue;
$default = $filter['default'];
switch($filter['type'])
{
case 'select':
if(empty($default)) break;
if(is_array($default)) $default = array_filter($default, function($val){return trim($val) != '';});
$value = "('" . implode("', '", $default) . "')";
$filterFormat[$field] = array('operator' => 'IN', 'value' => $value);
break;
case 'input':
$filterFormat[$field] = array('operator' => 'like', 'value' => "'%$default%'");
break;
case 'date':
case 'datetime':
$begin = $default['begin'];
$end = $default['end'];
if(!empty($begin)) $begin = date('Y-m-d 00:00:00', strtotime($begin));
if(!empty($end)) $end = date('Y-m-d 23:59:59', strtotime($end));
if(!empty($begin) and empty($end)) $filterFormat[$field] = array('operator' => '>', 'value' => "'$begin'");
if(empty($begin) and !empty($end)) $filterFormat[$field] = array('operator' => '<', 'value' => "'$end'");
if(!empty($begin) and !empty($end)) $filterFormat[$field] = array('operator' => 'BETWEEN', 'value' => "'$begin' and '$end'");
break;
}
}
}
return array($sql, $filterFormat);
}
/**
* Get sql field order.
*
* @param string $field
* @param object $statement sql parser statement[0]
* @access public
* @return string
*/
public function getSqlFieldOrder($field, $statement)
{
$order = 'ASC';
if(!isset($statement->order)) return $order;
foreach($statement->order as $orderInfo)
{
if($orderInfo->expr->expr == $field)
{
$order = $orderInfo->type;
break;
}
}
return $order;
}
/**
* Get groups from settings.
*
* @param array $settings
* @access public
* @return array
*/
public function getGroupsFromSettings($settings)
{
$groups = array();
foreach($settings as $key => $value)
{
if(strpos($key, 'group') !== false && $value) $groups[] = $value;
}
return array_unique($groups);
}
/**
* Get select fields from settings.
*
* @param array $settings
* @access public
* @return array
*/
public function getSelectFieldsFromSettings($settings)
{
$fields = array();
foreach($settings as $key => $value)
{
if(strpos($key, 'group') !== false && !empty($value)) $fields[] = $value;
}
if(isset($settings['columns']))
{
$columns = $settings['columns'];
foreach($columns as $column)
{
$columnField = $column['field'];
$columnSlice = zget($column, 'slice', 'noSlice');
$fields[] = $columnField;
if($columnSlice != 'noSlice') $fields[] = $columnSlice;
}
}
return array_filter(array_unique($fields));
}
/**
* Filter field settings with select fields.
*
* @param array $fieldSettings
* @param array $selectFields
* @access public
* @return array
*/
public function filterWithSelectFields($fieldSettings, $selectFields)
{
$selectFieldSettings = array();
foreach($fieldSettings as $key => $fieldSetting)
{
if(in_array($key, $selectFields)) $selectFieldSettings[$key] = $fieldSetting;
}
return $selectFieldSettings;
}
/**
* Implode group keys of record.
*
* @param array $groups
* @param object $record
* @access public
* @return string
*/
public function getGroupsKey($groups, $record)
{
$groupsKey = array();
foreach($groups as $group) $groupsKey[] = $record->$group;
return implode('_', $groupsKey);
}
/**
* Get slice field key of record.
*
* @param int $index
* @param string $slice
* @param string $field
* @param object $record
* @access public
* @return string
*/
public function getSliceFieldKey($index, $slice, $field, $record)
{
if($slice == 'noSlice') return $field . $index;
return $record->$slice . '_slice_' . $field . $index;
}
/**
* Init statistic column with slice.
*
* @param int $index
* @param string $field
* @param string $slice
* @param array $groups
* @param array $records
* @access public
* @return array
*/
public function initSliceColumnRecords($index, $field, $slice, $groups, $records)
{
$columnRecords = array();
$groupUnique = array();
$sliceUnique = array();
foreach($records as $record)
{
$groupUnionKey = $this->getGroupsKey($groups, $record);
$fieldKey = $this->getSliceFieldKey($index, $slice, $field, $record);
$sliceUnique[$fieldKey] = 1;
$groupUnique[$groupUnionKey] = $record;
}
$sliceKeys = array_keys($sliceUnique);
$groupUnionKeys = array_keys($groupUnique);
foreach($groupUnionKeys as $groupUnionKey)
{
$columnRecord = new stdclass();
foreach($groups as $group) $columnRecord->$group = $groupUnique[$groupUnionKey]->$group;
foreach($sliceKeys as $sliceKey)
{
$columnRecord->$sliceKey = array('count' => 0, 'distinct' => array(), 'sum' => 0, 'avg' => array(), 'max' => array(), 'min' => array());
}
$columnRecords[$groupUnionKey] = $columnRecord;
}
return $columnRecords;
}
/**
* Process column stat with slice.
*
* @param int $index
* @param string $field
* @param string $slice
* @param string $stat
* @param array $groups
* @param array $records
* @access public
* @return array
*/
public function processColumnStat($index, $field, $slice, $stat, $groups, $records)
{
$sliceRecords = $this->initSliceColumnRecords($index, $field, $slice, $groups, $records);
foreach($records as $record)
{
$groupUnionKey = $this->getGroupsKey($groups, $record);
$fieldKey = $this->getSliceFieldKey($index, $slice, $field, $record);
$sliceGroupRecord = $sliceRecords[$groupUnionKey];
$value = $record->$field;
$floatValue = is_numeric($value) ? (float)$value : 0;
switch($stat)
{
case 'sum':
$sliceGroupRecord->{$fieldKey}[$stat] += $floatValue;
break;
case 'avg':
case 'max':
case 'min':
$sliceGroupRecord->{$fieldKey}[$stat][] = $floatValue;
break;
case 'count':
$sliceGroupRecord->{$fieldKey}[$stat] += 1;
break;
case 'distinct':
$sliceGroupRecord->{$fieldKey}[$stat][] = $value;
break;
}
}
foreach($sliceRecords as $groupUnionKey => $sliceRecord)
{
$sliceFields = array_keys((array)$sliceRecord);
foreach($sliceFields as $sliceField)
{
/* 分组字段直接跳过。*/
/* Skip the group field directly. */
if(in_array($sliceField, $groups)) continue;
$sliceStat = $sliceRecord->{$sliceField}[$stat];
switch($stat)
{
case 'sum':
$sliceRecord->$sliceField = round($sliceStat, 2);
break;
case 'avg':
$sum = array_sum($sliceStat);
$count = count($sliceStat);
$sliceRecord->$sliceField = $count == 0 ? 0 : round($sum / $count, 2);
break;
case 'max':
$sliceRecord->$sliceField = $sliceStat ? round(max($sliceStat), 2) : 0;
break;
case 'min':
$sliceRecord->$sliceField = $sliceStat ? round(min($sliceStat), 2) : 0;
break;
case 'count':
$sliceRecord->$sliceField = $sliceStat;
break;
case 'distinct':
$sliceRecord->$sliceField = count(array_unique($sliceStat));
break;
}
}
}
return $sliceRecords;
}
/**
* Process column original.
*
* @param int $index
* @param string $field
* @param array $groups
* @param array $records
* @access public
* @return array
*/
public function processColumnOriginal($index, $field, $groups, $records)
{
$columnRecords = array();
foreach($records as $record)
{
$columnRecord = new stdclass();
foreach($groups as $group) $columnRecord->$group = $record->$group;
$columnRecord->{$field . $index} = $record->$field;
$columnRecords[] = $columnRecord;
}
return $columnRecords;
}
/**
* Append where filter to sql from filters.
*
* @param string $sql
* @param array $filters
* @access public
* @return string
*/
public function appendSelectAndWhereFilterToSql($sql, $fields, $filters)
{
$connectSQL = '';
if(!empty($filters) && !isset($filters[0]['from']))
{
$wheres = array();
foreach($filters as $field => $filter)
{
$wheres[] = "tt.`$field` {$filter['operator']} {$filter['value']}";
}
$whereStr = implode(' and ', $wheres);
$connectSQL .= " where $whereStr";
}
$fieldWithAlias = array();
foreach($fields as $field)
{
$fieldWithAlias[] = "tt.`$field`";
}
$fieldStr = implode(', ', $fieldWithAlias);
$sql = "select $fieldStr from ($sql) tt" . $connectSQL;
return $sql;
}
/**
* Generate group tree.
*
* @param array $groups
* @param array $records
* @access public
* @return array
*/
public function generateGroupTree($groups, $records)
{
$groupTree = array();
foreach($records as $record)
{
$currentGroupTree = &$groupTree;
foreach($groups as $group)
{
$groupValue = $record->$group;
if(!isset($currentGroupTree[$groupValue])) $currentGroupTree[$groupValue] = array();
$currentGroupTree = &$currentGroupTree[$groupValue];
}
}
return $groupTree;
}
/**
* Flatten group tree.
*
* @param array $groups
* @param array $groupTree
* @access public
* @return void
*/
public function flattenGroupTree($groups, $groupTree)
{
$flattenGroup = array_map(function($key, $value) {
return array($key, $value);
}, array_keys($groupTree), $groupTree);
$groupLevels = count($groups);
$currentLevel = 1;
$flattenGroupRecords = array();
while($currentLevel < count($groups))
{
$nextFlatten = array();
foreach($flattenGroup as $flatten)
{
$lastValue = array_pop($flatten);
foreach($lastValue as $key => $value)
{
/* 如果value为空那么说明到了最后一级直接构建分组键值的数组。*/
/* If the value is empty, it means that the last level has been reached, and the array of the group key value is constructed directly. */
if(empty($value))
{
$unionKey = implode('_', $flatten) . '_' . $key;
$flattenGroupRecords[$unionKey] = array();
continue;
}
$nextFlatten[] = array_merge($flatten, array($key, $value));
}
}
$flattenGroup = $nextFlatten;
$currentLevel ++;
}
return $flattenGroupRecords;
}
/**
* Sort merge records with group field.
*
* @param array $records
* @param array $mergeRecords
* @param array $groups
* @access public
* @return array
*/
public function orderByRecordsGroups($records, $mergeRecords, $groups)
{
$groupTree = $this->generateGroupTree($groups, $records);
$groupRecords = $this->flattenGroupTree($groups, $groupTree);
foreach($mergeRecords as $mergeRecord)
{
$groupUnionKey = $this->getGroupsKey($groups, $mergeRecord);
$groupRecords[$groupUnionKey][] = $mergeRecord;
}
$orderRecords = array();
foreach($groupRecords as $groupRecord)
{
$orderRecords = array_merge($orderRecords, $groupRecord);
}
return $orderRecords;
}
/**
* Sort array with item length.
*
* @param array $array
* @access public
* @return array
*/
public function sortWithItemLength($array)
{
usort($array, function($a, $b) {
$lengthA = count($a);
$lengthB = count($b);
if ($lengthA == $lengthB) {
return 0;
}
return ($lengthA > $lengthB) ? -1 : 1;
});
return $array;
}
/**
* Genereate table cols config.
*
* @param array $fields
* @param array $groups
* @param array $langs
* @access public
* @return array
*/
public function generateTableCols($fields, $groups, $langs)
{
$cols = array();
$clientLang = $this->app->getClientLang();
/* Build cols. */
foreach($groups as $group)
{
$fieldObject = $fields[$group]['object'];
$relatedField = $fields[$group]['field'];
$col = new stdclass();
$col->name = $group;
$col->field = $relatedField;
$col->isGroup = true;
$colLabel = $group;
if($fieldObject)
{
$this->app->loadLang($fieldObject);
if(isset($this->lang->$fieldObject->$relatedField)) $colLabel = $this->lang->$fieldObject->$relatedField;
}
if(isset($langs[$group]) and !empty($langs[$group][$clientLang])) $colLabel = $langs[$group][$clientLang];
$col->label = $colLabel;
$cols[0][] = $col;
}
return $cols;
}
/**
* Merge origin records.
*
* @param array $originColumns
* @param array $mergeRecords
* @access public
* @return void
*/
public function mergeOriginRecords($originColumns, $mergeRecords)
{
if(empty($originColumns)) return $mergeRecords;
$originColumns = $this->sortWithItemLength($originColumns);
foreach($originColumns as $columnIndex => $columnSetting)
{
if(empty($mergeRecords))
{
$mergeRecords = $columnSetting['records'];
continue;
}
$columnRecords = $columnSetting['records'];
foreach($mergeRecords as $index => $mergeRecord)
{
$mergeRecords[$index] = (object)array_merge((array)$mergeRecord, (array)$columnRecords[$index]);
}
}
return $mergeRecords;
}
/**
* Merge statistic records.
*
* @param array $statColumns
* @param array $groups
* @param array $mergeRecords
* @access public
* @return array
*/
public function mergeStatRecords($statColumns, $groups, $mergeRecords)
{
if(empty($statColumns)) return $mergeRecords;
$statColumns = $this->sortWithItemLength($statColumns);
foreach($statColumns as $columnIndex => $columnSetting)
{
if(empty($mergeRecords))
{
$mergeRecords = $columnSetting['records'];
continue;
}
$columnRecords = $columnSetting['records'];
foreach($mergeRecords as $key => $mergeRecord)
{
$groupUnionKey = $this->getGroupsKey($groups, $mergeRecord);
$mergeRecords[$key] = (object)array_merge((array)$mergeRecord, (array)$columnRecords[$groupUnionKey]);
}
}
return $mergeRecords;
}
/**
* Gen sheet.
*
* @param array $fields
* @param array $settings
* @param string $sql
* @param array $filters
* @param array $langs
* @access public
* @return array
*/
public function genSheet($fields, $settings, $sql, $filters, $langs = array())
{
$groups = $this->getGroupsFromSettings($settings);
$cols = $this->generateTableCols($fields, $groups, $langs);
$selectFields = $this->getSelectFieldsFromSettings($settings);
$fields = $this->filterWithSelectFields($fields, $selectFields);
/* Replace the variable with the default value. */
$sql = $this->initVarFilter($filters, $sql);
$sql = $this->trimSemicolon($sql);
$sql = $this->appendSelectAndWhereFilterToSql($sql, $selectFields, $filters);
$records = $this->dao->query($sql)->fetchAll();
$records = $this->mapRecordValueWithFieldOptions($records, $fields, $sql);
$showColTotal = zget($settings, 'columnTotal', 'noShow');
$mergeRecords = array();
if(isset($settings['columns']))
{
$columnSettings = $settings['columns'];
foreach($columnSettings as $columnIndex => $columnSetting)
{
$columnShowOrigin = isset($columnSetting['showOrigin']) ? $columnSetting['showOrigin'] : false;
$columnStat = $columnSetting['stat'];
$columnField = $columnSetting['field'];
$columnSlice = zget($columnSetting, 'slice', 'noSlice');
$cols = $this->getTableHeader($records, $columnSetting, $fields, $cols, $sql, $langs, $columnShowOrigin);
if($columnShowOrigin)
{
$columnRecords = $this->processColumnOriginal($columnIndex, $columnField, $groups, $records);
$columnRecords = $this->processShowData($columnRecords, $groups, $columnSetting, $showColTotal, $columnField . $columnIndex);
$columnSetting['records'] = $columnRecords;
$mergeRecords = $this->mergeOriginRecords(array($columnSetting), $mergeRecords);
}
elseif(!empty($columnStat))
{
$columnRecords = $this->processColumnStat($columnIndex, $columnField, $columnSlice, $columnStat, $groups, $records);
$columnRecords = $this->processShowData($columnRecords, $groups, $columnSetting, $showColTotal, $columnField . $columnIndex);
$columnSetting['records'] = $columnRecords;
$mergeRecords = $this->mergeStatRecords(array($columnSetting), $groups, $mergeRecords);
}
}
}
$mergeRecords = array_values($mergeRecords);
$mergeRecords = $this->orderByRecordsGroups($records, $mergeRecords, $groups);
$data = new stdclass();
$data->groups = $groups;
$data->cols = $cols;
$data->array = json_decode(json_encode($mergeRecords), true);
$data->columnTotal = isset($settings['columnTotal']) ? $settings['columnTotal'] : '';
$configs = $this->calculateMergeCellConfig($groups, $mergeRecords);
/* $data->groups array 代表分组,最多三个
* $data->cols array thead数据其中对象有三个属性name分组label列的名字isGroup标识是不是分组
* $data->arrays array tbody数据, 其中每一个数组内是一行td的数据
*
* $configs, eg: array(0 => array(0 => 2, 1 => 1), 2 => array(0 => 2))
* 代表在整个tbody中位于[0,0]坐标的td rowspan为2位于[0,1]坐标的td rowspan为1, 位于[2,0]坐标的td rowspan为2
*/
return array($data, $configs);
}
/**
* Map record value with field options.
*
* @param array $records
* @param array $fields
* @param string $sql
* @access public
* @return array
*/
public function mapRecordValueWithFieldOptions($records, $fields, $sql)
{
$this->app->loadConfig('dataview');
$records = json_decode(json_encode($records), true);
$fieldOptions = $this->getFieldsOptions($fields, $sql);
foreach($fields as $field => $fieldSetting)
{
$tableField = $fieldSetting['object'] . '-' . $fieldSetting['field'];
$fields[$field]['withComma'] = in_array($tableField, $this->config->dataview->multipleMappingFields);
$fields[$field]['options'] = isset($fieldOptions[$field]) ? $fieldOptions[$field] : array();
}
foreach($records as $index => $record)
{
foreach($fields as $field => $fieldSetting)
{
$withComma = $fieldSetting['withComma'];
$optionList = $fieldSetting['options'];
if($withComma)
{
$valueArr = array_filter(explode(',', $record[$field]));
$resultArr = array();
foreach($valueArr as $val)
{
$resultArr[] = isset($optionList[$val]) ? $optionList[$val] : $val;
}
$record[$field] = implode(',', $resultArr);
}
else
{
$valueKey = "$record[$field]";
$record[$field] = isset($optionList[$valueKey]) ? $optionList[$valueKey] : $record[$field];
}
}
$records[$index] = (object)$record;
}
return $records;
}
/**
* Calculate merge cell config.
*
* @param array $groups
* @param array $records
* @access public
* @return void
*/
public function calculateMergeCellConfig($groups, $records)
{
$configs = array();
foreach($groups as $index => $group)
{
if($index == 0)
{
$groupRecords = array();
foreach($records as $record) $groupRecords[$record->$group][] = $record;
}
$haveNext = isset($groups[$index + 1]);
$this->getColumnConfig($groupRecords, $configs, $groups, $index, $haveNext);
}
return $configs;
}
/**
* Get column config to merge table cell.
*
* @param array $groupRows
* @param array $configs
* @param array $groups
* @param int $index
* @access public
* @return void
*
* Init $groupRows like this: array('people1' => [0 => ['create' => 'people1', 'product' => 'product1']], 'people2' => ['create' => 'people2', 'product' => 'product2']])
* The second time the function is executed, groupRows is passed in array('people1_product1' => [0 => ['create' => 'people1', 'product' => 'product1']], 'people2_product2' => ['create' => 'people2', 'product' => 'product2']])
*
* The key value of this array is unique;
*/
public function getColumnConfig(&$groupRows, &$configs, $groups, $index, $haveNext)
{
$newRows = array();
$start = 1;
$next = $index + 1;
foreach($groupRows as $key => $datas)
{
$number = count($datas);
$configs[$start - 1][$next - 1] = $number;
$start += $number;
if($haveNext)
{
$nextGroup = $groups[$next];
foreach($datas as $data)
{
$newKey = $key . '_' . $data->$nextGroup;
$newRows[$newKey][] = $data;
}
}
}
if($haveNext) $groupRows = $newRows;
}
/**
* InitVarFilter.
*
* @param string $filters
* @param string $sql
* @access public
* @return void
*/
public function initVarFilter($filters = '', $sql = '')
{
if(empty($filters)) return $sql;
foreach($filters as $filter)
{
if(empty($filter['from'])) continue;
$default = isset($filter['default']) ? $filter['default'] : '';
if(is_array($default))
{
$default = array_filter($default, function($val){return !empty($val);});
$default = implode("', '", $default);
}
$sql = str_replace('$' . $filter['field'], "'{$default}'", $sql);
}
if(preg_match_all("/[\$]+[a-zA-Z0-9]+/", $sql, $out))
{
foreach($out[0] as $match) $sql = str_replace($match, "''", $sql);
}
return $sql;
}
/**
* Trim semicolon of sql.
*
* @param string $sql
* @access public
* @return string
*/
public function trimSemicolon($sql)
{
return str_replace(';', '', $sql);
}
/**
* Get the header of the table.
*
* @param array $columnRows
* @param array $column
* @param array $fields
* @param array $cols
* @param array $langs
* @access public
* @return array
*/
public function getTableHeader($columnRows, $column, $fields, $cols, $sql, $langs = array(), $showOrigin = false)
{
$stat = zget($column, 'stat', '');
$showMode = zget($column, 'showMode', 'default');
$monopolize = $showMode == 'default' ? '' : zget($column, 'monopolize', '');
$col = new stdclass();
$col->name = $column['field'];
$col->isGroup = false;
$col->showOrigin = $showOrigin;
$fieldObject = $fields[$column['field']]['object'];
$relatedField = $fields[$column['field']]['field'];
$colLabel = $column['field'];
if($fieldObject)
{
$this->app->loadLang($fieldObject);
if(isset($this->lang->$fieldObject->$relatedField)) $colLabel = $this->lang->$fieldObject->$relatedField;
}
$clientLang = $this->app->getClientLang();
if(isset($langs[$column['field']]) and !empty($langs[$column['field']][$clientLang])) $colLabel = $langs[$column['field']][$clientLang];
if(!$showOrigin)
{
$colLabel = str_replace('{$field}', $colLabel, $this->lang->pivot->colLabel);
$colLabel = str_replace('{$stat}', zget($this->lang->pivot->step2->statList, $stat), $colLabel);
if($showMode != 'default') $colLabel .= sprintf($this->lang->pivot->colShowMode, zget($this->lang->pivot->step2->showModeList, $showMode));
}
$col->label = $colLabel;
$slice = zget($column, 'slice', 'noSlice');
if($slice != 'noSlice' && !$showOrigin)
{
if(!isset($cols[1]))
{
foreach($cols[0] as $colData) $colData->rowspan = '2';
$cols[1] = array();
}
$sliceList = array();
foreach($columnRows as $rows) $sliceList[$rows->{$slice}] = $rows->{$slice};
$optionList = $this->getSysOptions($fields[$slice]['type'], $fields[$slice]['object'], $fields[$slice]['field'], $sql);
foreach($sliceList as $field)
{
$childCol = new stdclass();
$childCol->name = $field;
$childCol->isGroup = false;
$childCol->label = isset($optionList[$field]) ? $optionList[$field] : $field;
$childCol->colspan = $monopolize ? 2 : 1;
$cols[1][] = $childCol;
}
$col->colspan = count($sliceList);
if($monopolize) $col->colspan *= 2;
if(zget($column, 'showTotal', 'noShow') !== 'noShow')
{
$childCol = new stdclass();
$childCol->name = 'sum';
$childCol->isGroup = false;
$childCol->label = $this->lang->pivot->step2->total;
$childCol->colspan = $monopolize ? 2 : 1;
$cols[1][] = $childCol;
$col->colspan += $childCol->colspan;
}
$cols[0][] = $col;
}
else
{
$col->rowspan = !isset($cols[1]) ? '1' : '2';
$col->colspan = $monopolize ? 2 : 1;
$cols[0][] = $col;
}
return $cols;
}
/**
* Get ratio.
*
* @param float $value
* @param float $total
* @access public
* @return float
*/
public function getRatio($value, $total)
{
return $total == 0 ? '0%' : round((float)$value / (float)$total * 100, 2) . '%';
}
/**
* Process column show mode.
*
* @param array $columnRows
* @param array $groups
* @param arry $column
* @param string $showColTotal
* @param int $uuName
* @access public
* @return array
*/
public function processShowData($columnRows, $groups, $column, $showColTotal, $uuName)
{
$slice = zget($column, 'slice', 'noSlice');
$showMode = zget($column, 'showMode', 'default');
$showTotal = $slice == 'noSlice' ? 'noShow' : zget($column, 'showTotal', 'noShow');
$monopolize = $showMode == 'default' ? '' : zget($column, 'monopolize', '');
$colTotal = array();
$rowTotal = array();
$allTotal = 0;
foreach($columnRows as $index => $row)
{
if(!isset($rowTotal[$index])) $rowTotal[$index] = 0;
foreach($row as $field => $value)
{
if(in_array($field, $groups)) continue;
if(!isset($colTotal[$field])) $colTotal[$field] = 0;
if($monopolize)
{
if(!isset($colTotal["self_$field"])) $colTotal["self_$field"] = 0;
$colTotal["self_$field"] += (float)$value;
}
$colTotal[$field] += (float)$value;
$rowTotal[$index] += (float)$value;
$allTotal += (float)$value;
}
}
if($showMode == 'total')
{
foreach($columnRows as $index => $row)
{
$columnRow = new stdclass();
foreach($row as $field => $value)
{
if(in_array($field, $groups))
{
$columnRow->$field = $value;
continue;
}
if($monopolize) $columnRow->{"self_$field"} = $value;
$columnRow->{$field} = $this->getRatio($value, $allTotal);
}
if($showTotal == 'sum')
{
if($monopolize) $columnRow->{"sum_self_$uuName"} = (float)$rowTotal[$index];
$columnRow->{"sum_$uuName"} = $this->getRatio($rowTotal[$index], $allTotal);
}
$columnRows[$index] = $columnRow;
}
}
if($showMode == 'row')
{
foreach($columnRows as $index => $row)
{
$columnRow = new stdclass();
foreach($row as $field => $value)
{
if(in_array($field, $groups))
{
$columnRow->$field = $value;
continue;
}
if($monopolize) $columnRow->{"self_$field"} = $value;
$columnRow->{$field} = $this->getRatio($value, $rowTotal[$index]);
}
if($showTotal == 'sum')
{
if($monopolize) $columnRow->{"sum_self_$uuName"} = (float)$rowTotal[$index];
$columnRow->{'sum_' . $uuName} = $this->getRatio($rowTotal[$index], $rowTotal[$index]);
}
$columnRows[$index] = $columnRow;
}
}
if($showMode == 'column')
{
foreach($columnRows as $index => $row)
{
$columnRow = new stdclass();
foreach($row as $field => $value)
{
if(in_array($field, $groups))
{
$columnRow->$field = $value;
continue;
}
if($monopolize) $columnRow->{"self_$field"} = $value;
$columnRow->{$field} = $this->getRatio($value, $colTotal[$field]);
}
if($showTotal == 'sum')
{
if($monopolize) $columnRow->{"sum_self_$uuName"} = (float)$rowTotal[$index];
$columnRow->{'sum_' . $uuName} = $this->getRatio($rowTotal[$index], $allTotal);
}
$columnRows[$index] = $columnRow;
}
}
if($showMode == 'default' and $showTotal == 'sum')
{
foreach($columnRows as $index => $row) $row->{'sum_' . $uuName} = $rowTotal[$index];
}
if($showColTotal == 'sum')
{
if(empty($columnRows)) return $columnRows;
$colTotalRow = new stdClass();
foreach(reset($columnRows) as $field => $value)
{
if(in_array($field, $groups))
{
$colTotalRow->$field = '$togalGroup$';
}
else
{
if($showTotal == 'sum' and $field == "sum_$uuName")
{
if($showMode == 'default')
{
$colTotalRow->{$field} = $allTotal;
}
else
{
$colTotalRow->{$field} = $this->getRatio($allTotal, $allTotal);
}
continue;
}
if(strpos($field, 'sum_self_') !== false)
{
$colTotalRow->{$field} = $allTotal;
continue;
}
if(strpos($field, 'self_') !== false)
{
$colTotalRow->{$field} = $colTotal[$field];
continue;
}
if($showMode == 'default') $colTotalRow->$field = $colTotal[$field];
if($showMode == 'column') $colTotalRow->$field = $this->getRatio($colTotal[$field], $colTotal[$field]);
if(strpos(',total,row,', ",$showMode,") !== false) $colTotalRow->$field = $this->getRatio($colTotal[$field], $allTotal);
}
}
$groupKey = $this->getGroupsKey($groups, $colTotalRow);
$columnRows[$groupKey] = $colTotalRow;
}
return $columnRows;
}
/**
* Process data as slice table data.
*
* @param array $columnRows
* @param array $groups
* @param string $sliceField
* @param string $uuName
* @access public
* @return array
*/
public function processSliceData($columnRows, $groups, $sliceField, $uuName)
{
$sliceRows = array();
if($sliceField == 'noSlice')
{
foreach($columnRows as $columnRow)
{
$groupKeys = array();
foreach($groups as $group) $groupKeys[$group] = $columnRow->{$group};
$groupKeyStr = implode('_', $groupKeys);
$sliceRows[$groupKeyStr] = (object)$groupKeys;
$sliceRows[$groupKeyStr]->{$uuName} = $columnRow->{$uuName};
}
return $sliceRows;
}
$sliceList = array();
foreach($columnRows as $rows) $sliceList[$rows->{$sliceField}] = $rows->{$sliceField};
foreach($columnRows as $key => $columnRow)
{
$field = $columnRow->$sliceField . '_slice_' . $uuName;
$groupKeys = array();
foreach($groups as $group) $groupKeys[$group] = $columnRow->{$group};
$groupKeyStr = implode('_', $groupKeys);
if(!isset($sliceRows[$groupKeyStr]))
{
$sliceRows[$groupKeyStr] = (object)$groupKeys;
foreach($sliceList as $slice) $sliceRows[$groupKeyStr]->{$slice . '_slice_' . $uuName} = '';
}
$sliceRows[$groupKeyStr]->{$field} = $columnRow->{$uuName};
}
return $sliceRows;
}
/**
* Get sys options.
*
* @param string $type
* @param string $object
* @param string $field
* @access public
* @return array
*/
public function getSysOptions($type, $object = '', $field = '', $source = '', $saveAs = '')
{
$options = array('' => '');
switch($type)
{
case 'user':
$options = $this->loadModel('user')->getPairs('noletter');
break;
case 'product':
$options = $this->loadModel('product')->getPairs();
break;
case 'project':
$options = $this->loadModel('project')->getPairsByProgram();
break;
case 'execution':
$options = $this->loadModel('execution')->getPairs();
break;
case 'dept':
$options = $this->loadModel('dept')->getOptionMenu(0);
break;
case 'project.status':
$this->app->loadLang('project');
$options = $this->lang->project->statusList;
break;
case 'option':
if($field)
{
$path = $this->app->getModuleRoot() . 'dataview' . DS . 'table' . DS . "$object.php";
if(is_file($path))
{
include $path;
$options = $schema->fields[$field]['options'];
}
}
break;
case 'object':
if($field)
{
if(is_array($source))
{
$options = array();
foreach($source as $row) $options[$row->id] = $row->$field;
}
else
{
$useField = $field;
$useTable = $object;
$path = $this->app->getModuleRoot() . 'dataview' . DS . 'table' . DS . "$object.php";
if(is_file($path))
{
include $path;
$fieldObject = $schema->fields[$field]['object'];
$fieldShow = explode('.', $schema->fields[$field]['show']);
if($fieldObject) $useTable = $fieldObject;
if(count($fieldShow) == 2) $useField = $show[1];
}
$table = isset($this->config->objectTables[$useTable]) ? $this->config->objectTables[$useTable] : zget($this->config->objectTables, $object, '');
if($table)
{
$columns = $this->dbh->query("SHOW COLUMNS FROM $table")->fetchAll();
foreach($columns as $id => $column) $columns[$id] = (array)$column;
$fieldList = array_column($columns, 'Field');
$useField = in_array($useField, $fieldList) ? $useField : 'id';
$options = $this->dao->select("id, {$useField}")->from($table)->fetchPairs();
}
}
}
break;
case 'string':
case 'number':
if($field)
{
$options = array();
if(is_array($source))
{
foreach($source as $row) $options["{$row->$field}"] = $row->$field;
}
}
break;
}
if(is_string($source) and $source)
{
if(in_array($type, array('string', 'number')))
{
$keyField = $field;
$valueField = $saveAs ? $saveAs : $field;
$options = $this->getOptionsFromSql($source, $keyField, $valueField);
}
elseif($saveAs)
{
$options = $this->getOptionsFromSql($source, $field, $saveAs);
}
}
return array_filter($options);
}
/**
* Get pairs from column by keyField and valueField.
*
* @param string $sql
* @param string $keyField
* @param string $valueField
* @access public
* @return array
*/
public function getOptionsFromSql($sql, $keyField, $valueField)
{
$options = array();
$cols = $this->dbh->query($sql)->fetchAll();
$sample = current($cols);
if(!isset($sample->$keyField) or !isset($sample->$valueField)) return $options;
foreach($cols as $col)
{
$key = $col->$keyField;
$value = $col->$valueField;
$options[$key] = $value;
}
return $options;
}
/**
* Get tree of pivots and groups.
*
* @param int $groupID
* @param string $orderBy
* @access public
* @return array
*/
public function getPreviewPivots($dimensionID, $groupID, $pivotID = 0, $orderBy = 'id_desc')
{
if(!$groupID) return array(array(), array(), $groupID);
$group = $this->loadModel('tree')->getByID($groupID);
if(empty($group)) return array(array(), array(), $groupID);
$groups = $this->dao->select('id, name')->from(TABLE_MODULE)
->where('deleted')->eq('0')
->andWhere('type')->eq('pivot')
->beginIF($group->grade == '1')->andWhere('path')->like("%,$group->id,%")->fi()
->beginIF($group->grade == '2')->andWhere('path')->like("%,$group->parent,%")->fi()
->orderBy('`order`')
->fetchPairs();
if(empty($groups)) return array(array(), array(), $groupID);
$pivotGroups = array();
foreach($groups as $id => $groupName)
{
$pivotGroups[$id] = $this->dao->select('*')->from(TABLE_PIVOT)
->where('deleted')->eq(0)
->andWhere("FIND_IN_SET($id, `group`)")
->andWhere('stage')->ne('draft')
->orderBy($orderBy)
->fetchAll();
}
if(!$pivotGroups) return array(array(), array(), $groupID);
$pivotTree = '';
foreach($pivotGroups as $id => $pivots)
{
if(!$pivots) continue;
$pivots = $this->processPivot($pivots, false);
$groupName = zget($groups, $id, $id);
$title = "title='{$groupName}'";
$pivotTree .= "<li class='closed' $title><a>$groupName</a>";
$pivotTree .= "<ul>";
foreach($pivots as $pivot)
{
$className = "pivot-{$id}-{$pivot->id}";
$params = helper::safe64Encode("pivotID=$pivot->id");
$linkHtml = html::a(helper::createLink('pivot', 'preview', "dimensionID=$dimensionID&group=$id&module=pivot&medhot=show&params=$params"), $pivot->name, '_self', "id='module{$pivot->id}' title='{$pivot->name}'");
$pivotTree .= "<li class='$className'>$linkHtml</li>";
if(!$pivotID)
{
$pivotID = $pivot->id;
$groupID = $id;
}
}
$pivotTree .= "</ul></li>";
}
if($pivotTree) $pivotTree = "<ul id='pivotGroups' class='tree' data-ride='tree'>$pivotTree</ul>";
$pivot = $pivotID ? $this->getByID($pivotID, true) : array();
return array($pivotTree, $pivot, $groupID);
}
/**
* Adjust the action is clickable.
*
* @param object $pivot
* @param string $action
* @static
* @access public
* @return bool
*/
public static function isClickable($pivot, $action)
{
if($pivot->builtin) return false;
return true;
}
/**
* 通过合并单元格的数据对透视表进行分页
* Page pivot by configs merge cell data.
*
* @param array $configs
* @param int $page
* @param bool $useColumnTotal
* @static
* @access public
* @return bool
*/
public function pagePivot($configs, $page, $useColumnTotal)
{
$configs = array_values($configs);
// 当前在第几页
$nowPage = 1;
// 一共多少行
$pageCount = 0;
// 当前页目前多少行
$nowCount = 0;
// 记录当前第几个分组(项),共有多少行
$itemRow = array(0 => 0);
// 目标分页的起始行和结束行
$start = $end = -1;
foreach($configs as $key => $config)
{
if($nowPage == $page and $start == -1) $start = $pageCount;
if($nowPage == $page + 1 and $end == -1) $end = $pageCount;
$pageCount += $config[0];
$nowCount += $config[0];
$itemRow[] = $pageCount;
// 如果当前页超过了50行换一页
if($nowCount >= $this->config->pivot->recPerPage)
{
$nowCount = 0;
if(isset($configs[$key + 1])) $nowPage += 1;
}
}
if($start == -1) $start = 0;
if($end == -1) $end = $pageCount;
// 获得当前页面有多少"项"
$endKey = array_search($end, $itemRow);
$startKey = array_search($start, $itemRow);
$itemCount = $endKey - $startKey;
// 如果是最后一页且使用了显示列的汇总,项数-1
if($page == $nowPage and $useColumnTotal) $itemCount --;
return array($start, $end - 1, $itemCount, $nowPage);
}
/**
* Build table use data and rowspan.
*
* @param object $data
* @param array $configs
* @param int $page
* @access public
* @return void
*
*/
public function buildPivotTable($data, $configs, $page = 0)
{
$width = 128;
/* Init table. */
$table = "<div class='reportData'><table class='table table-condensed table-striped table-bordered table-fixed datatable' style='width: auto; min-width: 100%' data-fixed-left-width='400'>";
$showOrigins = array();
$hasShowOrigin = false;
foreach($data->cols[0] as $col)
{
$colspan = zget($col, 'colspan', 1);
$showOrigin = isset($col->showOrigin) ? $col->showOrigin : false;
$colShowOrigin = array_fill(0, $colspan, $showOrigin);
$showOrigins = array_merge($showOrigins, $colShowOrigin);
if($showOrigin) $hasShowOrigin = true;
}
/* Init table thead. */
$table .= "<thead>";
foreach($data->cols as $lineCols)
{
$table .= "<tr>";
foreach($lineCols as $col)
{
$isGroup = $col->isGroup;
$thName = $col->label;
$colspan = zget($col, 'colspan', 1);
$rowspan = zget($col, 'rowspan', 1);
if($isGroup) $thHtml = "<th data-flex='false' rowspan='$rowspan' colspan='$colspan' data-width='auto' class='text-center'>$thName</th>";
else $thHtml = "<th data-flex='true' rowspan='$rowspan' colspan='$colspan' data-type='number' data-width=$width class='text-center'>$thName</th>";
$table .= $thHtml;
}
$table .= "</tr>";
}
$table .= "</thead>";
/* Init table tbody. */
$table .= "<tbody>";
$rowCount = 0;
$useColumnTotal = (!empty($data->columnTotal) and $data->columnTotal === 'sum');
if($page) list($start, $end, $itemCount, $pageTotal) = $this->pagePivot($configs, $page, $useColumnTotal);
for($i = 0; $i < count($data->array); $i ++)
{
$rowCount ++;
if($page and ($i < $start or $i > $end)) continue;
if($useColumnTotal and $rowCount == count($data->array)) continue;
$line = array_values($data->array[$i]);
$table .= "<tr class='text-center'>";
for($j = 0; $j < count($line); $j ++)
{
$isGroup = !empty($data->cols[0][$j]) ? $data->cols[0][$j]->isGroup : false;
$rowspan = isset($configs[$i][$j]) ? $configs[$i][$j] : 1;
$hidden = isset($configs[$i][$j]) ? false : (!$isGroup ? false : true);
$showOrigin = $showOrigins[$j];
if($hasShowOrigin && !$isGroup && !$showOrigin)
{
$rowspan = isset($configs[$i]) ? end($configs[$i]) : 1;
$hidden = isset($configs[$i]) ? false : true;
}
$lineValue = $line[$j];
if($isGroup)
{
$groupName = $data->cols[0][$j]->name;
}
if(is_numeric($lineValue)) $lineValue = round($lineValue, 2);
if(!$hidden) $table .= "<td rowspan='$rowspan'>$lineValue</td>";
}
$table .= "</tr>";
}
/* Add column total. 如果分页了,只在最后一页展示 */
if($useColumnTotal and !empty($data->array) and (!$page or $page == $pageTotal))
{
$table .= "<tr class='text-center'>";
$table .= "<td colspan='" . count($data->groups) . "'>{$this->lang->pivot->step2->total}</td>";
foreach(end($data->array) as $field => $total)
{
if(in_array($field, $data->groups)) continue;
if(is_numeric($total)) $total = round($total, 2);
$table .= "<td>$total</td>";
}
$table .= "</tr>";
}
$table .= "</tbody>";
$table .= "</table></div>";
if($page)
{
$recTotal = $end - $start + 1;
$leftPage = $page - 1;
$rightPage = $page + 1;
if($recTotal) $table .= $this->getTablePager($itemCount, $leftPage, $rightPage, $page, $pageTotal);
}
echo $table;
}
public function getTablePager($itemCount, $leftPage, $rightPage, $page, $pageTotal)
{
$itemCountTip = sprintf($this->lang->pivot->recTotalTip, $itemCount);
$leftPageClass = $page == 1 ? 'disabled' : '';
$rightPageClass = $page == $pageTotal ? 'disabled' : '';
$pager = "
<div class='table-footer'>
<ul class='pager'>
<li><div class='pager-label recTotal'>{$itemCountTip}</div></li>
<li class='pager-item-left first-page $leftPageClass' onclick='queryPivotByPager(this)'>
<a class='pager-item' data-page='1' href='javascript:;'><i class='icon icon-first-page'></i></a>
</li>
<li class='pager-item-left left-page $leftPageClass' onclick='queryPivotByPager(this)'>
<a class='pager-item' data-page='{$leftPage}' href='javascript:;'><i class='icon icon-angle-left'></i></a>
</li>
<li><div class='pager-label page-number'><strong>{$page}/{$pageTotal}</strong></div></li>
<li class='pager-item-right right-page $rightPageClass' onclick='queryPivotByPager(this)'>
<a class='pager-item' data-page='{$rightPage}' href='javascript:;'><i class='icon icon-angle-right'></i></a>
</li>
<li class='pager-item-right last-page $rightPageClass' onclick='queryPivotByPager(this)'>
<a class='pager-item' data-page='{$pageTotal}' href='javascript:;'><i class='icon icon-last-page'></i></a>
</li>
</ul>
</div>";
return $pager;
}
public function getFullTablePager($recTotal, $recPerPage, $page, $leftPage, $rightPage, $pageTotal, $onclick = 'queryPivotByPager(this)')
{
$recTotalTip = sprintf($this->lang->pivot->recTotalTip, $recTotal);
$recPerPageTip = sprintf($this->lang->pivot->recPerPageTip, $recPerPage);
$leftPageClass = $page == 1 ? 'disabled' : '';
$rightPageClass = $page == $pageTotal ? 'disabled' : '';
$dropdownMenu = '';
foreach($this->config->pivot->recPerPageList as $perPage)
{
$active = $perPage == $recPerPage ? 'active' : '';
$dropdownMenu .= "<li class='recPerPage {$active}' onclick='{$onclick}'><a href='javascript:;' data-size='{$perPage}'>{$perPage}</a></li>";
}
$pager = "
<div class='table-footer'>
<ul class='pager'>
<li class='hidden current-page' data-page='{$page}'></li>
<li class='hidden current-recperpage' data-recperpage='{$recPerPage}'></li>
<li><div class='pager-label recTotal'>{$recTotalTip}</div></li>
<li>
<div class='btn-group pager-size-menu dropup'>
<button type='button' class='btn dropdown-toggle' data-toggle='dropdown' style='border-radius: 4px;'>{$recPerPageTip}<span class='caret'></span></button>
<ul class='dropdown-menu'>{$dropdownMenu}</ul>
</div>
</li>
<li class='pager-item-left first-page $leftPageClass' onclick='{$onclick}'>
<a class='pager-item' data-page='1' href='javascript:;'><i class='icon icon-first-page'></i></a>
</li>
<li class='pager-item-left left-page $leftPageClass' onclick='{$onclick}'>
<a class='pager-item' data-page='{$leftPage}' href='javascript:;'><i class='icon icon-angle-left'></i></a>
</li>
<li><div class='pager-label page-number'><strong>{$page}/{$pageTotal}</strong></div></li>
<li class='pager-item-right right-page $rightPageClass' onclick='{$onclick}'>
<a class='pager-item' data-page='{$rightPage}' href='javascript:;'><i class='icon icon-angle-right'></i></a>
</li>
<li class='pager-item-right last-page $rightPageClass' onclick='{$onclick}'>
<a class='pager-item' data-page='{$pageTotal}' href='javascript:;'><i class='icon icon-last-page'></i></a>
</li>
</ul>
</div>";
return $pager;
}
/**
* replace defined table names.
*
* @param string $sql
* @access public
* @return string
*/
public function replaceTableNames($sql)
{
if(preg_match_all("/TABLE_[A-Z]+/", $sql, $out))
{
rsort($out[0]);
foreach($out[0] as $table)
{
if(!defined($table)) continue;
$sql = str_replace($table, trim(constant($table), '`'), $sql);
}
}
$sql = preg_replace("/= *'\!/U", "!='", $sql);
return $sql;
}
public function getFieldsOptions($fieldSettings, $sql)
{
$options = array();
$sqlRecords = $this->dbh->query($sql)->fetchAll();
foreach($fieldSettings as $key => $fieldSetting)
{
$type = $fieldSetting['type'];
$object = $fieldSetting['object'];
$field = $fieldSetting['field'];
$source = $sql;
if(in_array($type, array('string', 'number', 'date'))) $source = $sqlRecords;
$options[$key] = $this->getSysOptions($type, $object, $field, $source);
}
return $options;
}
/**
* Gen sheet by origin sql.
*
* @param array $fields
* @param array $settings
* @param string $sql
* @param array $filters
* @param array $langs
* @access public
* @return string
*/
public function genOriginSheet($fields, $settings, $sql, $filters, $langs = array())
{
$sql = $this->initVarFilter($filters, $sql);
/* Process rows. */
$connectSQL = '';
if(!empty($filters) && !isset($filters[0]['from']))
{
$wheres = array();
foreach($filters as $field => $filter)
{
$wheres[] = "tt.`$field` {$filter['operator']} {$filter['value']}";
}
$whereStr = implode(' and ', $wheres);
$connectSQL .= " where $whereStr";
}
$this->app->loadClass('sqlparser', true);
$parser = new sqlparser($sql);
$statement = $parser->statements[0];
if(!$statement->limit)
{
$statement->limit = new stdclass();
$statement->limit->offset = 0;
$statement->limit->rowCount = 99999999;
}
$sql = $statement->build();
$columnSQL = "select * from ($sql) tt" . $connectSQL;
$rows = $this->dao->query($columnSQL)->fetchAll();
$rows = json_decode(json_encode($rows), true);
$cols = array();
$clientLang = $this->app->getClientLang();
/* Build cols. */
foreach($fields as $key => $field)
{
$col = new stdclass();
$col->name = $key;
$col->isGroup = true;
$fieldObject = $field['object'];
$relatedField = $field['field'];
$colLabel = $key;
if($fieldObject)
{
$this->app->loadLang($fieldObject);
if(isset($this->lang->$fieldObject->$relatedField)) $colLabel = $this->lang->$fieldObject->$relatedField;
}
if(isset($langs[$key]) and !empty($langs[$key][$clientLang])) $colLabel = $langs[$key][$clientLang];
$col->label = $colLabel;
$cols[0][] = $col;
}
$fieldOptions = $this->getFieldsOptions($fields, $sql);
foreach($rows as $key => $row)
{
foreach($row as $field => $value)
{
$optionList = isset($fieldOptions[$field]) ? $fieldOptions[$field] : array();
$row[$field] = isset($optionList[$value]) ? $optionList[$value] : $value;
}
$rows[$key] = $row;
}
$data = new stdclass();
$data->cols = $cols;
$data->array = $rows;
$configs = array_fill(0, count($rows), array_fill(0, count($fields), 1));
return array($data, $configs);
}
}
/**
* @param $pre
* @param $next
*
* @return int
*/
function sortSummary($pre, $next)
{
if($pre['validRate'] == $next['validRate']) return 0;
return $pre['validRate'] > $next['validRate'] ? -1 : 1;
}