Commit 3268166b authored by Johan Cwiklinski's avatar Johan Cwiklinski

Merge branch '9.5/bugfixes'

parents 4beca992 b3e7b253
......@@ -66,6 +66,29 @@ The present file will list all changes made to the project; according to the
- `getAllDatasFromTable` renamed to `getAllDataFromTable()`
- Usage of `$order` parameter in `getAllDataFromTable()` (`DbUtils::getAllDataFromTable()`)
#### Removed
- Usage of string `$condition` parameter in `CommonDBTM::find()`
- Usage of string `$condition` parameter in `Dropdown::addNewCondition()`
- Usage of string in `$option['condition']` parameter in `Dropdown::show()`
- `KnowbaseItemCategory::showFirstLevel()`
- `Ticket::getTicketActors()`
- `NotificationTarget::getProfileJoinSql()`
- `NotificationTarget::getDistinctUserSql()`
- `NotificationTargetCommonITILObject::getProfileJoinSql()`
- `RuleCollection::getRuleListQuery()`
- `getNextItem()`
- `getPreviousItem()`
- `CommonDBChild::getSQLRequestToSearchForItem()`
- `CommonDBConnexity::getSQLRequestToSearchForItem()`
- `CommonDBRelation::getSQLRequestToSearchForItem()`
- `Project::addVisibility()`
- `Project::addVisibilityJoins()`
- `Plugin::hasBeenInit()`
- 'SELECT DISTINCT' and 'DISTINCT FIELDS' criteria in `DBmysqlIterator::buildQuery()`
- `CommonDBTM::getTablesOf()`
- `CommonDBTM::getForeignKeyFieldsOf()`
## [9.4.2] unreleased
### API changes
......
......@@ -370,6 +370,20 @@ class Calendar extends CommonDropdown {
}
/**
* Determines if calendar has, at least, one working day.
*
* @since 9.4.3
*
* @return boolean
**/
public function hasAWorkingDay() {
$durations = $this->getDurationsCache();
return false !== $durations && array_sum($durations) > 0;
}
/**
* Is the time passed is in a working hour
*
......
......@@ -89,11 +89,6 @@ abstract class CommonDBConnexity extends CommonDBTM {
/// Disable auto forwarding information about entities ?
static public $disableAutoEntityForwarding = false;
/**
* Return the SQL request to get all the connexities corresponding to $itemtype[$items_id]
* That is used by cleanDBOnItem : the only interesting field is static::getIndexName()
* But CommonDBRelation also use it to get more complex result
*
* @since 9.4
*
* @param string $itemtype the type of the item to look for
......
......@@ -118,7 +118,7 @@ class CommonDBTM extends CommonGLPI {
protected $fkfield = "";
/**
* Search option of item. Initialized on first call to `self::getOptions()` and used as cache.
* Search option of item. Initialized on first call to self::getOptions() and used as cache.
*
* @var array
*
......@@ -184,7 +184,6 @@ class CommonDBTM extends CommonGLPI {
function __construct () {
}
/**
* Return the table used to store this object
*
* @param string $classname Force class (to avoid late_binding on inheritance)
......@@ -431,8 +430,6 @@ class CommonDBTM extends CommonGLPI {
/**
* Retrieve all items from the database
*
* @since 9.4 string condition is deprecated
*
* @param array $condition condition used to search if needed (empty get all) (default '')
* @param array|string $order order field if needed (default '')
* @param integer $limit limit retrieved data if needed (default '')
......@@ -441,60 +438,30 @@ class CommonDBTM extends CommonGLPI {
**/
function find($condition = [], $order = [], $limit = null) {
global $DB;
// Make new database object and fill variables
if (!is_array($condition)) {
Toolbox::deprecated('Using string condition in find is deprecated!');
$query = "SELECT *
FROM `".$this->getTable()."`";
if (!empty($condition)) {
$query .= " WHERE $condition";
}
if (!empty($order)) {
$query .= " ORDER BY $order";
}
if (!empty($limit)) {
$query .= " LIMIT ".intval($limit);
}
$data = [];
if ($result = $DB->query($query)) {
if ($DB->numrows($result)) {
while ($line = $DB->fetchAssoc($result)) {
$data[$line['id']] = $line;
}
}
}
} else {
//@since 9.4: use iterator
$criteria = [
'FROM' => $this->getTable()
];
$criteria = [
'FROM' => $this->getTable()
];
if (count($condition)) {
$criteria['WHERE'] = $condition;
}
if (count($condition)) {
$criteria['WHERE'] = $condition;
}
if (!is_array($order)) {
$order = [$order];
}
if (count($order)) {
$criteria['ORDERBY'] = $order;
}
if (!is_array($order)) {
$order = [$order];
}
if (count($order)) {
$criteria['ORDERBY'] = $order;
}
if ((int)$limit > 0) {
$criteria['LIMIT'] = (int)$limit;
}
if ((int)$limit > 0) {
$criteria['LIMIT'] = (int)$limit;
}
$data = [];
$iterator = $DB->request($criteria);
while ($line = $iterator->next()) {
$data[$line['id']] = $line;
}
$data = [];
$iterator = $DB->request($criteria);
while ($line = $iterator->next()) {
$data[$line['id']] = $line;
}
return $data;
......
......@@ -154,7 +154,6 @@ class DBmysqlIterator implements Iterator, Countable {
// Check field, orderby, limit, start in criterias
$field = "";
$dfield = "";
$distinct = false;
$orderby = null;
$limit = 0;
......@@ -173,12 +172,6 @@ class DBmysqlIterator implements Iterator, Countable {
unset($crit[$key]);
break;
case 'SELECT DISTINCT' :
case 'DISTINCT FIELDS' :
$dfield = $val;
unset($crit[$key]);
break;
case 'DISTINCT' :
if ($val) {
$distinct = true;
......@@ -236,30 +229,6 @@ class DBmysqlIterator implements Iterator, Countable {
$this->sql = 'SELECT ';
$first = true;
// Backward compatibility for "SELECT DISTINCT" and "DISTINCT FIELDS"
if (!empty($dfield)) {
Toolbox::logWarning('"SELECT DISTINCT" and "DISTINCT FIELDS" are depreciated.');
// Merge $field and $dfield
if (empty($field)) {
$field = $dfield;
} else {
if (is_array($field) && is_array($dfield)) {
$field = array_merge($dfield, $field);
} else if (is_array($field) && !is_array($dfield)) {
array_unshift($field, $dfield);
} else if (!is_array($field) && is_array($dfield)) {
$dfield[] = $field;
$field = $dfield;
} else { // both are strings
$field = [$dfield, $field];
}
}
$distinct = true;
unset($dfield);
}
// SELECT field list
if ($count) {
$this->sql .= 'COUNT(';
......
......@@ -75,15 +75,11 @@ class Dropdown {
*
* @return boolean : false if error and random id if OK
*
* @since 9.4.0 Usage of string in condition option is deprecated
* @since 9.5.0 Usage of string in condition option is removed
**/
static function show($itemtype, $options = []) {
global $DB, $CFG_GLPI;
if (array_key_exists('condition', $options) && !is_array($options['condition'])) {
Toolbox::deprecated('Using a string in condition option is deprecated.');
}
if ($itemtype && !($item = getItemForItemtype($itemtype))) {
return false;
}
......@@ -2325,12 +2321,7 @@ class Dropdown {
}
if (isset($post['condition']) && ($post['condition'] != '')) {
if (!is_array($post['condition']) && $post['condition'] != '') {
Toolbox::deprecated('Please no longer use raw SQL for conditions!');
$where[] = new \QueryExpression($post['condition']);
} else if (count($post['condition'])) {
$where = array_merge($where, $post['condition']);
}
$where = array_merge($where, $post['condition']);
}
$one_item = -1;
......
......@@ -675,27 +675,6 @@ abstract class NotificationTargetCommonITILObject extends NotificationTarget {
}
public function getProfileJoinSql() {
Toolbox::deprecated('Use getProfileJoinCriteria');
$query = parent::getProfileJoinSql();
if ($this->isPrivate()) {
$query .= " INNER JOIN `glpi_profiles`
ON (`glpi_profiles`.`id` = `glpi_profiles_users`.`profiles_id`
AND `glpi_profiles`.`interface` = 'central')
INNER JOIN `glpi_profilerights`
ON (`glpi_profiles`.`id` = `glpi_profilerights`.`profiles_id`
AND `glpi_profilerights`.`name` = 'followup'
AND `glpi_profilerights`.`rights` & ".
ITILFollowup::SEEPRIVATE.") ";
}
return $query;
}
public function getProfileJoinCriteria() {
$criteria = parent::getProfileJoinCriteria();
......
......@@ -132,7 +132,6 @@ class Plugin extends CommonDBTM {
}
}
/**
* Init a plugin including setup.php file
* launching plugin_init_NAME function after checking compatibility
*
......
......@@ -151,13 +151,20 @@ class QueuedNotification extends CommonDBTM {
&& isset($input['items_id']) && ($input['items_id'] >= 0)
&& isset($input['notificationtemplates_id']) && !empty($input['notificationtemplates_id'])
&& isset($input['recipient'])) {
$query = "`is_deleted` = 0
AND `itemtype` = '".$input['itemtype']."'
AND `items_id` = '".$input['items_id']."'
AND `entities_id` = '".$input['entities_id']."'
AND `notificationtemplates_id` = '".$input['notificationtemplates_id']."'
AND `recipient` = '".$input['recipient']."'";
foreach ($DB->request($this->getTable(), $query) as $data) {
$criteria = [
'FROM' => $this->getTable(),
'WHERE' => [
'is_deleted' => 0,
'itemtype' => $input['itemtype'],
'items_id' => $input['items_id'],
'entities_id' => $input['entities_id'],
'notificationtemplates_id' => $input['notificationtemplates_id'],
'recipient' => $input['recipient']
]
];
$iterator = $DB->request($criteria);
while ($data = $iterator->next()) {
$this->delete(['id' => $data['id']], 1);
}
}
......
......@@ -307,91 +307,99 @@ class TicketRecurrent extends CommonDropdown {
/**
* Compute next creation date of a ticket
* Compute next creation date of a ticket.
*
* New parameter in version 0.84 : $calendars_id
* @param string $begin_date Begin date of the recurrent ticket in 'Y-m-d H:i:s' format.
* @param string $end_date End date of the recurrent ticket in 'Y-m-d H:i:s' format,
* or 'NULL' or empty value.
* @param string|integer $periodicity Periodicity of creation, could be:
* - an integer corresponding to seconds,
* - a string using "/([0-9]+)(MONTH|YEAR)/" pattern.
* @param integer $create_before Anticipated creation delay in seconds.
* @param integer|null $calendars_id ID of the calendar to use to restrict creation to working hours,
* or 0 / null for no calendar.
*
* @param $begin_date datetime Begin date of the recurrent ticket
* @param $end_date datetime End date of the recurrent ticket
* @param $periodicity timestamp Periodicity of creation
* @param $create_before timestamp Create before specific timestamp
* @param $calendars_id integer ID of the calendar to used
* @return string Next creation date in 'Y-m-d H:i:s' format.
*
* @return datetime next creation date
**/
* @since 0.84 $calendars_id parameter added
*/
function computeNextCreationDate($begin_date, $end_date, $periodicity, $create_before,
$calendars_id) {
if (empty($begin_date) || ($begin_date == 'NULL')) {
$now = time();
$periodicity_pattern = '/([0-9]+)(MONTH|YEAR)/';
if (false === DateTime::createFromFormat('Y-m-d H:i:s', $begin_date)) {
// Invalid begin date.
return 'NULL';
}
if (!empty($end_date) && ($end_date <> 'NULL')) {
if (strtotime($end_date) < time()) {
return 'NULL';
}
$has_end_date = false !== DateTime::createFromFormat('Y-m-d H:i:s', $end_date);
if ($has_end_date && strtotime($end_date) < $now) {
// End date is in past.
return 'NULL';
}
$check = true;
if (preg_match('/([0-9]+)MONTH/', $periodicity)
|| preg_match('/([0-9]+)YEAR/', $periodicity)) {
$check = false;
if (!is_int($periodicity) && !preg_match($periodicity_pattern, $periodicity)) {
// Invalid periodicity.
return 'NULL';
}
// Compute periodicity values
$periodicity_as_interval = null;
$periodicity_in_seconds = $periodicity;
$matches = [];
if (preg_match($periodicity_pattern, $periodicity, $matches)) {
$periodicity_as_interval = "{$matches[1]} {$matches[2]}";
$periodicity_in_seconds = $matches[1]
* MONTH_TIMESTAMP
* ('YEAR' === $matches[2] ? 12 : 1);
} else if ($periodicity % DAY_TIMESTAMP == 0) {
$periodicity_as_interval = ($periodicity / DAY_TIMESTAMP) . ' DAY';
} else {
$periodicity_as_interval = ($periodicity / HOUR_TIMESTAMP) . ' HOUR';
}
if ($check
&& ($create_before > $periodicity)) {
Session::addMessageAfterRedirect(__('Invalid frequency. It must be greater than the preliminary creation.'),
false, ERROR);
// Check that anticipated creation delay is greater than periodicity.
if ($create_before > $periodicity_in_seconds) {
Session::addMessageAfterRedirect(
__('Invalid frequency. It must be greater than the preliminary creation.'),
false,
ERROR
);
return 'NULL';
}
if ($periodicity <> 0) {
// Standard time computation
$timestart = strtotime($begin_date) - $create_before;
$now = time();
if ($now > $timestart) {
$value = $periodicity;
$step = "second";
if (preg_match('/([0-9]+)MONTH/', $periodicity, $matches)) {
$value = $matches[1];
$step = 'MONTH';
} else if (preg_match('/([0-9]+)YEAR/', $periodicity, $matches)) {
$value = $matches[1];
$step = 'YEAR';
} else {
if (($value%DAY_TIMESTAMP)==0) {
$value = $value/DAY_TIMESTAMP;
$step = "DAY";
} else {
$value = $value/HOUR_TIMESTAMP;
$step = "HOUR";
}
}
// First occurence of creation
$occurence_time = strtotime($begin_date);
$creation_time = $occurence_time - $create_before;
while ($timestart < $now) {
$timestart = strtotime("+ $value $step", $timestart);
}
}
// Time start over end date
if (!empty($end_date) && ($end_date <> 'NULL')) {
if ($timestart > strtotime($end_date)) {
return 'NULL';
}
// Add steps while creation time is in past
while ($creation_time < $now) {
$creation_time = strtotime("+ $periodicity_as_interval", $creation_time);
$occurence_time = $creation_time + $create_before;
// Stop if end date reached
if ($has_end_date && $occurence_time > strtotime($end_date)) {
return 'NULL';
}
}
// Add steps while start time is not in working hours
$calendar = new Calendar();
if ($calendars_id && $calendar->getFromDB($calendars_id) && $calendar->hasAWorkingDay()) {
while (!$calendar->isAWorkingHour($occurence_time)) {
$creation_time = strtotime("+ $periodicity_as_interval", $creation_time);
$occurence_time = $creation_time + $create_before;
$calendar = new Calendar();
if ($calendars_id
&& $calendar->getFromDB($calendars_id)) {
$durations = $calendar->getDurationsCache();
if (array_sum($durations) > 0) { // working days exists
while (!$calendar->isAWorkingDay($timestart)) {
$timestart = strtotime("+ 1 day", $timestart);
}
// Stop if end date reached
if ($has_end_date && $occurence_time > strtotime($end_date)) {
return 'NULL';
}
}
return date("Y-m-d H:i:s", $timestart);
}
return 'NULL';
return date("Y-m-d H:i:s", $creation_time);
}
......
<?php
/**
* ---------------------------------------------------------------------
* GLPI - Gestionnaire Libre de Parc Informatique
* Copyright (C) 2015-2018 Teclib' and contributors.
*
* http://glpi-project.org
*
* based on GLPI - Gestionnaire Libre de Parc Informatique
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
*
* ---------------------------------------------------------------------
*
* LICENSE
*
* This file is part of GLPI.
*
* GLPI is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* GLPI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
* ---------------------------------------------------------------------
*/
namespace tests\units;
use \DbTestCase;
/* Test for inc/ticketrecurrent.class.php */
class TicketRecurrent extends DbTestCase {
/**
* Data provider for self::testConvertTagToImage().
*/
protected function computeNextCreationDateProvider() {
$start_of_current_month = date('Y-m-01 00:00:00');
$end_of_next_year = date('Y-m-d 23:59:59', strtotime('last day of next year'));
// Create a calendar where evey day except today is a working day
$calendar = new \Calendar();
$segment = new \CalendarSegment();
$calendar_id = $calendar->add(['name' => 'TicketRecurrent testing calendar']);
$this->integer($calendar_id)->isGreaterThan(0);
for ($day = 0; $day <= 6; $day++) {
if ($day == date('w')) {
continue;
}
$segment_id = $segment->add(
[
'calendars_id' => $calendar_id,
'day' => $day,
'begin' => '09:00:00',
'end' => '19:00:00'
]
);
$this->integer($segment_id)->isGreaterThan(0);
}
return [
// Empty begin date
[
'begin_date' => '',
'end_date' => $end_of_next_year,
'periodicity' => HOUR_TIMESTAMP,
'create_before' => 0,
'calendars_id' => 0,
'expected_value' => 'NULL',
],
// Invalid begin date
[
'begin_date' => '',
'end_date' => $end_of_next_year,
'periodicity' => HOUR_TIMESTAMP,
'create_before' => 0,
'calendars_id' => 0,
'expected_value' => 'NULL',
],
// Empty periodicity
[
'begin_date' => $start_of_current_month,
'end_date' => $end_of_next_year,
'periodicity' => '',
'create_before' => 0,
'calendars_id' => 0,
'expected_value' => 'NULL',
],
// Invalid periodicity
[
'begin_date' => $start_of_current_month,
'end_date' => $end_of_next_year,
'periodicity' => '3WEEK',
'create_before' => 0,
'calendars_id' => 0,
'expected_value' => 'NULL',
],
// Invalid anticipated creation delay compared to periodicity
[
'begin_date' => $start_of_current_month,
'end_date' => $end_of_next_year,
'periodicity' => HOUR_TIMESTAMP,
'create_before' => HOUR_TIMESTAMP * 2,
'calendars_id' => 0,
'expected_value' => 'NULL',
],
// End date in past
[
'begin_date' => '2018-03-26 15:00:00',
'end_date' => '2019-01-12 00:00:00',
'periodicity' => '1MONTH',
'create_before' => 0,
'calendars_id' => 0,
'expected_value' => 'NULL',
],
// Valid case 1: ticket created every hour with no anticipation and no calendar
[
'begin_date' => $start_of_current_month,
'end_date' => $end_of_next_year,
'periodicity' => HOUR_TIMESTAMP,
'create_before' => 0,
'calendars_id' => 0,
'expected_value' => date('Y-m-d H:00:00', strtotime('+ 1 hour')),
],
// Valid case 2: ticket created every hour with anticipation and no calendar
[
'begin_date' => $start_of_current_month,
'end_date' => $end_of_next_year,
'periodicity' => HOUR_TIMESTAMP,
'create_before' => HOUR_TIMESTAMP,
'calendars_id' => 0,
'expected_value' => date('Y-m-d H:00:00', strtotime('+ 1 hour')),
],
// Valid case 3: ticket created every hour with no anticipation and with calendar
[
'begin_date' => $start_of_current_month,
'end_date' => $end_of_next_year,
'periodicity' => HOUR_TIMESTAMP,
'create_before' => HOUR_TIMESTAMP,
'calendars_id' => $calendar_id,
'expected_value' => date('Y-m-d 08:00:00', strtotime('tomorrow')), // 1 hour anticipation
],
// Valid case 4: ticket created every day with no anticipation and no calendar
[
'begin_date' => $start_of_current_month,
'end_date' => $end_of_next_year,