<?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 Library
|
|
*
|
|
* \brief Functions to support accessing and processing of the Trackers.
|
|
*
|
|
* @package Tiki
|
|
* @subpackage Trackers
|
|
* @author Luis Argerich, Garland Foster, Eduardo Polidor, et. al.
|
|
* @copyright Copyright (c) 2002-2009, All Rights Reserved.
|
|
* See copyright.txt for details and a complete list of authors.
|
|
* @license LGPL - See license.txt for details.
|
|
* @version SVN $Rev: 25023 $
|
|
* @filesource
|
|
* @link http://dev.tiki.org/Trackers
|
|
* @since Always
|
|
*/
|
|
/**
|
|
* This script may only be included, so it is better to die if called directly.
|
|
*/
|
|
/**
|
|
* TrackerLib Class
|
|
*
|
|
* This class extends the TikiLib class.
|
|
*/
|
|
class TrackerLib extends TikiLib
|
|
{
|
|
public $trackerinfo_cache;
|
|
private $sectionFormats = [];
|
|
|
|
public function __construct()
|
|
{
|
|
$this->now = time();
|
|
$this->registerSectionFormat('flat', 'view', 'trackeroutput/layout_flat.tpl', tr('Flat'));
|
|
$this->registerSectionFormat('flat', 'edit', 'trackerinput/layout_flat.tpl', tr('Flat'));
|
|
$this->registerSectionFormat('tab', 'view', 'trackeroutput/layout_tab.tpl', tr('Tabs'));
|
|
$this->registerSectionFormat('tab', 'edit', 'trackerinput/layout_tab.tpl', tr('Tabs'));
|
|
}
|
|
|
|
public function registerSectionFormat($layout, $mode, $template, $label)
|
|
{
|
|
if ($template) {
|
|
$this->sectionFormats[$layout][$mode] = [
|
|
'template' => $template,
|
|
'label' => $label,
|
|
];
|
|
}
|
|
}
|
|
|
|
public function unregisterSectionFormat($layout)
|
|
{
|
|
unset($this->sectionFormats[$layout]);
|
|
}
|
|
|
|
public function getSectionFormatTemplate($layout, $mode)
|
|
{
|
|
if (isset($this->sectionFormats[$layout][$mode])) {
|
|
return $this->sectionFormats[$layout][$mode]['template'];
|
|
} elseif ($layout == 'config' || $layout === 'n') {
|
|
// Special handling for config, fallback to default flat (also for when sectionFormat gets saved as "n" in legacy trackers)
|
|
return $this->getSectionFormatTemplate('flat', $mode);
|
|
} else {
|
|
throw new Exception(tr('No template available for %0 - %1', $layout, $mode));
|
|
}
|
|
}
|
|
|
|
public function getGlobalSectionFormats()
|
|
{
|
|
$out = [];
|
|
foreach ($this->sectionFormats as $layout => $modes) {
|
|
if (count($modes) == 2) {
|
|
$first = reset($modes);
|
|
$out[$layout] = $first['label'];
|
|
}
|
|
}
|
|
|
|
$out['config'] = tr('Configured');
|
|
|
|
return $out;
|
|
}
|
|
|
|
private function attachments()
|
|
{
|
|
return $this->table('tiki_tracker_item_attachments');
|
|
}
|
|
|
|
private function comments()
|
|
{
|
|
return $this->table('tiki_comments');
|
|
}
|
|
|
|
private function itemFields()
|
|
{
|
|
return $this->table('tiki_tracker_item_fields', false);
|
|
}
|
|
|
|
private function trackers()
|
|
{
|
|
return $this->table('tiki_trackers');
|
|
}
|
|
|
|
private function items()
|
|
{
|
|
return $this->table('tiki_tracker_items');
|
|
}
|
|
|
|
private function fields()
|
|
{
|
|
return $this->table('tiki_tracker_fields');
|
|
}
|
|
|
|
private function options()
|
|
{
|
|
return $this->table('tiki_tracker_options');
|
|
}
|
|
|
|
private function logs()
|
|
{
|
|
return $this->table('tiki_tracker_item_field_logs', false);
|
|
}
|
|
|
|
private function groupWatches()
|
|
{
|
|
return $this->table('tiki_group_watches');
|
|
}
|
|
|
|
private function userWatches()
|
|
{
|
|
return $this->table('tiki_user_watches');
|
|
}
|
|
|
|
public function remove_field_images($fieldId)
|
|
{
|
|
$itemFields = $this->itemFields();
|
|
$values = $itemFields->fetchColumn('value', ['fieldId' => (int) $fieldId]);
|
|
foreach ($values as $file) {
|
|
if (file_exists($file)) {
|
|
unlink($file);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function add_item_attachment_hit($id)
|
|
{
|
|
global $prefs, $user;
|
|
if (StatsLib::is_stats_hit()) {
|
|
$attachments = $this->attachments();
|
|
$attachments->update(['hits' => $attachments->increment(1)], ['attId' => (int) $id]);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function get_item_attachment_owner($attId)
|
|
{
|
|
return $this->attachments()->fetchOne('user', ['attId' => (int) $attId]);
|
|
}
|
|
|
|
public function list_item_attachments($itemId, $offset = 0, $maxRecords = -1, $sort_mode = 'attId_asc', $find = '')
|
|
{
|
|
$attachments = $this->attachments();
|
|
|
|
$order = $attachments->sortMode($sort_mode);
|
|
$fields = ['user', 'attId', 'itemId', 'filename', 'filesize', 'filetype', 'hits', 'created', 'comment', 'longdesc', 'version'];
|
|
|
|
$conditions = [
|
|
'itemId' => (int) $itemId,
|
|
];
|
|
|
|
if ($find) {
|
|
$conditions['filename'] = $attachments->like("%$find%");
|
|
}
|
|
|
|
return [
|
|
'data' => $attachments->fetchAll($fields, $conditions, $maxRecords, $offset, $order),
|
|
'cant' => $attachments->fetchCount($conditions),
|
|
];
|
|
}
|
|
|
|
public function get_item_nb_attachments($itemId)
|
|
{
|
|
$attachments = $this->attachments();
|
|
|
|
$ret = $attachments->fetchRow(
|
|
['hits' => $attachments->sum('hits'), 'attachments' => $attachments->count()],
|
|
['itemId' => $itemId]
|
|
);
|
|
|
|
return $ret ? $ret : [];
|
|
}
|
|
|
|
public function get_item_nb_comments($itemId)
|
|
{
|
|
return $this->comments()->fetchCount(['object' => (int) $itemId, 'objectType' => 'trackeritem']);
|
|
}
|
|
|
|
public function list_all_attachments($offset = 0, $maxRecords = -1, $sort_mode = 'created_desc', $find = '')
|
|
{
|
|
$attachments = $this->attachments();
|
|
|
|
$fields = ['user', 'attId', 'itemId', 'filename', 'filesize', 'filetype', 'hits', 'created', 'comment', 'path'];
|
|
$order = $attachments->sortMode($sort_mode);
|
|
$conditions = [];
|
|
|
|
if ($find) {
|
|
$conditions['filename'] = $attachments->like("%$find%");
|
|
}
|
|
|
|
return [
|
|
'data' => $attachments->fetchAll($fields, $conditions, $maxRecords, $offset, $order),
|
|
'cant' => $attachments->fetchCount($conditions),
|
|
];
|
|
}
|
|
|
|
public function file_to_db($path, $attId)
|
|
{
|
|
if (is_readable($path)) {
|
|
$updateResult = $this->attachments()->update(
|
|
['data' => file_get_contents($path), 'path' => ''],
|
|
['attId' => (int) $attId]
|
|
);
|
|
|
|
if ($updateResult) {
|
|
unlink($path);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function db_to_file($path, $attId)
|
|
{
|
|
$attachments = $this->attachments();
|
|
|
|
$data = $attachments->fetchOne('data', ['attId' => (int) $attId]);
|
|
if (false !== file_put_contents($path, $data)) {
|
|
$attachments->update(['data' => '', 'path' => basename($path)], ['attId' => (int) $attId]);
|
|
}
|
|
}
|
|
|
|
public function get_item_attachment($attId)
|
|
{
|
|
return $this->attachments()->fetchFullRow(['attId' => (int) $attId]);
|
|
}
|
|
|
|
public function remove_item_attachment($attId = 0, $itemId = 0)
|
|
{
|
|
global $prefs;
|
|
$attachments = $this->attachments();
|
|
$paths = [];
|
|
|
|
if (empty($attId) && ! empty($itemId)) {
|
|
if ($prefs['t_use_db'] === 'n') {
|
|
$paths = $attachments->fetchColumn('path', ['itemId' => $itemId]);
|
|
}
|
|
|
|
$this->query('update `tiki_tracker_item_fields` ttif left join `tiki_tracker_fields` ttf using (`fieldId`) set `value`=? where ttif.`itemId`=? and ttf.`type`=?', ['', (int) $itemId, 'A']);
|
|
$attachments->deleteMultiple(['itemId' => $itemId]);
|
|
} elseif (! empty($attId)) {
|
|
if ($prefs['t_use_db'] === 'n') {
|
|
$paths = $attachments->fetchColumn('path', ['attId' => (int) $attId]);
|
|
}
|
|
$this->query('update `tiki_tracker_item_fields` ttif left join `tiki_tracker_fields` ttf using (`fieldId`) set `value`=? where ttif.`value`=? and ttf.`type`=?', ['', (string) $attId, 'A']);
|
|
$attachments->delete(['attId' => (int) $attId]);
|
|
}
|
|
foreach (array_filter($paths) as $path) {
|
|
@unlink($prefs['t_use_dir'] . $path);
|
|
}
|
|
}
|
|
|
|
public function replace_item_attachment($attId, $filename, $type, $size, $data, $comment, $user, $fhash, $version, $longdesc, $trackerId = 0, $itemId = 0, $options = '', $notif = true)
|
|
{
|
|
global $prefs;
|
|
$attachments = $this->attachments();
|
|
|
|
$comment = strip_tags($comment);
|
|
$now = $this->now;
|
|
if (empty($attId)) {
|
|
$attId = $attachments->insert(
|
|
[
|
|
'itemId' => (int) $itemId,
|
|
'filename' => $filename,
|
|
'filesize' => $size,
|
|
'filetype' => $type,
|
|
'data' => $data,
|
|
'created' => $now,
|
|
'hits' => 0,
|
|
'user' => $user,
|
|
'comment' => $comment,
|
|
'path' => $fhash,
|
|
'version' => $version,
|
|
'longdesc' => $longdesc,
|
|
]
|
|
);
|
|
} elseif (empty($filename)) {
|
|
$attachments->update(
|
|
[
|
|
'user' => $user,
|
|
'comment' => $comment,
|
|
'version' => $version,
|
|
'longdesc' => $longdesc,
|
|
],
|
|
['attId' => $attId]
|
|
);
|
|
} else {
|
|
$path = $attachments->fetchOne('path', ['attId' => (int) $attId]);
|
|
if ($path) {
|
|
@unlink($prefs['t_use_dir'] . $path);
|
|
}
|
|
|
|
$attachments->update(
|
|
[
|
|
'filename' => $filename,
|
|
'filesize' => $size,
|
|
'filetype' => $type,
|
|
'data' => $data,
|
|
'user' => $user,
|
|
'comment' => $comment,
|
|
'path' => $fhash,
|
|
'version' => $version,
|
|
'longdesc' => $longdesc,
|
|
],
|
|
['attId' => (int) $attId]
|
|
);
|
|
}
|
|
|
|
if (! $notif) {
|
|
return $attId;
|
|
}
|
|
|
|
$options["attachment"] = ["attId" => $attId, "filename" => $filename, "comment" => $comment];
|
|
$watchers = $this->get_notification_emails($trackerId, $itemId, $options);
|
|
|
|
if (count($watchers > 0)) {
|
|
$smarty = TikiLib::lib('smarty');
|
|
$trackerName = $this->trackers()->fetchOne('name', ['trackerId' => (int) $trackerId]);
|
|
|
|
$smarty->assign('mail_date', $this->now);
|
|
$smarty->assign('mail_user', $user);
|
|
$smarty->assign('mail_action', 'New File Attached to Item:' . $itemId . ' at tracker ' . $trackerName);
|
|
$smarty->assign('mail_itemId', $itemId);
|
|
$smarty->assign('mail_trackerId', $trackerId);
|
|
$smarty->assign('mail_trackerName', $trackerName);
|
|
$smarty->assign('mail_attId', $attId);
|
|
$smarty->assign('mail_data', $filename . "\n" . $comment . "\n" . $version . "\n" . $longdesc);
|
|
$foo = parse_url($_SERVER["REQUEST_URI"]);
|
|
$machine = $this->httpPrefix(true) . $foo["path"];
|
|
$smarty->assign('mail_machine', $machine);
|
|
$parts = explode('/', $foo['path']);
|
|
if (count($parts) > 1) {
|
|
unset($parts[count($parts) - 1]);
|
|
}
|
|
$smarty->assign('mail_machine_raw', $this->httpPrefix(true) . implode('/', $parts));
|
|
if (! isset($_SERVER["SERVER_NAME"])) {
|
|
$_SERVER["SERVER_NAME"] = $_SERVER["HTTP_HOST"];
|
|
}
|
|
include_once('lib/webmail/tikimaillib.php');
|
|
$smarty->assign('server_name', $_SERVER['SERVER_NAME']);
|
|
$desc = $this->get_isMain_value($trackerId, $itemId);
|
|
$smarty->assign('mail_item_desc', $desc);
|
|
foreach ($watchers as $w) {
|
|
$mail = new TikiMail($w['user']);
|
|
|
|
if (! isset($w['template'])) {
|
|
$w['template'] = '';
|
|
}
|
|
$content = $this->parse_notification_template($w['template']);
|
|
|
|
$mail->setSubject($smarty->fetchLang($w['language'], $content['subject']));
|
|
$mail_data = $smarty->fetchLang($w['language'], $content['template']);
|
|
if (isset($w['templateFormat']) && $w['templateFormat'] == 'html') {
|
|
$mail->setHtml($mail_data, str_replace(' ', ' ', strip_tags($mail_data)));
|
|
} else {
|
|
$mail->setText(str_replace(' ', ' ', strip_tags($mail_data)));
|
|
}
|
|
$mail->send([$w['email']]);
|
|
}
|
|
}
|
|
|
|
return $attId;
|
|
}
|
|
|
|
public function list_last_comments($trackerId = 0, $itemId = 0, $offset = -1, $maxRecords = -1)
|
|
{
|
|
global $user;
|
|
$mid = "1=1";
|
|
$bindvars = [];
|
|
|
|
if ($itemId != 0) {
|
|
$mid .= " and `itemId`=?";
|
|
$bindvars[] = (int) $itemId;
|
|
}
|
|
|
|
if ($trackerId != 0) {
|
|
$query = "select t.*, t.object itemId from `tiki_comments` t left join `tiki_tracker_items` a on t.`object`=a.`itemId` where $mid and a.`trackerId`=? and t.`objectType` = 'trackeritem' order by t.`commentDate` desc";
|
|
$bindvars[] = $trackerId;
|
|
$query_cant = "select count(*) from `tiki_comments` t left join `tiki_tracker_items` a on t.`object`=a.`itemId` where $mid and a.`trackerId`=? AND t.`objectType` = 'trackeritem' order by t.`commentDate` desc";
|
|
} else {
|
|
$query = "select t.*, t.object itemId, a.`trackerId` from `tiki_comments` t left join `tiki_tracker_items` a on t.`object`=a.`itemId` where $mid AND t.`objectType` = 'trackeritem' order by `commentDate` desc";
|
|
$query_cant = "select count(*) from `tiki_comments` where $mid AND `objectType` = 'trackeritem'";
|
|
}
|
|
|
|
$ret = $this->fetchAll($query, $bindvars, $maxRecords, $offset);
|
|
$cant = $this->getOne($query_cant, $bindvars);
|
|
|
|
foreach ($ret as $key => &$res) {
|
|
$itemObject = Tracker_Item::fromId($res['itemId']);
|
|
if (! $itemObject->canView()) {
|
|
--$cant;
|
|
unset($ret[$key]);
|
|
continue;
|
|
}
|
|
$res["parsed"] = $this->parse_comment($res["data"]);
|
|
}
|
|
|
|
return [
|
|
'data' => array_values($ret),
|
|
'cant' => $cant,
|
|
];
|
|
}
|
|
|
|
public function get_last_position($trackerId)
|
|
{
|
|
$fields = $this->fields();
|
|
return $fields->fetchOne($fields->max('position'), ['trackerId' => (int) $trackerId]);
|
|
}
|
|
|
|
public function get_tracker_item($itemId)
|
|
{
|
|
$res = $this->items()->fetchFullRow(['itemId' => (int) $itemId]);
|
|
if (! $res) {
|
|
return false;
|
|
}
|
|
|
|
$itemFields = $this->itemFields();
|
|
$fields = $itemFields->fetchMap('fieldId', 'value', ['itemId' => (int) $itemId]);
|
|
|
|
foreach ($fields as $id => $value) {
|
|
$res[$id] = $value;
|
|
}
|
|
return $res;
|
|
}
|
|
|
|
public function get_all_item_id($trackerId, $fieldId, $value)
|
|
{
|
|
$query = "select distinct ttif.`itemId` from `tiki_tracker_items` tti, `tiki_tracker_item_fields` ttif ";
|
|
$query .= " where tti.`itemId`=ttif.`itemId` and tti.`trackerId`=? and ttif.`fieldId`=? ";
|
|
$value = "%$value%";
|
|
$query .= " and ttif.`value` LIKE ?";
|
|
|
|
$result = $this->fetchAll($query, [(int) $trackerId, (int)$fieldId, $value]);
|
|
|
|
$itemIds = [];
|
|
foreach ($result as $row) {
|
|
$itemIds[] = $row['itemId'];
|
|
}
|
|
return $itemIds;
|
|
}
|
|
|
|
public function get_item_id($trackerId, $fieldId, $value, $partial = false)
|
|
{
|
|
$query = "select ttif.`itemId` from `tiki_tracker_items` tti, `tiki_tracker_fields` ttf, `tiki_tracker_item_fields` ttif ";
|
|
$query .= " where tti.`trackerId`=ttf.`trackerId` and ttif.`fieldId`=ttf.`fieldId` and tti.`itemId`=ttif.`itemId` and ttf.`trackerId`=? and ttf.`fieldId`=? ";
|
|
if ($partial) {
|
|
$value = "%$value%";
|
|
$query .= " and ttif.`value` LIKE ?";
|
|
} else {
|
|
$query .= " and ttif.`value`=?";
|
|
}
|
|
return $this->getOne($query, [(int) $trackerId, (int) $fieldId, $value]);
|
|
}
|
|
|
|
public function get_item($trackerId, $fieldId, $value)
|
|
{
|
|
$itemId = $this->get_item_id($trackerId, $fieldId, $value);
|
|
return $this->get_tracker_item($itemId);
|
|
}
|
|
|
|
/* experimental shared */
|
|
/* trackerId is useless */
|
|
public function get_item_value($trackerId, $itemId, $fieldId)
|
|
{
|
|
global $prefs;
|
|
|
|
static $cache = [];
|
|
$cacheKey = "$fieldId.$itemId";
|
|
if (isset($cache[$cacheKey])) {
|
|
return $cache[$cacheKey];
|
|
}
|
|
|
|
$value = $this->itemFields()->fetchOne('value', ['fieldId' => (int) $fieldId, 'itemId' => (int) $itemId]);
|
|
|
|
if ($this->is_multilingual($fieldId) == 'y') {
|
|
$list = json_decode($value, true);
|
|
if (isset($list[$prefs['language']])) {
|
|
return $list[$prefs['language']];
|
|
}
|
|
}
|
|
|
|
if (TikiLib::lib('tiki')->get_memory_avail() < 1048576 * 10) {
|
|
$cache = [];
|
|
}
|
|
$cache[$cacheKey] = $value;
|
|
|
|
return $value;
|
|
}
|
|
|
|
public function get_item_status($itemId)
|
|
{
|
|
$status = $this->items()->fetchOne('status', ['itemId' => (int) $itemId]);
|
|
return $status;
|
|
}
|
|
|
|
/*shared*/
|
|
public function list_tracker_items($trackerId, $offset, $maxRecords, $sort_mode, $fields, $status = '', $initial = '')
|
|
{
|
|
|
|
$filters = [];
|
|
if ($fields) {
|
|
$temp_max = count($fields["data"]);
|
|
for ($i = 0; $i < $temp_max; $i++) {
|
|
$fieldId = $fields["data"][$i]["fieldId"];
|
|
$filters[$fieldId] = $fields["data"][$i];
|
|
}
|
|
}
|
|
$csort_mode = '';
|
|
if (substr($sort_mode, 0, 2) == "f_") {
|
|
list($a,$csort_mode,$corder) = explode('_', $sort_mode, 3);
|
|
}
|
|
$trackerId = (int) $trackerId;
|
|
if ($trackerId == -1) {
|
|
$mid = " where 1=1 ";
|
|
$bindvars = [];
|
|
} else {
|
|
$mid = " where tti.`trackerId`=? ";
|
|
$bindvars = [$trackerId];
|
|
}
|
|
if ($status) {
|
|
$mid .= " and tti.`status`=? ";
|
|
$bindvars[] = $status;
|
|
}
|
|
if ($initial) {
|
|
$mid .= "and ttif.`value` like ?";
|
|
$bindvars[] = $initial . '%';
|
|
}
|
|
if (! $sort_mode) {
|
|
$temp_max = count($fields["data"]);
|
|
for ($i = 0; $i < $temp_max; $i++) {
|
|
if ($fields['data'][$i]['isMain'] == 'y') {
|
|
$csort_mode = $fields['data'][$i]['name'];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if ($csort_mode) {
|
|
$sort_mode = $csort_mode . "_desc";
|
|
$bindvars[] = $csort_mode;
|
|
$query = "select tti.*, ttif.`value` from `tiki_tracker_items` tti, `tiki_tracker_item_fields` ttif, `tiki_tracker_fields` ttf ";
|
|
$query .= " $mid and tti.`itemId`=ttif.`itemId` and ttf.`fieldId`=ttif.`fieldId` and ttf.`name`=? order by ttif.`value`";
|
|
$query_cant = "select count(*) from `tiki_tracker_items` tti, `tiki_tracker_item_fields` ttif, `tiki_tracker_fields` ttf ";
|
|
$query_cant .= " $mid and tti.`itemId`=ttif.`itemId` and ttf.`fieldId`=ttif.`fieldId` and ttf.`name`=? ";
|
|
} else {
|
|
if (! $sort_mode) {
|
|
$sort_mode = "lastModif_desc";
|
|
}
|
|
$query = "select * from `tiki_tracker_items` tti $mid order by " . $this->convertSortMode($sort_mode);
|
|
$query_cant = "select count(*) from `tiki_tracker_items` tti $mid ";
|
|
}
|
|
$result = $this->fetchAll($query, $bindvars, $maxRecords, $offset);
|
|
$cant = $this->getOne($query_cant, $bindvars);
|
|
$ret = [];
|
|
foreach ($result as $res) {
|
|
$fields = [];
|
|
$itid = $res["itemId"];
|
|
$query2 = "select ttif.`fieldId`,`name`,`value`,`type`,`isTblVisible`,`isMain`,`position`
|
|
from `tiki_tracker_item_fields` ttif, `tiki_tracker_fields` ttf
|
|
where ttif.`fieldId`=ttf.`fieldId` and `itemId`=? order by `position` asc";
|
|
$result2 = $this->fetchAll($query2, [(int) $res["itemId"]]);
|
|
$pass = true;
|
|
$kx = "";
|
|
foreach ($result2 as $res2) {
|
|
// Check if the field is visible!
|
|
$fieldId = $res2["fieldId"];
|
|
if (count($filters) > 0) {
|
|
if (isset($filters[$fieldId]["value"]) and $filters[$fieldId]["value"]) {
|
|
if (in_array($filters[$fieldId]["type"], ['a', 't'])) {
|
|
if (! stristr($res2["value"], $filters[$fieldId]["value"])) {
|
|
$pass = false;
|
|
}
|
|
} else {
|
|
if (strtolower($res2["value"]) != strtolower($filters[$fieldId]["value"])) {
|
|
$pass = false;
|
|
}
|
|
}
|
|
}
|
|
if (preg_replace("/[^a-zA-Z0-9]/", "", $res2["name"]) == $csort_mode) {
|
|
$kx = $res2["value"] . $itid;
|
|
}
|
|
}
|
|
$fields[] = $res2;
|
|
}
|
|
$res["field_values"] = $fields;
|
|
$res["comments"] = $this->table('tiki_comments')->fetchCount(['object' => (int) $itid, 'objectType' => 'trackeritem']);
|
|
if ($pass) {
|
|
$kl = $kx . $itid;
|
|
$ret["$kl"] = $res;
|
|
}
|
|
}
|
|
ksort($ret);
|
|
//$ret=$this->sort_items_by_condition($ret,$sort_mode);
|
|
$retval = [];
|
|
$retval["data"] = array_values($ret);
|
|
$retval["cant"] = $cant;
|
|
return $retval;
|
|
}
|
|
|
|
/*shared*/
|
|
public function get_user_items($auser, $with_groups = true)
|
|
{
|
|
global $user;
|
|
$items = [];
|
|
|
|
$query = "select ttf.`trackerId`, tti.`itemId` from `tiki_tracker_fields` ttf, `tiki_tracker_items` tti, `tiki_tracker_item_fields` ttif";
|
|
$query .= " where ttf.`fieldId`=ttif.`fieldId` and ttif.`itemId`=tti.`itemId` and `type`=? and tti.`status`=? and `value`=?";
|
|
$result = $this->fetchAll($query, ['u','o',$auser]);
|
|
$ret = [];
|
|
|
|
$trackers = $this->table('tiki_trackers');
|
|
$trackerFields = $this->table('tiki_tracker_fields');
|
|
$trackerItemFields = $this->table('tiki_tracker_item_fields');
|
|
//FIXME Perm:filter ?
|
|
foreach ($result as $res) {
|
|
$itemObject = Tracker_Item::fromId($res['itemId']);
|
|
if (! $itemObject->canView()) {
|
|
continue;
|
|
}
|
|
$itemId = $res["itemId"];
|
|
|
|
$trackerId = $res["trackerId"];
|
|
// Now get the isMain field for this tracker
|
|
$fieldId = $trackerFields->fetchOne('fieldId', ['isMain' => 'y', 'trackerId' => (int) $trackerId]);
|
|
// Now get the field value
|
|
$value = $trackerItemFields->fetchOne('value', ['fieldId' => (int) $fieldId, 'itemId' => (int) $itemId]);
|
|
$tracker = $trackers->fetchOne('name', ['trackerId' => (int) $trackerId]);
|
|
|
|
$aux["trackerId"] = $trackerId;
|
|
$aux["itemId"] = $itemId;
|
|
$aux["value"] = $value;
|
|
$aux["name"] = $tracker;
|
|
|
|
if (! in_array($itemId, $items)) {
|
|
$ret[] = $aux;
|
|
$items[] = $itemId;
|
|
}
|
|
}
|
|
|
|
if ($with_groups) {
|
|
$groups = $this->get_user_groups($auser);
|
|
|
|
foreach ($groups as $group) {
|
|
$query = "select ttf.`trackerId`, tti.`itemId` from `tiki_tracker_fields` ttf, `tiki_tracker_items` tti, `tiki_tracker_item_fields` ttif ";
|
|
$query .= " where ttf.`fieldId`=ttif.`fieldId` and ttif.`itemId`=tti.`itemId` and `type`=? and tti.`status`=? and `value`=?";
|
|
$result = $this->fetchAll($query, ['g', 'o', $group]);
|
|
|
|
foreach ($result as $res) {
|
|
$itemId = $res["itemId"];
|
|
|
|
$trackerId = $res["trackerId"];
|
|
// Now get the isMain field for this tracker
|
|
$fieldId = $trackerFields->fetchOne('fieldId', ['isMain' => 'y', 'trackerId' => (int)$trackerId]);
|
|
// Now get the field value
|
|
$value = $trackerItemFields->fetchOne('value', ['fieldId' => (int)$fieldId, 'itemId' => (int)$itemId]);
|
|
$tracker = $trackers->fetchOne('name', ['trackerId' => (int)$trackerId]);
|
|
|
|
$aux["trackerId"] = $trackerId;
|
|
$aux["itemId"] = $itemId;
|
|
$aux["value"] = $value;
|
|
$aux["name"] = $tracker;
|
|
|
|
if (! in_array($itemId, $items)) {
|
|
$ret[] = $aux;
|
|
$items[] = $itemId;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
/* experimental shared */
|
|
public function get_items_list($trackerId, $fieldId, $value, $status = 'o', $multiple = false, $sortFieldIds = null)
|
|
{
|
|
static $cache = [];
|
|
$cacheKey = implode('.', [
|
|
$trackerId, $fieldId, $value, $status, $multiple,
|
|
is_array($sortFieldIds) ? implode($sortFieldIds) : $sortFieldIds
|
|
]);
|
|
if (isset($cache[$cacheKey])) {
|
|
return $cache[$cacheKey];
|
|
}
|
|
$query = "select distinct tti.`itemId`, tti.`itemId` i from `tiki_tracker_items` tti, `tiki_tracker_item_fields` ttif ";
|
|
$bindvars = [];
|
|
if (is_string($sortFieldIds)) {
|
|
$sortFieldIds = preg_split('/\|/', $sortFieldIds, -1, PREG_SPLIT_NO_EMPTY);
|
|
}
|
|
if (! empty($sortFieldIds)) {
|
|
foreach ($sortFieldIds as $i => $sortFieldId) {
|
|
$query .= " left join `tiki_tracker_item_fields` ttif$i on ttif.`itemId` = ttif$i.`itemId` and ttif$i.`fieldId` = ?";
|
|
$bindvars[] = (int)$sortFieldId;
|
|
}
|
|
}
|
|
$query .= " where tti.`itemId`=ttif.`itemId` and ttif.`fieldId`=?";
|
|
$bindvars[] = (int)$fieldId;
|
|
if ($multiple) {
|
|
$query .= " and FIND_IN_SET(?, ttif.`value`)";
|
|
} else {
|
|
$query .= " and ttif.`value`=?";
|
|
}
|
|
$bindvars[] = $value;
|
|
if (! empty($status)) {
|
|
$query .= ' and ' . $this->in('tti.status', str_split($status, 1), $bindvars);
|
|
}
|
|
if (! empty($sortFieldIds)) {
|
|
$query .= " order by " . implode(
|
|
',',
|
|
array_map(
|
|
function ($i) {
|
|
return "ttif$i.value";
|
|
},
|
|
array_keys($sortFieldIds)
|
|
)
|
|
);
|
|
}
|
|
$items = $this->fetchAll($query, $bindvars);
|
|
$items = array_map(
|
|
function ($row) {
|
|
return $row['itemId'];
|
|
},
|
|
$items
|
|
);
|
|
if (TikiLib::lib('tiki')->get_memory_avail() < 1048576 * 10) {
|
|
$cache = [];
|
|
}
|
|
$cache[$cacheKey] = $items;
|
|
return $items;
|
|
}
|
|
|
|
public function get_tracker($trackerId)
|
|
{
|
|
return $this->table('tiki_trackers')->fetchFullRow(['trackerId' => (int) $trackerId]);
|
|
}
|
|
|
|
public function get_field_info($fieldId)
|
|
{
|
|
return $this->table('tiki_tracker_fields')->fetchFullRow(['fieldId' => (int) $fieldId]);
|
|
}
|
|
|
|
/**
|
|
* Marks fields as empty
|
|
* @param array $fields
|
|
* @return array
|
|
*/
|
|
public function mark_fields_as_empty($fields)
|
|
{
|
|
$lastHeader = -1;
|
|
$elemSinceLastHeader = 0;
|
|
foreach ($fields as $key => $trac) {
|
|
if (
|
|
! (empty($trac['value']) && empty($trac['cat'])
|
|
&& empty($trac['links']) && $trac['type'] != 's'
|
|
&& $trac['type'] != 'STARS' && $trac['type'] != 'h'
|
|
&& $trac['type'] != 'l' && $trac['type'] != 'W')
|
|
&& ! ($trac['options_array'][0] == 'password' && $trac['type'] == 'p')
|
|
) {
|
|
if ($trac['type'] == 'h') {
|
|
if ($lastHeader > 0 && $elemSinceLastHeader == 0) {
|
|
$fields[$lastHeader]['field_is_empty'] = true;
|
|
}
|
|
$lastHeader = $key;
|
|
$elemSinceLastHeader = 0;
|
|
} else {
|
|
$elemSinceLastHeader++;
|
|
}
|
|
// this has a value
|
|
continue;
|
|
}
|
|
$fields[$key]['field_is_empty'] = true;
|
|
}
|
|
if ($lastHeader > 0 && $elemSinceLastHeader == 0) {
|
|
$fields[$lastHeader]['field_is_empty'] = true;
|
|
}
|
|
return $fields;
|
|
}
|
|
|
|
// includePermissions: Include the permissions of each tracker in its element's "permissions" subelement
|
|
public function list_trackers($offset = 0, $maxRecords = -1, $sort_mode = 'name_asc', $find = '', $includePermissions = false)
|
|
{
|
|
$categlib = TikiLib::lib('categ');
|
|
$join = '';
|
|
$where = '';
|
|
$bindvars = [];
|
|
if ($jail = $categlib->get_jail()) {
|
|
$categlib->getSqlJoin($jail, 'tracker', '`tiki_trackers`.`trackerId`', $join, $where, $bindvars);
|
|
}
|
|
if ($find) {
|
|
$findesc = '%' . $find . '%';
|
|
$where .= ' and (`tiki_trackers`.`name` like ? or `tiki_trackers`.`description` like ?)';
|
|
$bindvars = array_merge($bindvars, [$findesc, $findesc]);
|
|
}
|
|
$query = "select * from `tiki_trackers` $join where 1=1 $where order by `tiki_trackers`." . $this->convertSortMode($sort_mode);
|
|
$query_cant = "select count(*) from `tiki_trackers` $join where 1=1 $where";
|
|
$result = $this->fetchAll($query, $bindvars, $maxRecords, $offset);
|
|
$cant = $this->getOne($query_cant, $bindvars);
|
|
$ret = [];
|
|
$list = [];
|
|
//FIXME Perm:filter ?
|
|
foreach ($result as $res) {
|
|
global $user;
|
|
$add = $this->user_has_perm_on_object($user, $res['trackerId'], 'tracker', 'tiki_p_view_trackers');
|
|
if ($add) {
|
|
if ($includePermissions) {
|
|
$res['permissions'] = Perms::get('tracker', $res['trackerId']);
|
|
}
|
|
$ret[] = $res;
|
|
$list[$res['trackerId']] = $res['name'];
|
|
}
|
|
}
|
|
$retval = [];
|
|
$retval["list"] = $list;
|
|
$retval["data"] = $ret;
|
|
$retval["cant"] = $cant;
|
|
return $retval;
|
|
}
|
|
|
|
/**
|
|
* Get trackers by ID
|
|
* @param array $columns
|
|
* @param $trackerIds
|
|
* @return array|bool
|
|
*/
|
|
public function getTrackersByIds($trackerIds, $columns = [])
|
|
{
|
|
return $this->trackers()->fetchAll($columns, [
|
|
'trackerId' => $this->trackers()->in($trackerIds)
|
|
]);
|
|
}
|
|
|
|
// This function gets the prefix alias page name e.g. Org:230 for the pretty tracker
|
|
// wiki page corresponding to a tracker item (230 in the example) using prefix aliases
|
|
// Returns false if no such page is found.
|
|
public function get_trackeritem_pagealias($itemId)
|
|
{
|
|
global $prefs;
|
|
$trackerId = $this->table('tiki_tracker_items')->fetchOne('trackerId', ['itemId' => $itemId]);
|
|
|
|
$semanticlib = TikiLib::lib('semantic');
|
|
$t_links = $semanticlib->getLinksUsing('trackerid', ['toPage' => $trackerId]);
|
|
|
|
if (count($t_links)) {
|
|
if ($prefs['feature_multilingual'] == 'y' && count($t_links) > 1) {
|
|
foreach ($t_links as $t) {
|
|
if ($prefs['language'] == TikiLib::lib('multilingual')->getLangOfPage($t['fromPage'])) {
|
|
$target = $t['fromPage'];
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
$target = $t_links[0]['fromPage'];
|
|
}
|
|
|
|
$p_links = $semanticlib->getLinksUsing('prefixalias', ['fromPage' => $target]);
|
|
if (count($p_links)) {
|
|
$ret = $p_links[0]['toPage'] . $itemId;
|
|
return $ret;
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public function concat_item_from_fieldslist($trackerId, $itemId, $fieldsId, $status = 'o', $separator = ' ', $list_mode = '', $strip_tags = false, $format = '', $item = [])
|
|
{
|
|
$res = '';
|
|
$values = [];
|
|
if (is_string($fieldsId)) {
|
|
$fieldsId = preg_split('/\|/', $fieldsId, -1, PREG_SPLIT_NO_EMPTY);
|
|
}
|
|
$definition = Tracker_Definition::get($trackerId);
|
|
if ($definition) {
|
|
foreach ($fieldsId as $k => $field) {
|
|
$myfield = $definition->getField($field);
|
|
|
|
$myfield['value'] = $this->get_item_value(
|
|
$trackerId,
|
|
$itemId,
|
|
$field
|
|
);
|
|
if (! isset($item['itemId'])) {
|
|
$item['itemId'] = $itemId;
|
|
}
|
|
$value = trim($this->field_render_value(
|
|
['field' => $myfield, 'process' => 'y', 'list_mode' => $list_mode, 'item' => $item]
|
|
));
|
|
|
|
if ($format) {
|
|
$values[] = $value;
|
|
} else {
|
|
if ($k > 0) {
|
|
$res .= $separator;
|
|
}
|
|
$res .= $value;
|
|
}
|
|
}
|
|
if ($format) {
|
|
if ($list_mode !== 'csv') {
|
|
// preserve spaces in the format string
|
|
$format = str_replace(' ', ' ', $format);
|
|
}
|
|
// use the underlying translation function to replace the %0 etc placeholders (and translate if necessary)
|
|
$res = tra($format, '', false, $values);
|
|
}
|
|
if ($strip_tags) {
|
|
$res = strip_tags($res);
|
|
}
|
|
} else {
|
|
Feedback::error(tr('Tracker %0 not found for Field %1', $trackerId, implode(',', $fieldsId)));
|
|
}
|
|
return $res;
|
|
}
|
|
|
|
public function concat_all_items_from_fieldslist($trackerId, $fieldsId, $status = 'o', $separator = ' ', $strip_tags = false)
|
|
{
|
|
if (is_string($fieldsId)) {
|
|
$fieldsId = preg_split('/\|/', $fieldsId, -1, PREG_SPLIT_NO_EMPTY);
|
|
}
|
|
$res = [];
|
|
$definition = Tracker_Definition::get($trackerId);
|
|
foreach ($fieldsId as $field) {
|
|
if ($myfield = $definition->getField($field)) {
|
|
$is_date = ($myfield['type'] == 'f');
|
|
$is_trackerlink = ($myfield['type'] == 'r');
|
|
$tmp = $this->get_all_items($trackerId, $field, $status);
|
|
$options = $myfield['options_map'];
|
|
foreach ($tmp as $key => $value) {
|
|
if ($is_date) {
|
|
$value = $this->date_format("%e/%m/%y", $value);
|
|
}
|
|
if ($is_trackerlink && $options['displayFieldsList'] && ! empty($options['displayFieldsList'][0])) {
|
|
$item = $this->get_tracker_item($key);
|
|
$itemId = $item[$field];
|
|
$value = $this->concat_item_from_fieldslist($options['trackerId'], $itemId, $options['displayFieldsList'], $status, $separator, '', $strip_tags);
|
|
}
|
|
if (! empty($res[$key])) {
|
|
$res[$key] .= $separator . $value;
|
|
} else {
|
|
$res[$key] = $value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return $res;
|
|
}
|
|
|
|
public function get_fields_from_fieldslist($trackerId, $fieldsId)
|
|
{
|
|
if (is_string($fieldsId)) {
|
|
$fieldsId = preg_split('/\|/', $fieldsId, -1, PREG_SPLIT_NO_EMPTY);
|
|
}
|
|
$res = [];
|
|
$definition = Tracker_Definition::get($trackerId);
|
|
foreach ($fieldsId as $field) {
|
|
if ($myfield = $definition->getField($field)) {
|
|
$res[$field] = $myfield['permName'];
|
|
}
|
|
}
|
|
return $res;
|
|
}
|
|
|
|
|
|
public function valid_status($status)
|
|
{
|
|
return in_array($status, ['o', 'c', 'p', 'op', 'oc', 'pc', 'opc']);
|
|
}
|
|
|
|
|
|
/**
|
|
* Gets an array of itemId => rendered value for a certain field for use in ItemLinks (mainly)
|
|
*
|
|
* @param int $trackerId
|
|
* @param int $fieldId
|
|
* @param string $status
|
|
* @return array
|
|
*/
|
|
public function get_all_items($trackerId, $fieldId, $status = 'o')
|
|
{
|
|
global $prefs, $user;
|
|
$cachelib = TikiLib::lib('cache');
|
|
|
|
if (! $trackerId) {
|
|
return [tr('*** ERROR: Tracker ID not set ***', $fieldId)];
|
|
}
|
|
if (! $fieldId) {
|
|
return [tr('*** ERROR: Field ID not set ***', $fieldId)];
|
|
}
|
|
|
|
$definition = Tracker_Definition::get($trackerId);
|
|
if (! $definition) {
|
|
// could be a deleted field referred to by a list type field
|
|
return [tr('*** ERROR: Tracker %0 not found ***', $trackerId)];
|
|
}
|
|
$field = $definition->getField($fieldId);
|
|
|
|
if (! $field) {
|
|
// could be a deleted field referred to by a list type field
|
|
return [tr('*** ERROR: Field %0 not found ***', $fieldId)];
|
|
}
|
|
|
|
$jail = '';
|
|
if ($prefs['feature_categories'] == 'y') {
|
|
$categlib = TikiLib::lib('categ');
|
|
$jail = $categlib->get_jail();
|
|
}
|
|
|
|
$sort_mode = "value_asc";
|
|
$cacheKey = 'trackerfield' . $fieldId . $status . $user;
|
|
if ($this->is_multilingual($fieldId) == 'y') {
|
|
$cacheKey .= $prefs['language'];
|
|
}
|
|
if (! empty($jail)) {
|
|
$cacheKey .= serialize($jail);
|
|
}
|
|
|
|
$cacheKey = md5($cacheKey);
|
|
|
|
if (( ! $ret = $cachelib->getSerialized($cacheKey) ) || ! $this->valid_status($status)) {
|
|
$sts = preg_split('//', $status, -1, PREG_SPLIT_NO_EMPTY);
|
|
$mid = " (" . implode('=? or ', array_fill(0, count($sts), 'tti.`status`')) . "=?) ";
|
|
$fieldIdArray = preg_split('/\|/', $fieldId, -1, PREG_SPLIT_NO_EMPTY);
|
|
$mid .= " and (" . implode('=? or ', array_fill(0, count($fieldIdArray), 'ttif.`fieldId`')) . "=?) ";
|
|
$bindvars = array_merge($sts, $fieldIdArray);
|
|
$join = '';
|
|
if (! empty($jail)) {
|
|
$categlib->getSqlJoin($jail, 'trackeritem', 'tti.`itemId`', $join, $mid, $bindvars);
|
|
}
|
|
$query = "select ttif.`itemId` , ttif.`value` FROM `tiki_tracker_items` tti,`tiki_tracker_item_fields` ttif $join ";
|
|
$query .= " WHERE $mid and tti.`itemId` = ttif.`itemId` order by " . $this->convertSortMode($sort_mode);
|
|
$items = $this->fetchAll($query, $bindvars);
|
|
Perms::bulk(['type' => 'trackeritem', 'parentId' => $trackerId], 'object', array_map(function ($res) {
|
|
return $res['itemId'];
|
|
}, $items));
|
|
$ret = [];
|
|
foreach ($items as $res) {
|
|
$itemId = $res['itemId'];
|
|
$itemObject = Tracker_Item::fromId($itemId);
|
|
if (! $itemObject) {
|
|
Feedback::error(tr('TrackerLib::get_all_items: No item for itemId %0', $itemId));
|
|
} elseif ($itemObject->canView()) {
|
|
$ret[] = $res;
|
|
}
|
|
}
|
|
$cachelib->cacheItem($cacheKey, serialize($ret));
|
|
}
|
|
|
|
$ret2 = [];
|
|
foreach ($ret as $res) {
|
|
$itemId = $res['itemId'];
|
|
$field['value'] = $res['value'];
|
|
$rendered = $this->field_render_value([
|
|
'field' => $field,
|
|
'process' => 'y',
|
|
'smarty_assign' => 'n',
|
|
]);
|
|
$ret2[$itemId] = trim(strip_tags($rendered), " \t\n\r\0\x0B\xC2\xA0");
|
|
}
|
|
return $ret2;
|
|
}
|
|
|
|
public function need_to_check_categ_perms($allfields = '')
|
|
{
|
|
global $prefs;
|
|
if ($allfields === false) {
|
|
// use for itemlink field - otherwise will be too slow
|
|
return false;
|
|
}
|
|
$needToCheckCategPerms = false;
|
|
if ($prefs['feature_categories'] == 'y') {
|
|
$categlib = TikiLib::lib('categ');
|
|
if (empty($allfields['data'])) {
|
|
$needToCheckCategPerms = true;
|
|
} else {
|
|
foreach ($allfields['data'] as $f) {
|
|
if ($f['type'] == 'e') {
|
|
$needToCheckCategPerms = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return $needToCheckCategPerms;
|
|
}
|
|
|
|
public function get_all_tracker_items($trackerId)
|
|
{
|
|
return $this->items()->fetchColumn('itemId', ['trackerId' => (int) $trackerId]);
|
|
}
|
|
|
|
public function getSqlStatus($status, &$mid, &$bindvars, $trackerId, $skip_status_perm_check = false)
|
|
{
|
|
global $user;
|
|
if (is_array($status)) {
|
|
$status = implode('', $status);
|
|
}
|
|
|
|
// Check perms
|
|
if (! $skip_status_perm_check && $status && ! $this->user_has_perm_on_object($user, $trackerId, 'tracker', 'tiki_p_view_trackers_pending') && ! $this->group_creator_has_perm($trackerId, 'tiki_p_view_trackers_pending')) {
|
|
$status = str_replace('p', '', $status);
|
|
}
|
|
if (! $skip_status_perm_check && $status && ! $this->user_has_perm_on_object($user, $trackerId, 'tracker', 'tiki_p_view_trackers_closed') && ! $this->group_creator_has_perm($trackerId, 'tiki_p_view_trackers_closed')) {
|
|
$status = str_replace('c', '', $status);
|
|
}
|
|
|
|
if (! $status) {
|
|
return false;
|
|
} elseif ($status == 'opc') {
|
|
return true;
|
|
} elseif (strlen($status) > 1) {
|
|
$sts = preg_split('//', $status, -1, PREG_SPLIT_NO_EMPTY);
|
|
if (count($sts)) {
|
|
$mid .= " and (" . implode('=? or ', array_fill(0, count($sts), '`status`')) . "=?) ";
|
|
$bindvars = array_merge($bindvars, $sts);
|
|
}
|
|
} else {
|
|
$mid .= " and tti.`status`=? ";
|
|
$bindvars[] = $status;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function group_creator_has_perm($trackerId, $perm)
|
|
{
|
|
global $prefs;
|
|
$definition = Tracker_Definition::get($trackerId);
|
|
if ($definition && $groupCreatorFieldId = $definition->getWriterGroupField()) {
|
|
$tracker_info = $definition->getInformation();
|
|
$perms = $this->get_special_group_tracker_perm($tracker_info);
|
|
return empty($perms[$perm]) ? false : true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* group creator perms can only add perms,they can not take away perm
|
|
and they are only used if tiki_p_view_trackers is not set for the tracker and if the tracker ha a group creator field
|
|
must always be combined with a filter on the groups
|
|
*/
|
|
public function get_special_group_tracker_perm($tracker_info, $global = false)
|
|
{
|
|
global $prefs;
|
|
$userlib = TikiLib::lib('user');
|
|
$smarty = TikiLib::lib('smarty');
|
|
$ret = [];
|
|
$perms = $userlib->get_object_permissions($tracker_info['trackerId'], 'tracker', $prefs['trackerCreatorGroupName']);
|
|
foreach ($perms as $perm) {
|
|
$ret[$perm['permName']] = 'y';
|
|
if ($global) {
|
|
$p = $perm['permName'];
|
|
global $$p;
|
|
$$p = 'y';
|
|
$smarty->assign("$p", 'y');
|
|
}
|
|
}
|
|
if ($tracker_info['writerGroupCanModify'] == 'y') {
|
|
// old configuration
|
|
$ret['tiki_p_modify_tracker_items'] = 'y';
|
|
if ($global) {
|
|
$tiki_p_modify_tracker_items = 'y';
|
|
$smarty->assign('tiki_p_modify_tracker_items', 'y');
|
|
}
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
/* to filter filterfield is an array of fieldIds
|
|
* and the value of each field is either filtervalue or exactvalue
|
|
* ex: filterfield=array('1','2', 'sqlsearch'=>array('3', '4'), '5')
|
|
* ex: filtervalue=array(array('this', '*that'), '')
|
|
* ex: exactvalue= array('', array('there', 'those'), 'these', array('>'=>10))
|
|
* will filter items with fielId 1 with a value %this% or %that, and fieldId 2 with the value there or those, and fieldId 3 or 4 containing these and fieldId 5 > 10
|
|
* listfields = array(fieldId=>array('type'=>, 'name'=>...), ...)
|
|
* allfields is only for performance issue - check if one field is a category
|
|
*/
|
|
public function list_items($trackerId, $offset = 0, $maxRecords = -1, $sort_mode = '', $listfields = '', $filterfield = '', $filtervalue = '', $status = '', $initial = '', $exactvalue = '', $filter = '', $allfields = null, $skip_status_perm_check = false, $skip_permission_check = false)
|
|
{
|
|
//echo '<pre>FILTERFIELD:'; print_r($filterfield); echo '<br />FILTERVALUE:';print_r($filtervalue); echo '<br />EXACTVALUE:'; print_r($exactvalue); echo '<br />STATUS:'; print_r($status); echo '<br />FILTER:'; print_r($filter); /*echo '<br />LISTFIELDS'; print_r($listfields);*/ echo '</pre>';
|
|
global $prefs;
|
|
|
|
$cat_table = '';
|
|
$sort_tables = '';
|
|
$sort_join_clauses = '';
|
|
$csort_mode = '';
|
|
$corder = '';
|
|
$trackerId = (int) $trackerId;
|
|
$numsort = false;
|
|
$prefSort = false;
|
|
|
|
$mid = ' WHERE tti.`trackerId` = ? ';
|
|
$bindvars = [$trackerId];
|
|
$join = '';
|
|
|
|
if (! empty($filter)) {
|
|
$mid2 = [];
|
|
if (! empty($filter['comment'])) {
|
|
$cat_table .= ' LEFT JOIN `tiki_comments` tc ON tc.`object` = tti.`itemId` AND tc.`objectType` = "trackeritem"';
|
|
$mid2[] = '(tc.`title` LIKE ? OR tc.`data` LIKE ?)';
|
|
$bindvars[] = '%' . $filter['comment'] . '%';
|
|
$bindvars[] = '%' . $filter['comment'] . '%';
|
|
unset($filter['comment']);
|
|
}
|
|
$this->parse_filter($filter, $mid2, $bindvars);
|
|
if (! empty($mid2)) {
|
|
$mid .= ' AND ' . implode(' AND ', $mid2);
|
|
}
|
|
}
|
|
|
|
if (! $this->getSqlStatus($status, $mid, $bindvars, $trackerId, $skip_status_perm_check) && ! $skip_status_perm_check && $status) {
|
|
return ['cant' => 0, 'data' => ''];
|
|
}
|
|
if (substr($sort_mode, 0, 2) == 'f_') {
|
|
list($a, $asort_mode, $corder) = preg_split('/_/', $sort_mode);
|
|
}
|
|
if ($initial) {
|
|
$mid .= ' AND ttif.`value` LIKE ?';
|
|
$bindvars[] = $initial . '%';
|
|
if (isset($asort_mode)) {
|
|
$mid .= ' AND ttif.`fieldId` = ?';
|
|
$bindvars[] = $asort_mode;
|
|
}
|
|
}
|
|
if (! $sort_mode) {
|
|
$sort_mode = 'lastModif_desc';
|
|
}
|
|
|
|
if (substr($sort_mode, 0, 2) == 'f_' or ! empty($filterfield)) {
|
|
if (substr($sort_mode, 0, 2) == 'f_') {
|
|
$csort_mode = 'sttif.`value` ';
|
|
$sort_tables = ' LEFT JOIN (`tiki_tracker_item_fields` sttif)'
|
|
. ' ON (tti.`itemId` = sttif.`itemId`'
|
|
. (! empty($asort_mode) ? " AND sttif.`fieldId` = $asort_mode" : '')
|
|
. ')';
|
|
// Do we need a numerical sort on the field ?
|
|
$field = $this->get_tracker_field($asort_mode);
|
|
|
|
if ($field) {
|
|
switch ($field['type']) {
|
|
case 'C':
|
|
case '*':
|
|
case 'q':
|
|
case 'n':
|
|
case 'f': // DateTime
|
|
case 'j': // JsCalendar
|
|
case 'CAL': // CalendarItem
|
|
$numsort = true;
|
|
break;
|
|
case 'DUR':
|
|
$csort_mode = Tracker_Field_Duration::getSortModeSql();
|
|
break;
|
|
case 'l':
|
|
// Do nothing, value is dynamic and thus cannot be sorted on
|
|
$csort_mode = 1;
|
|
$csort_tables = '';
|
|
break;
|
|
case 'r':
|
|
$link_field = (int)$field['fieldId'];
|
|
$remote_field = (int)$field['options_array'][1];
|
|
$sort_tables = '
|
|
LEFT JOIN `tiki_tracker_item_fields` itemlink ON tti.itemId = itemlink.itemId AND itemlink.fieldId = ' . $link_field . '
|
|
LEFT JOIN `tiki_tracker_item_fields` sttif ON itemlink.value = sttif.itemId AND sttif.fieldId = ' . $remote_field . '
|
|
';
|
|
break;
|
|
case 's':
|
|
// if ($field['name'] == 'Rating' || $field['name'] == tra('Rating')) { // No need to have that string, isn't it? Admins can replace for a more suited string in their use case
|
|
$numsort = true;
|
|
// }
|
|
break;
|
|
case 'p':
|
|
$prefSort = true;
|
|
break;
|
|
case 'e':
|
|
$csort_mode = "sttif.name";
|
|
$sort_tables = '
|
|
LEFT JOIN `tiki_tracker_item_fields` scttif ON scttif.`itemId` = tti.`itemId` AND scttif.`fieldId` = ' . (int) $field['fieldId'] . '
|
|
LEFT JOIN `tiki_categories` sttif ON substring_index(trim(both "," from scttif.value), ",", 1) = sttif.categId';
|
|
break;
|
|
case 'math':
|
|
if (strpos($field['options'], 'numeric_sort') == true) {
|
|
if ($corder == 'asc') {
|
|
$corder = 'nasc';
|
|
} else {
|
|
$corder = 'ndesc';
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
} else {
|
|
// don't sort of the field doesn't exist
|
|
$csort_mode = 1;
|
|
$corder = 'asc';
|
|
$sort_tables = '';
|
|
}
|
|
} else {
|
|
list($csort_mode, $corder) = preg_split('/_/', $sort_mode);
|
|
$csort_mode = 'tti.`' . $csort_mode . '` ';
|
|
}
|
|
|
|
if (empty($filterfield)) {
|
|
$nb_filtered_fields = 0;
|
|
} elseif (! is_array($filterfield)) {
|
|
$fv = $filtervalue;
|
|
$ev = $exactvalue;
|
|
$ff = (int) $filterfield;
|
|
$nb_filtered_fields = 1;
|
|
} else {
|
|
$nb_filtered_fields = count($filterfield);
|
|
}
|
|
|
|
$last = 0;
|
|
for ($i = 0; $i < $nb_filtered_fields; $i++) {
|
|
if (is_array($filterfield)) {
|
|
//multiple filter on an exact value or a like value - each value can be simple or an array
|
|
$ff = (int) $filterfield[$i];
|
|
$ff_array = $filterfield[$i]; // Need value as array used below
|
|
$ev = ! empty($exactvalue[$i]) ? $exactvalue[$i] : null;
|
|
$fv = ! empty($filtervalue[$i]) ? $filtervalue[$i] : null;
|
|
}
|
|
$filter = $this->get_tracker_field($ff);
|
|
|
|
// Determine if field is an item list field and postpone filtering till later if so
|
|
if ($filter["type"] == 'l' && isset($filter['options_array'][2]) && isset($filter['options_array'][2]) && isset($filter['options_array'][3])) {
|
|
$linkfilter[] = ['filterfield' => $ff, 'exactvalue' => $ev, 'filtervalue' => $fv];
|
|
continue;
|
|
}
|
|
|
|
$value = empty($fv) ? $ev : $fv;
|
|
$search_for_blank = ( is_null($ev) && is_null($fv) )
|
|
|| ( is_array($value) && count($value) == 1
|
|
&& ( empty($value[0])
|
|
|| ( is_array($value[0]) && count($value[0]) == 1 && empty($value[0][0]) )
|
|
)
|
|
);
|
|
|
|
$cat_table .= ' ' . ( $search_for_blank ? 'LEFT' : 'INNER' ) . " JOIN `tiki_tracker_item_fields` ttif$i ON ttif$i.`itemId` = tti.`itemId`";
|
|
$last++;
|
|
|
|
if (isset($ff_array['sqlsearch']) && is_array($ff_array['sqlsearch'])) {
|
|
$mid .= " AND ttif$i.`fieldId` in (" . implode(',', array_fill(0, count($ff_array['sqlsearch']), '?')) . ')';
|
|
$bindvars = array_merge($bindvars, $ff_array['sqlsearch']);
|
|
} elseif (isset($ff_array['usersearch']) && is_array($ff_array['usersearch'])) {
|
|
$mid .= " AND ttif$i.`fieldId` in (" . implode(',', array_fill(0, count($ff_array['usersearch']), '?')) . ')';
|
|
$bindvars = array_merge($bindvars, $ff_array['usersearch']);
|
|
} elseif ($ff) {
|
|
if ($search_for_blank) {
|
|
$cat_table .= " AND ttif$i.`fieldId` = " . (int)$ff;
|
|
} else {
|
|
$mid .= " AND ttif$i.`fieldId`=? ";
|
|
$bindvars[] = $ff;
|
|
}
|
|
}
|
|
|
|
if ($filter['type'] == 'p' && (! empty($fv) || ! empty($ev))) {
|
|
$definition = Tracker_Definition::get($trackerId);
|
|
$userFieldId = $definition->getUserField();
|
|
$prefName = '';
|
|
$trackerFieldOptions = $this->getOne('SELECT `options` FROM `tiki_tracker_fields` WHERE fieldId = ?', $ff);
|
|
if ($trackerFieldOptions && $trackerFieldOptions = json_decode($trackerFieldOptions)) {
|
|
$prefName = $trackerFieldOptions->type ?? '';
|
|
}
|
|
|
|
if ($userFieldId && $prefName) {
|
|
$cat_table .= " INNER JOIN `tiki_tracker_item_fields` uttif$i ON (uttif$i.`itemId` = tti.`itemId` AND uttif$i.`fieldId` = $userFieldId)";
|
|
$cat_table .= " INNER JOIN `tiki_user_preferences` tup$i ON (tup$i.user = uttif$i.`value`)";
|
|
$mid .= " AND tup$i.prefName = ? AND tup$i.value like ?";
|
|
$bindvars[] = $prefName;
|
|
$bindvars[] = $ev ?: "%$fv%";
|
|
}
|
|
} elseif ($filter['type'] == 'e' && $prefs['feature_categories'] == 'y' && (! empty($ev) || ! empty($fv))) {
|
|
//category
|
|
|
|
$value = empty($fv) ? $ev : $fv;
|
|
if (! is_array($value) && $value != '') {
|
|
$value = [$value];
|
|
$not = '';
|
|
} elseif (is_array($value) && array_key_exists('not', $value)) {
|
|
$value = [$value['not']];
|
|
$not = 'not';
|
|
}
|
|
if (empty($not) && count($value) == 1 && ( empty($value[0]) || ( is_array($value[0]) && count($value[0]) == 1 && empty($value[0][0]) ) )) {
|
|
$cat_table .= " left JOIN `tiki_objects` tob$ff ON (tob$ff.`itemId` = tti.`itemId` AND tob$ff.`type` = 'trackeritem')"
|
|
. " left JOIN `tiki_category_objects` tco$ff ON (tob$ff.`objectId` = tco$ff.`catObjectId`)";
|
|
$mid .= " AND tco$ff.`categId` IS NULL ";
|
|
continue;
|
|
}
|
|
if (empty($not)) {
|
|
$cat_table .= " INNER JOIN `tiki_objects` tob$ff ON (tob$ff.`itemId` = tti.`itemId`)"
|
|
. " INNER JOIN `tiki_category_objects` tco$ff ON (tob$ff.`objectId` = tco$ff.`catObjectId`)";
|
|
$mid .= " AND tob$ff.`type` = 'trackeritem' AND tco$ff.`categId` IN ( ";
|
|
} else {
|
|
$cat_table .= " left JOIN `tiki_objects` tob$ff ON (tob$ff.`itemId` = tti.`itemId`)"
|
|
. " left JOIN `tiki_category_objects` tco$ff ON (tob$ff.`objectId` = tco$ff.`catObjectId`)";
|
|
$mid .= " AND tob$ff.`type` = 'trackeritem' AND tco$ff.`categId` NOT IN ( ";
|
|
}
|
|
$first = true;
|
|
foreach ($value as $k => $catId) {
|
|
if (is_array($catId)) {
|
|
// this is a grouped AND logic for optimization indicated by the value being array
|
|
$innerfirst = true;
|
|
foreach ($catId as $c) {
|
|
if (is_array($c)) {
|
|
$innerfirst = true;
|
|
foreach ($c as $d) {
|
|
$bindvars[] = $d;
|
|
if ($innerfirst) {
|
|
$innerfirst = false;
|
|
} else {
|
|
$mid .= ',';
|
|
}
|
|
$mid .= '?';
|
|
}
|
|
} else {
|
|
$bindvars[] = $c;
|
|
$mid .= '?';
|
|
}
|
|
}
|
|
if ($k < count($value) - 1) {
|
|
$mid .= " ) AND ";
|
|
if (empty($not)) {
|
|
$ff2 = $ff . '_' . $k;
|
|
$cat_table .= " INNER JOIN `tiki_category_objects` tco$ff2 ON (tob$ff.`objectId` = tco$ff2.`catObjectId`)";
|
|
$mid .= "tco$ff2.`categId` IN ( ";
|
|
} else {
|
|
$ff2 = $ff . '_' . $k;
|
|
$cat_table .= " left JOIN `tiki_category_objects` tco$ff2 ON (tob$ff.`objectId` = tco$ff2.`catObjectId`)";
|
|
$mid .= "tco$ff2.`categId` NOT IN ( ";
|
|
}
|
|
}
|
|
} else {
|
|
$bindvars[] = $catId;
|
|
if ($first) {
|
|
$first = false;
|
|
} else {
|
|
$mid .= ',';
|
|
}
|
|
$mid .= '?';
|
|
}
|
|
}
|
|
$mid .= " ) ";
|
|
if (! empty($not)) {
|
|
$mid .= " OR tco$ff.`categId` IS NULL ";
|
|
}
|
|
} elseif ($filter['type'] == 'usergroups') {
|
|
$definition = Tracker_Definition::get($trackerId);
|
|
$userFieldId = $definition->getUserField();
|
|
$cat_table .= " INNER JOIN `tiki_tracker_item_fields` ttifu ON (tti.`itemId`=ttifu.`itemId`) INNER JOIN `users_users` uu ON ttifu.`value` REGEXP CONCAT('[[:<:]]', uu.`login`, '[[:>:]]') INNER JOIN `users_usergroups` uug ON (uug.`userId`=uu.`userId`)";
|
|
$mid .= ' AND ttifu.`fieldId`=? AND uug.`groupName`=? ';
|
|
$bindvars[] = $userFieldId;
|
|
$bindvars[] = empty($ev) ? $fv : $ev;
|
|
} elseif ($filter['type'] == 'u' && $ev > '') { // user selector and exact value
|
|
if (is_array($ev)) {
|
|
$keys = array_keys($ev);
|
|
if ($keys[0] === 'not') {
|
|
$mid .= " AND ( ttif$i.`value` NOT REGEXP " . implode(' OR ttif$i.`value` NOT REGEXP ', array_fill(0, count($ev), '?')) . " OR ttif$i.`value` IS NULL )";
|
|
} else {
|
|
$mid .= " AND ( ttif$i.`value` REGEXP " . implode(' OR ttif$i.`value` REGEXP ', array_fill(0, count($ev), '?')) . " )";
|
|
}
|
|
$bindvars = array_merge(
|
|
$bindvars,
|
|
array_values(array_map(function ($ev) {
|
|
return "[[:<:]]{$ev}[[:>:]]";
|
|
}, $ev))
|
|
);
|
|
} else {
|
|
$mid .= " AND ttif$i.`value` REGEXP ? ";
|
|
$bindvars[] = "[[:<:]]{$ev}[[:>:]]";
|
|
}
|
|
} elseif ($filter['type'] == '*') { // star
|
|
$mid .= " AND ttif$i.`value`*1>=? ";
|
|
$bindvars[] = $ev;
|
|
if (($j = array_search($ev, $filter['options_array'])) !== false && $j + 1 < count($filter['options_array'])) {
|
|
$mid .= " AND ttif$i.`value`*1<? ";
|
|
$bindvars[] = $filter['options_array'][$j + 1];
|
|
}
|
|
} elseif ($filter['type'] == 'r' && ($fv || $ev)) {
|
|
$cv = $fv ? $fv : $ev;
|
|
|
|
$cat_table .= " LEFT JOIN tiki_tracker_item_fields ttif{$i}_remote ON ttif$i.`value` = ttif{$i}_remote.`itemId` AND ttif{$i}_remote.`fieldId` = " . (int)$filter['options_array'][1] . ' ';
|
|
if (is_numeric($cv)) {
|
|
$mid .= " AND ( ttif{$i}_remote.`value` LIKE ? OR ttif$i.`value` = ? ) ";
|
|
$bindvars[] = $ev ? $ev : "%$fv%";
|
|
$bindvars[] = $cv;
|
|
} else {
|
|
$mid .= " AND ttif{$i}_remote.`value` LIKE ? ";
|
|
$bindvars[] = $ev ? $ev : "%$fv%";
|
|
}
|
|
} elseif ($filter['type'] == 'REL' && ($fv || $ev)) {
|
|
$rv = $ev ?: $fv;
|
|
$options = explode("\n", $rv);
|
|
foreach ($options as $option) {
|
|
$mid .= " AND (ttif$i.`value` LIKE ? OR ttif$i.`value` LIKE ?)";
|
|
$option = trim($option);
|
|
$bindvars[] = "%$option";
|
|
$bindvars[] = "%$option\n%";
|
|
}
|
|
} elseif ($ev > '') {
|
|
if (is_array($ev)) {
|
|
$keys = array_keys($ev);
|
|
if (in_array((string) $keys[0], ['<', '>'])) {
|
|
$mid .= " AND ttif$i.`value`" . $keys[0] . "? + 0";
|
|
$bindvars[] = $ev[$keys[0]];
|
|
} elseif (in_array((string) $keys[0], ['<=', '>='])) {
|
|
$mid .= " AND (ttif$i.`value`" . $keys[0] . "? + 0 OR ttif$i.`value` = ?)";
|
|
$bindvars[] = $ev[$keys[0]];
|
|
$bindvars[] = $ev[$keys[0]];
|
|
} elseif ($keys[0] === 'not') {
|
|
$mid .= " AND ( ttif$i.`value` not in (" . implode(',', array_fill(0, count($ev), '?')) . ") OR ttif$i.`value` IS NULL )";
|
|
$bindvars = array_merge($bindvars, array_values($ev));
|
|
} else {
|
|
$mid .= " AND ttif$i.`value` in (" . implode(',', array_fill(0, count($ev), '?')) . ")";
|
|
$bindvars = array_merge($bindvars, array_values($ev));
|
|
}
|
|
} elseif (isset($ff_array['sqlsearch']) && is_array($ff_array['sqlsearch'])) {
|
|
$mid .= " AND MATCH(ttif$i.`value`) AGAINST(? IN BOOLEAN MODE)";
|
|
$bindvars[] = $ev;
|
|
} elseif (isset($ff_array['usersearch']) && is_array($ff_array['usersearch'])) {
|
|
$mid .= " AND ttif$i.`value` REGEXP ? ";
|
|
$bindvars[] = "[[:<:]]{$ev}[[:>:]]";
|
|
} else {
|
|
$mid .= " AND ttif$i.`value`=? ";
|
|
$bindvars[] = $ev == '' ? $fv : $ev;
|
|
}
|
|
} elseif ($fv > '') {
|
|
if (! is_array($fv)) {
|
|
$value = [$fv];
|
|
} else {
|
|
$value = $fv;
|
|
}
|
|
$mid .= ' AND(';
|
|
$cpt = 0;
|
|
foreach ($value as $v) {
|
|
if ($cpt++) {
|
|
$mid .= ' OR ';
|
|
}
|
|
$mid .= " upper(ttif$i.`value`) like upper(?) ";
|
|
if (substr($v, 0, 1) == '*' || substr($v, 0, 1) == '%') {
|
|
$bindvars[] = '%' . substr($v, 1);
|
|
} elseif (substr($v, -1, 1) == '*' || substr($v, -1, 1) == '%') {
|
|
$bindvars[] = substr($v, 0, strlen($v) - 1) . '%';
|
|
} else {
|
|
$bindvars[] = '%' . $v . '%';
|
|
}
|
|
}
|
|
$mid .= ')';
|
|
} elseif ($filter['type'] == 'r' && is_null($ev) && is_null($fv)) {
|
|
$mid .= " AND ( ttif$i.`value`=? OR ttif$i.`value`=? OR ttif$i.`value` IS NULL )";
|
|
$bindvars[] = '';
|
|
$bindvars[] = '0';
|
|
} elseif (is_null($ev) && is_null($fv)) { // test null value
|
|
$mid .= " AND ( ttif$i.`value`=? OR ttif$i.`value` IS NULL )";
|
|
$bindvars[] = '';
|
|
}
|
|
}
|
|
} else {
|
|
if (strpos($sort_mode, '_') !== false) {
|
|
list($csort_mode, $corder) = preg_split('/_/', $sort_mode);
|
|
} else {
|
|
$csort_mode = $sort_mode;
|
|
$corder = 'asc';
|
|
}
|
|
$csort_mode = "`" . $csort_mode . "`";
|
|
if ($csort_mode == '`itemId`') {
|
|
$csort_mode = 'tti.`itemId`';
|
|
$numsort = true;
|
|
}
|
|
$sort_tables = '';
|
|
$cat_tables = '';
|
|
}
|
|
|
|
$categlib = TikiLib::lib('categ');
|
|
if ($jail = $categlib->get_jail()) {
|
|
$categlib->getSqlJoin($jail, 'trackeritem', 'tti.`itemId`', $join, $mid, $bindvars);
|
|
}
|
|
|
|
$base_tables = '('
|
|
. ' `tiki_tracker_items` tti'
|
|
. ' INNER JOIN `tiki_tracker_item_fields` ttif ON tti.`itemId` = ttif.`itemId`'
|
|
. ' INNER JOIN `tiki_tracker_fields` ttf ON ttf.`fieldId` = ttif.`fieldId`'
|
|
. ')' . $join;
|
|
|
|
$fieldIds = [];
|
|
if (! empty($listfields)) {
|
|
foreach ($listfields as $k => $f) {
|
|
if (isset($f['fieldId'])) {
|
|
$fieldIds[] = $f['fieldId'];
|
|
} else {
|
|
$fieldIds[] = $k; // sometimes filterfields are provided with the fieldId only on the array keys
|
|
}
|
|
}
|
|
}
|
|
if (! empty($filterfield)) {
|
|
// fix: could be that there is just one field. in this case it might be a scalar,
|
|
// not an array due to not handle $filterfield proper somewhere else in the code
|
|
if (! is_array($filterfield)) {
|
|
$filterfield = [$filterfield];
|
|
}
|
|
foreach ($filterfield as $f) {
|
|
if (! empty($f['sqlsearch'])) {
|
|
foreach ($f['sqlsearch'] as $subf) {
|
|
if (! in_array($subf, $fieldIds)) {
|
|
$fieldIds[] = $subf;
|
|
}
|
|
}
|
|
} elseif (! empty($f['usersearch'])) {
|
|
foreach ($f['usersearch'] as $subf) {
|
|
if (! in_array($subf, $fieldIds)) {
|
|
$fieldIds[] = $subf;
|
|
}
|
|
}
|
|
} else {
|
|
if (! in_array($f, $fieldIds)) {
|
|
$fieldIds[] = $f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (! empty($fieldIds)) {
|
|
$mid .= ' AND ' . $this->in('ttif.fieldId', $fieldIds, $bindvars);
|
|
}
|
|
|
|
if ($csort_mode == '`created`') {
|
|
$csort_mode = 'tti.created';
|
|
}
|
|
if ($corder == 'nasc' || $corder == 'ndesc') {
|
|
$numsort = true;
|
|
$corder = substr($corder, 1);
|
|
}
|
|
$query = 'SELECT tti.*'
|
|
. ', ' . ( ($numsort) ? "cast(max($csort_mode) as decimal)" : "max($csort_mode)") . ' as `sortvalue`'
|
|
. ' FROM ' . $base_tables . $sort_tables . $cat_table
|
|
. $mid
|
|
. ' GROUP BY tti.`itemId`, tti.`trackerId`, tti.`created`, tti.`createdBy`, tti.`status`, tti.`lastModif`, tti.`lastModifBy`, ' . $csort_mode
|
|
. ' ORDER BY ' . $this->convertSortMode('sortvalue_' . $corder);
|
|
if ($numsort) {
|
|
$query .= ',' . $this->convertSortMode($csort_mode);
|
|
}
|
|
//echo htmlentities($query); print_r($bindvars);
|
|
$query_cant = 'SELECT count(DISTINCT ttif.`itemId`) FROM ' . $base_tables . $sort_tables . $cat_table . $mid;
|
|
|
|
// save the result
|
|
$ret = [];
|
|
|
|
// Start loop to get the required number of items if permissions / filters are in use.
|
|
// The problem: If $maxItems and $offset are given,
|
|
// but the sql query returns items the user has no permissions or the filter criteria does not match,
|
|
// then only a subset of what is available would be returned.
|
|
|
|
// original requested number of items
|
|
$maxRecordsRequested = $maxRecords;
|
|
// original page (from pagination)
|
|
$offsetRequested = $offset;
|
|
// offset calculated on $offsetRequested
|
|
$currentOffset = 0;
|
|
// set to true when we have enough records or no records left.
|
|
$finished = false;
|
|
// used internaly - one time query that returns the total number of records without taking into account filter or permissions
|
|
$cant = $this->getOne($query_cant, $bindvars);
|
|
// $cant will be modified bc its used otherwise. so save the totalCount value
|
|
$totalCount = $cant;
|
|
// total number of records read so far
|
|
$currentCount = 0;
|
|
// number of records in the result set
|
|
$resultCount = 0;
|
|
|
|
// outer loop - grab more records bc it might be we must filter out records.
|
|
// 300 seems to be ok, bc paganination offers this as well as the size of the resultset
|
|
// NOTE: This value is important with respect to memory usage and performance - especially when lots of items (like 10k+) are in use.
|
|
$maxRecords = 300;
|
|
// offset used for sql query
|
|
$offset = 0;
|
|
|
|
// optimize permission check - preload ownership fields to be able to quickly enforce canSeeOwn or wrtier group can modify permissions
|
|
$definition = Tracker_Definition::get($trackerId);
|
|
$ownershipFields = $definition->getItemOwnerFields();
|
|
$groupOwnershipFields = $definition->getItemGroupOwnerFields();
|
|
if ($groupField = $definition->getWriterGroupField()) {
|
|
$groupOwnershipFields[] = $groupField;
|
|
}
|
|
|
|
while (! $finished) {
|
|
$ret1 = $this->fetchAll($query, $bindvars, $maxRecords, $offset);
|
|
// add. security - should not be necessary bc of check at the end. no records left - end outer loop
|
|
if (count($ret1) == 0) {
|
|
$finished = true;
|
|
}
|
|
|
|
if (! $skip_permission_check) {
|
|
// preload permissions for all items to be checked
|
|
Perms::bulk(['type' => 'trackeritem', 'parentId' => $trackerId], 'object', $ret1, 'itemId');
|
|
|
|
// preload ownership field values for all items to be checked
|
|
$ownershipData = [];
|
|
$table = $this->itemFields();
|
|
$rows = $table->fetchAll(['itemId', 'fieldId', 'value'], [
|
|
'itemId' => $table->in(array_map(function ($row) {
|
|
return $row['itemId'];
|
|
}, $ret1)),
|
|
'fieldId' => $table->in($ownershipFields)
|
|
]);
|
|
foreach ($rows as $row) {
|
|
$ownershipData[$row['itemId']][$row['fieldId']] = $this->parse_user_field($row['value']);
|
|
}
|
|
$rows = $table->fetchAll(['itemId', 'fieldId', 'value'], [
|
|
'itemId' => $table->in(array_map(function ($row) {
|
|
return $row['itemId'];
|
|
}, $ret1)),
|
|
'fieldId' => $table->in($groupOwnershipFields)
|
|
]);
|
|
foreach ($rows as $row) {
|
|
$ownershipData[$row['itemId']][$row['fieldId']] = $row['value'];
|
|
}
|
|
}
|
|
|
|
foreach ($ret1 as $res) {
|
|
$mem = TikiLib::lib('tiki')->get_memory_avail();
|
|
if ($mem > 0 && $mem < 1048576 * 10) { // Less than 10MB left?
|
|
// post an error even though it doesn't get displayed when using export as the output goes into the output file
|
|
Feedback::error(tr('Tracker list_items ran out of memory after %0 items.', count($ret)));
|
|
break;
|
|
}
|
|
|
|
if (! $skip_permission_check) {
|
|
// this is needed by permission checking inside tracker item
|
|
$res += $ownershipData[$res['itemId']] ?? [];
|
|
$itemObject = Tracker_Item::fromInfo($res);
|
|
if (! $itemObject->canView()) {
|
|
$cant--;
|
|
// skipped record bc of permissions - need to count for outer loop
|
|
$currentCount++;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
$res['itemUsers'] = [];
|
|
if ($listfields !== null && ! empty($listfields)) {
|
|
$res['field_values'] = $this->get_item_fields($trackerId, $res['itemId'], $listfields, $res['itemUsers']);
|
|
}
|
|
|
|
if (! empty($asort_mode)) {
|
|
foreach ($res['field_values'] as $i => $field) {
|
|
if ($field['fieldId'] == $asort_mode) {
|
|
$kx = $field['value'] . '.' . $res['itemId'];
|
|
}
|
|
}
|
|
}
|
|
if (isset($linkfilter) && $linkfilter) {
|
|
$filterout = false;
|
|
// NOTE: This implies filterfield if is link field has to be in fields set
|
|
foreach ($res['field_values'] as $i => $field) {
|
|
foreach ($linkfilter as $lf) {
|
|
if ($field['fieldId'] == $lf["filterfield"]) {
|
|
// extra comma at the front and back of filtervalue to avoid ambiguity in partial match
|
|
if ($lf["filtervalue"] && strpos(',' . implode(',', $field['items']) . ',', $lf["filtervalue"]) === false) {
|
|
$filterout = true;
|
|
break 2;
|
|
} elseif ($lf["exactvalue"] && ! in_array($lf['exactvalue'], $field['items'])) {
|
|
$filterout = true;
|
|
break 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ($filterout) {
|
|
$cant--;
|
|
// skipped record bc of filter criteria - need to count for outer loop
|
|
$currentCount++;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
$res['geolocation'] = TikiLib::lib('geo')->get_coordinates('trackeritem', $res['itemId']);
|
|
|
|
// have a field, adjust counter and check if we have enough items
|
|
$currentCount++;
|
|
$currentOffset++;
|
|
|
|
// field is stored in $res. See wether we can add it to the resultset, based on the requested offset
|
|
if (! $finished && $currentOffset > $offsetRequested) {
|
|
$resultCount++;
|
|
if (empty($kx)) {
|
|
// ex: if the sort field is non visible, $kx is null
|
|
$ret[] = $res;
|
|
} else {
|
|
$ret[$kx] = $res;
|
|
}
|
|
}
|
|
|
|
if ($resultCount == $maxRecordsRequested) {
|
|
// have enough items to return but keep filtering out the remainder to test for ownership or status perms
|
|
$finished = true;
|
|
}
|
|
} // foreach
|
|
|
|
// are items left?
|
|
if ($currentCount == $totalCount) {
|
|
$finished = true;
|
|
} else {
|
|
$offset += $maxRecords;
|
|
}
|
|
} // while
|
|
|
|
// End loop to get the required number of items if permissions / filters are in use
|
|
|
|
if ($prefSort) {
|
|
$corder === 'desc' ? krsort($ret) : ksort($ret);
|
|
}
|
|
|
|
$retval = [];
|
|
$retval['data'] = array_values($ret);
|
|
$retval['cant'] = $cant;
|
|
return $retval;
|
|
}
|
|
|
|
/* listfields fieldId=>fielddefinition */
|
|
public function get_item_fields($trackerId, $itemId, $listfields, &$itemUsers, $alllang = false)
|
|
{
|
|
global $prefs, $user, $tiki_p_admin_trackers;
|
|
|
|
$definition = Tracker_Definition::get($trackerId);
|
|
$info = $this->get_tracker_item((int) $itemId);
|
|
$factory = $definition->getFieldFactory();
|
|
|
|
$itemUsers = array_map(function ($userField) use ($info) {
|
|
return isset($info[$userField]) ? $this->parse_user_field($info[$userField]) : [];
|
|
}, $definition->getItemOwnerFields());
|
|
|
|
if ($itemUsers) {
|
|
$itemUsers = call_user_func_array('array_merge', $itemUsers);
|
|
}
|
|
|
|
$fields = [];
|
|
foreach ($listfields as $fieldId => $fopt) {
|
|
if (empty($fopt['fieldId'])) {
|
|
// to accept listfield as a simple table
|
|
$fopt['fieldId'] = $fieldId;
|
|
}
|
|
|
|
$fopt['trackerId'] = $trackerId;
|
|
$fopt['itemId'] = (int)$itemId;
|
|
|
|
$handler = $factory->getHandler($fopt, $info);
|
|
if ($handler) {
|
|
$get = $this->extend_GET($fopt); // extend context
|
|
$fopt = array_merge($fopt, $handler->getFieldData());
|
|
$fields[] = $fopt;
|
|
$this->restore_GET($get); // restore context
|
|
}
|
|
}
|
|
|
|
return($fields);
|
|
}
|
|
|
|
/**
|
|
* Make sure $_GET is extended with the $fopt (in get_item_fields) before calling $handler->getFieldData()
|
|
* Some trackers use tiki syntax replacement, that uses $_GET in ParserLib::parse_wiki_argvariable, extending
|
|
* with $fopt makes sure that that the wiki syntax parser gets the right context variables
|
|
*
|
|
* @param Array $array Values to add to $_GET
|
|
* @return Array a copy of the original $_GET array
|
|
*/
|
|
protected function extend_GET($array)
|
|
{
|
|
$get = $_GET;
|
|
foreach ($array as $key => $value) {
|
|
$_GET[$key] = $value;
|
|
}
|
|
return $get;
|
|
}
|
|
|
|
/**
|
|
* Use to restore the $_GET context with the copy of $_GET returned by self::extend_GET
|
|
*
|
|
* @param Array $get the array to restore as $_GET
|
|
*/
|
|
protected function restore_GET($get)
|
|
{
|
|
$_GET = $get;
|
|
}
|
|
|
|
public function replace_item($trackerId, $itemId, $ins_fields, $status = '', $ins_categs = 0, $bulk_import = false, $skip_sync = false)
|
|
{
|
|
global $user, $prefs, $tiki_p_admin_trackers, $tiki_p_admin_users;
|
|
$final_event = 'tiki.trackeritem.update';
|
|
|
|
if (! $bulk_import) {
|
|
$transaction = $this->begin();
|
|
}
|
|
|
|
$categlib = TikiLib::lib('categ');
|
|
$cachelib = TikiLib::lib('cache');
|
|
$smarty = TikiLib::lib('smarty');
|
|
$logslib = TikiLib::lib('logs');
|
|
$userlib = TikiLib::lib('user');
|
|
$tikilib = TikiLib::lib('tiki');
|
|
$notificationlib = TikiLib::lib('notification');
|
|
|
|
$items = $this->items();
|
|
$itemFields = $this->itemFields();
|
|
$fields = $this->fields();
|
|
|
|
if (! empty($itemId)) { // check the item really exists
|
|
$itemId = (int) $this->items()->fetchOne('itemId', [ 'itemId' => $itemId]);
|
|
}
|
|
|
|
$fil = [];
|
|
if (! empty($itemId)) {
|
|
$fil = $itemFields->fetchMap('fieldId', 'value', ['itemId' => $itemId]);
|
|
}
|
|
|
|
$old_values = $fil;
|
|
|
|
$tracker_definition = Tracker_Definition::get($trackerId);
|
|
|
|
if (method_exists($tracker_definition, 'getInformation') == false) {
|
|
return -1;
|
|
}
|
|
|
|
$tracker_info = $tracker_definition->getInformation();
|
|
|
|
if (! empty($itemId)) {
|
|
$new_itemId = 0;
|
|
$oldStatus = $this->items()->fetchOne('status', ['itemId' => $itemId]);
|
|
|
|
$status = $status ? $status : $oldStatus;
|
|
$fil['status'] = $status;
|
|
$old_values['status'] = $oldStatus;
|
|
|
|
if ($status != $oldStatus) {
|
|
$this->change_status([$itemId], $status);
|
|
} else {
|
|
$this->update_items(
|
|
[$itemId],
|
|
[
|
|
'lastModif' => $tikilib->now,
|
|
'lastModifBy' => $user,
|
|
],
|
|
false
|
|
);
|
|
}
|
|
|
|
$version = $this->last_log_version($itemId) + 1;
|
|
} else {
|
|
if (empty($status) && isset($tracker_info['newItemStatus'])) {
|
|
// set status based on tracker setting of status not explicitly requested
|
|
$status = $tracker_info['newItemStatus'];
|
|
}
|
|
if (empty($status)) {
|
|
$status = 'o';
|
|
}
|
|
$fil['status'] = $status;
|
|
$old_values['status'] = '';
|
|
$oldStatus = '';
|
|
|
|
$new_itemId = $items->insert(
|
|
[
|
|
'trackerId' => (int) $trackerId,
|
|
'created' => $this->now,
|
|
'createdBy' => $user,
|
|
'lastModif' => $this->now,
|
|
'lastModifBy' => $user,
|
|
'status' => $status,
|
|
]
|
|
);
|
|
|
|
$logslib->add_action('Created', $new_itemId, 'trackeritem');
|
|
$version = 0;
|
|
|
|
$final_event = 'tiki.trackeritem.create';
|
|
}
|
|
|
|
$currentItemId = $itemId ? $itemId : $new_itemId;
|
|
$item_info = $this->get_item_info($currentItemId);
|
|
|
|
if (! empty($oldStatus) || ! empty($status)) {
|
|
if (! empty($itemId) && $oldStatus != $status) {
|
|
$this->log($version, $itemId, -1, $oldStatus);
|
|
}
|
|
}
|
|
|
|
// If this is a user tracker it needs to be detected right here before actual looping of fields happen
|
|
$trackersync_user = $user;
|
|
foreach ($ins_fields["data"] as $i => $array) {
|
|
if (isset($array['type']) && $array['type'] == 'u' && isset($array['options_array'][0]) && $array['options_array'][0] == '1') {
|
|
if ($prefs['user_selector_realnames_tracker'] == 'y' && $array['type'] == 'u') {
|
|
if (! $userlib->user_exists($array['value'])) {
|
|
$finalusers = $userlib->find_best_user([$array['value']], '', 'login');
|
|
if (! empty($finalusers[0]) && ! (isset($_REQUEST['register']) && isset($_REQUEST['name']) && $_REQUEST['name'] == $array['value'])) {
|
|
// It could be in fact that a new user is required (when no match is found or during registration even if match is found)
|
|
$ins_fields['data'][$i]['value'] = $finalusers[0];
|
|
}
|
|
}
|
|
}
|
|
$trackersync_user = $array['value'];
|
|
}
|
|
}
|
|
|
|
$final = [];
|
|
$postSave = [];
|
|
$suppliedFields = [];
|
|
|
|
foreach ($ins_fields["data"] as $i => $array) {
|
|
// Old values were prefilled at the begining of the function and only replaced at the end of the iteration
|
|
$fieldId = $array['fieldId'];
|
|
$suppliedFields[] = $fieldId;
|
|
$old_value = isset($fil[$fieldId]) ? $fil[$fieldId] : null;
|
|
|
|
$handler = $this->get_field_handler($array, array_merge($item_info, $fil));
|
|
|
|
if (method_exists($handler, 'postSaveHook')) {
|
|
// postSaveHook will be called with final value saved
|
|
// after saving all item fields
|
|
$postSave[] = [
|
|
'fieldId' => $fieldId,
|
|
'handler' => $handler,
|
|
];
|
|
}
|
|
|
|
if (method_exists($handler, 'handleFinalSave')) {
|
|
// handleFinalSave will be called after all other fields are saved, and
|
|
// will get as parameter all other field data (other than ones that also
|
|
// use finalSave).
|
|
$final[] = [
|
|
'field' => $array,
|
|
'handler' => $handler,
|
|
];
|
|
continue;
|
|
}
|
|
|
|
if (method_exists($handler, 'handleSave')) {
|
|
$array = array_merge($array, $handler->handleSave(! isset($array['value']) ? null : $array['value'], $old_value));
|
|
$value = ! isset($array['value']) ? null : $array['value'];
|
|
|
|
if ($value !== false) {
|
|
$this->modify_field($currentItemId, $array['fieldId'], $value);
|
|
|
|
if ($itemId && $old_value != $value) {
|
|
// On update, save old value
|
|
$this->log($version, $itemId, $array['fieldId'], $old_value);
|
|
}
|
|
$fil[$fieldId] = $value;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
$value = isset($array["value"]) ? $array["value"] : null;
|
|
|
|
if (isset($array['type']) && $array['type'] == 'p' && ($user == $trackersync_user || $tiki_p_admin_users == 'y')) {
|
|
if ($array['options_array'][0] == 'password') {
|
|
if (! empty($array['value']) && $prefs['change_password'] == 'y' && ($e = $userlib->check_password_policy($array['value'])) == '') {
|
|
$userlib->change_user_password($trackersync_user, $array['value']);
|
|
}
|
|
if (! empty($itemId)) {
|
|
$this->log($version, $itemId, $array['fieldId'], '?');
|
|
}
|
|
} elseif ($array['options_array'][0] == 'email') {
|
|
if (! empty($array['value']) && validate_email($array['value']) && ($prefs['user_unique_email'] != 'y' || ! $userlib->other_user_has_email($trackersync_user, $array['value']))) {
|
|
$old_value = $userlib->get_user_email($trackersync_user);
|
|
$userlib->change_user_email($trackersync_user, $array['value']);
|
|
}
|
|
if (! empty($itemId) && $old_value != $array['value']) {
|
|
$this->log($version, $itemId, $array['fieldId'], $old_value);
|
|
}
|
|
} else {
|
|
$old_value = $tikilib->get_user_preference($trackersync_user, $array['options_array'][0]);
|
|
$tikilib->set_user_preference($trackersync_user, $array['options_array'][0], $array['value']);
|
|
if (! empty($itemId) && $old_value != $array['value']) {
|
|
$this->log($version, $itemId, $array['fieldId'], $array['value']);
|
|
}
|
|
}
|
|
// Should not store value in tracker database as it won't be reliable (what if pref is changed afterwards?)
|
|
$value = '';
|
|
$fil[$fieldId] = $value;
|
|
$this->modify_field($currentItemId, $array['fieldId'], $value);
|
|
} elseif (isset($array['type']) && $array['type'] == 'k') { //page selector
|
|
if ($array['value'] != '') {
|
|
$this->modify_field($currentItemId, $array['fieldId'], $value);
|
|
if ($itemId) {
|
|
// On update, save old value
|
|
$this->log($version, $itemId, $array['fieldId'], $old_value);
|
|
}
|
|
$fil[$fieldId] = $value;
|
|
if (! $this->page_exists($array['value'])) {
|
|
$opts = $array['options_array'];
|
|
if (! empty($opts[2])) {
|
|
$IP = $this->get_ip_address();
|
|
$info = $this->get_page_info($opts[2]);
|
|
$this->create_page($array['value'], 0, $info['data'], $this->now, '', $user, $IP, $info['description'], $info['lang'], $info['is_html'], [], $info['wysiwyg'], $info['wiki_authors_style']);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
$is_date = isset($array['type']) ? in_array($array["type"], ['f', 'j']) : false;
|
|
|
|
if ($currentItemId || ( isset($array['type']) && $array['type'] !== 'q')) { // autoincrement
|
|
$this->modify_field($currentItemId, $fieldId, $value);
|
|
if ($old_value != $value) {
|
|
if ($is_date) {
|
|
$dformat = $prefs['short_date_format'] . ' ' . $prefs['short_time_format'];
|
|
if (! empty($old_value)) {
|
|
$old_value = $this->date_format($dformat, (int) $old_value);
|
|
}
|
|
if (! empty($new_value)) {
|
|
$new_value = $this->date_format($dformat, (int) $value);
|
|
} else {
|
|
$new_value = $value;
|
|
}
|
|
} else {
|
|
$new_value = $value;
|
|
}
|
|
if (
|
|
$old_value != $new_value && ! empty($itemId) &&
|
|
$array['type'] !== 'W' // not for webservices
|
|
) {
|
|
$this->log($version, $itemId, $array['fieldId'], $old_value);
|
|
}
|
|
}
|
|
}
|
|
|
|
$fil[$fieldId] = $value;
|
|
}
|
|
}
|
|
|
|
// delete empty actionlog version to prevent history date overlap
|
|
if ($version > 0 && $this->last_log_version($itemId) + 1 == $version) {
|
|
$logslib->delete_action('Updated', $itemId, 'trackeritem', $version);
|
|
}
|
|
|
|
// get permnames
|
|
$permNames = [];
|
|
foreach ($fil as $fieldId => $value) {
|
|
$field = $tracker_definition->getField($fieldId);
|
|
if (! empty($field)) {
|
|
if ($field['type'] !== 'W') { // not for webservices
|
|
$permNames[$fieldId] = $field['permName'];
|
|
} else {
|
|
unset($fil[$fieldId], $old_values[$fieldId]); // webservice values are just a cache and not useful for diffs etc
|
|
}
|
|
}
|
|
}
|
|
|
|
if (count($final)) {
|
|
$data = [];
|
|
foreach ($fil as $fieldId => $value) {
|
|
if (isset($permNames[$fieldId])) {
|
|
$data[$permNames[$fieldId]] = $value;
|
|
}
|
|
}
|
|
|
|
foreach ($final as $job) {
|
|
if (isset($job['field']['value'])) {
|
|
$data[$job['field']['permName']] = $job['field']['value'];
|
|
}
|
|
$value = $job['handler']->handleFinalSave($data);
|
|
$data[$job['field']['permName']] = $value;
|
|
$this->modify_field($currentItemId, $job['field']['fieldId'], $value);
|
|
$fil[$job['field']['fieldId']] = $value;
|
|
$permNames[$job['field']['fieldId']] = $job['field']['permName'];
|
|
}
|
|
}
|
|
|
|
foreach ($postSave as $job) {
|
|
$value = $fil[$job['fieldId']];
|
|
$job['handler']->postSaveHook($value);
|
|
}
|
|
|
|
$values_by_permname = [];
|
|
$old_values_by_permname = [];
|
|
foreach ($fil as $fieldId => $value) {
|
|
if (! empty($permNames[$fieldId])) {
|
|
$values_by_permname[$permNames[$fieldId]] = $value;
|
|
}
|
|
}
|
|
foreach ($old_values as $fieldId => $value) {
|
|
if (! empty($permNames[$fieldId])) {
|
|
$old_values_by_permname[$permNames[$fieldId]] = $value;
|
|
}
|
|
}
|
|
|
|
$arguments = [
|
|
'type' => 'trackeritem',
|
|
'object' => $currentItemId,
|
|
'user' => $GLOBALS['user'],
|
|
'version' => $version,
|
|
'trackerId' => $trackerId,
|
|
'supplied' => $suppliedFields,
|
|
'values' => $fil,
|
|
'old_values' => $old_values,
|
|
'values_by_permname' => $values_by_permname,
|
|
'old_values_by_permname' => $old_values_by_permname,
|
|
'bulk_import' => $bulk_import,
|
|
'skip_sync' => $skip_sync,
|
|
'aggregate' => sha1("trackeritem/$currentItemId"),
|
|
];
|
|
|
|
// this needs to trigger no matter of the size as trackeritem categorization depends on this and other event types as well
|
|
TikiLib::events()->trigger(
|
|
$final_event,
|
|
$arguments
|
|
);
|
|
|
|
if (! $bulk_import) {
|
|
$transaction->commit();
|
|
}
|
|
|
|
return $currentItemId;
|
|
}
|
|
|
|
public function modify_field($itemId, $fieldId, $value)
|
|
{
|
|
$field = $this->get_tracker_field($fieldId);
|
|
if (! empty($field['encryptionKeyId'])) {
|
|
try {
|
|
$key = new Tiki\Encryption\Key($field['encryptionKeyId']);
|
|
$value = $key->encryptData($value);
|
|
} catch (Tiki\Encryption\NotFoundException $e) {
|
|
Feedback::error(tr('Field "%0" is encrypted with a key that no longer exists!', $field['name']));
|
|
} catch (Tiki\Encryption\Exception $e) {
|
|
Feedback::error(tr('Field "%0" is encrypted using key "%1" but where was an error enrypting the data: %2', $field['name'], $key->get('name'), $e->getMessage()));
|
|
}
|
|
$info = '<div class="description form-text">' . $info . '</div>';
|
|
}
|
|
|
|
$conditions = [
|
|
'itemId' => (int) $itemId,
|
|
'fieldId' => (int) $field['fieldId'],
|
|
];
|
|
|
|
$this->itemFields()->insertOrUpdate(['value' => $value], $conditions);
|
|
}
|
|
|
|
public function groupName($tracker_info, $itemId)
|
|
{
|
|
if (empty($tracker_info['autoCreateGroupInc'])) {
|
|
$groupName = $tracker_info['name'];
|
|
} else {
|
|
$userlib = TikiLib::lib('user');
|
|
$group_info = $userlib->get_groupId_info($tracker_info['autoCreateGroupInc']);
|
|
$groupName = $group_info['groupName'];
|
|
}
|
|
return "$groupName $itemId";
|
|
}
|
|
|
|
public function _format_data($field, $data)
|
|
{
|
|
$data = trim($data);
|
|
if ($field['type'] == 'a') {
|
|
if (isset($field["options_array"][3]) and $field["options_array"][3] > 0 and strlen($data) > $field["options_array"][3]) {
|
|
$data = substr($data, 0, $field["options_array"][3]) . " (...)";
|
|
}
|
|
} elseif ($field['type'] == 'c') {
|
|
if ($data != 'y') {
|
|
$data = 'n';
|
|
}
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Called from tiki-list_trackers.php import button
|
|
*
|
|
* @param int $trackerId
|
|
* @param resource $csvHandle file handle to import
|
|
* @param bool $replace_rows make new items for those with existing itemId
|
|
* @param string $dateFormat used for item fields of type date
|
|
* @param string $encoding defaults "UTF8"
|
|
* @param string $csvDelimiter defaults to ","
|
|
* @param bool $updateLastModif default true
|
|
* @param bool $convertItemLinkValues default false attempts to find a linked or related item for ItemLink and Relations fields
|
|
* @return number items imported
|
|
*/
|
|
public function import_csv($trackerId, $csvHandle, $replace_rows = true, $dateFormat = '', $encoding = 'UTF8', $csvDelimiter = ',', $updateLastModif = true, $convertItemLinkValues = false)
|
|
{
|
|
$tikilib = TikiLib::lib('tiki');
|
|
$unifiedsearchlib = TikiLib::lib('unifiedsearch');
|
|
|
|
$items = $this->items();
|
|
$itemFields = $this->itemFields();
|
|
|
|
$tracker_info = $this->get_tracker_options($trackerId);
|
|
if (($header = fgetcsv($csvHandle, 100000, $csvDelimiter)) === false) {
|
|
return 'Illegal first line';
|
|
}
|
|
if ($encoding == 'UTF-8') {
|
|
// See en.wikipedia.org/wiki/Byte_order_mark
|
|
if (substr($header[0], 0, 3) == "\xef\xbb\xbf") {
|
|
$header[0] = substr($header[0], 3);
|
|
}
|
|
}
|
|
$max = count($header);
|
|
if ($max === 1 and strpos($header, "\t") !== false) {
|
|
Feedback::error(tr('No fields found in header, not a comma-separated values file?'));
|
|
return 0;
|
|
}
|
|
for ($i = 0; $i < $max; $i++) {
|
|
if ($encoding == 'ISO-8859-1') {
|
|
$header[$i] = utf8_encode($header[$i]);
|
|
}
|
|
$header[$i] = preg_replace('/ -- [0-9]*$/', ' -- ', $header[$i]);
|
|
}
|
|
if (count($header) != count(array_unique($header))) {
|
|
return 'Duplicate header names';
|
|
}
|
|
$total = 0;
|
|
$need_reindex = [];
|
|
$fields = $this->list_tracker_fields($trackerId, 0, -1, 'position_asc', '');
|
|
|
|
// prepare autoincrement fields
|
|
$auto_fields = [];
|
|
foreach ($fields['data'] as $field) {
|
|
if ($field['type'] === 'q') {
|
|
$auto_fields[(int) $field['fieldId']] = $field;
|
|
}
|
|
}
|
|
|
|
// prepare ItemLink fields
|
|
if ($convertItemLinkValues) {
|
|
$itemlink_options = [];
|
|
foreach ($fields['data'] as $field) {
|
|
if ($field['type'] === 'r') {
|
|
$itemlink_options[(int) $field['fieldId']] = $field['options_array'];
|
|
}
|
|
}
|
|
}
|
|
|
|
// mandatory fields check
|
|
$utilities = new \Services_Tracker_Utilities();
|
|
$definition = Tracker_Definition::get($trackerId);
|
|
$line = 0;
|
|
$errors = [];
|
|
while (($data = fgetcsv($csvHandle, 100000, $csvDelimiter)) !== false) {
|
|
$line++;
|
|
if ($encoding == 'ISO-8859-1') {
|
|
for ($i = 0; $i < $max; $i++) {
|
|
$data[$i] = utf8_encode($data[$i]);
|
|
}
|
|
}
|
|
$itemId = 0;
|
|
$datafields = [];
|
|
for ($i = 0; $i < $max; ++$i) {
|
|
if ($header[$i] == 'itemId') {
|
|
$itemId = $data[$i];
|
|
}
|
|
if (! preg_match('/ -- $/', $header[$i])) {
|
|
continue;
|
|
}
|
|
$h = preg_replace('/ -- $/', '', $header[$i]);
|
|
foreach ($fields['data'] as $field) {
|
|
if ($field['name'] == $h) {
|
|
$datafields[$field['permName']] = $data[$i];
|
|
}
|
|
}
|
|
}
|
|
$lineErrors = $utilities->validateItem($definition, ['itemId' => $itemId, 'fields' => $datafields]);
|
|
foreach ($lineErrors as $error) {
|
|
$errors[] = tr('Line %0:', $line) . ' ' . $error;
|
|
}
|
|
}
|
|
|
|
if (count($errors) > 0) {
|
|
Feedback::error([
|
|
'title' => tr('Import file contains errors. Please review and fix before importing.'),
|
|
'mes' => $errors
|
|
]);
|
|
return 0;
|
|
}
|
|
|
|
// back to first row excluding header
|
|
fseek($csvHandle, 0);
|
|
fgetcsv($csvHandle, 100000, $csvDelimiter);
|
|
|
|
while (($data = fgetcsv($csvHandle, 100000, $csvDelimiter)) !== false) {
|
|
$status = 'o';
|
|
$itemId = 0;
|
|
$created = $tikilib->now;
|
|
$lastModif = $created;
|
|
$cats = '';
|
|
for ($i = 0; $i < $max; $i++) {
|
|
if ($encoding == 'ISO-8859-1') {
|
|
$data[$i] = utf8_encode($data[$i]);
|
|
}
|
|
if ($header[$i] == 'status') {
|
|
if ($data[$i] == 'o' || $data[$i] == 'p' || $data[$i] == 'c') {
|
|
$status = $data[$i];
|
|
}
|
|
} elseif ($header[$i] == 'itemId') {
|
|
$itemId = $data[$i];
|
|
} elseif ($header[$i] == 'created') {
|
|
$created = $this->parse_imported_date($data[$i], $dateFormat);
|
|
;
|
|
} elseif ($header[$i] == 'lastModif') {
|
|
$lastModif = $this->parse_imported_date($data[$i], $dateFormat);
|
|
} elseif ($header[$i] == 'categs') { // for old compatibility
|
|
$cats = preg_split('/,/', trim($data[$i]));
|
|
}
|
|
}
|
|
$t = $this->get_tracker_for_item($itemId);
|
|
if ($itemId && $t && $t == $trackerId && $replace_rows) {
|
|
if (in_array('status', $header)) {
|
|
$update['status'] = $status;
|
|
}
|
|
if (in_array('created', $header)) {
|
|
$update['created'] = (int) $created;
|
|
}
|
|
if ($updateLastModif) {
|
|
$update['lastModif'] = (int) $lastModif;
|
|
}
|
|
if (! empty($update)) {
|
|
$items->update($update, ['itemId' => (int) $itemId]);
|
|
}
|
|
} else {
|
|
$itemId = $items->insert(
|
|
[
|
|
'trackerId' => (int) $trackerId,
|
|
'created' => (int) $created,
|
|
'lastModif' => (int) $lastModif,
|
|
'status' => $status,
|
|
]
|
|
);
|
|
if (empty($itemId) || $itemId < 1) {
|
|
Feedback::error(tr(
|
|
'Problem inserting tracker item: trackerId=%0, created=%1, lastModif=%2, status=%3',
|
|
$trackerId,
|
|
$created,
|
|
$lastModif,
|
|
$status
|
|
));
|
|
} else {
|
|
// deal with autoincrement fields
|
|
foreach ($auto_fields as $afield) {
|
|
$auto_handler = $this->get_field_handler($afield, $this->get_item_info($itemId));
|
|
if (! empty($auto_handler)) {
|
|
$auto_val = $auto_handler->handleSave(null, null);
|
|
$itemFields->insert(['itemId' => (int) $itemId, 'fieldId' => (int) $afield['fieldId'], 'value' => $auto_val['value']]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
$need_reindex[] = $itemId;
|
|
if (! empty($cats)) {
|
|
$this->categorized_item($trackerId, $itemId, "item $itemId", $cats);
|
|
}
|
|
for ($i = 0; $i < $max; ++$i) {
|
|
if (! preg_match('/ -- $/', $header[$i])) {
|
|
continue;
|
|
}
|
|
$h = preg_replace('/ -- $/', '', $header[$i]);
|
|
foreach ($fields['data'] as $field) {
|
|
if ($field['name'] == $h) {
|
|
if ($field['type'] == 'p' && $field['options_array'][0] == 'password') {
|
|
//$userlib->change_user_password($user, $ins_fields['data'][$i]['value']);
|
|
continue;
|
|
}
|
|
|
|
if ($data[$i] === 'NULL') {
|
|
$data[$i] = '';
|
|
}
|
|
// remove escaped quotes \" etc
|
|
$data[$i] = stripslashes($data[$i]);
|
|
|
|
switch ($field['type']) {
|
|
case 'e':
|
|
$cats = preg_split('/%%%/', trim($data[$i]));
|
|
$catIds = [];
|
|
if (! empty($cats)) {
|
|
foreach ($cats as $c) {
|
|
$categlib = TikiLib::lib('categ');
|
|
if ($cId = $categlib->get_category_id(trim($c))) {
|
|
$catIds[] = $cId;
|
|
}
|
|
}
|
|
if (! empty($catIds)) {
|
|
$this->categorized_item($trackerId, $itemId, "item $itemId", $catIds);
|
|
}
|
|
}
|
|
$data[$i] = '';
|
|
break;
|
|
case 's':
|
|
$data[$i] = '';
|
|
break;
|
|
case 'y': // Country selector
|
|
$data[$i] = preg_replace('/ /', "_", $data[$i]);
|
|
break;
|
|
case 'a':
|
|
$data[$i] = preg_replace('/\%\%\%/', "\r\n", $data[$i]);
|
|
break;
|
|
case 'c':
|
|
if (strtolower($data[$i]) == 'yes' || strtolower($data[$i]) == 'on' || $data[$i] == 1 || strtolower($data[$i]) == 'y') {
|
|
$data[$i] = 'y';
|
|
} else {
|
|
$data[$i] = 'n';
|
|
}
|
|
break;
|
|
case 'f':
|
|
case 'j':
|
|
$data[$i] = $this->parse_imported_date($data[$i], $dateFormat);
|
|
break;
|
|
case 'r':
|
|
if ($convertItemLinkValues && $data[$i]) {
|
|
$val = $this->get_item_id(
|
|
$itemlink_options[$field['fieldId']][0], // other trackerId (option 0)
|
|
$itemlink_options[$field['fieldId']][1], // other fieldId (option 1)
|
|
$data[$i] // value
|
|
);
|
|
if ($val !== null) {
|
|
$data[$i] = $val;
|
|
} else {
|
|
Feedback::error(
|
|
tr(
|
|
'Problem converting tracker item link field: trackerId=%0, fieldId=%1, itemId=%2',
|
|
$trackerId,
|
|
$field['fieldId'],
|
|
$itemId
|
|
)
|
|
);
|
|
}
|
|
}
|
|
break;
|
|
case 'REL': // Relations
|
|
if ($convertItemLinkValues && $data[$i] && ! $field['options_map']['readonly']) {
|
|
$filter = [];
|
|
$results = [];
|
|
|
|
parse_str($field['options_map']['filter'], $filter);
|
|
$filter['title'] = $data[$i];
|
|
|
|
$query = $unifiedsearchlib->buildQuery($filter);
|
|
$query->setRange(0, 1);
|
|
|
|
try {
|
|
$results = $query->search($unifiedsearchlib->getIndex());
|
|
} catch (Search_Elastic_TransportException $e) {
|
|
Feedback::error(tr('Search functionality currently unavailable.'));
|
|
} catch (Exception $e) {
|
|
Feedback::error($e->getMessage());
|
|
}
|
|
|
|
if (count($results)) {
|
|
$data[$i] = $results[0]['object_id'];
|
|
TikiLib::lib('relation')->add_relation($field['options_map']['relation'], 'trackeritem', $itemId, $results[0]['object_type'], $data[$i]);
|
|
} else {
|
|
Feedback::error(
|
|
tr(
|
|
'Problem converting tracker relation field: trackerId=%0, fieldId=%1, itemId=%2 from value "%3"',
|
|
$trackerId,
|
|
$field['fieldId'],
|
|
$itemId,
|
|
$data[$i]
|
|
)
|
|
);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
if ($this->get_item_value($trackerId, $itemId, $field['fieldId']) !== false) {
|
|
$itemFields->update(['value' => $data[$i]], ['itemId' => (int) $itemId, 'fieldId' => (int) $field['fieldId']]);
|
|
} else {
|
|
$itemFields->insert(['itemId' => (int) $itemId, 'fieldId' => (int) $field['fieldId'], 'value' => $data[$i]]);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
$total++;
|
|
}
|
|
|
|
$cant_items = $items->fetchCount(['trackerId' => (int) $trackerId]);
|
|
$this->trackers()->update(['items' => (int) $cant_items, 'lastModif' => $this->now], ['trackerId' => (int) $trackerId]);
|
|
|
|
global $prefs;
|
|
if ($prefs['feature_search'] === 'y' && $prefs['unified_incremental_update'] === 'y') {
|
|
$unifiedsearchlib = TikiLib::lib('unifiedsearch');
|
|
|
|
foreach ($need_reindex as $id) {
|
|
$unifiedsearchlib->invalidateObject('trackeritem', $id);
|
|
}
|
|
$unifiedsearchlib->processUpdateQueue();
|
|
}
|
|
|
|
return $total;
|
|
}
|
|
|
|
public function parse_imported_date($dateString, $dateFormat)
|
|
{
|
|
|
|
$tikilib = TikiLib::lib('tiki');
|
|
$date = 0;
|
|
|
|
if (is_numeric($dateString)) {
|
|
$date = (int)$dateString;
|
|
} elseif ($dateFormat == 'mm/dd/yyyy') {
|
|
list($m, $d, $y) = preg_split('#/#', $dateString);
|
|
if ($y && $m && $d) {
|
|
$date = $tikilib->make_time(0, 0, 0, $m, $d, $y);
|
|
}
|
|
} elseif ($dateFormat == 'dd/mm/yyyy') {
|
|
list($d, $m, $y) = preg_split('#/#', $dateString);
|
|
if ($y && $m && $d) {
|
|
$date = $tikilib->make_time(0, 0, 0, $m, $d, $y);
|
|
}
|
|
} elseif ($dateFormat == 'yyyy-mm-dd') {
|
|
list($y, $m, $d) = preg_split('#-#', $dateString);
|
|
if ($y && $m && $d) {
|
|
$date = $tikilib->make_time(0, 0, 0, $m, $d, $y);
|
|
}
|
|
}
|
|
|
|
if (! $date) { // previous attempts failed, try a more flexible approach
|
|
$date = strtotime($dateString);
|
|
}
|
|
|
|
return $date;
|
|
}
|
|
|
|
public function dump_tracker_csv($trackerId)
|
|
{
|
|
$tikilib = TikiLib::lib('tiki');
|
|
$tracker_info = $this->get_tracker_options($trackerId);
|
|
$fields = $this->list_tracker_fields($trackerId, 0, -1, 'position_asc', '');
|
|
|
|
$trackerId = (int) $trackerId;
|
|
|
|
// Check if can view field otherwise exclude it
|
|
$item = Tracker_Item::newItem($trackerId);
|
|
foreach ($fields['data'] as $k => $field) {
|
|
if (! $item->canViewField($field['fieldId'])) {
|
|
unset($fields['data'][$k]);
|
|
}
|
|
}
|
|
|
|
// write out file header
|
|
session_write_close();
|
|
$this->write_export_header('UTF-8', $trackerId);
|
|
|
|
// then "field names -- index" as first line
|
|
$str = '';
|
|
$str .= 'itemId,status,created,lastModif,'; // these headings weren't quoted in the previous export function
|
|
if (count($fields['data']) > 0) {
|
|
foreach ($fields['data'] as $field) {
|
|
$str .= '"' . $field['name'] . ' -- ' . $field['fieldId'] . '",';
|
|
}
|
|
}
|
|
echo $str;
|
|
|
|
// prepare queries
|
|
$mid = ' WHERE tti.`trackerId` = ? ';
|
|
$bindvars = [$trackerId];
|
|
$join = '';
|
|
|
|
$query_items = 'SELECT tti.itemId, tti.status, tti.created, tti.lastModif'
|
|
. ' FROM `tiki_tracker_items` tti'
|
|
. $mid
|
|
. ' ORDER BY tti.`itemId` ASC';
|
|
$query_fields = 'SELECT tti.itemId, ttif.`value`, ttf.`type`'
|
|
. ' FROM ('
|
|
. ' `tiki_tracker_items` tti'
|
|
. ' INNER JOIN `tiki_tracker_item_fields` ttif ON tti.`itemId` = ttif.`itemId`'
|
|
. ' INNER JOIN `tiki_tracker_fields` ttf ON ttf.`fieldId` = ttif.`fieldId`'
|
|
. ')'
|
|
. $mid
|
|
. ' ORDER BY tti.`itemId` ASC, ttf.`position` ASC';
|
|
$base_tables = '('
|
|
. ' `tiki_tracker_items` tti'
|
|
. ' INNER JOIN `tiki_tracker_item_fields` ttif ON tti.`itemId` = ttif.`itemId`'
|
|
. ' INNER JOIN `tiki_tracker_fields` ttf ON ttf.`fieldId` = ttif.`fieldId`'
|
|
. ')' . $join;
|
|
|
|
|
|
$query_cant = 'SELECT count(DISTINCT ttif.`itemId`) FROM ' . $base_tables . $mid;
|
|
$cant = $this->getOne($query_cant, $bindvars);
|
|
|
|
$avail_mem = $tikilib->get_memory_avail();
|
|
$maxrecords_items = (int)(($avail_mem - 10 * 1024 * 1025) / 5000); // depends on size of items table (fixed)
|
|
if ($maxrecords_items < 0) {
|
|
// cope with memory_limit = -1
|
|
$maxrecords_items = -1;
|
|
}
|
|
$offset_items = 0;
|
|
|
|
$items = $this->get_dump_items_array($query_items, $bindvars, $maxrecords_items, $offset_items);
|
|
|
|
$avail_mem = $tikilib->get_memory_avail(); // update avail after getting first batch of items
|
|
$maxrecords = (int) ($avail_mem / 40000) * count($fields['data']); // depends on number of fields
|
|
if ($maxrecords < 0) {
|
|
// cope with memory_limit = -1
|
|
$maxrecords = $cant * count($fields['data']);
|
|
}
|
|
$canto = $cant * count($fields['data']);
|
|
$offset = 0;
|
|
$lastItem = -1;
|
|
$count = 0;
|
|
$icount = 0;
|
|
$field_values = [];
|
|
|
|
// write out rows
|
|
for ($offset = 0; $offset < $canto; $offset = $offset + $maxrecords) {
|
|
$field_values = $this->fetchAll($query_fields, $bindvars, $maxrecords, $offset);
|
|
$mem = memory_get_usage(true);
|
|
|
|
foreach ($field_values as $res) {
|
|
if ($lastItem != $res['itemId']) {
|
|
$lastItem = $res['itemId'];
|
|
echo "\n" . $items[$lastItem]['itemId'] . ',' . $items[$lastItem]['status'] . ',' . $items[$lastItem]['created'] . ',' . $items[$lastItem]['lastModif'] . ','; // also these fields weren't traditionally escaped
|
|
$count++;
|
|
$icount++;
|
|
if ($icount > $maxrecords_items && $maxrecords_items > 0) {
|
|
$offset_items += $maxrecords_items;
|
|
$items = $this->get_dump_items_array($query_items, $bindvars, $maxrecords_items, $offset_items);
|
|
$icount = 0;
|
|
}
|
|
}
|
|
echo '"' . str_replace(['"', "\r\n", "\n"], ['\\"', '%%%', '%%%'], $res['value']) . '",';
|
|
}
|
|
ob_flush();
|
|
flush();
|
|
//if ($offset == 0) { $maxrecords = 1000 * count($fields['data']); }
|
|
}
|
|
echo "\n";
|
|
ob_end_flush();
|
|
}
|
|
|
|
public function get_dump_items_array($query, $bindvars, $maxrecords, $offset)
|
|
{
|
|
$items_array = $this->fetchAll($query, $bindvars, $maxrecords, $offset);
|
|
$items = [];
|
|
foreach ($items_array as $item) {
|
|
$items[$item['itemId']] = $item;
|
|
}
|
|
unset($items_array);
|
|
return $items;
|
|
}
|
|
|
|
public function write_export_header($encoding = null, $trackerId = null)
|
|
{
|
|
if (! $encoding) {
|
|
$encoding = $_REQUEST['encoding'];
|
|
}
|
|
if (! $trackerId) {
|
|
$trackerId = $_REQUEST['trackerId'];
|
|
}
|
|
if (! empty($_REQUEST['file'])) {
|
|
if (preg_match('/.csv$/', $_REQUEST['file'])) {
|
|
$file = $_REQUEST['file'];
|
|
} else {
|
|
$file = $_REQUEST['file'] . '.csv';
|
|
}
|
|
} else {
|
|
$file = tra('tracker') . '_' . $trackerId . '.csv';
|
|
}
|
|
header("Content-type: text/comma-separated-values; charset:" . $encoding);
|
|
header("Content-Disposition: attachment; filename=$file");
|
|
header("Expires: 0");
|
|
header("Cache-Control: must-revalidate, post-check=0,pre-check=0");
|
|
header("Pragma: public");
|
|
}
|
|
|
|
// check the validity of each field values of a tracker item
|
|
// and the presence of mandatory fields
|
|
public function check_field_values($ins_fields, $categorized_fields = '', $trackerId = '', $itemId = '')
|
|
{
|
|
global $prefs;
|
|
$mandatory_fields = [];
|
|
$erroneous_values = [];
|
|
if (isset($ins_fields) && isset($ins_fields['data'])) {
|
|
$fields = []; // make a copy of $ins_fields['data'] where the field id is the array key
|
|
foreach ($ins_fields['data'] as $f) {
|
|
$fields[$f['fieldId']] = $f;
|
|
}
|
|
|
|
foreach ($ins_fields['data'] as $f) {
|
|
if ($f['type'] == 'b' && ! empty($f['value'])) {
|
|
if (is_numeric($f['value'])) {
|
|
$f['name'] = $f['name'] . ' Currency';
|
|
$mandatory_fields[] = $f;
|
|
}
|
|
}
|
|
if ($f['type'] == 'f' && $f['isMandatory'] != 'y' && empty($f['value'])) {
|
|
$ins_id = 'ins_' . $f['fieldId'];
|
|
if (
|
|
! empty($_REQUEST[$ins_id . 'Month']) || ! empty($_REQUEST[$ins_id . 'Day']) || ! empty($_REQUEST[$ins_id . 'Year']) ||
|
|
! empty($_REQUEST[$ins_id . 'Hour']) || ! empty($_REQUEST[$ins_id . 'Minute'])
|
|
) {
|
|
$erroneous_values[] = $f;
|
|
}
|
|
}
|
|
if ($f['type'] != 'q' and isset($f['isMandatory']) && $f['isMandatory'] == 'y') {
|
|
if (($f['type'] == 'e' || in_array($f['fieldId'], $categorized_fields)) && empty($f['value'])) { // category: value is now categ id's
|
|
$mandatory_fields[] = $f;
|
|
} elseif (in_array($f['type'], ['a', 't']) && ($this->is_multilingual($f['fieldId']) == 'y')) {
|
|
if (! isset($multi_languages)) {
|
|
$multi_languages = $prefs['available_languages'];
|
|
}
|
|
//Check recipient
|
|
if (isset($f['lingualvalue'])) {
|
|
$isAvalueDefined = false; // check if a mandatory field contain at least one language value
|
|
|
|
foreach ($f['lingualvalue'] as $val) {
|
|
foreach ($multi_languages as $num => $tmplang) {
|
|
if (isset($val['lang']) || isset($val['value']) || (($val['lang'] == $tmplang) && strlen($val['value']) != 0)) {
|
|
$isAvalueDefined = true;
|
|
}
|
|
}
|
|
}
|
|
// mandatory field does not contain any value.
|
|
if (! $isAvalueDefined) {
|
|
foreach ($f['lingualvalue'] as $val) {
|
|
foreach ($multi_languages as $num => $tmplang) {
|
|
if (! isset($val['lang']) || ! isset($val['value']) || (($val['lang'] == $tmplang) && strlen($val['value']) == 0)) {
|
|
$mandatory_fields[] = $f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} elseif (is_array($f['value'])) {
|
|
$isAvalueDefined = false; // check if a mandatory field contain at least one language value
|
|
|
|
foreach ($f['value'] as $key => $val) {
|
|
foreach ($multi_languages as $num => $tmplang) {
|
|
if ($key == $tmplang && $val) {
|
|
$isAvalueDefined = true;
|
|
}
|
|
}
|
|
}
|
|
// mandatory field does not contain any value
|
|
if (! $isAvalueDefined) {
|
|
foreach ($f['value'] as $key => $val) {
|
|
foreach ($multi_languages as $num => $tmplang) {
|
|
if ($key == $tmplang && empty($val)) {
|
|
$mandatory_fields[] = $f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
$mandatory_fields[] = $f;
|
|
}
|
|
} elseif (in_array($f['type'], ['u', 'g']) && $f['options_array'][0] == 1) {
|
|
;
|
|
} elseif ($f['type'] == 'c' && (empty($f['value']) || $f['value'] == 'n')) {
|
|
$mandatory_fields[] = $f;
|
|
} elseif ($f['type'] == 'A' && ! empty($itemId) && empty($f['value'])) {
|
|
$val = $this->get_item_value($trackerId, $itemId, $f['fieldId']);
|
|
if (empty($val)) {
|
|
$mandatory_fields[] = $f;
|
|
}
|
|
} elseif ($f['type'] == 'r' && empty(array_filter((array) $f['value']))) { // ItemLink - '0' counts as empty
|
|
$mandatory_fields[] = $f;
|
|
} elseif (! isset($f['value']) || ! is_array($f['value']) && strlen($f['value']) == 0 || is_array($f['value']) && empty($f['value'])) {
|
|
$mandatory_fields[] = $f;
|
|
}
|
|
}
|
|
if (! empty($f['value'])) {
|
|
switch ($f['type']) {
|
|
// IP address (only for IPv4)
|
|
case 'I':
|
|
$validator = new Laminas\Validator\Ip();
|
|
if (! $validator->isValid($f['value'])) {
|
|
$erroneous_values[] = $f;
|
|
}
|
|
break;
|
|
// numeric
|
|
case 'n':
|
|
if (! is_numeric($f['value'])) {
|
|
$f['error'] = tra('Field is not numeric');
|
|
$erroneous_values[] = $f;
|
|
}
|
|
break;
|
|
|
|
// email
|
|
case 'm':
|
|
if (! validate_email($f['value'], $prefs['validateEmail'])) {
|
|
$erroneous_values[] = $f;
|
|
}
|
|
break;
|
|
|
|
// password
|
|
case 'p':
|
|
if ($f['options_array'][0] == 'password') {
|
|
$userlib = TikiLib::lib('user');
|
|
if (($e = $userlib->check_password_policy($f['value'])) != '') {
|
|
$erroneous_values[] = $f;
|
|
}
|
|
} elseif ($f['options_array'][0] == 'email') {
|
|
if (! validate_email($f['value'])) {
|
|
$erroneous_values[] = $f;
|
|
}
|
|
}
|
|
break;
|
|
case 'a':
|
|
if (isset($f['options_array'][5]) && $f['options_array'][5] > 0) {
|
|
if (count(preg_split('/\s+/', trim($f['value']))) > $f['options_array'][5]) {
|
|
$erroneous_values[] = $f;
|
|
}
|
|
}
|
|
if (isset($f['options_array'][6]) && $f['options_array'][6] == 'y') {
|
|
if (in_array($f['value'], $this->list_tracker_field_values($trackerId, $f['fieldId'], 'opc', 'y', '', $itemId))) {
|
|
$erroneous_values[] = $f;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
$handler = $this->get_field_handler($f, $this->get_item_info($itemId));
|
|
if (method_exists($handler, 'isValid')) {
|
|
$validationResponse = $handler->isValid($fields);
|
|
if ($validationResponse !== true) {
|
|
if (! empty($f['validationMessage'])) {
|
|
$f['errorMsg'] = $f['validationMessage'];
|
|
} elseif (! empty($validationResponse)) {
|
|
$f['errorMsg'] = $validationResponse;
|
|
} else {
|
|
$f['errorMsg'] = tr('Unknown error');
|
|
}
|
|
$erroneous_values[] = $f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$res = [];
|
|
$res['err_mandatory'] = $mandatory_fields;
|
|
$res['err_value'] = $erroneous_values;
|
|
return $res;
|
|
}
|
|
|
|
public function remove_tracker_item($itemId, $bulk_mode = false)
|
|
{
|
|
global $user, $prefs;
|
|
$res = $this->items()->fetchFullRow(['itemId' => (int) $itemId]);
|
|
$trackerId = $res['trackerId'];
|
|
$status = $res['status'];
|
|
|
|
// keep copy of item for putting info into final event
|
|
$itemInfo = $this->get_tracker_item($itemId);
|
|
|
|
// ---- save image list before sql query ---------------------------------
|
|
$fieldList = $this->list_tracker_fields($trackerId, 0, -1, 'name_asc', '');
|
|
|
|
$statusTypes = $this->status_types();
|
|
$statusString = isset($statusTypes[$status]['label']) ? $statusTypes[$status]['label'] : '';
|
|
|
|
$imgList = [];
|
|
foreach ($fieldList['data'] as $f) {
|
|
$data_field[] = ['name' => tr($f['name']),'value' => $this->get_item_value($trackerId, $itemId, $f['fieldId'])];
|
|
if ($f['type'] == 'i') {
|
|
$imgList[] = $this->get_item_value($trackerId, $itemId, $f['fieldId']);
|
|
}
|
|
}
|
|
|
|
if (! $bulk_mode) {
|
|
$watchers = $this->get_notification_emails($trackerId, $itemId, $this->get_tracker_options($trackerId));
|
|
|
|
if (count($watchers) > 0) {
|
|
$smarty = TikiLib::lib('smarty');
|
|
$trackerName = $this->trackers()->fetchOne('name', ['trackerId' => (int) $trackerId]);
|
|
$smarty->assign('mail_date', $this->now);
|
|
$smarty->assign('mail_user', $user);
|
|
$smarty->assign('mail_action', 'deleted');
|
|
$smarty->assign('mail_itemId', $itemId);
|
|
$smarty->assign('mail_item_desc', $itemId);
|
|
$smarty->assign('mail_fields', $data_field);
|
|
$smarty->assign('mail_field_status', $statusString);
|
|
$smarty->assign('mail_trackerId', $trackerId);
|
|
$smarty->assign('mail_trackerName', $trackerName);
|
|
$smarty->assign('mail_data', '');
|
|
$foo = parse_url($_SERVER["REQUEST_URI"]);
|
|
$machine = $this->httpPrefix(true) . $foo["path"];
|
|
$smarty->assign('mail_machine', $machine);
|
|
$parts = explode('/', $foo['path']);
|
|
if (count($parts) > 1) {
|
|
unset($parts[count($parts) - 1]);
|
|
}
|
|
$smarty->assign('mail_machine_raw', $this->httpPrefix(true) . implode('/', $parts));
|
|
if (! isset($_SERVER["SERVER_NAME"])) {
|
|
$_SERVER["SERVER_NAME"] = $_SERVER["HTTP_HOST"];
|
|
}
|
|
include_once('lib/webmail/tikimaillib.php');
|
|
$smarty->assign('server_name', $_SERVER['SERVER_NAME']);
|
|
foreach ($watchers as $w) {
|
|
$mail = new TikiMail($w['user']);
|
|
|
|
if (! isset($w['template'])) {
|
|
$w['template'] = '';
|
|
}
|
|
$content = $this->parse_notification_template($w['template']);
|
|
|
|
$mail->setSubject($smarty->fetchLang($w['language'], $content['subject']));
|
|
$mail_data = $smarty->fetchLang($w['language'], $content['template']);
|
|
if (isset($w['templateFormat']) && $w['templateFormat'] == 'html') {
|
|
$mail->setHtml($mail_data, str_replace(' ', ' ', strip_tags($mail_data)));
|
|
} else {
|
|
$mail->setText(str_replace(' ', ' ', strip_tags($mail_data)));
|
|
}
|
|
$mail->send([$w['email']]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove the object and uncategorize etc while the item still exists
|
|
$this->remove_object("trackeritem", $itemId);
|
|
$itemFields = $this->itemFields()->fetchAll(['fieldId'], ['itemId' => $itemId]);
|
|
foreach ($itemFields as $itemField) {
|
|
$this->remove_object("trackeritemfield", sprintf("%d:%d", (int)$itemId, (int)$itemField['fieldId']));
|
|
}
|
|
|
|
$this->trackers()->update(
|
|
['lastModif' => $this->now, 'items' => $this->trackers()->decrement(1)],
|
|
['trackerId' => (int) $trackerId]
|
|
);
|
|
|
|
$this->itemFields()->deleteMultiple(['itemId' => (int) $itemId]);
|
|
$this->comments()->deleteMultiple(['object' => (int) $itemId, 'objectType' => 'trackeritem']);
|
|
$this->attachments()->deleteMultiple(['itemId' => (int) $itemId]);
|
|
$this->groupWatches()->deleteMultiple(['object' => (int) $itemId, 'event' => 'tracker_item_modified']);
|
|
$this->userWatches()->deleteMultiple(['object' => (int) $itemId, 'event' => 'tracker_item_modified']);
|
|
// Now delete the actual tracker item
|
|
$result = $this->items()->delete(['itemId' => (int) $itemId]);
|
|
|
|
$this->remove_stale_comment_watches();
|
|
|
|
// ---- delete image from disk -------------------------------------
|
|
foreach ($imgList as $img) {
|
|
if (file_exists($img)) {
|
|
unlink($img);
|
|
}
|
|
}
|
|
|
|
// remove votes/ratings
|
|
$userVotings = $this->table('tiki_user_votings');
|
|
$userVotings->delete(['id' => $userVotings->like("tracker.$trackerId.$itemId.%")]);
|
|
|
|
$cachelib = TikiLib::lib('cache');
|
|
$cachelib->invalidate('trackerItemLabel' . $itemId);
|
|
foreach ($fieldList['data'] as $f) {
|
|
$this->invalidate_field_cache($f['fieldId']);
|
|
}
|
|
|
|
$options = $this->get_tracker_options($trackerId);
|
|
if (isset($option) && isset($option['autoCreateCategories']) && $option['autoCreateCategories'] == 'y') {
|
|
$categlib = TikiLib::lib('categ');
|
|
$currentCategId = $categlib->get_category_id("Tracker Item $itemId");
|
|
$categlib->remove_category($currentCategId);
|
|
}
|
|
|
|
if (isset($options['autoCreateGroup']) && $options['autoCreateGroup'] == 'y') {
|
|
$userlib = TikiLib::lib('user');
|
|
$groupName = $this->groupName($options, $itemId);
|
|
$userlib->remove_group($groupName);
|
|
}
|
|
$this->remove_item_log($itemId);
|
|
$todolib = TikiLib::lib('todo');
|
|
$todolib->delObjectTodo('trackeritem', $itemId);
|
|
|
|
$multilinguallib = TikiLib::lib('multilingual');
|
|
$multilinguallib->detachTranslation('trackeritem', $itemId);
|
|
|
|
$tx = TikiDb::get()->begin();
|
|
|
|
$child = $this->findLinkedItems(
|
|
$itemId,
|
|
function ($field, $handler) use ($trackerId) {
|
|
return $handler->cascadeDelete($trackerId);
|
|
}
|
|
);
|
|
|
|
foreach ($child as $i) {
|
|
$this->remove_tracker_item($i);
|
|
}
|
|
|
|
$tx->commit();
|
|
|
|
TikiLib::lib('logs')->add_action('Removed', $itemId, 'trackeritem');
|
|
|
|
TikiLib::events()->trigger(
|
|
'tiki.trackeritem.delete',
|
|
[
|
|
'type' => 'trackeritem',
|
|
'object' => $itemId,
|
|
'trackerId' => $trackerId,
|
|
'user' => $GLOBALS['user'],
|
|
'values' => $itemInfo,
|
|
]
|
|
);
|
|
|
|
return $result;
|
|
}
|
|
|
|
public function findUncascadedDeletes($itemId, $trackerId)
|
|
{
|
|
$fields = [];
|
|
$child = $this->findLinkedItems(
|
|
$itemId,
|
|
function ($field, $handler) use ($trackerId, &$fields) {
|
|
if (! $handler->cascadeDelete($trackerId)) {
|
|
$fields[] = $field['fieldId'];
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
);
|
|
|
|
return ['itemIds' => $child, 'fieldIds' => array_unique($fields)];
|
|
}
|
|
|
|
public function replaceItemReferences($replacement, $itemIds, $fieldIds)
|
|
{
|
|
$table = $this->itemFields();
|
|
$table->update(['value' => $replacement], [
|
|
'itemId' => $table->in($itemIds),
|
|
'fieldId' => $table->in($fieldIds),
|
|
]);
|
|
|
|
$events = TikiLib::events();
|
|
foreach ($itemIds as $itemId) {
|
|
$events->trigger('tiki.trackeritem.update', [
|
|
'type' => 'trackeritem',
|
|
'object' => $itemId,
|
|
'user' => $GLOBALS['user'],
|
|
]);
|
|
}
|
|
}
|
|
|
|
// filter examples: array('fieldId'=>array(1,2,3)) to look for a list of fields
|
|
// array('or'=>array('isSearchable'=>'y', 'isTplVisible'=>'y')) for fields that are visible ou searchable
|
|
// array('not'=>array('isHidden'=>'y')) for fields that are not hidden
|
|
public function parse_filter($filter, &$mids, &$bindvars)
|
|
{
|
|
$tikilib = TikiLib::lib('tiki');
|
|
foreach ($filter as $type => $val) {
|
|
if ($type == 'or') {
|
|
$midors = [];
|
|
$this->parse_filter($val, $midors, $bindvars);
|
|
$mids[] = '(' . implode(' or ', $midors) . ')';
|
|
} elseif ($type == 'not') {
|
|
$midors = [];
|
|
$this->parse_filter($val, $midors, $bindvars);
|
|
$mids[] = '!(' . implode(' and ', $midors) . ')';
|
|
} elseif ($type == 'createdBefore') {
|
|
$mids[] = 'tti.`created` < ?';
|
|
$bindvars[] = $val;
|
|
} elseif ($type == 'createdAfter') {
|
|
$mids[] = 'tti.`created` > ?';
|
|
$bindvars[] = $val;
|
|
} elseif ($type == 'lastModifBefore') {
|
|
$mids[] = 'tti.`lastModif` < ?';
|
|
$bindvars[] = $val;
|
|
} elseif ($type == 'lastModifAfter') {
|
|
$mids[] = 'tti.`lastModif` > ?';
|
|
$bindvars[] = $val;
|
|
} elseif ($type == 'notItemId') {
|
|
$mids[] = 'tti.`itemId` NOT IN(' . implode(",", array_fill(0, count($val), '?')) . ')';
|
|
$bindvars = $val;
|
|
} elseif (is_array($val)) {
|
|
if (count($val) > 0) {
|
|
if (! strstr($type, '`')) {
|
|
$type = "`$type`";
|
|
}
|
|
$mids[] = "$type in (" . implode(",", array_fill(0, count($val), '?')) . ')';
|
|
$bindvars = array_merge($bindvars, $val);
|
|
}
|
|
} else {
|
|
if (! strstr($type, '`')) {
|
|
$type = "`$type`";
|
|
}
|
|
$mids[] = "$type=?";
|
|
$bindvars[] = $val;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Lists all the fields for an existing tracker
|
|
public function list_tracker_fields($trackerId, $offset = 0, $maxRecords = -1, $sort_mode = 'position_asc', $find = '', $tra_name = true, $filter = '', $fields = '')
|
|
{
|
|
global $prefs;
|
|
$smarty = TikiLib::lib('smarty');
|
|
$fieldsTable = $this->fields();
|
|
|
|
if (! empty($trackerId)) {
|
|
$conditions = ['trackerId' => (int) $trackerId];
|
|
} else {
|
|
return [];
|
|
}
|
|
if ($find) {
|
|
$conditions['name'] = $fieldsTable->like("%$find%");
|
|
}
|
|
if (! empty($fields)) {
|
|
$conditions['fieldId'] = $fieldsTable->in($fields);
|
|
}
|
|
|
|
if (! empty($filter)) {
|
|
$mids = [];
|
|
$bindvars = [];
|
|
$this->parse_filter($filter, $mids, $bindvars);
|
|
$conditions['filter'] = $fieldsTable->expr(implode(' AND ', $mids), $bindvars);
|
|
}
|
|
|
|
$result = $fieldsTable->fetchAll($fieldsTable->all(), $conditions, $maxRecords, $offset, $fieldsTable->sortMode($sort_mode));
|
|
$cant = $fieldsTable->fetchCount($conditions);
|
|
|
|
$factory = new Tracker_Field_Factory();
|
|
foreach ($result as & $res) {
|
|
$typeInfo = $factory->getFieldInfo($res['type']);
|
|
$options = Tracker_Options::fromSerialized($res['options'], $typeInfo);
|
|
$res['options_array'] = $options->buildOptionsArray();
|
|
$res['options_map'] = $options->getAllParameters();
|
|
$res['itemChoices'] = ( $res['itemChoices'] != '' ) ? unserialize($res['itemChoices']) : [];
|
|
$res['visibleBy'] = ($res['visibleBy'] != '') ? unserialize($res['visibleBy']) : [];
|
|
$res['editableBy'] = ($res['editableBy'] != '') ? unserialize($res['editableBy']) : [];
|
|
if ($tra_name && $prefs['feature_multilingual'] == 'y' && $prefs['language'] != 'en') {
|
|
$res['name'] = tra($res['name']);
|
|
}
|
|
if ($res['type'] == 'p' && $res['options_array'][0] == 'language') {
|
|
$langLib = TikiLib::lib('language');
|
|
$smarty->assign('languages', $langLib->list_languages());
|
|
}
|
|
$ret[] = $res;
|
|
}
|
|
|
|
return [
|
|
'data' => $result,
|
|
'cant' => $cant,
|
|
];
|
|
}
|
|
|
|
// Inserts or updates a tracker
|
|
public function replace_tracker($trackerId, $name, $description, $options, $descriptionIsParsed)
|
|
{
|
|
$trackers = $this->trackers();
|
|
|
|
if ($descriptionIsParsed == 'y') {
|
|
$parserlib = TikiLib::lib('parser');
|
|
$description = $parserlib->process_save_plugins(
|
|
$description,
|
|
[
|
|
'type' => 'tracker',
|
|
'itemId' => $trackerId,
|
|
]
|
|
);
|
|
}
|
|
|
|
$data = [
|
|
'name' => $name,
|
|
'description' => $description,
|
|
'descriptionIsParsed' => $descriptionIsParsed,
|
|
'lastModif' => $this->now,
|
|
];
|
|
|
|
$logOption = 'Updated';
|
|
if ($trackerId) {
|
|
$finalEvent = 'tiki.tracker.update';
|
|
$conditions = ['trackerId' => (int) $trackerId];
|
|
if ($trackers->fetchCount($conditions)) {
|
|
$trackers->update($data, $conditions);
|
|
} else {
|
|
$data['trackerId'] = (int) $trackerId;
|
|
$data['items'] = 0;
|
|
$data['created'] = $this->now;
|
|
$trackers->insert($data);
|
|
$logOption = 'Created';
|
|
}
|
|
} else {
|
|
$finalEvent = 'tiki.tracker.create';
|
|
$data['created'] = $this->now;
|
|
$trackerId = $trackers->insert($data);
|
|
}
|
|
|
|
$wikiParsed = $descriptionIsParsed == 'y';
|
|
$wikilib = TikiLib::lib('wiki');
|
|
$wikilib->update_wikicontent_relations($description, 'tracker', (int)$trackerId, $wikiParsed);
|
|
$wikilib->update_wikicontent_links($description, 'tracker', (int)$trackerId, $wikiParsed);
|
|
|
|
$optionTable = $this->options();
|
|
$optionTable->deleteMultiple(['trackerId' => (int) $trackerId]);
|
|
|
|
foreach ($options as $kopt => $opt) {
|
|
$this->replace_tracker_option((int) $trackerId, $kopt, $opt);
|
|
}
|
|
|
|
$definition = Tracker_Definition::get($trackerId);
|
|
$ratingId = $definition->getRateField();
|
|
|
|
if (isset($options['useRatings']) && $options['useRatings'] == 'y') {
|
|
if (! $ratingId) {
|
|
$ratingId = 0;
|
|
}
|
|
|
|
$ratingoptions = isset($options['ratingOptions']) ? $options['ratingOptions'] : '';
|
|
$showratings = isset($options['showRatings']) ? $options['showRatings'] : 'n';
|
|
$this->replace_tracker_field($trackerId, $ratingId, 'Rating', 's', '-', '-', $showratings, 'y', 'n', '-', 0, $ratingoptions);
|
|
}
|
|
$this->clear_tracker_cache($trackerId);
|
|
$this->update_tracker_summary(['trackerId' => $trackerId]);
|
|
|
|
if ($logOption) {
|
|
$logslib = TikiLib::lib('logs');
|
|
$logslib->add_action(
|
|
$logOption,
|
|
$trackerId,
|
|
'tracker',
|
|
[
|
|
'name' => $data['name'],
|
|
]
|
|
);
|
|
}
|
|
|
|
TikiLib::events()->trigger($finalEvent, [
|
|
'type' => 'tracker',
|
|
'object' => $trackerId,
|
|
'user' => $GLOBALS['user'],
|
|
]);
|
|
|
|
return $trackerId;
|
|
}
|
|
|
|
public function replace_tracker_option($trackerId, $name, $value)
|
|
{
|
|
$optionTable = $this->options();
|
|
$optionTable->insertOrUpdate(['value' => $value], ['trackerId' => $trackerId, 'name' => $name]);
|
|
}
|
|
|
|
public function clear_tracker_cache($trackerId)
|
|
{
|
|
global $prefs;
|
|
|
|
$cachelib = TikiLib::lib('cache');
|
|
|
|
foreach ($this->get_all_tracker_items($trackerId) as $itemId) {
|
|
$cachelib->invalidate('trackerItemLabel' . $itemId);
|
|
}
|
|
if (in_array('trackerrender', $prefs['unified_cached_formatters'])) {
|
|
$cachelib->empty_type_cache('search_valueformatter');
|
|
}
|
|
}
|
|
|
|
|
|
public function replace_tracker_field($trackerId, $fieldId, $name, $type, $isMain, $isSearchable, $isTblVisible, $isPublic, $isHidden, $isMandatory, $position, $options, $description = '', $isMultilingual = '', $itemChoices = null, $errorMsg = '', $visibleBy = null, $editableBy = null, $descriptionIsParsed = 'n', $validation = '', $validationParam = '', $validationMessage = '', $permName = null, $rules = null, $encryptionKeyId = null)
|
|
{
|
|
// Serialize choosed items array (items of the tracker field to be displayed in the list proposed to the user)
|
|
if (is_array($itemChoices) && count($itemChoices) > 0 && ! empty($itemChoices[0])) {
|
|
$itemChoices = serialize($itemChoices);
|
|
} else {
|
|
$itemChoices = '';
|
|
}
|
|
if (is_array($visibleBy) && count($visibleBy) > 0 && ! empty($visibleBy[0])) {
|
|
$visibleBy = serialize($visibleBy);
|
|
} else {
|
|
$visibleBy = '';
|
|
}
|
|
if (is_array($editableBy) && count($editableBy) > 0 && ! empty($editableBy[0])) {
|
|
$editableBy = serialize($editableBy);
|
|
} else {
|
|
$editableBy = '';
|
|
}
|
|
if ($descriptionIsParsed == 'y') {
|
|
$parserlib = TikiLib::lib('parser');
|
|
$description = $parserlib->process_save_plugins(
|
|
$description,
|
|
[
|
|
'type' => 'trackerfield',
|
|
'itemId' => $fieldId,
|
|
]
|
|
);
|
|
}
|
|
|
|
$fields = $this->fields();
|
|
|
|
$data = [
|
|
'name' => $name,
|
|
'permName' => empty($permName) ? null : $permName,
|
|
'type' => $type,
|
|
'isMain' => $isMain,
|
|
'isSearchable' => $isSearchable,
|
|
'isTblVisible' => $isTblVisible,
|
|
'isPublic' => $isPublic,
|
|
'isHidden' => $isHidden,
|
|
'isMandatory' => $isMandatory,
|
|
'position' => (int) $position,
|
|
'options' => $options,
|
|
'isMultilingual' => $isMultilingual,
|
|
'description' => $description,
|
|
'itemChoices' => $itemChoices,
|
|
'errorMsg' => $errorMsg,
|
|
'visibleBy' => $visibleBy,
|
|
'editableBy' => $editableBy,
|
|
'descriptionIsParsed' => $descriptionIsParsed,
|
|
'validation' => $validation,
|
|
'validationParam' => $validationParam,
|
|
'validationMessage' => $validationMessage,
|
|
'rules' => $rules,
|
|
'encryptionKeyId' => $encryptionKeyId,
|
|
];
|
|
|
|
$logOption = null;
|
|
|
|
if ($fieldId) {
|
|
// -------------------------------------
|
|
// remove images when needed
|
|
$old_field = $this->get_tracker_field($fieldId);
|
|
if (! empty($old_field['fieldId'])) {
|
|
if ($old_field['type'] == 'i' && $type != 'i') {
|
|
$this->remove_field_images($fieldId);
|
|
}
|
|
|
|
$fields->update($data, ['fieldId' => (int) $fieldId]);
|
|
$logOption = 'modify_field';
|
|
|
|
$data['trackerId'] = (int) $old_field['trackerId'];
|
|
} else {
|
|
$data['trackerId'] = (int) $trackerId;
|
|
$data['fieldId'] = (int) $fieldId;
|
|
$fields->insert($data);
|
|
$logOption = 'add_field';
|
|
}
|
|
} else {
|
|
$data['trackerId'] = (int) $trackerId;
|
|
$fieldId = $fields->insert($data);
|
|
$logOption = 'add_field';
|
|
|
|
if (! $permName) {
|
|
// Apply a default value to perm name when not specified
|
|
$fields->update(['permName' => 'f_' . $fieldId], ['fieldId' => $fieldId]);
|
|
}
|
|
|
|
$itemFields = $this->itemFields();
|
|
foreach ($this->get_all_tracker_items($trackerId) as $itemId) {
|
|
$itemFields->deleteMultiple(['itemId' => (int) $itemId, 'fieldId' => $fieldId]);
|
|
$itemFields->insert(['itemId' => (int) $itemId, 'fieldId' => (int) $fieldId, 'value' => '']);
|
|
}
|
|
}
|
|
|
|
$wikiParsed = $descriptionIsParsed == 'y';
|
|
$wikilib = TikiLib::lib('wiki');
|
|
$wikilib->update_wikicontent_relations($description, 'trackerfield', (int)$fieldId, $wikiParsed);
|
|
$wikilib->update_wikicontent_links($description, 'trackerfield', (int)$fieldId, $wikiParsed);
|
|
|
|
if ($logOption) {
|
|
$logslib = TikiLib::lib('logs');
|
|
$logslib->add_action(
|
|
'Updated',
|
|
$data['trackerId'],
|
|
'tracker',
|
|
[
|
|
'operation' => $logOption,
|
|
'fieldId' => $fieldId,
|
|
'name' => $data['name'],
|
|
]
|
|
);
|
|
|
|
TikiLib::events()->trigger(
|
|
$logOption == 'add_field' ? 'tiki.trackerfield.create' : 'tiki.trackerfield.update',
|
|
['type' => 'trackerfield', 'object' => $fieldId]
|
|
);
|
|
}
|
|
|
|
$this->clear_tracker_cache($trackerId);
|
|
return $fieldId;
|
|
}
|
|
|
|
public function replace_rating($trackerId, $itemId, $fieldId, $user, $new_rate)
|
|
{
|
|
global $tiki_p_tracker_vote_ratings, $tiki_p_tracker_revote_ratings;
|
|
$itemFields = $this->itemFields();
|
|
|
|
if ($new_rate === null) {
|
|
$new_rate = 0;
|
|
}
|
|
|
|
if ($tiki_p_tracker_vote_ratings != 'y') {
|
|
return;
|
|
}
|
|
$key = "tracker.$trackerId.$itemId";
|
|
$olrate = $this->get_user_vote($key, $user) ?: 0;
|
|
$allow_revote = $tiki_p_tracker_revote_ratings == 'y';
|
|
$count = $itemFields->fetchCount(['itemId' => (int) $itemId, 'fieldId' => (int) $fieldId]);
|
|
$tikilib = TikiLib::lib('tiki');
|
|
if (! $tikilib->register_user_vote($user, $key, $new_rate, [], $allow_revote)) {
|
|
return;
|
|
}
|
|
|
|
if (! $count) {
|
|
$itemFields->insert(['value' => (int) $new_rate, 'itemId' => (int) $itemId, 'fieldId' => (int) $fieldId]);
|
|
$outValue = $new_rate;
|
|
} else {
|
|
$conditions = [
|
|
'itemId' => (int) $itemId,
|
|
'fieldId' => (int) $fieldId,
|
|
];
|
|
|
|
$val = $itemFields->fetchOne('value', $conditions);
|
|
$outValue = $val - $olrate + $new_rate;
|
|
|
|
$itemFields->update(['value' => $outValue], $conditions);
|
|
}
|
|
|
|
TikiLib::events()->trigger('tiki.trackeritem.rating', [
|
|
'type' => 'trackeritem',
|
|
'object' => (int) $itemId,
|
|
'trackerId' => (int) $trackerId,
|
|
'fieldId' => (int) $fieldId,
|
|
'user' => $user,
|
|
'rating' => $new_rate, // User's selected value, not the stored one
|
|
]);
|
|
|
|
return $outValue;
|
|
}
|
|
|
|
public function replace_star($userValue, $trackerId, $itemId, &$field, $user, $updateField = true)
|
|
{
|
|
global $tiki_p_tracker_vote_ratings, $tiki_p_tracker_revote_ratings, $prefs;
|
|
if ($field['type'] != '*' && $field['type'] != 'STARS') {
|
|
return;
|
|
}
|
|
if ($userValue != 'NULL' && isset($field['rating_options']) && ! in_array($userValue, $field['rating_options'])) {
|
|
return;
|
|
}
|
|
if ($userValue != 'NULL' && ! isset($field['rating_options']) && ! in_array($userValue, $field['options_array'])) {
|
|
// backward compatibility with trackerlist rating which does not have rating options
|
|
return;
|
|
}
|
|
if ($tiki_p_tracker_vote_ratings != 'y') {
|
|
return;
|
|
}
|
|
$key = "tracker.$trackerId.$itemId." . $field['fieldId'];
|
|
|
|
$allow_revote = $tiki_p_tracker_revote_ratings == 'y';
|
|
$tikilib = TikiLib::lib('tiki');
|
|
$result = $tikilib->register_user_vote($user, $key, $userValue, [], $allow_revote);
|
|
|
|
$votings = $this->table('tiki_user_votings');
|
|
$data = $votings->fetchRow(['count' => $votings->count(), 'total' => $votings->sum('optionId')], ['id' => $key]);
|
|
$field['numvotes'] = $data['count'];
|
|
$field['my_rate'] = $userValue;
|
|
$field['voteavg'] = $field['value'] = $data['total'] / $field['numvotes'];
|
|
|
|
if ($result) {
|
|
TikiLib::events()->trigger('tiki.trackeritem.rating', [
|
|
'type' => 'trackeritem',
|
|
'object' => $itemId,
|
|
'trackerId' => $trackerId,
|
|
'fieldId' => $field['fieldId'],
|
|
'user' => $user,
|
|
'rating' => $userValue,
|
|
]);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
public function remove_tracker($trackerId)
|
|
{
|
|
$transaction = $this->begin();
|
|
|
|
// ---- delete image from disk -------------------------------------
|
|
$fieldList = $this->list_tracker_fields($trackerId, 0, -1, 'name_asc', '');
|
|
foreach ($fieldList['data'] as $f) {
|
|
if ($f['type'] == 'i') {
|
|
$this->remove_field_images($f['fieldId']);
|
|
}
|
|
}
|
|
|
|
$option = $this->get_tracker_options($trackerId);
|
|
if (isset($option) && isset($option['autoCreateCategories']) && $option['autoCreateCategories'] == 'y') {
|
|
$categlib = TikiLib::lib('categ');
|
|
$currentCategId = $categlib->get_category_id("Tracker $trackerId");
|
|
$categlib->remove_category($currentCategId);
|
|
}
|
|
|
|
foreach ($this->get_all_tracker_items($trackerId) as $itemId) {
|
|
$this->remove_tracker_item($itemId);
|
|
}
|
|
|
|
$fields = $this->fields()->fetchAll(['fieldId'], ['trackerId' => $trackerId]);
|
|
foreach ($fields as $field) {
|
|
$this->remove_object("trackerfield", $field['fieldId']);
|
|
}
|
|
|
|
$conditions = [
|
|
'trackerId' => (int) $trackerId,
|
|
];
|
|
|
|
$this->fields()->deleteMultiple($conditions);
|
|
$this->options()->deleteMultiple($conditions);
|
|
$this->trackers()->delete($conditions);
|
|
|
|
// remove votes/ratings
|
|
$userVotings = $this->table('tiki_user_votings');
|
|
$userVotings->delete(['id' => $userVotings->like("tracker.$trackerId.%")]);
|
|
|
|
$this->remove_object('tracker', $trackerId);
|
|
|
|
$logslib = TikiLib::lib('logs');
|
|
$logslib->add_action('Removed', $trackerId, 'tracker');
|
|
|
|
$this->clear_tracker_cache($trackerId);
|
|
|
|
TikiLib::events()->trigger('tiki.tracker.delete', [
|
|
'type' => 'tracker',
|
|
'object' => $trackerId,
|
|
'user' => $GLOBALS['user'],
|
|
]);
|
|
|
|
$transaction->commit();
|
|
|
|
return true;
|
|
}
|
|
|
|
public function remove_tracker_field($fieldId, $trackerId)
|
|
{
|
|
$cachelib = TikiLib::lib('cache');
|
|
$logslib = TikiLib::lib('logs');
|
|
|
|
// -------------------------------------
|
|
// remove images when needed
|
|
$field = $this->get_tracker_field($fieldId);
|
|
if ($field['type'] == 'i') {
|
|
$this->remove_field_images($fieldId);
|
|
}
|
|
|
|
$handler = $this->get_field_handler($field);
|
|
if ($handler && method_exists($handler, 'handleFieldRemove')) {
|
|
$handler->handleFieldRemove();
|
|
}
|
|
|
|
$conditions = [
|
|
'fieldId' => (int) $fieldId,
|
|
];
|
|
|
|
$this->fields()->delete($conditions);
|
|
$this->itemFields()->deleteMultiple($conditions);
|
|
|
|
$this->invalidate_field_cache($fieldId);
|
|
|
|
$this->clear_tracker_cache($trackerId);
|
|
|
|
$logslib = TikiLib::lib('logs');
|
|
$logslib->add_action(
|
|
'Updated',
|
|
$trackerId,
|
|
'tracker',
|
|
[
|
|
'operation' => 'remove_field',
|
|
'fieldId' => $fieldId,
|
|
]
|
|
);
|
|
$this->remove_object('trackerfield', $fieldId);
|
|
TikiLib::events()->trigger(
|
|
'tiki.trackerfield.delete',
|
|
['type' => 'trackerfield', 'object' => $fieldId]
|
|
);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* get_trackers_containing
|
|
*
|
|
* \brief Get tracker names containing ... (useful for auto-complete)
|
|
*
|
|
* @author luci
|
|
* @param mixed $name
|
|
* @access public
|
|
* @return
|
|
*/
|
|
public function get_trackers_containing($name)
|
|
{
|
|
if (empty($name)) {
|
|
return [];
|
|
}
|
|
//FIXME: perm filter ?
|
|
$result = $this->fetchAll(
|
|
'SELECT `name` FROM `tiki_trackers` WHERE `name` LIKE ?',
|
|
[$name . '%'],
|
|
10
|
|
);
|
|
$names = [];
|
|
foreach ($result as $row) {
|
|
$names[] = $row['name'];
|
|
}
|
|
return $names;
|
|
}
|
|
|
|
/**
|
|
* Returns the trackerId of the tracker possessing the item ($itemId)
|
|
*/
|
|
public function get_tracker_for_item($itemId)
|
|
{
|
|
return $this->items()->fetchOne('trackerId', ['itemId' => (int) $itemId]);
|
|
}
|
|
|
|
public function get_tracker_options($trackerId)
|
|
{
|
|
return $this->options()->fetchMap('name', 'value', ['trackerId' => (int) $trackerId]);
|
|
}
|
|
|
|
public function get_trackers_options($trackerId, $option = '', $find = '', $not = '')
|
|
{
|
|
$options = $this->options();
|
|
$conditions = [];
|
|
|
|
if (! empty($trackerId)) {
|
|
$conditions['trackerId'] = (int) $trackerId;
|
|
}
|
|
|
|
if (! empty($option)) {
|
|
$conditions['name'] = $option;
|
|
}
|
|
|
|
if ($not == 'null' || $not == 'empty') {
|
|
$conditions['value'] = $options->not('');
|
|
}
|
|
|
|
if (! empty($find)) {
|
|
$conditions['value'] = $options->like("%$find%");
|
|
}
|
|
|
|
return $options->fetchAll($options->all(), $conditions);
|
|
}
|
|
|
|
public function get_tracker_field($fieldIdOrPermName, $useCache = true)
|
|
{
|
|
static $cache = [];
|
|
if ($useCache && isset($cache[$fieldIdOrPermName])) {
|
|
return $cache[$fieldIdOrPermName];
|
|
}
|
|
if ((int)$fieldIdOrPermName > 0) {
|
|
$res = $this->fields()->fetchFullRow(['fieldId' => (int)$fieldIdOrPermName]);
|
|
} else {
|
|
$res = $this->fields()->fetchFullRow(['permName' => $fieldIdOrPermName]);
|
|
}
|
|
if ($res) {
|
|
$factory = new Tracker_Field_Factory();
|
|
$options = Tracker_Options::fromSerialized($res['options'], $factory->getFieldInfo($res['type']));
|
|
$res['options_array'] = $options->buildOptionsArray();
|
|
$res['itemChoices'] = ! empty($res['itemChoices']) ? unserialize($res['itemChoices']) : [];
|
|
$res['visibleBy'] = ! empty($res['visibleBy']) ? unserialize($res['visibleBy']) : [];
|
|
$res['editableBy'] = ! empty($res['editableBy']) ? unserialize($res['editableBy']) : [];
|
|
if (TikiLib::lib('tiki')->get_memory_avail() < 1048576 * 10) {
|
|
$cache = [];
|
|
}
|
|
$cache[$fieldIdOrPermName] = $res;
|
|
return $res;
|
|
}
|
|
}
|
|
|
|
public function get_field_id($trackerId, $name, $lookup = 'name')
|
|
{
|
|
return $this->fields()->fetchOne('fieldId', ['trackerId' => (int) $trackerId, $lookup => $name]);
|
|
}
|
|
|
|
/**
|
|
* Return a tracker field id from it's type. By default
|
|
* it return only the first field of the searched type.
|
|
*
|
|
* @param int $trackerId tracker id
|
|
* @param string $type field type (in general an one letter code)
|
|
* @param string $option a value (or values separated by comma) that a tracker field must have in its options (it will be used inside a LIKE statement so most of the times it is a good idea to use %)
|
|
* @param bool $first if true return only the first field of the searched type, if false return all the fields of the searched type
|
|
* @param string $name filter by tracker field name
|
|
* @return int|array tracker field id or list of tracker fields ids
|
|
*/
|
|
public function get_field_id_from_type($trackerId, $type, $option = null, $first = true, $name = null)
|
|
{
|
|
static $memo;
|
|
if (! is_array($type) && isset($memo[$trackerId][$type][$option])) {
|
|
return $memo[$trackerId][$type][$option];
|
|
}
|
|
|
|
$conditions = [
|
|
'trackerId' => (int) $trackerId,
|
|
];
|
|
$fields = $this->fields();
|
|
|
|
if (is_array($type)) {
|
|
$conditions['type'] = $fields->in($type, true);
|
|
} else {
|
|
$conditions['type'] = $fields->exactly($type);
|
|
}
|
|
|
|
if (! empty($option)) {
|
|
throw new Exception("\$option parameter no longer supported. Code needs fixing.");
|
|
}
|
|
|
|
if (! empty($name)) {
|
|
$conditions['name'] = $name;
|
|
}
|
|
|
|
if ($first) {
|
|
$fieldId = $fields->fetchOne('fieldId', $conditions);
|
|
$memo[$trackerId][$type][$option] = $fieldId;
|
|
return $fieldId;
|
|
} else {
|
|
return $fields->fetchColumn('fieldId', $conditions);
|
|
}
|
|
}
|
|
|
|
public function get_page_field($trackerId)
|
|
{
|
|
$definition = Tracker_Definition::get($trackerId);
|
|
$score = 0;
|
|
$out = null;
|
|
|
|
foreach ($definition->getFields() as $field) {
|
|
if ($field['type'] == 'k') {
|
|
if ($score < 3 && $field['options_map']['autoassign'] == '1') {
|
|
$score = 3;
|
|
$out = $field;
|
|
} elseif ($score < 2 && $field['options_map']['create'] == '1') {
|
|
// Not sure about this one, old code used to say "has a 1 somewhere in the options string"
|
|
// Create seems to be the most likely candidate
|
|
$score = 2;
|
|
$out = $field;
|
|
} else {
|
|
$score = 1;
|
|
$out = $field;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
/*
|
|
** function only used for the popup for more infos on attachments
|
|
* returns an array with field=>value
|
|
*/
|
|
public function get_moreinfo($attId)
|
|
{
|
|
$query = "select o.`value`, o.`trackerId` from `tiki_tracker_options` o";
|
|
$query .= " left join `tiki_tracker_items` i on o.`trackerId`=i.`trackerId` ";
|
|
$query .= " left join `tiki_tracker_item_attachments` a on i.`itemId`=a.`itemId` ";
|
|
$query .= " where a.`attId`=? and o.`name`=?";
|
|
$result = $this->query($query, [(int) $attId, 'orderAttachments']);
|
|
$resu = $result->fetchRow();
|
|
if ($resu) {
|
|
$resu['orderAttachments'] = $resu['value'];
|
|
}
|
|
if (strstr($resu['orderAttachments'], '|')) {
|
|
$fields = preg_split('/,/', substr($resu['orderAttachments'], strpos($resu['orderAttachments'], '|') + 1));
|
|
$res = $this->attachments()->fetchRow($fields, ['attId' => (int) $attId]);
|
|
$res["trackerId"] = $resu['trackerId'];
|
|
$res["longdesc"] = isset($res['longdesc']) ? TikiLib::lib('parser')->parse_data($res['longdesc']) : '';
|
|
} else {
|
|
$res = [tra("Message") => tra("No extra information for that attached file. ")];
|
|
$res['trackerId'] = 0;
|
|
}
|
|
return $res;
|
|
}
|
|
|
|
public function field_types()
|
|
{
|
|
|
|
$types = [];
|
|
|
|
$factory = new Tracker_Field_Factory(false);
|
|
foreach ($factory->getFieldTypes() as $key => $info) {
|
|
$types[$key] = [
|
|
'label' => $info['name'],
|
|
'opt' => count($info['params']) === 0,
|
|
'help' => $this->build_help_for_type($info),
|
|
];
|
|
}
|
|
|
|
return $types;
|
|
}
|
|
|
|
private function build_help_for_type($info)
|
|
{
|
|
$function = tr('Function');
|
|
$text = "<p><strong>$function:</strong> {$info['description']}</p>";
|
|
|
|
if (count($info['params'])) {
|
|
$text .= '<dl>';
|
|
foreach ($info['params'] as $key => $param) {
|
|
if (isset($param['count'])) {
|
|
$text .= "<dt>{$param['name']}[{$param['count']}]</dt>";
|
|
} else {
|
|
$text .= "<dt>{$param['name']}</dt>";
|
|
}
|
|
|
|
$text .= "<dd>{$param['description']}</dd>";
|
|
|
|
if (isset($param['options'])) {
|
|
$text .= "<dd><ul>";
|
|
foreach ($param['options'] as $k => $label) {
|
|
$text .= "<li><strong>{$k}</strong> = <em>$label</em></li>";
|
|
}
|
|
$text .= "</ul></dd>";
|
|
}
|
|
}
|
|
$text .= '</dl>';
|
|
}
|
|
|
|
return "<div>{$text}</div>";
|
|
}
|
|
|
|
/**
|
|
* @param string $lg The language key to translate the status labels, if different than preferences.
|
|
* @return mixed
|
|
*/
|
|
public function status_types($lg = '')
|
|
{
|
|
$status['o'] = ['name' => 'open', 'label' => tra('Open', $lg),'perm' => 'tiki_p_view_trackers',
|
|
'image' => 'img/icons/status_open.gif', 'iconname' => 'status-open'];
|
|
$status['p'] = ['name' => 'pending', 'label' => tra('Pending', $lg),'perm' => 'tiki_p_view_trackers_pending',
|
|
'image' => 'img/icons/status_pending.gif', 'iconname' => 'status-pending'];
|
|
$status['c'] = ['name' => 'closed', 'label' => tra('Closed', $lg),'perm' => 'tiki_p_view_trackers_closed',
|
|
'image' => 'img/icons/status_closed.gif', 'iconname' => 'status-closed'];
|
|
return $status;
|
|
}
|
|
|
|
public function get_isMain_value($trackerId, $itemId)
|
|
{
|
|
global $prefs;
|
|
|
|
$query = "select tif.`value` from `tiki_tracker_item_fields` tif, `tiki_tracker_items` i, `tiki_tracker_fields` tf where i.`itemId`=? and i.`itemId`=tif.`itemId` and tf.`fieldId`=tif.`fieldId` and tf.`isMain`=? ORDER BY tf.`position`";
|
|
$result = $this->getOne($query, [ (int) $itemId, "y"]);
|
|
|
|
if (! $trackerId) {
|
|
$trackerId = (int) $this->table('tiki_tracker_items')->fetchOne('trackerId', ['itemId' => $itemId]);
|
|
}
|
|
$main_field_type = $this->get_main_field_type($trackerId);
|
|
|
|
// for ItemLink, AutoIncrement, UserPref and Category fields use the proper output method
|
|
if (in_array($main_field_type, ['r','q', 'p', 'e'])) {
|
|
$definition = Tracker_Definition::get($trackerId);
|
|
$field = $definition->getField($this->get_main_field($trackerId));
|
|
$item = $this->get_tracker_item($itemId);
|
|
$handler = $this->get_field_handler($field, $item);
|
|
// when called from \ObjectLib::get_title Category fields need to have getFieldData run before the category name can be rendered
|
|
$field = array_merge($field, $handler->getFieldData());
|
|
$handler = $this->get_field_handler($field, $item);
|
|
|
|
$result = $handler->renderOutput(['list_mode' => 'csv']);
|
|
}
|
|
|
|
if (strlen($result) && $result[0] === '{') {
|
|
$decoded = json_decode($result, true);
|
|
if ($decoded !== null) { // might start with a "{" but may not be a json_encoded value
|
|
if (isset($decoded[$prefs['language']])) {
|
|
return $decoded[$prefs['language']];
|
|
} elseif (is_array($decoded)) {
|
|
return reset($decoded);
|
|
}
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
public function get_main_field_type($trackerId)
|
|
{
|
|
return $this->fields()->fetchOne('type', ['isMain' => 'y', 'trackerId' => $trackerId], ['position' => 'ASC']);
|
|
}
|
|
|
|
public function get_main_field($trackerId)
|
|
{
|
|
return $this->fields()->fetchOne('fieldId', ['isMain' => 'y', 'trackerId' => $trackerId], ['position' => 'ASC']);
|
|
}
|
|
|
|
/**
|
|
* @param int $itemId
|
|
*
|
|
* @return string title to append to the end of a sefurl
|
|
*/
|
|
public function get_title_sefurl($itemId)
|
|
{
|
|
global $prefs;
|
|
|
|
$trackerId = $this->get_tracker_for_item($itemId);
|
|
if (empty($title)) {
|
|
$title = $this->get_isMain_value($trackerId, $itemId);
|
|
}
|
|
include_once('tiki-sefurl.php'); // make sure we have these constants
|
|
$title = preg_replace(PATTERN_TO_CLEAN_TEXT, CLEAN_CHAR, $this->take_away_accent($title));
|
|
$title = preg_replace('/' . CLEAN_CHAR . CLEAN_CHAR . '+/', '-', $title);
|
|
$title = preg_replace('/' . CLEAN_CHAR . '+$/', '', $title);
|
|
|
|
if (! empty($prefs['feature_sefurl_title_max_size'])) {
|
|
if (strlen($title) > $prefs['feature_sefurl_title_max_size']) {
|
|
$title = substr($title, 0, ($prefs['feature_sefurl_title_max_size'] + 1));
|
|
$titleMaxLength = strrpos($title, CLEAN_CHAR);
|
|
if ($titleMaxLength > 0) {
|
|
$title = substr($title, 0, $titleMaxLength);
|
|
}
|
|
}
|
|
}
|
|
return $title;
|
|
}
|
|
|
|
public function categorized_item($trackerId, $itemId, $mainfield, $ins_categs, $parent_categs_only = [], $override_perms = false, $managed_fields = null)
|
|
{
|
|
global $prefs;
|
|
|
|
// Collect the list of possible categories, those provided by a complete form
|
|
// The update_object_categories function will limit changes to those
|
|
$managed_categories = [];
|
|
|
|
$definition = Tracker_Definition::get($trackerId);
|
|
foreach ($definition->getCategorizedFields() as $t) {
|
|
if ($managed_fields && ! in_array($t, $managed_fields)) {
|
|
continue;
|
|
}
|
|
|
|
$this->itemFields()->insert(['itemId' => $itemId, 'fieldId' => $t, 'value' => ''], true);
|
|
|
|
$field = $definition->getField($t);
|
|
$handler = $this->get_field_handler($field);
|
|
$data = $handler->getFieldData();
|
|
$datalist = $data['list'];
|
|
if (! empty($parent_categs_only)) {
|
|
foreach ($datalist as $k => $entry) {
|
|
$parentId = TikiLib::lib('categ')->get_category_parent($entry['categId']);
|
|
if (! in_array($parentId, $parent_categs_only)) {
|
|
unset($datalist[$k]);
|
|
}
|
|
}
|
|
}
|
|
|
|
$managed_categories = array_merge(
|
|
$managed_categories,
|
|
array_map(
|
|
function ($entry) {
|
|
return $entry['categId'];
|
|
},
|
|
$datalist
|
|
)
|
|
);
|
|
}
|
|
|
|
$this->update_item_categories($itemId, $managed_categories, $ins_categs, $override_perms);
|
|
|
|
$items = $this->findLinkedItems(
|
|
$itemId,
|
|
function ($field, $handler) use ($trackerId) {
|
|
return $handler->cascadeCategories($trackerId);
|
|
}
|
|
);
|
|
|
|
$searchlib = TikiLib::lib('unifiedsearch');
|
|
$index = $prefs['feature_search'] === 'y' && $prefs['unified_incremental_update'] === 'y';
|
|
|
|
foreach ($items as $child) {
|
|
$this->update_item_categories($child, $managed_categories, $ins_categs, $override_perms);
|
|
|
|
if ($index) {
|
|
$searchlib->invalidateObject('trackeritem', $child);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function update_item_categories($itemId, $managed_categories, $ins_categs, $override_perms)
|
|
{
|
|
$categlib = TikiLib::lib('categ');
|
|
$cat_desc = '';
|
|
$cat_name = $this->get_isMain_value(null, $itemId);
|
|
|
|
// The following needed to ensure category field exist for item (to be readable by list_items)
|
|
// Update 2016: Needs to be the non-sefurl in case the feature is disabled later as this is stored in tiki_objects
|
|
// and used in tiki-browse_categories.php and other places
|
|
$cat_href = "tiki-view_tracker_item.php?itemId=$itemId";
|
|
|
|
$categlib->update_object_categories($ins_categs, $itemId, 'trackeritem', $cat_desc, $cat_name, $cat_href, $managed_categories, $override_perms);
|
|
}
|
|
|
|
public function move_up_last_fields($trackerId, $fieldId, $delta = 1)
|
|
{
|
|
$type = ($delta > 0) ? 'increment' : 'decrement';
|
|
|
|
$this->fields()->update(
|
|
['position' => $this->fields()->$type(abs($delta))],
|
|
['trackerId' => (int) $trackerId, 'fieldId' => (int) $fieldId]
|
|
);
|
|
}
|
|
|
|
/* list all the values of a field
|
|
*/
|
|
public function list_tracker_field_values($trackerId, $fieldId, $status = 'o', $distinct = 'y', $lang = '', $exceptItemId = '')
|
|
{
|
|
$mid = '';
|
|
$bindvars[] = (int) $fieldId;
|
|
if (! $this->getSqlStatus($status, $mid, $bindvars, $trackerId)) {
|
|
return null;
|
|
}
|
|
$sort_mode = "value_asc";
|
|
$distinct = $distinct == 'y' ? 'distinct' : '';
|
|
if (! empty($exceptItemId)) {
|
|
$mid .= ' and ttif.`itemId` != ? ';
|
|
$bindvars[] = $exceptItemId;
|
|
}
|
|
$query = "select $distinct(ttif.`value`) from `tiki_tracker_item_fields` ttif, `tiki_tracker_items` tti where tti.`itemId`= ttif.`itemId`and ttif.`fieldId`=? $mid order by " . $this->convertSortMode($sort_mode);
|
|
$result = $this->query($query, $bindvars);
|
|
$ret = [];
|
|
while ($res = $result->fetchRow()) {
|
|
$ret[] = $res['value'];
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
/* tests if a value exists in a field
|
|
*/
|
|
public function check_field_value_exists($value, $fieldId, $exceptItemId = 0)
|
|
{
|
|
$itemFields = $this->itemFields();
|
|
|
|
$conditions = [
|
|
'fieldId' => (int) $fieldId,
|
|
'value' => $value,
|
|
];
|
|
|
|
if ($exceptItemId > 0) {
|
|
$conditions['itemId'] = $itemFields->not((int) $exceptItemId);
|
|
}
|
|
|
|
return $itemFields->fetchCount($conditions) > 0;
|
|
}
|
|
|
|
public function is_multilingual($fieldId)
|
|
{
|
|
global $prefs;
|
|
|
|
if ($fieldId < 1) {
|
|
return 'n';
|
|
}
|
|
|
|
if ($prefs['feature_multilingual'] != 'y') {
|
|
return 'n';
|
|
}
|
|
|
|
$is = $this->fields()->fetchOne('isMultilingual', ['fieldId' => (int) $fieldId]);
|
|
|
|
return ($is == 'y') ? 'y' : 'n';
|
|
}
|
|
|
|
/* return the values of $fieldIdOut of an item that has a value $value for $fieldId */
|
|
public function get_filtered_item_values($fieldId, $value, $fieldIdOut)
|
|
{
|
|
$query = "select ttifOut.`value` from `tiki_tracker_item_fields` ttifOut, `tiki_tracker_item_fields` ttif
|
|
where ttifOut.`itemId`= ttif.`itemId`and ttif.`fieldId`=? and ttif.`value`=? and ttifOut.`fieldId`=?";
|
|
$bindvars = [$fieldId, $value, $fieldIdOut];
|
|
$result = $this->query($query, $bindvars);
|
|
$ret = [];
|
|
while ($res = $result->fetchRow()) {
|
|
$ret[] = $res['value'];
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
/* look if a tracker has only one item per user and if an item has already being created for the user or the IP*/
|
|
public function get_user_item(&$trackerId, $trackerOptions, $userparam = null, $user = null, $status = '')
|
|
{
|
|
global $prefs;
|
|
$tikilib = TikiLib::lib('tiki');
|
|
$userlib = TikiLib::lib('user');
|
|
if (empty($user)) {
|
|
$user = $GLOBALS['user'];
|
|
}
|
|
if (empty($trackerId) && $prefs['userTracker'] == 'y') {
|
|
$utid = $userlib->get_tracker_usergroup($user);
|
|
if (! empty($utid['usersTrackerId'])) {
|
|
$trackerId = $utid['usersTrackerId'];
|
|
$itemId = $this->get_item_id($trackerId, $utid['usersFieldId'], $user);
|
|
}
|
|
return $itemId;
|
|
}
|
|
|
|
$definition = Tracker_Definition::get($trackerId);
|
|
$userreal = $userparam != null ? $userparam : $user;
|
|
if (! empty($userreal)) {
|
|
if ($fieldId = $definition->getUserField()) {
|
|
// user creator field
|
|
$value = $userreal;
|
|
$items = $this->get_items_list($trackerId, $fieldId, $value, $status, true);
|
|
if (! empty($items)) {
|
|
return $items[0];
|
|
}
|
|
}
|
|
}
|
|
if ($fieldId = $definition->getAuthorIpField()) {
|
|
// IP creator field
|
|
$IP = $tikilib->get_ip_address();
|
|
$items = $this->get_items_list($trackerId, $fieldId, $IP, $status);
|
|
if (! empty($items)) {
|
|
return $items[0];
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
public function get_item_creators($trackerId, $itemId)
|
|
{
|
|
$definition = Tracker_Definition::get($trackerId);
|
|
|
|
$owners = array_map(function ($fieldId) use ($trackerId, $itemId) {
|
|
|
|
$owners = $this->get_item_value($trackerId, $itemId, $fieldId);
|
|
return $this->parse_user_field($owners);
|
|
}, $definition->getItemOwnerFields());
|
|
|
|
if ($owners) {
|
|
return call_user_func_array('array_merge', $owners);
|
|
} else {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/* find the best fieldwhere you can do a filter on the initial
|
|
* 1) if sort_mode and sort_mode is a text and the field is visible
|
|
* 2) the first main taht is text
|
|
*/
|
|
public function get_initial_field($list_fields, $sort_mode)
|
|
{
|
|
if (preg_match('/^f_([^_]*)_?.*/', $sort_mode, $matches)) {
|
|
if (isset($list_fields[$matches[1]])) {
|
|
$type = $list_fields[$matches[1]]['type'];
|
|
if (in_array($type, ['t', 'a', 'm'])) {
|
|
return $matches[1];
|
|
}
|
|
}
|
|
}
|
|
foreach ($list_fields as $fieldId => $field) {
|
|
if ($field['isMain'] == 'y' && in_array($field['type'], ['t', 'a', 'm'])) {
|
|
return $fieldId;
|
|
}
|
|
}
|
|
}
|
|
|
|
public function get_nb_items($trackerId)
|
|
{
|
|
return $this->items()->fetchCount(['trackerId' => (int) $trackerId]);
|
|
}
|
|
|
|
public function duplicate_tracker($trackerId, $name, $description = '', $descriptionIsParsed = 'n')
|
|
{
|
|
$tracker_info = $this->get_tracker($trackerId);
|
|
|
|
if ($options = $this->get_tracker_options($trackerId)) {
|
|
$tracker_info = array_merge($tracker_info, $options);
|
|
} else {
|
|
$options = [];
|
|
}
|
|
|
|
$newTrackerId = $this->replace_tracker(0, $name, $description, [], $descriptionIsParsed);
|
|
$fields = $this->list_tracker_fields($trackerId, 0, -1, 'position_asc', '');
|
|
foreach ($fields['data'] as $field) {
|
|
$newFieldId = $this->replace_tracker_field(
|
|
$newTrackerId,
|
|
0,
|
|
$field['name'],
|
|
$field['type'],
|
|
$field['isMain'],
|
|
$field['isSearchable'],
|
|
$field['isTblVisible'],
|
|
$field['isPublic'],
|
|
$field['isHidden'],
|
|
$field['isMandatory'],
|
|
$field['position'],
|
|
$field['options'],
|
|
$field['description'],
|
|
$field['isMultilingual'],
|
|
$field['itemChoices'],
|
|
$field['errorMsg'],
|
|
$field['visibleBy'],
|
|
$field['editableBy'],
|
|
$field['descriptionIsParsed'],
|
|
$field['validation'],
|
|
$field['validationParam'],
|
|
$field['validationMessage'],
|
|
null,
|
|
$field['rules'],
|
|
$field['encryptionKeyId']
|
|
);
|
|
if ($options['defaultOrderKey'] == $field['fieldId']) {
|
|
$options['defaultOrderKey'] = $newFieldId;
|
|
}
|
|
}
|
|
|
|
foreach ($options as $name => $val) {
|
|
$this->options()->insert(['trackerId' => $newTrackerId, 'name' => $name, 'value' => $val]);
|
|
}
|
|
return $newTrackerId;
|
|
}
|
|
|
|
public function get_notification_emails($trackerId, $itemId, $options, $status = '', $oldStatus = '')
|
|
{
|
|
global $prefs, $user;
|
|
$watchers_global = $this->get_event_watches('tracker_modified', $trackerId);
|
|
$watchers_local = $this->get_local_notifications($itemId, $status, $oldStatus);
|
|
$watchers_item = $itemId ? $this->get_event_watches('tracker_item_modified', $itemId, ['trackerId' => $trackerId]) : [];
|
|
|
|
if ($this->get_user_preference($user, 'user_tracker_watch_editor') != "y") {
|
|
for ($i = count($watchers_global) - 1; $i >= 0; --$i) {
|
|
if ($watchers_global[$i]['user'] == $user) {
|
|
unset($watchers_global[$i]);
|
|
break;
|
|
}
|
|
}
|
|
for ($i = count($watchers_local) - 1; $i >= 0; --$i) {
|
|
if ($watchers_local[$i]['user'] == $user) {
|
|
unset($watchers_local[$i]);
|
|
break;
|
|
}
|
|
}
|
|
for ($i = count($watchers_item) - 1; $i >= 0; --$i) {
|
|
if ($watchers_item[$i]['user'] == $user) {
|
|
unset($watchers_item[$i]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// use daily reports feature if tracker item has been added or updated
|
|
if ($prefs['feature_daily_report_watches'] == 'y' && ! empty($status)) {
|
|
$reportsManager = Reports_Factory::build('Reports_Manager');
|
|
$reportsManager->addToCache(
|
|
$watchers_global,
|
|
['event' => 'tracker_item_modified', 'itemId' => $itemId, 'trackerId' => $trackerId, 'user' => $user]
|
|
);
|
|
$reportsManager->addToCache(
|
|
$watchers_item,
|
|
['event' => 'tracker_item_modified', 'itemId' => $itemId, 'trackerId' => $trackerId, 'user' => $user]
|
|
);
|
|
}
|
|
|
|
// use daily reports feature if a file was attached or removed from a tracker item
|
|
if ($prefs['feature_daily_report_watches'] == 'y' && isset($options["attachment"])) {
|
|
$reportsManager = Reports_Factory::build('Reports_Manager');
|
|
$reportsManager->addToCache(
|
|
$watchers_global,
|
|
[
|
|
'event' => 'tracker_file_attachment',
|
|
'itemId' => $itemId,
|
|
'trackerId' => $trackerId,
|
|
'user' => $user,
|
|
"attachment" => $options["attachment"]
|
|
]
|
|
);
|
|
$reportsManager->addToCache(
|
|
$watchers_item,
|
|
[
|
|
'event' => 'tracker_file_attachment',
|
|
'itemId' => $itemId,
|
|
'trackerId' => $trackerId,
|
|
'user' => $user,
|
|
'attachment' => $options['attachment']
|
|
]
|
|
);
|
|
}
|
|
|
|
$watchers_outbound = [];
|
|
if (array_key_exists("outboundEmail", $options) && $options["outboundEmail"]) {
|
|
$emails3 = preg_split('/,/', $options['outboundEmail']);
|
|
foreach ($emails3 as $w) {
|
|
global $user_preferences;
|
|
$tikilib = TikiLib::lib('tiki');
|
|
$userlib = TikiLib::lib('user');
|
|
$u = $userlib->get_user_by_email($w);
|
|
$tikilib->get_user_preferences($u, ['user', 'language', 'mailCharset']);
|
|
if (empty($user_preferences[$u]['language'])) {
|
|
$user_preferences[$u]['language'] = $prefs['site_language'];
|
|
}
|
|
if (empty($user_preferences[$u]['mailCharset'])) {
|
|
$user_preferences[$u]['mailCharset'] = $prefs['users_prefs_mailCharset'];
|
|
}
|
|
$watchers_outbound[] = ['email' => $w, 'user' => $u, 'language' => $user_preferences[$u]['language'], 'mailCharset' => $user_preferences[$u]['mailCharset']];
|
|
}
|
|
}
|
|
|
|
$emails = [];
|
|
$watchers = [];
|
|
foreach (['watchers_global', 'watchers_local', 'watchers_item', 'watchers_outbound'] as $ws) {
|
|
if (! empty($$ws)) {
|
|
foreach ($$ws as $w) {
|
|
$wl = strtolower($w['email']);
|
|
if (! in_array($wl, $emails)) {
|
|
$emails[] = $wl;
|
|
$watchers[] = $w;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return $watchers;
|
|
}
|
|
|
|
/* sort allFileds function of a list of fields */
|
|
public function sort_fields($allFields, $listFields)
|
|
{
|
|
$tmp = [];
|
|
foreach ($listFields as $fieldId) {
|
|
if (substr($fieldId, 0, 1) == '-') {
|
|
$fieldId = substr($fieldId, 1);
|
|
}
|
|
foreach ($allFields['data'] as $i => $field) {
|
|
if ($field['fieldId'] == $fieldId && $field['fieldId']) {
|
|
$tmp[] = $field;
|
|
$allFields['data'][$i]['fieldId'] = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// do not forget the admin fields like user selector
|
|
foreach ($allFields['data'] as $field) {
|
|
if ($field['fieldId']) {
|
|
$tmp[] = $field;
|
|
}
|
|
}
|
|
$allFields['data'] = $tmp;
|
|
$allFields['cant'] = count($tmp);
|
|
return $allFields;
|
|
}
|
|
|
|
/* return all the values+field options of an item for a type field (ex: return all the user selector value for an item) */
|
|
public function get_item_values_by_type($itemId, $typeField)
|
|
{
|
|
$query = "select ttif.`value`, ttf.`options` from `tiki_tracker_fields` ttf, `tiki_tracker_item_fields` ttif";
|
|
$query .= " where ttif.`itemId`=? and ttf.`type`=? and ttf.`fieldId`=ttif.`fieldId`";
|
|
$ret = $this->fetchAll($query, [$itemId, $typeField]);
|
|
$factory = new Tracker_Field_Factory();
|
|
$typeInfo = $factory->getFieldInfo($typeField);
|
|
foreach ($ret as &$res) {
|
|
$options = Tracker_Options::fromSerialized($res['options'], $typeInfo);
|
|
$res['options_map'] = $options->getAllParameters();
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
/* return all the emails that are locally watching an item */
|
|
public function get_local_notifications($itemId, $status = '', $oldStatus = '')
|
|
{
|
|
global $user_preferences, $prefs, $user;
|
|
$tikilib = TikiLib::lib('tiki');
|
|
$userlib = TikiLib::lib('user');
|
|
$emails = [];
|
|
// user field watching item
|
|
$res = $this->get_item_values_by_type($itemId, 'u');
|
|
if (is_array($res)) {
|
|
foreach ($res as $f) {
|
|
if (isset($f['options_map']['notify']) && $f['options_map']['notify'] != 0 && ! empty($f['value'])) {
|
|
$fieldUsers = $this->parse_user_field($f['value']);
|
|
foreach ($fieldUsers as $fieldUser) {
|
|
if ($f['options_map']['notify'] == 2 && $user == $fieldUser) {
|
|
// Don't send email to oneself
|
|
continue;
|
|
}
|
|
$email = $userlib->get_user_email($fieldUser);
|
|
if (! empty($fieldUser) && ! empty($email)) {
|
|
$tikilib->get_user_preferences($fieldUser, ['email', 'user', 'language', 'mailCharset']);
|
|
$emails[] = ['email' => $email, 'user' => $fieldUser, 'language' => $user_preferences[$fieldUser]['language'],
|
|
'mailCharset' => $user_preferences[$fieldUser]['mailCharset'], 'template' => $f['options_map']['notify_template'], 'templateFormat' => $f['options_map']['notify_template_format']];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// email field watching status change
|
|
if ($status != $oldStatus) {
|
|
$res = $this->get_item_values_by_type($itemId, 'm');
|
|
if (is_array($res)) {
|
|
foreach ($res as $f) {
|
|
if (
|
|
(isset($f['options_map']['watchopen']) && $f['options_map']['watchopen'] == 'o' && $status == 'o')
|
|
|| (isset($f['options_map']['watchpending']) && $f['options_map']['watchpending'] == 'p' && $status == 'p')
|
|
|| (isset($f['options_map']['watchclosed']) && $f['options_map']['watchclosed'] == 'c' && $status == 'c')
|
|
) {
|
|
$emails[] = ['email' => $f['value'], 'user' => '', 'language' => $prefs['language'], 'mailCharset' => $prefs['users_prefs_mailCharset'], 'action' => 'status'];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return $emails;
|
|
}
|
|
|
|
public function get_join_values($trackerId, $itemId, $fieldIds, $finalTrackerId = '', $finalFields = '', $separator = ' ', $status = '')
|
|
{
|
|
$smarty = TikiLib::lib('smarty');
|
|
$select[] = "`tiki_tracker_item_fields` t0";
|
|
$where[] = " t0.`itemId`=?";
|
|
$bindVars[] = $itemId;
|
|
$mid = '';
|
|
for ($i = 0, $tmp_count = count($fieldIds) - 1; $i < $tmp_count; $i += 2) {
|
|
$j = $i + 1;
|
|
$k = $j + 1;
|
|
$select[] = "`tiki_tracker_item_fields` t$j";
|
|
$select[] = "`tiki_tracker_item_fields` t$k";
|
|
$where[] = "t$i.`value`=t$j.`value` and t$i.`fieldId`=? and t$j.`fieldId`=?";
|
|
$bindVars[] = $fieldIds[$i];
|
|
$bindVars[] = $fieldIds[$j];
|
|
$where[] = "t$j.`itemId`=t$k.`itemId` and t$k.`fieldId`=?";
|
|
$bindVars[] = $fieldIds[$k];
|
|
}
|
|
if (! empty($status)) {
|
|
$this->getSqlStatus($status, $mid, $bindVars, $trackerId);
|
|
$select[] = '`tiki_tracker_items` tti';
|
|
$mid .= " and tti.`itemId`=t$k.`itemId`";
|
|
}
|
|
$query = "select t$k.* from " . implode(',', $select) . ' where ' . implode(' and ', $where) . $mid;
|
|
$result = $this->query($query, $bindVars);
|
|
$ret = [];
|
|
while ($res = $result->fetchRow()) {
|
|
$field_value['value'] = $res['value'];
|
|
$field_value['trackerId'] = $trackerId;
|
|
$field_value['type'] = $this->fields()->fetchOne('type', ['fieldId' => (int) $res['fieldId']]);
|
|
if (! $field_value['type']) {
|
|
$ret[$res['itemId']] = tra('Tracker field setup error - display field not found: ') . '#' . $res['fieldId'];
|
|
} else {
|
|
$ret[$res['itemId']] = $this->get_field_handler($field_value, $res)->renderOutput(['showlinks' => 'n', 'list_mode' => 'n']);
|
|
}
|
|
if (is_array($finalFields) && count($finalFields)) {
|
|
$i = 0;
|
|
foreach ($finalFields as $f) {
|
|
if (! $i++) {
|
|
continue;
|
|
}
|
|
$field_value = $this->get_tracker_field($f);
|
|
$ff = $this->get_item_value($finalTrackerId, $res['itemId'], $f);
|
|
;
|
|
$field_value['value'] = $ff;
|
|
$ret[$res['itemId']] = $this->get_field_handler($field_value, $res)->renderOutput(['showlinks' => 'n']);
|
|
}
|
|
}
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
public function get_left_join_sql($fieldIds)
|
|
{
|
|
$sql = '';
|
|
for ($i = 0, $tmp_count = count($fieldIds); $i < $tmp_count; $i += 3) {
|
|
$j = $i + 1;
|
|
$k = $j + 1;
|
|
$tti = $i ? "t$i" : 'tti';
|
|
$sttif = $k < $tmp_count - 1 ? "t$k" : 'sttif';
|
|
$sql .= " LEFT JOIN (`tiki_tracker_item_fields` t$i) ON ($tti.`itemId`= t$i.`itemId` and t$i.`fieldId`=" . $fieldIds[$i] . ")";
|
|
$sql .= " LEFT JOIN (`tiki_tracker_item_fields` t$j) ON (t$i.`value`= t$j.`value` and t$j.`fieldId`=" . $fieldIds[$j] . ")";
|
|
$sql .= " LEFT JOIN (`tiki_tracker_item_fields` $sttif) ON (t$j.`itemId`= $sttif.`itemId` and $sttif.`fieldId`=" . $fieldIds[$k] . ")";
|
|
}
|
|
return $sql;
|
|
}
|
|
|
|
public function get_item_by_field_values($values)
|
|
{
|
|
if (empty($values)) {
|
|
return 0;
|
|
}
|
|
$query = "select tti.itemId from `tiki_tracker_items` tti";
|
|
$bindVars = [];
|
|
foreach ($values as $fieldId => $value) {
|
|
$query .= " INNER JOIN `tiki_tracker_item_fields` ttif$fieldId ON ttif$fieldId.itemId = tti.itemId AND ttif$fieldId.fieldId = ? AND ttif$fieldId.value = ?";
|
|
$bindVars[] = $fieldId;
|
|
$bindVars[] = $value;
|
|
}
|
|
$result = $this->query($query, $bindVars);
|
|
if ($res = $result->fetchRow()) {
|
|
return $res['itemId'];
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public function get_item_info($itemId)
|
|
{
|
|
return $this->items()->fetchFullRow(['itemId' => (int) $itemId]);
|
|
}
|
|
|
|
public function rename_page($old, $new)
|
|
{
|
|
global $prefs;
|
|
|
|
$query = "update `tiki_tracker_item_fields` ttif left join `tiki_tracker_fields` ttf on (ttif.fieldId = ttf.fieldId) set ttif.`value`=? where ttif.`value`=? and (ttf.`type` = ? or ttf.`type` = ?)";
|
|
$this->query($query, [$new, $old, 'k', 'wiki']);
|
|
|
|
$relationlib = TikiLib::lib('relation');
|
|
$wikilib = TikiLib::lib('wiki');
|
|
$relatedfields = $relationlib->get_object_ids_with_relations_from('wiki page', $new, 'tiki.wiki.linkedfield'); // $new because attributes have been changed
|
|
$relateditems = $relationlib->get_object_ids_with_relations_from('wiki page', $new, 'tiki.wiki.linkeditem');
|
|
foreach ($relateditems as $itemId) {
|
|
foreach ($relatedfields as $fieldId) {
|
|
$field = $this->get_tracker_field($fieldId);
|
|
$toSync = false;
|
|
$nameFieldId = 0;
|
|
if ($field['type'] == 'wiki') {
|
|
$trackerId = $field['trackerId'];
|
|
$definition = Tracker_Definition::get($trackerId);
|
|
$field = $definition->getField($fieldId);
|
|
if ($field['options_map']['syncwikipagename'] != 'n') {
|
|
$toSync = true;
|
|
}
|
|
$nameFieldId = $field['options_map']['fieldIdForPagename'];
|
|
} elseif ($prefs['tracker_wikirelation_synctitle'] == 'y') {
|
|
$toSync = true;
|
|
}
|
|
if ($toSync) {
|
|
$value = $this->get_item_value(0, $itemId, $fieldId);
|
|
if ($wikilib->get_namespace($value) && $value != $new) {
|
|
$this->modify_field($itemId, $fieldId, $new);
|
|
} elseif (! $wikilib->get_namespace($value) && $value != $wikilib->get_without_namespace($new)) {
|
|
$this->modify_field($itemId, $fieldId, $wikilib->get_without_namespace($new));
|
|
}
|
|
if ($nameFieldId) {
|
|
$this->modify_field($itemId, $nameFieldId, $wikilib->get_without_namespace($new));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Note that this is different from function rename_page
|
|
*/
|
|
public function rename_linked_page($args)
|
|
{
|
|
global $prefs;
|
|
$relationlib = TikiLib::lib('relation');
|
|
$wikilib = TikiLib::lib('wiki');
|
|
$wikipages = $relationlib->get_object_ids_with_relations_to('trackeritem', $args['object'], 'tiki.wiki.linkeditem');
|
|
foreach ($wikipages as $pageName) {
|
|
// determine if field has changed
|
|
$relatedfields = $relationlib->get_object_ids_with_relations_from('wiki page', $pageName, 'tiki.wiki.linkedfield');
|
|
foreach ($relatedfields as $fieldId) {
|
|
if (
|
|
isset($args['values'][$fieldId]) and isset($args['old_values'][$fieldId])
|
|
&& $args['values'][$fieldId] != $args['old_values'][$fieldId]
|
|
) {
|
|
if ($wikilib->get_namespace($args['values'][$fieldId])) {
|
|
$newname = $args['values'][$fieldId];
|
|
} elseif ($namespace = $wikilib->get_namespace($pageName)) {
|
|
$newname = $namespace . $prefs['namespace_separator'] . $wikilib->get_without_namespace($args['values'][$fieldId]);
|
|
} else {
|
|
$newname = $args['values'][$fieldId];
|
|
}
|
|
$wikilib->wiki_rename_page($pageName, $newname, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public function setup_wiki_fields($args)
|
|
{
|
|
$definition = Tracker_Definition::get($args['trackerId']);
|
|
$itemId = $args['object'];
|
|
$values = $args['values'];
|
|
|
|
if ($definition && $fieldIds = $definition->getWikiFields()) {
|
|
foreach ($fieldIds as $fieldId) {
|
|
if (! empty($values[$fieldId])) {
|
|
TikiLib::lib('relation')->add_relation('tiki.wiki.linkeditem', 'wiki page', $values[$fieldId], 'trackeritem', $itemId);
|
|
TikiLib::lib('relation')->add_relation('tiki.wiki.linkedfield', 'wiki page', $values[$fieldId], 'trackerfield', $fieldId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public function update_wiki_fields($args)
|
|
{
|
|
global $prefs;
|
|
$wikilib = TikiLib::lib('wiki');
|
|
$definition = Tracker_Definition::get($args['trackerId']);
|
|
$values = $args['values'];
|
|
$old_values = $args['old_values'];
|
|
$itemId = $args['object'];
|
|
|
|
if ($definition && $fieldIds = $definition->getWikiFields()) {
|
|
foreach ($fieldIds as $fieldId) {
|
|
$field = $definition->getField($fieldId);
|
|
if ($field['options_map']['syncwikipagename'] != 'n') {
|
|
$nameFieldId = $field['options_map']['fieldIdForPagename'];
|
|
if (
|
|
! empty($values[$nameFieldId]) && ! empty($old_values[$nameFieldId]) && ! empty($old_values[$fieldId])
|
|
&& $values[$nameFieldId] != $old_values[$nameFieldId]
|
|
) {
|
|
if ($namespace = $wikilib->get_namespace($old_values[$fieldId])) {
|
|
$newname = $namespace . $prefs['namespace_separator'] . $wikilib->get_without_namespace($values[$nameFieldId]);
|
|
} else {
|
|
$newname = $values[$nameFieldId];
|
|
}
|
|
$args['values'][$fieldId] = $newname;
|
|
$this->modify_field($itemId, $fieldId, $newname);
|
|
$wikilib->wiki_rename_page($old_values[$fieldId], $newname, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public function delete_wiki_fields($args)
|
|
{
|
|
$definition = Tracker_Definition::get($args['trackerId']);
|
|
$itemId = $args['object'];
|
|
|
|
if ($definition && $fieldIds = $definition->getWikiFields()) {
|
|
foreach ($fieldIds as $fieldId) {
|
|
$field = $definition->getField($fieldId);
|
|
|
|
if ($field['options_map']['syncwikipagedelete'] == 'y' && ! empty($args['values'][$fieldId])) {
|
|
$pagename = $args['values'][$fieldId];
|
|
TikiLib::lib('tiki')->remove_all_versions($pagename);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public function build_date($input, $format, $ins_id)
|
|
{
|
|
if (is_array($format)) {
|
|
$format = $format['options_array'][0];
|
|
}
|
|
|
|
$tikilib = TikiLib::lib('tiki');
|
|
$value = '';
|
|
$monthIsNull = empty($input[$ins_id . 'Month']) || $input[$ins_id . 'Month'] == null || $input[$ins_id . 'Month'] == 'null' || $input[$ins_id . 'Month'] == '';
|
|
$dayIsNull = empty($input[$ins_id . 'Day']) || $input[$ins_id . 'Day'] == null || $input[$ins_id . 'Day'] == 'null' || $input[$ins_id . 'Day'] == '';
|
|
$yearIsNull = empty($input[$ins_id . 'Year']) || $input[$ins_id . 'Year'] == null || $input[$ins_id . 'Year'] == 'null' || $input[$ins_id . 'Year'] == '';
|
|
$hourIsNull = ! isset($input[$ins_id . 'Hour']) || $input[$ins_id . 'Hour'] == null || $input[$ins_id . 'Hour'] == 'null' || $input[$ins_id . 'Hour'] == '' || $input[$ins_id . 'Hour'] == ' ';
|
|
$minuteIsNull = empty($input[$ins_id . 'Minute']) || $input[$ins_id . 'Minute'] == null || $input[$ins_id . 'Minute'] == 'null' || $input[$ins_id . 'Minute'] == '' || $input[$ins_id . 'Minute'] == ' ';
|
|
if ($format == 'd') {
|
|
if ($monthIsNull || $dayIsNull || $yearIsNull) {
|
|
// all the values must be blank
|
|
$value = '';
|
|
} else {
|
|
$value = $tikilib->make_time(0, 0, 0, $input[$ins_id . 'Month'], $input[$ins_id . 'Day'], $input[$ins_id . 'Year']);
|
|
}
|
|
} elseif ($format == 't') { // all the values must be blank
|
|
if ($hourIsNull || $minuteIsNull) {
|
|
$value = '';
|
|
} else {
|
|
//if (isset($input[$ins_id.'Meridian']) && $input[$ins_id.'Meridian'] == 'pm') $input[$ins_id.'Hour'] += 12;
|
|
$now = $tikilib->now;
|
|
//Convert 12-hour clock hours to 24-hour scale to compute time
|
|
if (isset($input[$ins_id . 'Meridian'])) {
|
|
$input[$ins_id . 'Hour'] = date('H', strtotime($input[$ins_id . 'Hour'] . ':00 ' . $input[$ins_id . 'Meridian']));
|
|
}
|
|
$value = $tikilib->make_time($input[$ins_id . 'Hour'], $input[$ins_id . 'Minute'], 0, $tikilib->date_format("%m", $now), $tikilib->date_format("%d", $now), $tikilib->date_format("%Y", $now));
|
|
}
|
|
} else {
|
|
if ($monthIsNull || $dayIsNull || $yearIsNull || $hourIsNull || $minuteIsNull) {
|
|
// all the values must be blank
|
|
$value = '';
|
|
} else {
|
|
//if (isset($input[$ins_id.'Meridian']) && $input[$ins_id.'Meridian'] == 'pm') $input[$ins_id.'Hour'] += 12;
|
|
//Convert 12-hour clock hours to 24-hour scale to compute time
|
|
if (isset($input[$ins_id . 'Meridian'])) {
|
|
$input[$ins_id . 'Hour'] = date('H', strtotime($input[$ins_id . 'Hour'] . ':00 ' . $input[$ins_id . 'Meridian']));
|
|
}
|
|
$value = $tikilib->make_time($input[$ins_id . 'Hour'], $input[$ins_id . 'Minute'], 0, $input[$ins_id . 'Month'], $input[$ins_id . 'Day'], $input[$ins_id . 'Year']);
|
|
}
|
|
}
|
|
return $value;
|
|
}
|
|
|
|
/* get the fields from the pretty tracker template
|
|
* return a list of fieldIds
|
|
*/
|
|
public function get_pretty_fieldIds($resource, $type = 'wiki', &$prettyModifier = [], $trackerId = 0)
|
|
{
|
|
$tikilib = TikiLib::lib('tiki');
|
|
$smarty = TikiLib::lib('smarty');
|
|
if ($type == 'wiki') {
|
|
$wiki_info = $tikilib->get_page_info($resource);
|
|
if (! empty($wiki_info)) {
|
|
$f = $wiki_info['data'];
|
|
}
|
|
} else {
|
|
if (strpos($resource, 'templates/') === 0) {
|
|
$resource = substr($resource, 10);
|
|
}
|
|
$resource_name = $smarty->get_filename($resource);
|
|
$f = file_get_contents($resource_name);
|
|
}
|
|
if (! empty($f)) {
|
|
//matches[1] = field name
|
|
//matches[2] = trailing modifier text
|
|
//matches[3] = modifier name ('output' or 'template')
|
|
//matches[4] = modifier parameter (template name in this case)
|
|
preg_match_all('/\$f_(\w+)(\|(output|template):?([^}]*))?}/', $f, $matches);
|
|
$ret = [];
|
|
foreach ($matches[1] as $i => $val) {
|
|
if (ctype_digit($val)) {
|
|
$ret[] = $val;
|
|
} elseif ($fieldId = $this->table('tiki_tracker_fields')->fetchOne('fieldId', ['permName' => $val, 'trackerId' => $trackerId])) {
|
|
$ret[] = $fieldId;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check through modifiers in the pretty tracker template.
|
|
* If |output, store modifier as output. In wikiplugin_tracker, this will make it such that the field is output only
|
|
* If |template, it will check to see if a template is specified (e.g. $f_title|template:"title.tpl"). If not, default to tracker_input_field tpl
|
|
*/
|
|
foreach ($matches[3] as $i => $val) {
|
|
if ($val == 'output') {
|
|
$v = $matches[1][$i];
|
|
if (ctype_digit($v)) {
|
|
$prettyModifier[$v] = "output";
|
|
} elseif ($fieldId = $this->table('tiki_tracker_fields')->fetchOne('fieldId', ['permName' => $v, 'trackerId' => $trackerId])) {
|
|
$prettyModifier[$fieldId] = "output";
|
|
}
|
|
} elseif ($val == "template") {
|
|
$v = $matches[1][$i];
|
|
$tpl = ! empty($matches[4][$i]) ? $matches[4][$i] : "tracker_input_field.tpl"; //fetches template from pretty tracker template. if none, set to default
|
|
$tpl = trim($tpl, '"\''); //trim quotations from template name
|
|
if (ctype_digit($v)) {
|
|
$prettyModifier[$v] = $tpl;
|
|
} elseif ($fieldId = $this->table('tiki_tracker_fields')->fetchOne('fieldId', ['permName' => $v, 'trackerId' => $trackerId])) {
|
|
$prettyModifier[$fieldId] = $tpl;
|
|
}
|
|
}
|
|
}
|
|
return $ret;
|
|
}
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $value string or array to process
|
|
*/
|
|
public function replace_pretty_tracker_refs(&$value)
|
|
{
|
|
$smarty = TikiLib::lib('smarty');
|
|
|
|
if (is_array($value)) {
|
|
foreach ($value as &$v) {
|
|
$this->replace_pretty_tracker_refs($v);
|
|
}
|
|
} else {
|
|
// array syntax for callback function needed for some versions of PHP (5.2.0?) - thanks to mariush on http://php.net/preg_replace_callback
|
|
$value = preg_replace_callback('/\{\$(f_\w+)\}/', [ &$this, '_pretty_tracker_replace_value'], $value);
|
|
}
|
|
}
|
|
|
|
public static function _pretty_tracker_replace_value($matches)
|
|
{
|
|
$smarty = TikiLib::lib('smarty');
|
|
$s_var = null;
|
|
if (! empty($matches[1])) {
|
|
$s_var = $smarty->getTemplateVars($matches[1]);
|
|
}
|
|
if (! is_null($s_var)) {
|
|
$r = $s_var;
|
|
} else {
|
|
$r = $matches[0];
|
|
}
|
|
return $r;
|
|
}
|
|
|
|
public function nbComments($user)
|
|
{
|
|
return $this->comments()->fetchCount(['userName' => $user, 'objectType' => 'trackeritem']);
|
|
}
|
|
|
|
public function lastModif($trackerId)
|
|
{
|
|
return $this->items()->fetchOne($this->items()->max('lastModif'), ['trackerId' => (int) $trackerId]);
|
|
}
|
|
|
|
public function get_field($fieldId, $fields)
|
|
{
|
|
foreach ($fields as $f) {
|
|
if ($f['fieldId'] == $fieldId) {
|
|
return $f;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public function flaten($fields)
|
|
{
|
|
$new = [];
|
|
if (empty($fields)) {
|
|
return $new;
|
|
}
|
|
foreach ($fields as $field) {
|
|
if (is_array($field)) {
|
|
$new = array_merge($new, $this->flaten($field));
|
|
} else {
|
|
$new[] = $field;
|
|
}
|
|
}
|
|
return $new;
|
|
}
|
|
|
|
public function test_field_type($fields, $types)
|
|
{
|
|
$new = $this->flaten($fields);
|
|
$table = $this->fields();
|
|
|
|
return $table->fetchCount(['fieldId' => $table->in($new),'type' => $table->in($types, true)]);
|
|
}
|
|
|
|
public function get_computed_info($options, $trackerId = 0, &$fields = null)
|
|
{
|
|
preg_match_all('/#([0-9]+)/', $options, $matches);
|
|
$nbDates = 0;
|
|
foreach ($matches[1] as $k => $match) {
|
|
if (empty($fields)) {
|
|
$allfields = $this->list_tracker_fields($trackerId, 0, -1, 'position_asc', '');
|
|
$fields = $allfields['data'];
|
|
}
|
|
foreach ($fields as $k => $field) {
|
|
if ($field['fieldId'] == $match && in_array($field['type'], ['f', 'j'])) {
|
|
++$nbDates;
|
|
$info = $field;
|
|
break;
|
|
} elseif ($field['fieldId'] == $match && $field['type'] == 'C') {
|
|
$info = $this-> get_computed_info($field['options'], $trackerId, $fields);
|
|
if (! empty($info) && ($info['computedtype'] == 'f' || $info['computedtype'] == 'j')) {
|
|
++$nbDates;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ($nbDates == 0) {
|
|
return null;
|
|
} elseif ($nbDates % 2 == 0) {
|
|
return ['computedtype' => 'duration', 'options' => $info['options'] ,'options_array' => $info['options_array']];
|
|
} else {
|
|
return ['computedtype' => 'f', 'options' => $info['options'] ,'options_array' => $info['options_array']];
|
|
}
|
|
}
|
|
|
|
public function change_status($items, $status)
|
|
{
|
|
global $prefs, $user;
|
|
$tikilib = TikiLib::lib('tiki');
|
|
|
|
if (! count($items)) {
|
|
return;
|
|
}
|
|
|
|
$toUpdate = [];
|
|
|
|
foreach ($items as $i) {
|
|
if (is_array($i) && isset($i['itemId'])) {
|
|
$i = $i['itemId'];
|
|
}
|
|
|
|
$toUpdate[] = $i;
|
|
}
|
|
|
|
$table = $this->items();
|
|
$map = $table->fetchMap(
|
|
'itemId',
|
|
'trackerId',
|
|
[
|
|
'itemId' => $table->in($toUpdate),
|
|
]
|
|
);
|
|
|
|
foreach ($toUpdate as $itemId) {
|
|
$trackerId = $map[$itemId];
|
|
$child = $this->findLinkedItems(
|
|
$itemId,
|
|
function ($field, $handler) use ($trackerId) {
|
|
return $handler->cascadeStatus($trackerId);
|
|
}
|
|
);
|
|
|
|
$toUpdate = array_merge($toUpdate, $child);
|
|
}
|
|
|
|
$this->update_items(
|
|
$toUpdate,
|
|
[
|
|
'status' => $status,
|
|
'lastModif' => $tikilib->now,
|
|
'lastModifBy' => $user,
|
|
],
|
|
true
|
|
);
|
|
}
|
|
|
|
private function update_items(array $toUpdate, array $fields, $refresh_index)
|
|
{
|
|
global $prefs;
|
|
$logslib = TikiLib::lib('logs');
|
|
$table = $this->items();
|
|
$table->updateMultiple(
|
|
$fields,
|
|
['itemId' => $table->in($toUpdate)]
|
|
);
|
|
|
|
foreach ($toUpdate as $itemId) {
|
|
$version = $this->last_log_version($itemId) + 1;
|
|
if (($logslib->add_action('Updated', $itemId, 'trackeritem', $version)) == 0) {
|
|
$version = 0;
|
|
}
|
|
}
|
|
|
|
if ($prefs['feature_search'] === 'y' && $prefs['unified_incremental_update'] === 'y') {
|
|
$searchlib = TikiLib::lib('unifiedsearch');
|
|
|
|
foreach ($toUpdate as $child) {
|
|
$searchlib->invalidateObject('trackeritem', $child);
|
|
}
|
|
|
|
if ($refresh_index && $toUpdate) {
|
|
require_once('lib/search/refresh-functions.php');
|
|
refresh_index('trackeritem', $toUpdate[0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function log($version, $itemId, $fieldId, $value = '')
|
|
{
|
|
if (empty($version)) {
|
|
return;
|
|
}
|
|
if ($value === null) {
|
|
$value = ''; // we want to log it after all, so change is in history
|
|
}
|
|
$values = (array) $value;
|
|
foreach ($values as $v) {
|
|
$this->logs()->insert(['version' => $version, 'itemId' => $itemId, 'fieldId' => $fieldId, 'value' => $v]);
|
|
}
|
|
}
|
|
|
|
public function last_log_version($itemId)
|
|
{
|
|
$logs = $this->logs();
|
|
|
|
return $logs->fetchOne($logs->max('version'), ['itemId' => $itemId]);
|
|
}
|
|
|
|
public function remove_item_log($itemId)
|
|
{
|
|
$this->logs()->deleteMultiple(['itemId' => $itemId]);
|
|
}
|
|
|
|
public function get_item_history($item_info = null, $fieldId = 0, $filter = '', $offset = 0, $max = -1)
|
|
{
|
|
global $prefs;
|
|
if (! empty($fieldId)) {
|
|
$mid2[] = $mid[] = 'ttifl.`fieldId`=?';
|
|
$bindvars[] = $fieldId;
|
|
}
|
|
if (! empty($item_info['itemId'])) {
|
|
$mid[] = 'ttifl.`itemId`=?';
|
|
$bindvars[] = $item_info['itemId'];
|
|
if ($prefs['feature_categories'] == 'y') {
|
|
$categlib = TikiLib::lib('categ');
|
|
$item_categs = $categlib->get_object_categories('trackeritem', $item_info['itemId']);
|
|
}
|
|
}
|
|
$query = 'select ttifl.*, ttf.* from `tiki_tracker_item_fields` ttifl left join `tiki_tracker_fields` ttf on (ttf.`fieldId`=ttifl.`fieldId`) where ' . implode(' and ', $mid);
|
|
$all = $this->fetchAll($query, $bindvars, -1, 0);
|
|
foreach ($all as $f) {
|
|
if (! empty($item_categs) && $f['type'] == 'e') {
|
|
//category
|
|
$f['options_array'] = explode(',', $f['options']);
|
|
if (ctype_digit($f['options_array'][0]) && $f['options_array'][0] > 0) {
|
|
$type = (isset($f['options_array'][3]) && $f['options_array'][3] == 1) ? 'descendants' : 'children';
|
|
$cfilter = ['identifier' => $f['options_array'][0], 'type' => $type];
|
|
$field_categs = $categlib->getCategories($cfilter, true, false);
|
|
} else {
|
|
$field_categs = [];
|
|
}
|
|
$aux = [];
|
|
foreach ($field_categs as $cat) {
|
|
$aux[] = $cat['categId'];
|
|
}
|
|
$field_categs = $aux;
|
|
$check = array_intersect($field_categs, $item_categs);
|
|
if (! empty($check)) {
|
|
$f['value'] = implode(',', $check);
|
|
}
|
|
}
|
|
$last[$f['fieldId']] = $f['value'];
|
|
}
|
|
|
|
$last[-1] = $item_info['status'];
|
|
if (empty($item_info['itemId'])) {
|
|
$join = 'ttifl.`itemId`';
|
|
$bindvars = array_merge(['trackeritem'], $bindvars);
|
|
} else {
|
|
$join = '?';
|
|
$bindvars = array_merge(['trackeritem', $item_info['itemId']], $bindvars);
|
|
}
|
|
|
|
$itemsBindvars = [$item_info['itemId']];
|
|
$itemsWhere = '`itemId`=?';
|
|
if (! empty($filter['version'])) {
|
|
$itemsWhere .= ' AND `version` = ?';
|
|
$itemsBindvars[] = $filter['version'];
|
|
}
|
|
|
|
$count = $this->getOne('SELECT COUNT(DISTINCT `version`) FROM `tiki_tracker_item_field_logs` WHERE ' . $itemsWhere, $itemsBindvars);
|
|
$itemVersions = $this->fetchAll(
|
|
'SELECT DISTINCT ttifl.`version` FROM `tiki_tracker_item_field_logs` ttifl WHERE ' . $itemsWhere . ' ORDER BY `version` DESC',
|
|
$itemsBindvars,
|
|
$max,
|
|
$offset
|
|
);
|
|
|
|
if (! empty($itemVersions)) {
|
|
$mid[] = 'ttifl.`version`<=?';
|
|
$bindvars[] = $itemVersions[0]['version'];
|
|
$mid[] = 'ttifl.`version`>=?';
|
|
$bindvars[] = $itemVersions[count($itemVersions) - 1]['version'];
|
|
}
|
|
|
|
$itemObject = Tracker_Item::fromId($item_info['itemId']);
|
|
|
|
$query = 'SELECT ttifl.`version`, ttifl.`fieldId`, ttifl.`value`, ta.`user`, ta.`lastModif` ' .
|
|
'FROM `tiki_tracker_item_field_logs` ttifl ' .
|
|
'LEFT JOIN `tiki_actionlog` ta ON (ta.`comment`=ttifl.`version` AND ta.`objectType`=? AND ta.`object`=' . $join . ') ' .
|
|
'WHERE ' . implode(' AND ', $mid) . ' ORDER BY ttifl.`itemId` ASC, ttifl.`version` DESC, ttifl.`fieldId` ASC';
|
|
|
|
$all = $this->fetchAll($query, $bindvars);
|
|
$history['data'] = [];
|
|
foreach ($all as $hist) {
|
|
$hist['new'] = isset($last[$hist['fieldId']]) ? $last[$hist['fieldId']] : '';
|
|
if ($hist['new'] == $hist['value']) {
|
|
continue;
|
|
}
|
|
$last[$hist['fieldId']] = $hist['value'];
|
|
if (! $itemObject->canViewField($hist['fieldId'])) {
|
|
continue;
|
|
}
|
|
if (! empty($filter['version']) && $filter['version'] != $hist['version']) {
|
|
continue;
|
|
}
|
|
$history['data'][] = $hist;
|
|
}
|
|
$history['cant'] = $count;
|
|
return $history;
|
|
}
|
|
|
|
public function item_has_history($itemId)
|
|
{
|
|
return $this->table('tiki_tracker_item_fields')->fetchCount([ 'itemId' => $itemId ]);
|
|
}
|
|
|
|
public function move_item($trackerId, $itemId, $newTrackerId)
|
|
{
|
|
$newFields = $this->list_tracker_fields($newTrackerId, 0, -1, 'name_asc');
|
|
foreach ($newFields['data'] as $field) {
|
|
$translation[$field['name']] = $field;
|
|
}
|
|
$this->items()->update(['trackerId' => $newTrackerId], ['itemId' => $itemId]);
|
|
$this->trackers()->update(['items' => $this->trackers()->decrement(1)], ['trackerId' => $trackerId]);
|
|
$this->trackers()->update(['items' => $this->trackers()->increment(1)], ['trackerId' => $newTrackerId]);
|
|
|
|
$newFields = $this->list_tracker_fields($newTrackerId, 0, -1, 'name_asc');
|
|
$query = 'select ttif.*, ttf.`name`, ttf.`type`, ttf.`options` from `tiki_tracker_item_fields` ttif, `tiki_tracker_fields` ttf where ttif.itemId=? and ttif.`fieldId`=ttf.`fieldId`';
|
|
$fields = $this->fetchAll($query, [$itemId]);
|
|
|
|
foreach ($fields as $field) {
|
|
if (empty($translation[$field['name']]) || $field['type'] != $translation[$field['name']]['type'] || $field['options'] != $translation[$field['name']]['options']) {
|
|
// delete the field
|
|
$this->itemFields()->delete(['itemId' => $field['itemId'], 'fieldId' => $field['fieldId']]);
|
|
} else {
|
|
// transfer
|
|
$this->itemFields()->update(
|
|
[
|
|
'fieldId' => $translation[$field['name']]['fieldId'],
|
|
],
|
|
[
|
|
'itemId' => $field['itemId'],
|
|
'fieldId' => $field['fieldId'],
|
|
]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* copy the fields of one item ($from) to another one ($to) of the same tracker - except/only for some fields */
|
|
/* note: can not use the generic function as they return not all the multilingual fields */
|
|
public function copy_item($from, $to, $except = null, $only = null, $status = null)
|
|
{
|
|
global $user, $prefs;
|
|
|
|
if ($prefs['feature_categories'] == 'y') {
|
|
$categlib = TikiLib::lib('categ');
|
|
$cats = $categlib->get_object_categories('trackeritem', $from);
|
|
}
|
|
if (empty($to)) {
|
|
$is_new = 'y';
|
|
$info_to['trackerId'] = $this->items()->fetchOne('trackerId', ['itemId' => $from]);
|
|
$info_to['status'] = empty($status) ? $this->items()->fetchOne('status', ['itemId' => $from]) : $status;
|
|
$info_to['created'] = $info_to['lastModif'] = $this->now;
|
|
$info_to['createdBy'] = $info_to['lastModifBy'] = $user;
|
|
$to = $this->items()->insert($info_to);
|
|
}
|
|
|
|
$query = 'select ttif.*, ttf.`type`, ttf.`options` from `tiki_tracker_item_fields` ttif left join `tiki_tracker_fields` ttf on (ttif.`fieldId` = ttf.`fieldId`) where `itemId`=?';
|
|
$result = $this->fetchAll($query, [$from]);
|
|
$clean = [];
|
|
$factory = new Tracker_Field_Factory();
|
|
foreach ($result as $res) {
|
|
$typeInfo = $factory->getFieldInfo($res['type']);
|
|
$options = Tracker_Options::fromSerialized($res['options'], $typeInfo);
|
|
$res['options_array'] = $options->buildOptionsArray();
|
|
|
|
if ($prefs['feature_categories'] == 'y' && $res['type'] == 'e') {
|
|
//category
|
|
if (
|
|
(! empty($except) && in_array($res['fieldId'], $except))
|
|
|| (! empty($only) && ! in_array($res['fieldId'], $only))
|
|
) {
|
|
// take away the categories from $cats
|
|
if (ctype_digit($res['options_array'][0]) && $res['options_array'][0] > 0) {
|
|
$filter = ['identifier' => $res['options_array'][0], 'type' => 'children'];
|
|
} else {
|
|
$filter = null;
|
|
}
|
|
$children = $categlib->getCategories($filter, true, false);
|
|
$local = [];
|
|
foreach ($children as $child) {
|
|
$local[] = $child['categId'];
|
|
}
|
|
$cats = array_diff($cats, $local);
|
|
}
|
|
}
|
|
|
|
if (
|
|
(! empty($except) && in_array($res['fieldId'], $except))
|
|
|| (! empty($only) && ! in_array($res['fieldId'], $only))
|
|
|| ($res['type'] == 'q')
|
|
) {
|
|
continue;
|
|
}
|
|
if (! empty($is_new) && in_array($res['type'], ['u', 'g', 'I']) && ($res['options_array'][0] == 1 || $res['options_array'][0] == 2)) {
|
|
$res['value'] = ($res['type'] == 'u') ? $user : (($res['type'] == 'g') ? $_SESSION['u_info']['group'] : TikiLib::get_ip_address());
|
|
}
|
|
if (in_array($res['type'], ['A', 'N'])) {
|
|
// attachment - image
|
|
continue; //not done yet
|
|
}
|
|
//echo "duplic".$res['fieldId'].' '. $res['value'].'<br>';
|
|
if (! in_array($res['fieldId'], $clean)) {
|
|
$this->itemFields()->delete(['itemId' => $to, 'fieldId' => $res['fieldId']]);
|
|
$clean[] = $res['fieldId'];
|
|
}
|
|
|
|
$data = [
|
|
'itemId' => $to,
|
|
'fieldId' => $res['fieldId'],
|
|
'value' => $res['value'],
|
|
];
|
|
|
|
$this->itemFields()->insert($data);
|
|
}
|
|
|
|
if (! empty($cats)) {
|
|
$trackerId = $this->items()->fetchOne('trackerId', ['itemId' => $from]);
|
|
$this->categorized_item($trackerId, $to, "item $to", $cats);
|
|
}
|
|
return $to;
|
|
}
|
|
|
|
public function export_attachment($itemId, $archive)
|
|
{
|
|
global $prefs;
|
|
$files = $this->list_item_attachments($itemId, 0, -1, 'attId_asc');
|
|
foreach ($files['data'] as $file) {
|
|
$localZip = "item_$itemId/" . $file['filename'];
|
|
$complete = $this->get_item_attachment($file['attId']);
|
|
if (! empty($complete['path']) && file_exists($prefs['t_use_dir'] . $complete['path'])) {
|
|
if (! $archive->addFile($prefs['t_use_dir'] . $complete['path'], $localZip)) {
|
|
return false;
|
|
}
|
|
} elseif (! empty($complete['data'])) {
|
|
if (! $archive->addFromString($localZip, $complete['data'])) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* fill a calendar structure with items
|
|
* fieldIds contains one date or 2 dates
|
|
*/
|
|
public function fillTableViewCell($items, $fieldIds, &$cell)
|
|
{
|
|
$smarty = TikiLib::lib('smarty');
|
|
if (empty($items)) {
|
|
return;
|
|
}
|
|
$iStart = -1;
|
|
$iEnd = -1;
|
|
foreach ($items[0]['field_values'] as $i => $field) {
|
|
if ($field['fieldId'] == $fieldIds[0]) {
|
|
$iStart = $i;
|
|
$iEnd = $i; //$end can be the same as start
|
|
} elseif (count($fieldIds) > 1 && $field['fieldId'] == $fieldIds[1]) {
|
|
$iEnd = $i;
|
|
}
|
|
}
|
|
foreach ($cell as $i => $line) {
|
|
foreach ($line as $j => $day) {
|
|
if (! $day['focus']) {
|
|
continue;
|
|
}
|
|
$overs = [];
|
|
foreach ($items as $item) {
|
|
$endDay = TikiLib::make_time(23, 59, 59, $day['month'], $day['day'], $day['year']);
|
|
if (
|
|
(count($fieldIds) == 1 && $item['field_values'][$iStart]['value'] >= $day['date'] && $item['field_values'][$iStart]['value'] <= $endDay)
|
|
|| (count($fieldIds) > 1 && $item['field_values'][$iStart]['value'] <= $endDay && $item['field_values'][$iEnd]['value'] >= $day['date'])
|
|
) {
|
|
$cell[$i][$j]['items'][] = $item;
|
|
$overs[] = preg_replace('|(<br /> *)*$|m', '', $item['over']);
|
|
}
|
|
}
|
|
if (! empty($overs)) {
|
|
$smarty->assign_by_ref('overs', $overs);
|
|
$cell[$i][$j]['over'] = $smarty->fetch('tracker_calendar_over.tpl');
|
|
}
|
|
}
|
|
}
|
|
//echo '<pre>'; print_r($cell); echo '</pre>';
|
|
}
|
|
|
|
public function get_tracker_by_name($name)
|
|
{
|
|
return $this->trackers()->fetchOne('trackerId', ['name' => $name]);
|
|
}
|
|
|
|
public function get_field_by_name($trackerId, $fieldName)
|
|
{
|
|
return $this->fields()->fetchOne('fieldId', ['trackerId' => $trackerId, 'name' => $fieldName]);
|
|
}
|
|
|
|
public function get_field_by_names($trackerName, $fieldName)
|
|
{
|
|
$trackerId = $this->trackers()->fetchOne('trackerId', ['name' => $trackerName]);
|
|
return $fieldId = $this->fields()->fetchOne('fieldId', ['trackerId' => $trackerId, 'name' => $fieldName]);
|
|
}
|
|
|
|
public function get_fields_by_names($trackerName, $fieldNames)
|
|
{
|
|
$fields = [];
|
|
foreach ($fieldNames as $fieldName) {
|
|
$fields[$fieldName] = $this->get_field_by_names($trackerName, $fieldName);
|
|
}
|
|
return $fields;
|
|
}
|
|
|
|
public function get_fields_by_type($type)
|
|
{
|
|
$fields = $this->fields();
|
|
return $fields->fetchAll(
|
|
$fields->all(),
|
|
['type' => $fields->exactly($type)]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get a field handler for a specific fieldtype. The handler comes initialized with the field / item data passed.
|
|
* @param array $field.
|
|
* <pre>
|
|
* $field = array(
|
|
* // required
|
|
* 'trackerId' => 1 // trackerId
|
|
* );
|
|
* </pre
|
|
* @param array $item - array('itemId1' => value1, 'itemid2' => value2)
|
|
* @return Tracker_Field_Abstract $tracker_field_handler - i.e. Tracker_Field_Text
|
|
*/
|
|
public function get_field_handler($field, $item = [])
|
|
{
|
|
if (! isset($field['trackerId'])) {
|
|
return false;
|
|
}
|
|
|
|
$trackerId = (int) $field['trackerId'];
|
|
$definition = Tracker_Definition::get($trackerId);
|
|
|
|
if (! $definition) {
|
|
return false;
|
|
}
|
|
|
|
return $definition->getFieldFactory()->getHandler($field, $item);
|
|
}
|
|
|
|
public function get_field_value($field, $item)
|
|
{
|
|
$handler = $this->get_field_handler($field, $item);
|
|
$values = $handler->getFieldData();
|
|
|
|
return isset($values['value']) ? $values['value'] : null;
|
|
}
|
|
|
|
private function parse_comment($data)
|
|
{
|
|
return nl2br(htmlspecialchars($data));
|
|
}
|
|
|
|
public function send_replace_item_notifications($args)
|
|
{
|
|
global $prefs, $user;
|
|
|
|
// Don't send a notification if this operation is part of a bulk import
|
|
if ($args['bulk_import']) {
|
|
return;
|
|
}
|
|
|
|
$trackerId = $args['trackerId'];
|
|
$itemId = $args['object'];
|
|
|
|
$new_values = $args['values'];
|
|
$old_values = $args['old_values'];
|
|
|
|
$tracker_definition = Tracker_Definition::get($trackerId);
|
|
if (! $tracker_definition) {
|
|
return;
|
|
}
|
|
$tracker_info = $tracker_definition->getInformation();
|
|
|
|
$watchers = $this->get_notification_emails($trackerId, $itemId, $tracker_info, $new_values['status'], $old_values['status']);
|
|
|
|
// not a great test for a new item but we don't get the event type here
|
|
$created = empty($old_values) || $old_values === ['status' => ''];
|
|
$notifyOn = isset($tracker_info['notifyOn']) ? $tracker_info['notifyOn'] : 'both';
|
|
if ($created && ($notifyOn != 'both' && $notifyOn != 'creation')) {
|
|
return;
|
|
} else if (! $created && ($notifyOn != 'both' && $notifyOn != 'update')) {
|
|
return;
|
|
}
|
|
|
|
if (count($watchers) > 0) {
|
|
$simpleEmail = isset($tracker_info['simpleEmail']) ? $tracker_info['simpleEmail'] : "n";
|
|
|
|
$trackerName = $tracker_info['name'];
|
|
if (! isset($_SERVER["SERVER_NAME"])) {
|
|
$_SERVER["SERVER_NAME"] = $_SERVER["HTTP_HOST"];
|
|
}
|
|
include_once('lib/webmail/tikimaillib.php');
|
|
if ($simpleEmail == "n") {
|
|
$mail_main_value_fieldId = $this->get_main_field($trackerId);
|
|
$mail_main_value_field = $tracker_definition->getField($mail_main_value_fieldId);
|
|
if (in_array($mail_main_value_field['type'], ['r', 'q'])) {
|
|
// Item Link & auto-inc are special cases as field value is not the displayed text. There might be other such field types.
|
|
$handler = $this->get_field_handler($mail_main_value_field);
|
|
$desc = $handler->renderOutput(['list_mode' => 'csv']);
|
|
} else {
|
|
$desc = $this->get_item_value($trackerId, $itemId, $mail_main_value_fieldId);
|
|
}
|
|
$smarty = TikiLib::lib('smarty');
|
|
|
|
$smarty->assign('mail_date', $this->now);
|
|
$smarty->assign('mail_user', $user);
|
|
$smarty->assign('mail_itemId', $itemId);
|
|
$smarty->assign('mail_item_desc', $desc);
|
|
$smarty->assign('mail_trackerId', $trackerId);
|
|
$smarty->assign('mail_trackerName', $trackerName);
|
|
$smarty->assign('server_name', $_SERVER['SERVER_NAME']);
|
|
$foo = parse_url($_SERVER["REQUEST_URI"]);
|
|
$machine = $this->httpPrefix(true) . $foo["path"];
|
|
$smarty->assign('mail_machine', $machine);
|
|
$parts = explode('/', $foo['path']);
|
|
if (count($parts) > 1) {
|
|
unset($parts[count($parts) - 1]);
|
|
}
|
|
$smarty->assign('mail_machine_raw', $this->httpPrefix(true) . implode('/', $parts));
|
|
foreach ($watchers as $watcher) {
|
|
// assign these variables inside the loop as this->tracker_render_values overrides them in case trackeroutput or similar is used
|
|
$smarty->assign_by_ref('status', $new_values['status']);
|
|
$smarty->assign_by_ref('status_old', $old_values['status']);
|
|
// expose the pretty tracker fields to the email tpls
|
|
foreach ($tracker_definition->getFields() as $field) {
|
|
$fieldId = $field['fieldId'];
|
|
$old_value = isset($old_values[$fieldId]) ? $old_values[$fieldId] : '';
|
|
$new_value = isset($new_values[$fieldId]) ? $new_values[$fieldId] : '';
|
|
$smarty->assign('f_' . $fieldId, $new_value);
|
|
$smarty->assign('f_' . $field['permName'], $new_value);
|
|
$smarty->assign('f_old_' . $fieldId, $old_value);
|
|
$smarty->assign('f_old_' . $field['permName'], $old_value);
|
|
$smarty->assign('f_name_' . $fieldId, $field['name']);
|
|
$smarty->assign('f_name_' . $field['permName'], $field['name']);
|
|
}
|
|
$watcher['language'] = $this->get_user_preference($watcher['user'], 'language', $prefs['site_language']);
|
|
if ($created) {
|
|
$label = tra('Item Creation', $watcher['language']);
|
|
} else {
|
|
$label = tra('Item Modification', $watcher['language']);
|
|
}
|
|
$mail_action = "\r\n$label\r\n\r\n";
|
|
$mail_action .= tra('Tracker', $watcher['language']) . ":\n " . tra($trackerName, $watcher['language']) . "\r\n";
|
|
$mail_action .= tra('Item', $watcher['language']) . ":\n $itemId $desc";
|
|
|
|
$smarty->assign('mail_action', $mail_action);
|
|
|
|
if (! isset($watcher['template'])) {
|
|
$watcher['template'] = '';
|
|
}
|
|
$content = $this->parse_notification_template($watcher['template']);
|
|
|
|
$subject = $smarty->fetchLang($watcher['language'], $content['subject']);
|
|
|
|
// get the diff for changes for this watcher
|
|
$the_data = $this->generate_watch_data($old_values, $new_values, $trackerId, $itemId, $args['version'], $watcher['user']);
|
|
|
|
if (empty($the_data) && $prefs['tracker_always_notify'] !== 'y') {
|
|
continue;
|
|
}
|
|
|
|
if ($tracker_info['doNotShowEmptyField'] === 'y') {
|
|
// remove empty fields if tracker says so
|
|
$the_data = preg_replace('/\[-\[.*?\]-\] -\[\(.*?\)\]-:\n\n----------\n/', '', $the_data);
|
|
}
|
|
|
|
list($watcher_data, $watcher_subject) = $this->translate_watch_data($the_data, $subject, $watcher['language']);
|
|
|
|
$smarty->assign('mail_data', $watcher_data);
|
|
if (isset($watcher['action'])) {
|
|
$smarty->assign('mail_action', $watcher['action']);
|
|
}
|
|
$smarty->assign('mail_to_user', $watcher['user']);
|
|
$mail_data = $smarty->fetchLang($watcher['language'], $content['template']);
|
|
|
|
// if the tpl returns nothing then don't send the mail
|
|
if (! empty($mail_data)) {
|
|
$mail = new TikiMail($watcher['user']);
|
|
$mail->setSubject($watcher_subject);
|
|
if (isset($watcher['templateFormat']) && $watcher['templateFormat'] == 'html') {
|
|
$mail->setHtml($mail_data, str_replace(' ', ' ', strip_tags($mail_data)));
|
|
} else {
|
|
$mail->setText(str_replace(' ', ' ', strip_tags($mail_data)));
|
|
}
|
|
$mail->send([$watcher['email']]);
|
|
}
|
|
}
|
|
} else {
|
|
// Use simple email
|
|
$foo = parse_url($_SERVER["REQUEST_URI"]);
|
|
$machine = $this->httpPrefix(true) . $foo["path"];
|
|
$parts = explode('/', $foo['path']);
|
|
if (count($parts) > 1) {
|
|
unset($parts[count($parts) - 1]);
|
|
}
|
|
$machine = $this->httpPrefix(true) . implode('/', $parts);
|
|
|
|
$userlib = TikiLib::lib('user');
|
|
|
|
if (! empty($user)) {
|
|
$my_sender = $userlib->get_user_email($user);
|
|
} else {
|
|
// look if a email field exists
|
|
$fieldId = $this->get_field_id_from_type($trackerId, 'm');
|
|
if (! empty($fieldId)) {
|
|
$my_sender = $this->get_item_value($trackerId, $itemId, $fieldId);
|
|
}
|
|
}
|
|
|
|
$the_data = $this->generate_watch_data($old_values, $new_values, $trackerId, $itemId, $args['version']);
|
|
|
|
if (empty($the_data) && $prefs['tracker_always_notify'] !== 'y') {
|
|
return;
|
|
}
|
|
|
|
// Try to find a Subject in $the_data looking for strings marked "-[Subject]-" TODO: remove the tra (language translation by submitter)
|
|
$the_string = '/^\[-\[' . tra('Subject') . '\]-\] -\[[^\]]*\]-:\n(.*)/m';
|
|
$subject_test_unchanged = preg_match($the_string, $the_data, $unchanged_matches);
|
|
$the_string = '/^\[-\[' . tra('Subject') . '\]-\]:\n(.*)\n(.*)\n\n(.*)\n(.*)/m';
|
|
$subject_test_changed = preg_match($the_string, $the_data, $matches);
|
|
$subject = '';
|
|
|
|
if ($subject_test_unchanged == 1) {
|
|
$subject = $unchanged_matches[1];
|
|
}
|
|
if ($subject_test_changed == 1) {
|
|
$subject = $matches[1] . ' ' . $matches[2] . ' ' . $matches[3] . ' ' . $matches[4];
|
|
}
|
|
|
|
$i = 0;
|
|
foreach ($watchers as $watcher) {
|
|
$watcher['language'] = $this->get_user_preference($watcher['user'], 'language', $prefs['site_language']);
|
|
$mail = new TikiMail($watcher['user']);
|
|
list($watcher_data, $watcher_subject) = $this->translate_watch_data($the_data, $subject, $watcher['language']);
|
|
|
|
$mail->setSubject('[' . $trackerName . '] ' . str_replace('> ', '', $watcher_subject) . ' (' . tra('Tracker was modified at %0 by %1', $watcher['language'], false, [$_SERVER["SERVER_NAME"], $user]) . ')');
|
|
$mail->setText(tra('View the tracker item at:', $watcher['language']) . " $machine/tiki-view_tracker_item.php?itemId=$itemId\n\n" . $watcher_data);
|
|
if (! empty($my_sender)) {
|
|
$mail->setReplyTo($my_sender);
|
|
}
|
|
$mail->send([$watcher['email']]);
|
|
$i++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private function parse_notification_template($template)
|
|
{
|
|
$tikilib = TikiLib::lib('tiki');
|
|
$subject = "";
|
|
if (! empty($template)) { //tpl
|
|
if (! preg_match('/^(:?tpl)?wiki\:/', $template, $match)) {
|
|
if (! preg_match('/\.tpl$/', $template)) { // template file
|
|
$template .= '.tpl';
|
|
}
|
|
$template = 'mail/' . $template;
|
|
$subject = str_replace('.tpl', '_subject.tpl', $template);
|
|
} else { // wiki template
|
|
$pageName = substr($template, strlen($match[0]));
|
|
if (! $tikilib->page_exists($pageName)) {
|
|
Feedback::error(tr('Missing wiki email template page "%0"', htmlspecialchars($template)));
|
|
$template = '';
|
|
} else {
|
|
$subject_name = str_replace('tpl', 'subject tpl', $pageName);
|
|
if ($tikilib->page_exists($subject_name)) {
|
|
$subject = $match[0] . $subject_name;
|
|
} else {
|
|
$subject_name = str_replace('tpl', 'subject-tpl', $pageName);
|
|
if ($tikilib->page_exists($subject_name)) {
|
|
$subject = $match[0] . $subject_name;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (empty($template)) {
|
|
$template = 'mail/tracker_changed_notification.tpl';
|
|
}
|
|
if (empty($subject)) {
|
|
$subject = 'mail/tracker_changed_notification_subject.tpl';
|
|
}
|
|
return [
|
|
'subject' => $subject,
|
|
'template' => $template,
|
|
];
|
|
}
|
|
|
|
|
|
/**
|
|
* Translate the watch data and subject for each watcher
|
|
*
|
|
* @param string $the_data
|
|
* @param string $subject
|
|
* @param string $language
|
|
* @return array translated [data, subject]
|
|
*/
|
|
private function translate_watch_data($the_data, $subject, $language)
|
|
{
|
|
// first we look for strings marked "-[...]-" to translate by watcher language
|
|
$watcher_subject = $subject;
|
|
$watcher_data = $the_data;
|
|
|
|
if (preg_match_all('/-\[([^\]]*)\]-/', $the_data, $tra_matches) > 0 && $language !== 'en') {
|
|
foreach ($tra_matches[1] as $match) {
|
|
// now we replace the marked strings with correct translations
|
|
$tra_replace = tra($match, $language);
|
|
$tra_match = "/-\[" . preg_quote($match) . "\]-/m";
|
|
$watcher_subject = preg_replace($tra_match, $tra_replace, $watcher_subject);
|
|
$watcher_data = preg_replace($tra_match, $tra_replace, $watcher_data);
|
|
}
|
|
}
|
|
return [$watcher_data, $watcher_subject];
|
|
}
|
|
|
|
private function generate_watch_data($old, $new, $trackerId, $itemId, $version, $watcher = '')
|
|
{
|
|
global $prefs;
|
|
|
|
$userslib = TikiLib::lib('user');
|
|
|
|
$tracker_definition = Tracker_Definition::get($trackerId);
|
|
if (! $tracker_definition) {
|
|
return '';
|
|
}
|
|
|
|
$oldStatus = $old['status'];
|
|
$newStatus = $new['status'];
|
|
$changed = false;
|
|
|
|
$the_data = '';
|
|
if (! empty($oldStatus) || ! empty($newStatus)) {
|
|
if (! empty($itemId) && $oldStatus != $newStatus) {
|
|
$this->log($version, $itemId, -1, $oldStatus);
|
|
}
|
|
$the_data .= '-[Status]-: ';
|
|
$statusTypes = $this->status_types('en'); // Fetch in english to translate to watcher language
|
|
if (isset($oldStatus) && $oldStatus != $newStatus) {
|
|
$the_data .= isset($statusTypes[$oldStatus]['label']) ? '-[' . $statusTypes[$oldStatus]['label'] . ']- -> ' : '';
|
|
$changed = true;
|
|
}
|
|
|
|
if (! empty($newStatus)) {
|
|
$the_data .= '-[' . $statusTypes[$newStatus]['label'] . ']-';
|
|
}
|
|
$the_data .= "\n----------\n";
|
|
}
|
|
|
|
foreach ($tracker_definition->getFields() as $field) {
|
|
$fieldId = $field['fieldId'];
|
|
|
|
$old_value = isset($old[$fieldId]) ? $old[$fieldId] : '';
|
|
$new_value = isset($new[$fieldId]) ? $new[$fieldId] : '';
|
|
|
|
if ($old_value == $new_value) {
|
|
continue;
|
|
}
|
|
|
|
$handler = $this->get_field_handler($field);
|
|
if ($handler) {
|
|
$userOk = (! $watcher || $watcher === 'admin');
|
|
if (! $userOk && is_array($field['visibleBy']) && ! empty($field['visibleBy'])) {
|
|
foreach ($field['visibleBy'] as $group) {
|
|
$userOk = $userslib->user_is_in_group($watcher, $group);
|
|
if ($userOk) {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
$userOk = true;
|
|
}
|
|
if ($userOk) {
|
|
$the_data .= $handler->watchCompare($old_value, $new_value);
|
|
}
|
|
} else {
|
|
$the_data .= tr('Tracker field not enabled: fieldId=%0 type=%1', $field['fieldId'], tra($field['type'])) . "\n";
|
|
}
|
|
$the_data .= "\n----------\n";
|
|
$changed = true;
|
|
}
|
|
|
|
if ($changed || $prefs['tracker_always_notify'] === 'y') {
|
|
return $the_data;
|
|
} else {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
private function tracker_is_syncable($trackerId)
|
|
{
|
|
global $prefs;
|
|
if (! empty($prefs["user_trackersync_trackers"])) {
|
|
$trackersync_trackers = unserialize($prefs["user_trackersync_trackers"]);
|
|
return in_array($trackerId, $trackersync_trackers);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private function get_tracker_item_users($trackerId, $values)
|
|
{
|
|
global $user, $prefs;
|
|
$userlib = TikiLib::lib('user');
|
|
$trackersync_users = [$user];
|
|
|
|
$definition = Tracker_Definition::get($trackerId);
|
|
|
|
if ($definition) {
|
|
$fieldId = $definition->getUserField();
|
|
$value = isset($values[$fieldId]) ? $values[$fieldId] : '';
|
|
|
|
if ($value) {
|
|
$trackersync_users = $this->parse_user_field($value);
|
|
}
|
|
}
|
|
|
|
return $trackersync_users;
|
|
}
|
|
|
|
private function get_tracker_item_coordinates($trackerId, $values)
|
|
{
|
|
$definition = Tracker_Definition::get($trackerId);
|
|
|
|
if ($definition && $fieldId = $definition->getGeolocationField()) {
|
|
if (isset($values[$fieldId])) {
|
|
return TikiLib::lib('geo')->parse_coordinates($values[$fieldId]);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function sync_user_lang($args)
|
|
{
|
|
global $prefs;
|
|
|
|
$trackerId = $args['trackerId'];
|
|
|
|
if ($prefs['user_trackersync_lang'] != 'y') {
|
|
return;
|
|
}
|
|
|
|
if (! $this->tracker_is_syncable($trackerId)) {
|
|
return;
|
|
}
|
|
|
|
$trackersync_users = $this->get_tracker_item_users($trackerId, $args['values']);
|
|
if (empty($trackersync_users)) {
|
|
return;
|
|
}
|
|
|
|
$definition = Tracker_Definition::get($trackerId);
|
|
if ($definition && $fieldId = $definition->getLanguageField()) {
|
|
foreach ($trackersync_users as $trackersync_user) {
|
|
TikiLib::lib('tiki')->set_user_preference($trackersync_user, 'language', $args['values'][$fieldId]);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function sync_user_realname($args)
|
|
{
|
|
global $prefs;
|
|
|
|
$trackerId = $args['trackerId'];
|
|
|
|
if (! $this->tracker_is_syncable($trackerId)) {
|
|
return;
|
|
}
|
|
|
|
$trackersync_users = $this->get_tracker_item_users($trackerId, $args['values']);
|
|
if (empty($trackersync_users)) {
|
|
return;
|
|
}
|
|
|
|
if (! empty($prefs["user_trackersync_realname"])) {
|
|
// Fields to concatenate are delimited by + and priority sets are delimited by ,
|
|
$trackersync_realnamefields = preg_split('/\s*,\s*/', $prefs["user_trackersync_realname"]);
|
|
|
|
foreach ($trackersync_realnamefields as $fields) {
|
|
$parts = [];
|
|
$fields = preg_split('/\s*\+\s*/', $fields);
|
|
foreach ($fields as $field) {
|
|
$field = (int) $field;
|
|
if (isset($args['values'][$field])) {
|
|
$parts[] = $args['values'][$field];
|
|
}
|
|
}
|
|
|
|
$realname = implode(' ', $parts);
|
|
|
|
if (! empty($realname)) {
|
|
foreach ($trackersync_users as $trackersync_user) {
|
|
TikiLib::lib('tiki')->set_user_preference($trackersync_user, 'realName', $realname);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public function sync_user_geo($args)
|
|
{
|
|
global $prefs;
|
|
|
|
$trackerId = $args['trackerId'];
|
|
|
|
if (! $this->tracker_is_syncable($trackerId)) {
|
|
return;
|
|
}
|
|
|
|
$trackersync_users = $this->get_tracker_item_users($trackerId, $args['values']);
|
|
if (empty($trackersync_users)) {
|
|
return;
|
|
}
|
|
|
|
if ($geo = $this->get_tracker_item_coordinates($trackerId, $args['values'])) {
|
|
$tikilib = TikiLib::lib('tiki');
|
|
|
|
foreach ($trackersync_users as $trackersync_user) {
|
|
$tikilib->set_user_preference($trackersync_user, 'lon', $geo['lon']);
|
|
$tikilib->set_user_preference($trackersync_user, 'lat', $geo['lat']);
|
|
if (! empty($geo['zoom'])) {
|
|
$tikilib->set_user_preference($trackersync_user, 'zoom', $geo['zoom']);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public function sync_item_geo($args)
|
|
{
|
|
$trackerId = $args['trackerId'];
|
|
$itemId = $args['object'];
|
|
|
|
if ($geo = $this->get_tracker_item_coordinates($trackerId, $args['values'])) {
|
|
if ($geo && $itemId) {
|
|
TikiLib::lib('geo')->set_coordinates('trackeritem', $itemId, $geo);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function sync_user_groups($args)
|
|
{
|
|
global $prefs;
|
|
|
|
$trackerId = $args['trackerId'];
|
|
|
|
if (! $this->tracker_is_syncable($trackerId)) {
|
|
return;
|
|
}
|
|
|
|
$trackersync_users = $this->get_tracker_item_users($trackerId, $args['values']);
|
|
if (empty($trackersync_users)) {
|
|
return;
|
|
}
|
|
|
|
if (empty($prefs["user_trackersync_groups"])) {
|
|
return;
|
|
}
|
|
|
|
$definition = Tracker_Definition::get($trackerId);
|
|
$userslib = TikiLib::lib('user');
|
|
|
|
$trackersync_groupfields = preg_split('/\s*,\s*/', $prefs["user_trackersync_groups"]);
|
|
foreach ($trackersync_groupfields as $field) {
|
|
$field = (int)$field;
|
|
if (! isset($args['values'][$field])) {
|
|
continue;
|
|
}
|
|
$field = $definition->getField($field);
|
|
$handler = $this->get_field_handler($field, $args['values']);
|
|
$group = $handler->renderOutput();
|
|
if (empty($group) || ! $userslib->group_exists($group)) {
|
|
continue;
|
|
}
|
|
foreach ($trackersync_users as $trackersync_user) {
|
|
if (! $userslib->user_exists($trackersync_user)) {
|
|
continue;
|
|
}
|
|
if ($userslib->user_is_in_group($trackersync_user, $group)) {
|
|
continue;
|
|
}
|
|
$userslib->assign_user_to_group($trackersync_user, $group);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function sync_item_auto_categories($args)
|
|
{
|
|
$trackerId = $args['trackerId'];
|
|
$itemId = $args['object'];
|
|
$definition = Tracker_Definition::get($trackerId);
|
|
|
|
if ($definition && $definition->isEnabled('autoCreateCategories')) {
|
|
$categlib = TikiLib::lib('categ');
|
|
$tracker_item_desc = $this->get_isMain_value($trackerId, $itemId);
|
|
|
|
// Verify that parentCat exists Or Create It
|
|
$parentcategId = $categlib->get_category_id("Tracker $trackerId");
|
|
if (! isset($parentcategId)) {
|
|
$parentcategId = $categlib->add_category(0, "Tracker $trackerId", $definition->getConfiguration('description'));
|
|
}
|
|
// Verify that the sub Categ doesn't already exists
|
|
$currentCategId = $categlib->get_category_id("Tracker Item $itemId");
|
|
if (! isset($currentCategId) || $currentCategId == 0) {
|
|
$currentCategId = $categlib->add_category($parentcategId, "Tracker Item $itemId", $tracker_item_desc);
|
|
} else {
|
|
$categlib->update_category($currentCategId, "Tracker Item $itemId", $tracker_item_desc, $parentcategId);
|
|
}
|
|
$cat_type = "trackeritem";
|
|
$cat_objid = $itemId;
|
|
$cat_desc = '';
|
|
$cat_name = "Tracker Item $itemId";
|
|
$cat_href = "tiki-view_tracker_item.php?trackerId=$trackerId&itemId=$itemId";
|
|
// ?? HAS to do it ?? $categlib->uncategorize_object($cat_type, $cat_objid);
|
|
$catObjectId = $categlib->is_categorized($cat_type, $cat_objid);
|
|
if (! $catObjectId) {
|
|
$catObjectId = $categlib->add_categorized_object($cat_type, $cat_objid, $cat_desc, $cat_name, $cat_href);
|
|
}
|
|
$categlib->categorize($catObjectId, $currentCategId);
|
|
}
|
|
}
|
|
|
|
private function get_viewable_category_field_cats($trackerId)
|
|
{
|
|
$definition = Tracker_Definition::get($trackerId);
|
|
$categories = [];
|
|
|
|
if (! $definition) {
|
|
return [];
|
|
}
|
|
|
|
foreach ($definition->getFields() as $field) {
|
|
if ($field['type'] == 'e') {
|
|
$parentId = $field['options_array'][0];
|
|
$descends = isset($field['options_array'][3]) && $field['options_array'][3] == 1;
|
|
if (ctype_digit($parentId) && $parentId > 0) {
|
|
$cats = TikiLib::lib('categ')->getCategories(['identifier' => $parentId, 'type' => $descends ? 'descendants' : 'children']);
|
|
} else {
|
|
$cats = [];
|
|
}
|
|
|
|
foreach ($cats as $c) {
|
|
$categories[] = $c['categId'];
|
|
}
|
|
}
|
|
}
|
|
|
|
return array_unique(array_filter($categories));
|
|
}
|
|
|
|
public function invalidate_item_cache($args)
|
|
{
|
|
$itemId = $args['object'];
|
|
|
|
$cachelib = TikiLib::lib('cache');
|
|
$cachelib->invalidate('trackerItemLabel' . $itemId);
|
|
|
|
if (isset($args['values']) && isset($args['old_values'])) {
|
|
$fields = array_merge(array_keys($args['values']), array_keys($args['old_values']));
|
|
$fields = array_unique($fields);
|
|
}
|
|
|
|
if (! empty($fields)) {
|
|
foreach ($fields as $fieldId) {
|
|
$old = isset($args['old_values'][$fieldId]) ? $args['old_values'][$fieldId] : null;
|
|
$new = isset($args['values'][$fieldId]) ? $args['values'][$fieldId] : null;
|
|
|
|
if ($old !== $new) {
|
|
$this->invalidate_field_cache($fieldId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private function invalidate_field_cache($fieldId)
|
|
{
|
|
global $prefs, $user;
|
|
$multi_languages = $prefs['available_languages'];
|
|
if (! $multi_languages) {
|
|
$multi_languages = [];
|
|
}
|
|
|
|
$multi_languages[] = '';
|
|
|
|
$cachelib = TikiLib::lib('cache');
|
|
|
|
foreach ($multi_languages as $lang) {
|
|
$cachelib->invalidate(md5('trackerfield' . $fieldId . 'o' . $user . $lang));
|
|
$cachelib->invalidate(md5('trackerfield' . $fieldId . 'c' . $user . $lang));
|
|
$cachelib->invalidate(md5('trackerfield' . $fieldId . 'p' . $user . $lang));
|
|
$cachelib->invalidate(md5('trackerfield' . $fieldId . 'op' . $user . $lang));
|
|
$cachelib->invalidate(md5('trackerfield' . $fieldId . 'oc' . $user . $lang));
|
|
$cachelib->invalidate(md5('trackerfield' . $fieldId . 'pc' . $user . $lang));
|
|
$cachelib->invalidate(md5('trackerfield' . $fieldId . 'opc' . $user . $lang));
|
|
}
|
|
}
|
|
|
|
public function group_tracker_create($args)
|
|
{
|
|
global $user, $group;
|
|
$trackerId = $args['trackerId'];
|
|
$itemId = $args['object'];
|
|
$new_itemId = isset($args['new_itemId']) ? $args['new_itemId'] : '';
|
|
$tracker_info = isset($args['tracker_info']) ? $args['tracker_info'] : '';
|
|
$definition = Tracker_Definition::get($trackerId);
|
|
|
|
if ($definition && $definition->isEnabled('autoCreateGroup')) {
|
|
$creatorGroupFieldId = $definition->getWriterGroupField();
|
|
|
|
if (! empty($creatorGroupFieldId) && $definition->isEnabled('autoAssignGroupItem')) {
|
|
$autoCopyGroup = $definition->getConfiguration('autoCopyGroup');
|
|
if ($autoCopyGroup) {
|
|
$this->modify_field($new_itemId, $tracker_info['autoCopyGroup'], $group);
|
|
$fil[$tracker_info['autoCopyGroup']] = $group;
|
|
}
|
|
}
|
|
$desc = $this->get_isMain_value($trackerId, $itemId);
|
|
if (empty($desc)) {
|
|
$desc = $definition->getConfiguration('description');
|
|
}
|
|
|
|
$userlib = TikiLib::lib('user');
|
|
$groupName = $args['values'][$creatorGroupFieldId];
|
|
if ($userlib->add_group($groupName, $desc, '', 0, $trackerId, '', 'y', 0, '', '', $creatorGroupFieldId)) {
|
|
if ($groupId = $definition->getConfiguration('autoCreateGroupInc')) {
|
|
$userlib->group_inclusion($groupName, $this->table('users_groups')->fetchOne('groupName', ['id' => $groupId]));
|
|
}
|
|
}
|
|
if ($definition->isEnabled('autoAssignCreatorGroup')) {
|
|
$userlib->assign_user_to_group($user, $groupName);
|
|
}
|
|
if ($definition->isEnabled('autoAssignCreatorGroupDefault')) {
|
|
$userlib->set_default_group($user, $groupName);
|
|
$_SESSION['u_info']['group'] = $groupName;
|
|
}
|
|
}
|
|
}
|
|
|
|
public function update_tracker_summary($args)
|
|
{
|
|
$items = $this->items();
|
|
$trackerId = (int) $args['trackerId'];
|
|
$cant_items = $items->fetchCount(['trackerId' => $trackerId]);
|
|
$this->trackers()->update(['items' => (int) $cant_items, 'lastModif' => $this->now], ['trackerId' => $trackerId]);
|
|
}
|
|
|
|
public function sync_freetags($args)
|
|
{
|
|
$definition = Tracker_Definition::get($args['trackerId']);
|
|
|
|
if ($definition && $field = $definition->getFreetagField()) {
|
|
global $user;
|
|
$freetaglib = TikiLib::lib('freetag');
|
|
$freetaglib->update_tags($user, $args['object'], 'trackeritem', $args['values'][$field]);
|
|
}
|
|
}
|
|
|
|
public function update_create_missing_pages($args)
|
|
{
|
|
global $user;
|
|
$tikilib = TikiLib::lib('tiki');
|
|
|
|
$definition = Tracker_Definition::get($args['trackerId']);
|
|
if (! $definition) {
|
|
return;
|
|
}
|
|
|
|
foreach ($definition->getFields() as $field) {
|
|
$fieldId = $field['fieldId'];
|
|
$value = isset($args['values'][$fieldId]) ? $args['values'][$fieldId] : '';
|
|
if ($field['type'] == 'k' && $value != '' && ! empty($field['options'][2])) {
|
|
if (! $this->page_exists($value)) {
|
|
$IP = $this->get_ip_address();
|
|
$info = $this->get_page_info($field['options'][2]);
|
|
$tikilib->create_page($value, 0, $info['data'], $tikilib->now, '', $user, $IP, $info['description'], $info['lang'], $info['is_html'], [], $info['wysiwyg'], $info['wiki_authors_style']);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public function get_maximum_value($fieldId)
|
|
{
|
|
return $this->itemFields()->fetchOne($this->itemFields()->expr('MAX(CAST(`value` as UNSIGNED))'), ['fieldId' => (int) $fieldId]);
|
|
}
|
|
|
|
public function sync_categories($args)
|
|
{
|
|
$definition = Tracker_Definition::get($args['trackerId']);
|
|
if (! $definition) {
|
|
return;
|
|
}
|
|
|
|
$ins_categs = [];
|
|
$parent_categs_only = [];
|
|
$tosync = false;
|
|
$managed_fields = [];
|
|
|
|
$categorizedFields = $definition->getCategorizedFields();
|
|
|
|
if (isset($args['supplied'])) {
|
|
// Exclude fields that were not part of the request
|
|
$categorizedFields = array_intersect($categorizedFields, $args['supplied']);
|
|
}
|
|
|
|
foreach ($categorizedFields as $fieldId) {
|
|
if (isset($args['values'][$fieldId])) {
|
|
$ins_categs = array_merge($ins_categs, array_filter(explode(',', $args['values'][$fieldId])));
|
|
$managed_fields[] = $fieldId;
|
|
$tosync = true;
|
|
}
|
|
}
|
|
|
|
if ($tosync) {
|
|
$this->categorized_item($args['trackerId'], $args['object'], "item {$args['object']}", $ins_categs, null, false, $managed_fields);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Render a field value for input or output. The result depends on the fieldtype.
|
|
* Note: Each fieldtype has its own input/output handler.
|
|
* @param array $params - either a complete field array or a trackerid and a permName
|
|
* <pre>
|
|
* $param = array(
|
|
* // required
|
|
* 'field' => array( 'fieldId' => 1, 'trackerId' => 2, 'permName' => 'myPermName', 'etc' => '...')
|
|
* //'trackerId' => 1 // instread of 'field'
|
|
* //'permName>' => 'myPermName' // instread of 'field'
|
|
*
|
|
* // optional
|
|
* 'item' => array('fieldId1' => fieldValue1, 'fieldId2' => fieldValue2) // optional
|
|
* 'itemId' = 5 // itemId
|
|
* 'process' => 'y' // renders the value using the correct field handler
|
|
* 'oldValue' => '' // renders the new and old values using \Tracker_Field_Abstract::renderDiff
|
|
* 'list_mode' => '' // i.e. 'y', 'cvs' or 'text' will be used in \Tracker_Field_Abstract::renderOutput
|
|
* 'smarty_assign' => 'y' // set to n to not assign the value to the $f_fieldId smarty value for pretty trackers
|
|
* )
|
|
* </pre>
|
|
* @return string - rendered value (with html ?). i.e from $r = $handler->renderInput($context), renderOutput or renderDiff
|
|
* @throws Exception
|
|
*/
|
|
public function field_render_value($params)
|
|
{
|
|
// accept either a complete field definition or a trackerId/permName
|
|
if (isset($params['field'])) {
|
|
$field = $params['field'];
|
|
} elseif (isset($params['trackerId'], $params['permName'])) {
|
|
$definition = Tracker_Definition::get($params['trackerId']);
|
|
$field = $definition->getFieldFromPermName($params['permName']);
|
|
} elseif (isset($params['fieldId'])) {
|
|
$field = $this->get_field_info($params['fieldId']);
|
|
} else {
|
|
return tr('Field not specified');
|
|
}
|
|
|
|
// preset $item = array('itemId' => value). Either from param or empty
|
|
$item = isset($params['item']) ? $params['item'] : [];
|
|
|
|
// if we have an itemId, pass it to our new item structure
|
|
if (isset($params['itemId'])) {
|
|
$item['itemId'] = $params['itemId'];
|
|
}
|
|
|
|
// check wether we have a value assigned to $fields.
|
|
// This might be the case if $fields was passed through $params and not from the tracker definition.
|
|
// Build the $items['fieldId'] = value structure
|
|
if (isset($field['fieldId'])) {
|
|
if (isset($field['value'])) {
|
|
$item[$field['fieldId']] = $field['value'];
|
|
} elseif (isset($item['itemId'])) {
|
|
$item[$field['fieldId']] = $this->get_item_value(null, $item['itemId'], $field['fieldId']);
|
|
} elseif (isset($params['value'])) {
|
|
$field['value'] = $params['value'];
|
|
$field['ins_' . $field['fieldId']] = $field['value'];
|
|
$item[$field['fieldId']] = $field['value'];
|
|
}
|
|
}
|
|
|
|
// get the handler for the specific fieldtype.
|
|
$handler = $this->get_field_handler($field, $item);
|
|
|
|
if ($handler) {
|
|
if (! isset($field['value'])) {
|
|
$data = $handler->getFieldData();
|
|
$field['value'] = $data['value'];
|
|
}
|
|
|
|
$r = null;
|
|
|
|
if (! empty($field['encryptionKeyId'])) {
|
|
try {
|
|
$key = new Tiki\Encryption\Key($field['encryptionKeyId']);
|
|
$field['value'] = $item[$field['fieldId']] = $key->decryptData($field['value']);
|
|
} catch (Tiki\Encryption\NotFoundException $e) {
|
|
$r = tr('Field is encrypted with a key that no longer exists!');
|
|
} catch (Tiki\Encryption\Exception $e) {
|
|
$field['value'] = $item[$field['fieldId']] = '';
|
|
$r = tr('Field data is encrypted using key "%0" but where was an error decrypting the data: %1', $key->get('name'), $e->getMessage());
|
|
$r .= ' ' . $key->manualEntry();
|
|
}
|
|
$handler = $this->get_field_handler($field, $item);
|
|
$field = array_merge($field, $handler->getFieldData());
|
|
$handler = $this->get_field_handler($field, $item);
|
|
}
|
|
|
|
if (isset($params['process']) && $params['process'] == 'y') {
|
|
if ($field['type'] === 'e') { // category
|
|
if (! is_array($field['value'])) {
|
|
$categIds = explode(',', $field['value']);
|
|
} else {
|
|
$categIds = $field['value'];
|
|
}
|
|
$requestData = ['ins_' . $field['fieldId'] => $categIds];
|
|
} else {
|
|
$requestData = $field;
|
|
}
|
|
$linkedField = $handler->getFieldData($requestData);
|
|
$field = array_merge($field, $linkedField);
|
|
$field['ins_id'] = 'ins_' . $field['fieldId'];
|
|
$handler = $this->get_field_handler($field, $item);
|
|
}
|
|
|
|
$context = $params;
|
|
$fieldId = $field['fieldId'];
|
|
unset($context['item']);
|
|
unset($context['field']);
|
|
if (empty($context['list_mode'])) {
|
|
$context['list_mode'] = 'n';
|
|
}
|
|
|
|
if (! is_null($r)) {
|
|
// already rendered (decryption error)
|
|
} elseif (! empty($params['editable']) && $params['field']['type'] !== 'STARS') {
|
|
if ($params['editable'] === true) {
|
|
// Some callers pass true/false instead of an actual mode, default to block
|
|
$params['editable'] = 'block';
|
|
}
|
|
|
|
if ($params['editable'] == 'direct') {
|
|
$r = $handler->renderInput($context);
|
|
$params['editable'] = 'block';
|
|
$fetchUrl = null;
|
|
} else {
|
|
$r = $handler->renderOutput($context);
|
|
$fetchUrl = [
|
|
'controller' => 'tracker',
|
|
'action' => 'fetch_item_field',
|
|
'trackerId' => $field['trackerId'],
|
|
'itemId' => $item['itemId'],
|
|
'fieldId' => $field['fieldId'],
|
|
'listMode' => $context['list_mode']
|
|
];
|
|
}
|
|
|
|
$r = new Tiki_Render_Editable(
|
|
$r,
|
|
[
|
|
'layout' => $params['editable'],
|
|
'label' => $field['name'],
|
|
'group' => ! empty($params['editgroup']) ? $params['editgroup'] : false,
|
|
'field' => [
|
|
'id' => "{$field['fieldId']}{$field['trackerId']}{$item['itemId']}",
|
|
'type' => $field['type']
|
|
],
|
|
'object_store_url' => [
|
|
'controller' => 'tracker',
|
|
'action' => 'update_item',
|
|
'trackerId' => $field['trackerId'],
|
|
'itemId' => $item['itemId'],
|
|
],
|
|
'field_fetch_url' => $fetchUrl,
|
|
]
|
|
);
|
|
} elseif (isset($params['oldValue'])) {
|
|
$r = $handler->renderDiff($context);
|
|
} else {
|
|
$r = $handler->renderOutput($context);
|
|
}
|
|
|
|
if (empty($params['smarty_assign']) || $params['smarty_assign'] !== 'n') {
|
|
TikiLib::lib('smarty')->assign("f_$fieldId", $r);
|
|
$fieldPermName = $field['permName'];
|
|
TikiLib::lib('smarty')->assign("f_$fieldPermName", $r);
|
|
}
|
|
return $r;
|
|
}
|
|
}
|
|
|
|
public function get_child_items($itemId)
|
|
{
|
|
return $this->fetchAll('SELECT permName as field, itemId FROM tiki_tracker_item_fields v INNER JOIN tiki_tracker_fields f ON v.fieldId = f.fieldId WHERE f.type = \'r\' AND v.value = ?', [$itemId]);
|
|
}
|
|
|
|
public function get_field_by_perm_name($permName)
|
|
{
|
|
return $this->get_tracker_field($permName);
|
|
}
|
|
|
|
public function refresh_index_on_master_update($args)
|
|
{
|
|
// Event handler
|
|
// See pref tracker_refresh_itemlink_detail
|
|
|
|
$modifiedFields = [];
|
|
foreach ($args['old_values'] as $key => $old) {
|
|
if (! isset($args['values'][$key]) || $args['values'][$key] != $old) {
|
|
$modifiedFields[] = $key;
|
|
}
|
|
}
|
|
|
|
$items = $this->findLinkedItems(
|
|
$args['object'],
|
|
function ($field, $handler) use ($modifiedFields, $args) {
|
|
return $handler->itemsRequireRefresh($args['trackerId'], $modifiedFields);
|
|
}
|
|
);
|
|
|
|
$searchlib = TikiLib::lib('unifiedsearch');
|
|
foreach ($items as $itemId) {
|
|
$searchlib->invalidateObject('trackeritem', $itemId);
|
|
}
|
|
}
|
|
|
|
private function findLinkedItems($itemId, $callback)
|
|
{
|
|
$fields = $this->table('tiki_tracker_fields');
|
|
$list = $fields->fetchAll(
|
|
$fields->all(),
|
|
['type' => $fields->exactly('r')]
|
|
);
|
|
|
|
$toConsider = [];
|
|
|
|
foreach ($list as $field) {
|
|
$handler = $this->get_field_handler($field);
|
|
|
|
if ($handler && $callback($field, $handler)) {
|
|
$toConsider[] = $field['fieldId'];
|
|
}
|
|
}
|
|
|
|
$itemFields = $this->itemFields();
|
|
$items = $itemFields->fetchColumn(
|
|
'itemId',
|
|
[
|
|
'fieldId' => $itemFields->in($toConsider),
|
|
'value' => $itemId,
|
|
]
|
|
);
|
|
|
|
return array_unique($items);
|
|
}
|
|
|
|
public function refresh_itemslist_index($args)
|
|
{
|
|
// Event handler
|
|
// See pref tracker_refresh_itemslist_detail
|
|
|
|
$modifiedFields = [];
|
|
foreach ($args['old_values'] as $key => $old) {
|
|
if (! isset($args['values'][$key]) || $args['values'][$key] != $old) {
|
|
$modifiedFields[] = $key;
|
|
}
|
|
}
|
|
foreach ($args['values'] as $key => $new) {
|
|
if (! isset($args['old_values'][$key]) || $args['old_values'][$key] != $new) {
|
|
$modifiedFields[] = $key;
|
|
}
|
|
}
|
|
$modifiedFields = array_unique($modifiedFields);
|
|
|
|
$items = [];
|
|
|
|
$fields = $this->table('tiki_tracker_fields');
|
|
$list = $fields->fetchAll(
|
|
$fields->all(),
|
|
['type' => $fields->exactly('l')]
|
|
);
|
|
foreach ($list as $field) {
|
|
$handler = $this->get_field_handler($field);
|
|
if ($handler && $handler->itemsRequireRefresh($args['trackerId'], $modifiedFields)) {
|
|
$itemId = $args['object'];
|
|
|
|
$fieldIdHere = (int) $handler->getOption('fieldIdHere');
|
|
$fieldIdThere = (int) $handler->getOption('fieldIdThere');
|
|
|
|
// quick way of getting all ItemsList items pointing to the itemId via the field we examine
|
|
if (empty($fieldIdThere)) {
|
|
$query = "SELECT itemId
|
|
FROM tiki_tracker_item_fields ttif
|
|
WHERE ttif.fieldId = ?
|
|
AND ttif.`value` = ?";
|
|
$bindvars = [$fieldIdHere, $itemId];
|
|
} else {
|
|
$query = "SELECT COALESCE(ttif2.itemId, ttif1.value) as itemId
|
|
FROM tiki_tracker_item_fields ttif1
|
|
LEFT JOIN tiki_tracker_item_fields ttif2 ON (ttif2.value = ttif1.value OR ttif2.value = ttif1.itemId) AND ttif2.fieldId = ?
|
|
WHERE ttif1.fieldId = ?
|
|
AND ttif1.itemId = ?";
|
|
$bindvars = [$fieldIdHere, $fieldIdThere, $itemId];
|
|
}
|
|
|
|
$fieldItems = $this->fetchAll($query, $bindvars);
|
|
$fieldItems = array_map(
|
|
function ($row) {
|
|
return $row['itemId'];
|
|
},
|
|
$fieldItems
|
|
);
|
|
$items = array_merge($items, $fieldItems);
|
|
}
|
|
}
|
|
$items = array_unique($items);
|
|
|
|
$searchlib = TikiLib::lib('unifiedsearch');
|
|
foreach ($items as $itemId) {
|
|
$searchlib->invalidateObject('trackeritem', $itemId);
|
|
}
|
|
}
|
|
|
|
public function update_user_account($args)
|
|
{
|
|
// Try to find if the tracker is a user tracker, flag update to associated user
|
|
|
|
$fields = array_keys($args['values']);
|
|
if (! $fields) {
|
|
return;
|
|
}
|
|
$table = $this->table('users_groups');
|
|
$fields = array_filter($fields, 'is_numeric');
|
|
$field = $table->fetchOne(
|
|
'usersFieldId',
|
|
[
|
|
'usersFieldId' => $table->in($fields),
|
|
]
|
|
);
|
|
|
|
if ($field && ! empty($args['values'][$field])) {
|
|
TikiLib::events()->trigger(
|
|
'tiki.user.update',
|
|
[
|
|
'type' => 'user',
|
|
'object' => $args['values'][$field],
|
|
]
|
|
);
|
|
}
|
|
}
|
|
// connect a user to his user item on the email field / email user
|
|
public function update_user_item($user, $email, $emailFieldId)
|
|
{
|
|
$field = $this->get_tracker_field($emailFieldId);
|
|
$trackerId = $field['trackerId'];
|
|
$definition = Tracker_Definition::get($trackerId);
|
|
$userFieldId = $definition->getUserField();
|
|
$listfields[$userFieldId] = $definition->getField($userFieldId);
|
|
$filterfields[0] = $emailFieldId; // Email field in the user tracker
|
|
$exactvalue[0] = $email;
|
|
$items = $this->list_items($trackerId, 0, -1, 'created', $listfields, $filterfields, '', 'opc', '', $exactvalue);
|
|
$found = false;
|
|
foreach ($items['data'] as $item) {
|
|
if (empty($item['field_values'][0]['value'])) {
|
|
$found = true;
|
|
$this->modify_field($item['itemId'], $userFieldId, $user);
|
|
} elseif ($item['field_values'][0]['value'] == $user) {
|
|
$found = true;
|
|
}
|
|
}
|
|
return $found;
|
|
}
|
|
|
|
|
|
/**
|
|
* Called from lib/setup/events.php when object are categorized.
|
|
* This is to ensure that article and trackeritem categories stay in sync when article indexing is on
|
|
* as part of the RSS Article generator feature.
|
|
* @param $args
|
|
* @param $event
|
|
* @param $priority
|
|
* @throws Exception
|
|
*/
|
|
public function sync_tracker_article_categories($args, $event, $priority)
|
|
{
|
|
global $prefs;
|
|
$catlib = TikiLib::lib('categ');
|
|
if ($args['type'] == 'article') {
|
|
//if it's an article, find the associated trackeritem per the relation
|
|
$relationlib = TikiLib::lib('relation');
|
|
$artRelation = $relationlib->get_relations_to('article', $args['object'], 'tiki.article.attach', '', '1');
|
|
if (empty($artRelation)) {
|
|
return;
|
|
}
|
|
$tracker_item_id = $artRelation[0]['itemId'];
|
|
//if the tracker isn't the article tracker as per the pref, don't sync
|
|
if (! $tracker_item_id || $prefs['tracker_article_trackerId'] != $this->get_tracker_for_item($tracker_item_id)) {
|
|
return;
|
|
}
|
|
// get the trackeritem's categories and add or remove the same categories that the article had
|
|
// added or removed as per the event
|
|
$categories = $catlib->get_object_categories('trackeritem', $tracker_item_id);
|
|
$categories_old = $categories;
|
|
foreach ($args['added'] as $added) {
|
|
if (! in_array($added, $categories)) {
|
|
$categories[] = $added;
|
|
}
|
|
}
|
|
foreach ($args['removed'] as $removed) {
|
|
if (in_array($removed, $categories)) {
|
|
$categories = array_diff($categories, [$removed]);
|
|
}
|
|
}
|
|
//update the trackeritems categories if there were new ones added/removed
|
|
if ($categories != $categories_old) {
|
|
$catlib->update_object_categories($categories, $tracker_item_id, 'trackeritem');
|
|
}
|
|
} elseif ($args['type'] == 'trackeritem') {
|
|
//if trackeritem, make sure it's the article tracker that we're dealing with
|
|
$trackerId = $this->get_tracker_for_item($args['object']);
|
|
if ($prefs['tracker_article_trackerId'] != $trackerId) {
|
|
return;
|
|
}
|
|
$definition = Tracker_Definition::get($trackerId);
|
|
//find the article field in this tracker and from there find the relation for the
|
|
$relationlib = TikiLib::lib('relation');
|
|
$artRelation = $relationlib->get_relations_from('trackeritem', $args['object'], 'tiki.article.attach', '', '1');
|
|
if (empty($artRelation)) {
|
|
return;
|
|
}
|
|
$articleId = $artRelation[0]['itemId'];
|
|
// get the articles's categories and add or remove the same categories that the trackeritem had
|
|
// added or removed as per the event
|
|
$categories = $catlib->get_object_categories('article', $articleId);
|
|
$categories_old = $categories;
|
|
foreach ($args['added'] as $added) {
|
|
if (! in_array($added, $categories)) {
|
|
$categories[] = $added;
|
|
}
|
|
}
|
|
foreach ($args['removed'] as $removed) {
|
|
if (in_array($removed, $categories)) {
|
|
$categories = array_diff($categories, [$removed]);
|
|
}
|
|
}
|
|
//update the article's categories if there were new ones added/removed
|
|
if ($categories != $categories_old) {
|
|
$catlib->update_object_categories($categories, $articleId, 'article');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when accessing contents of a Tracker UserSelector field.
|
|
* Purpose is to parse the csv string of usernames stored inside and format an array.
|
|
* @param $value csv-formatted string
|
|
* @return array of resulting usernames
|
|
*/
|
|
public function parse_user_field($value)
|
|
{
|
|
return array_filter(
|
|
array_map(function ($user) {
|
|
return trim($user ?? '');
|
|
}, is_array($value) ? $value : str_getcsv($value ?? ''))
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Given a configured system tracker with currency exchange rates and a date,
|
|
* return all available currency rates valid for that time.
|
|
* @param $date
|
|
* @return array of exchange rates
|
|
*/
|
|
public function exchange_rates($date)
|
|
{
|
|
global $prefs;
|
|
|
|
if ($prefs['tracker_system_currency'] != 'y') {
|
|
return [];
|
|
}
|
|
|
|
if (is_numeric($date)) {
|
|
$date = date('Y-m-d', $date);
|
|
} elseif (! empty($date)) {
|
|
$date = date('Y-m-d', strtotime($date));
|
|
} else {
|
|
$date = date('Y-m-d');
|
|
}
|
|
|
|
static $rates = [];
|
|
if (isset($rates[$date])) {
|
|
return $rates[$date];
|
|
}
|
|
|
|
$rates[$date] = [];
|
|
$trackerId = $prefs['tracker_system_currency_tracker'];
|
|
$currencyField = $prefs['tracker_system_currency_currency'];
|
|
$dateField = $prefs['tracker_system_currency_date'];
|
|
$rateField = $prefs['tracker_system_currency_rate'];
|
|
|
|
if ($trackerId && $currencyField && $dateField && $rateField) {
|
|
$currencies = $this->list_tracker_field_values($trackerId, $currencyField);
|
|
foreach ($currencies as $currency) {
|
|
$rates[$date][$currency] = $this->getOne(
|
|
'SELECT ttif3.value as rate FROM tiki_tracker_items tti
|
|
LEFT JOIN tiki_tracker_item_fields ttif1 ON tti.itemId = ttif1.itemId AND ttif1.fieldId = ?
|
|
LEFT JOIN tiki_tracker_item_fields ttif2 ON tti.itemId = ttif2.itemId AND ttif2.fieldId = ?
|
|
LEFT JOIN tiki_tracker_item_fields ttif3 ON tti.itemId = ttif3.itemId AND ttif3.fieldId = ?
|
|
WHERE tti.trackerId = ? AND ttif1.value = ? AND DATE_FORMAT(FROM_UNIXTIME(ttif2.value), \'%Y-%m-%d\') <= ?
|
|
ORDER BY ttif2.value DESC',
|
|
[$currencyField, $dateField, $rateField, $trackerId, $currency, $date]
|
|
);
|
|
if ($prefs['tracker_system_currency_direction'] == 'reverse' && $rates[$date][$currency]) {
|
|
$rates[$date][$currency] = 1 / $rates[$date][$currency];
|
|
}
|
|
}
|
|
}
|
|
return $rates[$date];
|
|
}
|
|
|
|
/**
|
|
* Generate unique tracker field Permanent name
|
|
* @param $definition
|
|
* @param $permName
|
|
* @param int $maxAllowedSize
|
|
* @return string
|
|
* @throws Services_Exception_DuplicateValue
|
|
*/
|
|
public static function generatePermName($definition, $permName, $maxAllowedSize = Tracker_Item::PERM_NAME_MAX_ALLOWED_SIZE)
|
|
{
|
|
// Ensure that PermName is no longer than 50 characters, since the maximum allowed by MySQL Full
|
|
// Text Search as Unified Search Index is 64, and trackers will internally prepend "tracker_field_",
|
|
// which are another 14 characters (50+14=64). We could allow longer permanent names when other search
|
|
// index engines are the ones being used, but this will probably only delay the problem until the admin
|
|
// wants to change the search engine for some reason (some constrains in Lucene or Elasticsearch,
|
|
// as experience demonstrated in some production sites in real use cases over long periods of time).
|
|
// And to increase chances to avoid conflict when long names only differ in the end of the long string,
|
|
// where some meaningful info resides, we'll get the first (PERM_NAME_MAX_ALLOWED_SIZE - 10) chars, 1 underscore and the last 9 chars.
|
|
$permName = (strlen($permName) > $maxAllowedSize) ? substr($permName, 0, ($maxAllowedSize - 10)) . '_' . substr($permName, -9) : $permName;
|
|
|
|
// Quick way to solve permName conflict, which is very common in languages that only use characters considered
|
|
// special for this purpose (ie: hebrew). Ideally we should use fieldId, but it haven't been defined yet.
|
|
$tries = 0;
|
|
while ($definition->getFieldFromPermName($permName)) {
|
|
$permName = substr($permName, 0, ($maxAllowedSize - 5)) . "_" . rand(1000, 9999);
|
|
// Let's avoid theoretical chance of infinite loop
|
|
if (++$tries > 100) {
|
|
throw new Services_Exception_DuplicateValue('permName', $permName);
|
|
}
|
|
}
|
|
|
|
return $permName;
|
|
}
|
|
|
|
// Add backlinks from selected page by 'k' (see page selector trackerfield)
|
|
public function add_page_selector_backlink($itemId, $fieldId, $value)
|
|
{
|
|
$tikilib = TikiLib::lib('tiki');
|
|
$pageFrom = 'objectlink:trackeritemfield:' . $itemId . ':' . $fieldId;
|
|
$tikilib->clear_links($pageFrom);
|
|
$pageTo = $value;
|
|
$tikilib->replace_link($pageFrom, $pageTo);
|
|
}
|
|
|
|
// Update and stores relations with wiki page
|
|
public function update_page_selector_relations($value, $itemId)
|
|
{
|
|
$wikilib = TikiLib::lib('wiki');
|
|
$objectType = 'trackeritemfield';
|
|
$wikilib->update_wikicontent_relations($value, $objectType, $itemId);
|
|
}
|
|
}
|