You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1327 lines
41 KiB

<?php
// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
//
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
// $Id$
/**
* Tracker Query Library
*
* A full featured, chainable, ORM for trackers
* <p>
* chainable became popular in jQuery $(this)->fn()->fn() and is more and more popular in php. Tracker_Query uses them
* to make Trackers, which are somewhat complex, easy.
*
*
* Examples of usage (fake tracker called 'Event Tracker':
* //using id
* $results = Tracker_Query(1)
* ->byName()
* ->itemId(100)
* ->query();
*
* //using by name
* $results = Tracker_Query('Event Tracker')
* ->byName()
* ->limit(1)
* ->query();
*
*
*<p>
* The Output of tracker query is built as tracker(items(fields())) with the keys being the id (or name for fields if byName is called).
* Standard output example ($this->byName() not called):
* Array
* (
* [367] => Array //item repeats, key = itemId
* (
* [19] => 369 //item field values, key = fieldId
* [20] => 366
* [status5] => c
* [trackerId] => 5
* [itemId] => 367
* [11] => internal
* [162] => Array //items list associated to filedId 162
* (
* [0] => 176 // itemId
* [1] => Event Name // static name of an itemId
* )
* )
* )
* )
*
* ByName output example ($this->byName() called):
* Array
* (
* [367] => Array //item repeats, key = itemId
* (
* [Minute End] => 369 //item field values, key = fieldId
* [Minute Start] => 366
* [status5] => c
* [trackerId] => 5
* [itemId] => 367
* [Use Case] => internal
* [Events] => Array //items list associated to filedId 162
* (
* [0] => 176 // itemId
* [1] => Event Name // static name of an itemId
* )
* )
* )
* )
*
* @package Tiki
* @subpackage Trackers
* @author Robert Plummer
* @link http://dev.tiki.org/Tracker_Query
* @since TIki 8
*/
class Tracker_Query
{
private $tracker;
private $start = 0;
private $end = 0;
private $itemId = 0;
private $equals = [];
private $search = [];
private $fields = [];
private $status = "opc";
private $sort = null;
private $limit = 100; //added limit so default wouldn't crash system
private $offset = 0;
private $byName = false;
private $desc = false;
private $render = true;
private $excludeDetails = false;
private $lastModif = true;
private $delimiter = "[{|!|}]";
private $debug = false;
private $concat = true;
private $filterType = [];
private $inputDefaults = [];
public $itemsRaw = [];
public $permissionsChecks = true;
public $limitReached = false;
/**
* Instantiates a tracker query
*
* @access public static
* @param mixed $tracker id, (or name if called $this->byName() before query)
* @return new self
*/
public static function tracker($tracker)
{
return (new Tracker_Query($tracker));
}
/**
* Overrides permissions
*
* @access public
* @param bool $permissionsChecks, default true
* @return new self
*/
public function permissionsChecks($permissionsChecks = true)
{
$this->permissionsChecks = $permissionsChecks;
return $this;
}
/**
* change the start date unit, needs called before $this->query()
*
* @access public
* @param mixed $start unix time stamp, int or string
* @return $this for chainability
*/
public function start($start)
{
$this->start = $start;
return $this;
}
/**
* change the end date unit, needs called before $this->query()
*
* @access public
* @param mixed $end unix time stamp, int or string
* @return $this for chainability
*/
public function end($end)
{
$this->end = $end;
return $this;
}
/**
* change the itemid, needs called before $this->query()
*
* @access public
* @param int $itemId to limit output to 1 item with this id
* @return $this for chainability
*/
public function itemId($itemId)
{
$this->itemId = (int)$itemId;
return $this;
}
/**
* add a filter for refining results, needs called before $this->query()
*
* @access public
* @param array $filter an array with keys type (like, and, or), field (id or name), and value (value needed
* from tracker file item to be returned as a result)
* @return $this for chainability
*/
public function filter($filter = [])
{
$filter = array_merge(
[
'field' => '',
'type' => 'and',
'value' => ''
],
$filter
);
$this->fields[] = $filter['field'];
$this->filterType[] = $filter['type']; //really only things that should be accepted are "and" and "or", woops, and "like"
if ($filter['type'] == 'like') {
$this->search[] = $filter['value'];
} else {
$this->equals[] = $filter['value'];
}
return $this;
}
/**
* filter results on a mysql level using 'and' type, needs called before $this->query()
*
* @access public
* @param mixed $field either id or name when $this->byName() is called
* @param string $value
* @return $this for chainability
*/
public function filterFieldByValue($field, $value)
{
return $this->filter(['field' => $field, 'value' => $value, 'type' => 'and']);
}
/**
* filter results on a mysql level using 'like' + 'and' type, needs called before $this->query()
*
* @access public
* @param mixed $field either id or name when $this->byName() is called
* @param string $value
* @return $this for chainability
*/
public function filterFieldByValueLike($field, $value)
{
return $this->filter(['field' => $field, 'value' => $value, 'type' => 'like']);
}
/**
* filter results on a mysql level using 'or' type, needs called before $this->query()
*
* @access public
* @param mixed $field either id or name when $this->byName() is called
* @param string $value
* @return $this for chainability
*/
public function filterFieldByValueOr($field, $value)
{
return $this->filter(['field' => $field, 'value' => $value, 'type' => 'or']);
}
/**
* deprecated, filter results on a mysql level on field value, needs called before $this->query()
*
* @access public
* @param array $equals
* @return $this for chainability
*/
public function equals($equals = [])
{
trigger_error("Deprecated, use filter method instead");
$this->equals = $equals;
return $this;
}
/**
* deprecated, filter results on a mysql level on field value, needs called before $this->query()
*
* @access public
* @param array $search either id or name when $this->byName() is called
* @return $this for chainability
*/
public function search($search)
{
trigger_error("Deprecated, use filter method instead");
$this->search = $search;
return $this;
}
/**
* deprecated, filter results on a mysql level on field value, needs called before $this->query()
*
* @access public
* @param array $fields either id or name when $this->byName() is called
* @return $this for chainability
*/
public function fields($fields = [])
{
trigger_error("Deprecated, use filter method instead");
$this->fields = $fields;
return $this;
}
/**
* Filter tracker items on status, needs called before $this->query()
*
* @access public
* @param string $status any of or any combination of the 3 characters 'opc'
* @return $this for chainability
*/
public function status($status)
{
$this->status = $status;
return $this;
}
/**
* Not yet implemented
*
* @access public
* @param string $sort any of or any combination of the 3 characters 'opc'
* @return $this for chainability
*/
public function sort($sort)
{
$this->sort = $sort;
return $this;
}
/**
* Change limit of items, danger with large numbers, needs called before $this->query()
*
* @access public
* @param int $limit amount of items to return, maximum
* @return $this for chainability
*/
public function limit($limit)
{
$this->limit = $limit;
return $this;
}
/**
* Change offset, needs called before $this->query()
*
* @access public
* @param int $offset amount of items to offset
* @return $this for chainability
*/
public function offset($offset)
{
$this->offset = $offset;
return $this;
}
/**
* Change tracker to use all, in tracker and fields, needs called before $this->query()
*
* @access public
* @param bool $byName default to true, optional
* @return $this for chainability
*/
public function byName($byName = true)
{
$this->byName = $byName;
return $this;
}
/**
* order by lastModified, needs called before $this->query(), default to true, needs called before $this->query()
*
* @access public
* @return $this for chainability
*/
public function lastModif()
{
$this->lastModif = true;
return $this;
}
/**
* order by created, needs called before $this->query(), default to false, needs called before $this->query()
*
* @access public
* @return $this for chainability
*/
public function created()
{
$this->lastModif = false;
return $this;
}
/**
* Remove details that come with each tracker item, status, itemId, trackerId, needs called before $this->query()
* Default is to include details
*
* @access public
* @param bool $excludeDetails default to true, optional
* @return $this for chainability
*/
public function excludeDetails($excludeDetails = true)
{
$this->excludeDetails = $excludeDetails;
return $this;
}
/**
* Sort descending, default false, needs called before $this->query()
*
* @access public
* @param bool $desc default to true, optional
* @return $this for chainability
*/
public function desc($desc = true)
{
$this->desc = $desc;
return $this;
}
/**
* Turn rendering for tracker item fields off, effective to make tracker interactions much MUCH faster, needs called before $this->query()
*
* @access public
* @param bool $render
* @return $this for chainability
*/
public function render($render)
{
$this->render = $render;
return $this;
}
/**
* sets limit to 1 and calls $this->query()
*
* @access public
* @return query
*/
public function getOne()
{
return $this
->limit(1)
->query();
}
/**
* calls desc, sets limit to 1 and calls $this->query()
*
* @access public
* @return query
*/
public function getLast()
{
return $this
->desc(true)
->limit(1)
->query();
}
/**
* calls getOne, and returns only the itemId
*
* @access public
* @return int $key itemId
*/
public function getItemId()
{
$query = $this->getOne();
$key = (int)end(array_keys($query));
$key = ($key > 0 ? $key : 0);
return $key;
}
/**
* turn debug on, if having problems, outputs the built mysql query and result set of the query, kills php
*
* @access public
* @param bool $debug, default = true
* @param bool $concat, default = true
* @return $this for chainability
*/
public function debug($debug = true, $concat = true)
{
$this->debug = $debug;
$this->concat = $concat;
return $this;
}
/**
* permission check on view
*
* @access public
* @return bool view
*/
public function canView()
{
if ($this->permissionsChecks == false) {
return true;
}
return Perms::get([ 'type' => 'tracker', 'object' => $this->trackerId() ])->view;
}
/**
* permission check on edit
*
* @access public
* @return bool edit
*/
public function canEdit()
{
if ($this->permissionsChecks == false) {
return true;
}
return Perms::get([ 'type' => 'tracker', 'object' => $this->trackerId() ])->edit;
}
/**
* permission check on delete
*
* @access public
* @return bool delete
*/
public function canDelete()
{
if ($this->permissionsChecks == false) {
return true;
}
return Perms::get([ 'type' => 'tracker', 'object' => $this->trackerId() ])->delete;
}
/**
* Setup temporary table for joining trackers together
*
* @access public
* @param mixed $tracker, id or tracker name if $this->byName() called
*/
public function __construct($tracker = '')
{
global $tikilib;
$this->tracker = $tracker;
$tikilib->query(
"DROP TABLE IF EXISTS temp_tracker_field_options;"
);
$tikilib->query(
"CREATE TEMPORARY TABLE temp_tracker_field_options (
trackerIdHere INT,
trackerIdThere INT,
fieldIdThere INT,
fieldIdHere INT,
displayFieldIdThere INT,
displayFieldIdHere INT,
linkToItems INT,
type VARCHAR(1),
options VARCHAR(50)
);"
);
$tikilib->query(
"INSERT INTO temp_tracker_field_options
SELECT
tiki_tracker_fields.trackerId,
REPLACE(SUBSTRING(
SUBSTRING_INDEX(tiki_tracker_fields.options, ',', 1),
LENGTH(SUBSTRING_INDEX(tiki_tracker_fields.options, ',', 1 -1)) + 1
),
',', ''),
REPLACE(SUBSTRING(
SUBSTRING_INDEX(tiki_tracker_fields.options, ',', 2),
LENGTH(SUBSTRING_INDEX(tiki_tracker_fields.options, ',', 2 -1)) + 1
),
',', ''),
REPLACE(SUBSTRING(
SUBSTRING_INDEX(tiki_tracker_fields.options, ',', 3),
LENGTH(SUBSTRING_INDEX(tiki_tracker_fields.options, ',', 3 -1)) + 1
),
',', ''),
REPLACE(SUBSTRING(
SUBSTRING_INDEX(tiki_tracker_fields.options, ',', 4),
LENGTH(SUBSTRING_INDEX(tiki_tracker_fields.options, ',', 4 -1)) + 1
),
',', ''),
tiki_tracker_fields.fieldId,
REPLACE(SUBSTRING(
SUBSTRING_INDEX(tiki_tracker_fields.options, ',', 5),
LENGTH(SUBSTRING_INDEX(tiki_tracker_fields.options, ',', 5 -1)) + 1
),
',', ''),
tiki_tracker_fields.type,
tiki_tracker_fields.options
FROM tiki_tracker_fields
WHERE tiki_tracker_fields.type = 'l';"
);
$tikilib->query(
"SET group_concat_max_len = 4294967295;"
);
/*For any fields that have multi items, we use php to parse those out, there shouldn't be too many
*/
foreach ($tikilib->fetchAll("SELECT * FROM temp_tracker_field_options WHERE options LIKE '%|%'") as $row) {
$option = explode(",", $row["options"]);
$displayFieldIdsThere = explode("|", $option["3"]);
foreach ($displayFieldIdsThere as $key => $displayFieldIdThere) {
if ($key > 0) {
$tikilib->query(
"INSERT INTO temp_tracker_field_options VALUES (?,?,?,?,?,?,?,?,?)",
[
$row["trackerIdHere"],
$row["trackerIdThere"],
$row["fieldIdThere"],
$row["fieldIdHere"],
$displayFieldIdThere,
$row["displayFieldIdHere"],
$row["linkToItems"],
$row["type"],
$row["options"]
]
);
}
}
}
}
/**
* Adds the field names to the beginning of the array of tracker items
*
*/
public static function prepend_field_header(&$trackerPrimary = [], $nameOrder = [])
{
global $tikilib;
$result = $tikilib->fetchAll("SELECT fieldId, trackerId, name FROM tiki_tracker_fields");
$header = [];
foreach ($result as $row) {
$header[$row['fieldId']] = $row['name'];
}
$joinedTrackerHeader = [];
foreach ($trackerPrimary as $item) {
foreach ($item as $key => $field) {
$joinedTrackerHeader[$key] = $header[$key];
}
}
if (! empty($nameOrder)) {
$sortedHeader = [];
$unsortedHeader = [];
foreach ($nameOrder as $name) {
foreach ($joinedTrackerHeader as $key => $field) {
if ($field == $name) {
$sortedHeader[$key] = $field;
} else {
$unsortedHeader[$key] = $field;
}
}
}
$joinedTrackerHeader = $sortedHeader + $unsortedHeader;
}
$joinedTrackerHeader = ["HEADER" => $joinedTrackerHeader];
return $joinedTrackerHeader + $trackerPrimary;
}
/**
* Simple direction parsing from string to type
*
*/
private static function sortDirection($dir)
{
switch ($dir) {
case "asc":
$dir = SORT_ASC;
break;
case "desc":
$dir = SORT_DESC;
break;
case "regular":
$dir = SORT_REGULAR;
break;
case "numeric":
$dir = SORT_NUMERIC;
break;
case "string":
$dir = SORT_STRING;
break;
default:
$dir = SORT_ASC;
}
return $dir;
}
public static function arfsort(&$array, $fieldList)
{
if (! is_array($fieldList)) {
$fieldList = explode('|', $fieldList);
$fieldList = [[$fieldList[0], self::sortDirection($fieldList[1])]];
} else {
for ($i = 0, $count_fieldList = count($fieldList); $i < $count_fieldList; ++$i) {
$fieldList[$i] = explode('|', $fieldList[$i]);
$fieldList[$i] = [$fieldList[$i][0], self::sortDirection($fieldList[$i][1])];
}
}
$GLOBALS['__ARFSORT_LIST__'] = $fieldList;
usort($array, 'arfsortFunc');
}
public function arfsortFunc($a, $b)
{
foreach ($GLOBALS['__ARFSORT_LIST__'] as $f) {
switch ($f[1]) {
case SORT_NUMERIC:
$strc = ((float)$b[$f[0]] > (float)$a[$f[0]] ? -1 : 1);
return $strc;
break;
default:
$strc = strcasecmp($b[$f[0]], $a[$f[0]]);
if ($strc != 0) {
return $strc * (! empty($f[1]) && $f[1] == SORT_DESC ? 1 : -1);
}
}
}
return 0;
}
private function concatStr($field)
{
if ($this->concat == false) {
return $field;
} else {
return "GROUP_CONCAT(" . $field . " SEPARATOR '" . $this->delimiter . "')";
}
}
/**
* Get current tracker id
*
* @access public
* @return int $trackerId
*/
public function trackerId()
{
if ($this->byName == true) {
$trackerId = TikiLib::lib('trk')->get_tracker_by_name($this->tracker);
} else {
$trackerId = $this->tracker;
}
if (! empty($trackerId) && ! is_numeric($trackerId)) {
throw new Exception("Opps, looks like you need to call ->byName();");
}
return $trackerId;
}
/**
* Query, where the mysql command is built and executed, filtered, and rendered
* Orders results in a way that is human understandable and can be manipulated easily
* The end result is a very simple array setup as follows:
* array( //tracker(s)
* array( //items
* [itemId] => array (
* [fieldId or FieldName] => value,
* [fieldId or FieldName] => array( //items list
* [0] => '',
* [1] => ''
* )
* )
* )
* )
*/
public function query()
{
$trklib = TikiLib::lib('trk');
$tikilib = TikiLib::lib('tiki');
$params = [];
$fields_safe = "";
$status_safe = "";
$isSearch = false;
$trackerId = $this->trackerId();
if (empty($trackerId) || $this->canView() == false) {//if we can't find a tracker, then return
return [];
}
$trackerDefinition = Tracker_Definition::get($trackerId);
$trackerFieldDefinition = $trackerDefinition->getFieldsIdKeys();
$params[] = $trackerId;
if (! empty($this->start) && empty($this->search)) {
$params[] = $this->start;
}
if (! empty($this->end) && empty($this->search)) {
$params[] = $this->end;
}
if (! empty($this->itemId) && empty($this->search)) {
$params[] = $this->itemId;
}
/*Get field ids from names*/
if ($this->byName == true && ! empty($this->fields)) {
$fieldIds = [];
foreach ($this->fields as $field) {
$fieldIds[] = $tikilib->getOne(
"SELECT fieldId FROM tiki_tracker_fields" .
" LEFT JOIN tiki_trackers ON (tiki_trackers.trackerId = tiki_tracker_fields.trackerId)" .
" WHERE" .
" tiki_trackers.name = ? AND tiki_tracker_fields.name = ?",
[$this->tracker, $field]
);
}
$this->fields = $fieldIds;
}
if (count($this->fields) > 0 && (count($this->equals) > 0 || count($this->search) > 0)) {
for ($i = 0, $count_fields = count($this->fields); $i < $count_fields; $i++) {
if (strlen($this->fields[$i]) > 0) {
if ($i > 0) {
switch ($this->filterType[$i]) {
case "or":
$fields_safe .= " OR ";
break;
case "and":
$fields_safe .= " OR "; //Even though this is OR, we do a check later to limit more values, so initially we may have more results than are given later on, simply because of how trackers are stored and how group_concat allows us to manipulate trackers
break;
case "like":
$fields_safe .= " AND ";
break;
}
}
$fields_safe .= " ( search_item_fields.fieldId = ? ";
$params[] = $this->fields[$i];
if (isset($this->equals[$i])) {
$fields_safe .= " AND search_item_fields.value = ? ";
$params[] = $this->equals[$i];
}
if (isset($this->search[$i]) && strlen($this->search[$i]) > 0 && $this->filterType[$i] == "like") {
$fields_safe .= " AND search_item_fields.value LIKE ? ";
$params[] = '%' . $this->search[$i] . '%';
}
$fields_safe .= " ) ";
}
}
if (strlen($fields_safe) > 0) {
$fields_safe = " AND ( $fields_safe ) ";
$isSearch = true;
}
}
if (strlen($this->status) > 0) {
for ($i = 0, $strlen_status = strlen($this->status); $i < $strlen_status; $i++) {
if (strlen($this->status[$i]) > 0) {
$status_safe .= " tiki_tracker_items.status = ? ";
if ($i + 1 < strlen($this->status) && strlen($this->status) > 1) {
$status_safe .= " OR ";
}
$params[] = $this->status[$i];
}
}
if (strlen($status_safe) > 0) {
$status_safe = " AND ( $status_safe ) ";
}
}
if (! empty($this->limit) && is_numeric($this->limit) == false) {
unset($this->limit);
}
if (isset($this->offset) && ! empty($this->offset) && is_numeric($this->offset) == false) {
unset($this->offset);
}
$dateUnit = ($this->lastModif ? 'lastModif' : 'created');
$query =
"SELECT
tiki_tracker_items.status,
tiki_tracker_item_fields.itemId,
tiki_tracker_fields.trackerId,
" . $this->concatStr("tiki_tracker_fields.name") . " AS fieldNames,
" . $this->concatStr("tiki_tracker_item_fields.fieldId") . " AS fieldIds,
" . $this->concatStr("IFNULL(items_right.value, tiki_tracker_item_fields.value)") . " AS item_values
FROM tiki_tracker_item_fields " . ($isSearch == true ? " AS search_item_fields " : "") . "
" . ($isSearch == true ? "
LEFT JOIN tiki_tracker_item_fields ON
search_item_fields.itemId = tiki_tracker_item_fields.itemId
" : "" ) . "
LEFT JOIN tiki_tracker_fields ON
tiki_tracker_fields.fieldId = tiki_tracker_item_fields.fieldId
LEFT JOIN tiki_trackers ON
tiki_trackers.trackerId = tiki_tracker_fields.trackerId
LEFT JOIN tiki_tracker_items ON tiki_tracker_items.itemId = tiki_tracker_item_fields.itemId
LEFT JOIN temp_tracker_field_options items_left_display ON
items_left_display.displayFieldIdHere = tiki_tracker_item_fields.fieldId
LEFT JOIN tiki_tracker_item_fields items_left ON (
items_left.fieldId = items_left_display.fieldIdHere AND
items_left.itemId = tiki_tracker_item_fields.itemId
)
LEFT JOIN tiki_tracker_item_fields items_middle ON (
items_middle.value = items_left.value AND
items_left_display.fieldIdThere = items_middle.fieldId
)
LEFT JOIN tiki_tracker_item_fields items_right ON (
items_right.itemId = items_middle.itemId AND
items_right.fieldId = items_left_display.displayFieldIdThere
)
WHERE
tiki_trackers.trackerId = ?
" . (! empty($this->start) ? " AND tiki_tracker_items." . $dateUnit . " > ? " : "") . "
" . (! empty($this->end) ? " AND tiki_tracker_items." . $dateUnit . " < ? " : "") . "
" . (! empty($this->itemId) ? " AND tiki_tracker_item_fields.itemId = ? " : "") . "
" . (! empty($fields_safe) ? $fields_safe : "") . "
" . (! empty($status_safe) ? $status_safe : "") . "
GROUP BY
tiki_tracker_item_fields.itemId
" . ($isSearch == true ? ", search_item_fields.fieldId, search_item_fields.itemId " : "" ) . "
ORDER BY
tiki_tracker_items." . $dateUnit . " " . ($this->desc == true ? 'DESC' : 'ASC') . "
" . (! empty($this->limit) ? " LIMIT " . $this->limit : "") . "
" . (! empty($this->offset) ? " OFFSET " . $this->offset : "");
if ($this->debug == true) {
$result = [$query, $params];
print_r($result);
print_r($tikilib->fetchAll($query, $params));
die;
} else {
$result = $tikilib->fetchAll($query, $params);
}
$newResult = [];
$neededMatches = count($this->fields);
foreach ($this->fields as $i => $field) {
if ($this->filterType[$i] != 'and') {
$neededMatches--;
}
}
foreach ($result as $key => $row) {
if (isset($newResult[$row['itemId']])) {
continue;
}
$newRow = [];
$fieldNames = explode($this->delimiter, $row['fieldNames']);
$fieldIds = explode($this->delimiter, $row['fieldIds']);
$itemValues = explode($this->delimiter, $row['item_values']);
$matchCount = 0;
foreach ($fieldIds as $key => $fieldId) {
$field = ($this->byName == true ? $fieldNames[$key] : $fieldId);
$value = '';
//This script attempts to narrow the results further by using an "AND" style checking of the returned result since it cannot be made at this time in mysql
if ($neededMatches > 0) {
$i = array_search($fieldId, $this->fields, true);
if ($i !== false) {
if ($this->equals[$i] == $itemValues[$key] && $this->filterType[$i] == 'and') {
$matchCount++;
}
}
}
//End "AND" style checking of results
if ($this->render == true) {
$value = $this->renderFieldValue($trackerFieldDefinition[$fieldId], $itemValues[$key]);
} else {
$value = $itemValues[$key];
}
if (! isset($this->itemsRaw[$row['itemId']])) {
$this->itemsRaw[$row['itemId']] = [];
}
if (isset($newRow[$field])) {
if (is_array($newRow[$field]) == false) {
$newRow[$field] = [$newRow[$field]];
$this->itemsRaw[$row['itemId']][$field] = [$itemValues[$key]]; //raw values
}
$newRow[$field][] = $value;
$this->itemsRaw[$row['itemId']][$field][] = $itemValues[$key]; //raw values
} else {
$newRow[$field] = $value;
$this->itemsRaw[$row['itemId']][$field] = $itemValues[$key]; //raw values
}
}
if ($this->excludeDetails == false) {
$newRow['status' . $trackerId] = $row['status'];
$newRow['trackerId'] = $row['trackerId'];
$newRow['itemId'] = $row['itemId'];
}
if ($neededMatches == 0 || $neededMatches == $matchCount) {
$newResult[$row['itemId']] = $newRow;
}
}
unset($result);
$this->limitReached = (count($newResult) > $this->limit ? true : false);
return $newResult;
}
/**
* renders the field value
*
* @access private
* @param array $fieldDefinition
* @param string $value
* @return mixed $value rendered field value
*/
private function renderFieldValue($fieldDefinition, $value)
{
$trklib = TikiLib::lib('trk');
$fieldDefinition['value'] = $value;
//if type is text, no need to render value
switch ($fieldDefinition['type']) {
case 't'://text
case 'S'://static text
return $value;
}
return $trklib->field_render_value(
[
'field' => $fieldDefinition,
'process' => 'y',
'list_mode' => 'y'
]
);
}
/**
* Removed fields from result
*
* @access private
* @param array $fieldDefinition
* @param string $value
* @return mixed $value rendered field value
*/
public static function filter_fields_from_tracker_query($tracker, $fieldIdsToRemove = [], $fieldIdsToShow = [])
{
if (empty($fieldIdsToShow) == false) {
$newTracker = [];
foreach ($tracker as $key => $item) {
$newTracker[$key] = [];
foreach ($fieldIdsToShow as $fieldIdToShow) {
$newTracker[$key][$fieldIdToShow] = $tracker[$key][$fieldIdToShow];
}
}
return $newTracker;
}
if (empty($fieldIdsToRemove) == false) {
foreach ($tracker as $key => $item) {
foreach ($fieldIdsToRemove as $fieldIdToRemove) {
unset($tracker[$key][$fieldIdToRemove]);
}
}
}
return $tracker;
}
/**
* Joins tracker arrays together.
*
*/
public static function join_trackers($trackerLeft, $trackerRight, $fieldLeftId, $joinType)
{
$joinedTracker = [];
switch ($joinType) {
case "outer":
foreach ($trackerRight as $key => $itemRight) {
$match = false;
foreach ($trackerLeft as $itemLeft) {
if ($key == $itemLeft[$fieldLeftId]) {
$match = true;
$joinedTracker[$key] = $itemLeft + $itemRight;
} else {
$joinedTracker[$key] = $itemLeft;
}
}
if ($match == false) {
$joinedTracker[$key] = $itemRight;
}
}
break;
default:
foreach ($trackerLeft as $key => $itemLeft) {
if (isset($trackerRight[$itemLeft[$fieldLeftId]]) == true) {
$joinedTracker[$key] = $itemLeft + $trackerRight[$itemLeft[$fieldLeftId]];
} else {
$joinedTracker[$key] = $itemLeft;
}
}
}
return $joinedTracker;
}
public static function to_csv($array, $header = false, $col_sep = ",", $row_sep = "\n", $qut = '"', $fileName = 'file.csv')
{
header("Content-type: application/csv");
header("Content-Disposition: attachment; filename=" . $fileName);
header("Pragma: no-cache");
header("Expires: 0");
if (! is_array($array)) {
return false;
}
$output = '';
//Header row.
if ($header == true) {
foreach ($array[0] as $key => $val) {
//Escaping quotes.
$key = str_replace($qut, "$qut$qut", $key);
$output .= "$col_sep$qut$key$qut";
}
$output = substr($output, 1) . "\n";
}
$cellKeys = [];
$cellKeysSet = false;
foreach ($array as $key => $val) {
$tmp = '';
if ($cellKeysSet == false) {
foreach ($val as $cell_key => $cell_val) {
$cellKeys[] = $cell_key;
}
$cellKeysSet = true;
}
foreach ($cellKeys as $cellKey) {
//Escaping quotes.
if (is_array($val[$cellKey]) == true) {
$val[$cellKey] = implode(" ", $val[$cellKey]);
}
$cell_val = str_replace("\n", " ", $val[$cellKey]);
$cell_val = str_replace($qut, "$qut$qut", $cell_val);
$tmp .= "$col_sep$qut$cell_val$qut";
}
$output .= substr($tmp, 1) . $row_sep;
}
return $output;
}
/**
* Programmatic and simplified way of replacing or updating a tracker item, meant for api ease and accessibility
* Does not check permissions
*
* @param array $data example array(fieldId=>'value', fieldId=>'value') or array('fieldName'=>'value', 'fieldName'=>'value')
* @return int $itemId
*/
public function replaceItem($data = [])
{
$itemData = [];
$fields = TikiLib::lib("trk")->list_tracker_fields($this->trackerId());
for ($i = 0, $fieldCount = count($fields['data']); $i < $fieldCount; $i++) {
if ($this->byName == true) {
$fields['data'][$i]['value'] = $data[$fields['data'][$i]['name']];
} else {
$fields['data'][$i]['value'] = $data[$fields['data'][$i]['fieldId']];
}
}
$itemId = TikiLib::lib("trk")->replace_item($this->trackerId(), $this->itemId, $fields);
return $itemId;
}
/**
* Get inputs for tracker item, useful for building interface for interacting with trackers
*
* @param int $itemId, 0 for new item
* @param bool $includeJs injects header js for item into field value
* @return array $fields array of fields just like that found in query, but the value of each field being the input
*/
private function getInputsForItem($itemId = 0, $includeJs = true)
{
$headerlib = TikiLib::lib("header");
$itemId = (int)$itemId;
if ($includeJs == true) {
$headerlibClone = clone $headerlib;
}
$trackerId = $this->trackerId();
if ($trackerId < 1) {
return [];
}
$trackerDefinition = Tracker_Definition::get($trackerId);
$fields = [];
$fieldFactory = new Tracker_Field_Factory($trackerDefinition);
$itemData = TikiLib::lib("trk")->get_tracker_item($itemId);
foreach ($trackerDefinition->getFields() as $field) {
$fieldKey = ($this->byName == true ? $field['name'] : $field['fieldId']);
if ($includeJs == true) {
$headerlib->clear_js();
}
$field['ins_id'] = "ins_" . $field['fieldId'];
if ($itemId == 0 && isset($this->inputDefaults)) {
$field['value'] = $this->inputDefaults[$fieldKey];
}
$fieldHandler = $fieldFactory->getHandler($field, $itemData);
$fieldInput = $fieldHandler->renderInput();
if ($includeJs == true) {
$fieldInput = $fieldInput . $headerlib->output_js();
}
$fields[$fieldKey] = $fieldInput;
}
if ($includeJs == true) { //restore the header to the way it was originally
$headerlib = $headerlibClone;
}
return $fields;
}
/**
* Set input defaults, useful when inserting a new item and you want to set the default values
*
* @param array $defaults, array of defaults, array(array(fieldKey=>defaultValue),array(fieldKey=>defaultValue))
* @return $this for chainability
*/
public function inputDefaults($defaults = [])
{
$this->inputDefaults = $defaults;
return $this;
}
/**
* A set of tracker items with inputs
*
* @param bool $includeJs, default = false
* @return $this for chainability
*/
public function queryInputs($includeJs = false)
{
if ($this->canEdit() == false) {
return [];
}
$query = $this->query();
$items = [];
foreach ($query as $itemId => $item) {
$items[] = $this->getInputsForItem($itemId, $includeJs);
}
return $items;
}
/**
* A single tracker item with inputs
*
* @param bool $includeJs, default = false
* @return $this for chainability
*/
public function queryInput($includeJs = false)
{
return $this->getInputsForItem($this->itemId, $includeJs);
}
/**
* Delete a tracker item
*
* @param bool $bulkMode, default = false
* @return $this for chainability
*/
public function delete($bulkMode = false)
{
$trklib = TikiLib::lib('trk');
if ($this->canDelete()) {
$results = $this->query();
foreach ($results as $itemId => $result) {
$trklib->remove_tracker_item($itemId, $bulkMode);
}
}
}
}