Unverified Commit 734eece5 authored by Alexandre Delaunay's avatar Alexandre Delaunay Committed by GitHub
Browse files

rework reservations (#7587)

* rework reservations

* fix height

* clean unused params

* missing eof

* popHeader

* clean

* remove dead code

* make passing params to tabs more generic

* prevent override of reservation libs

* translation

* encapsulate update event result into a json

* add changelog entries

* separate last view storage from planning
parent cf82b084
......@@ -87,6 +87,9 @@ The present file will list all changes made to the project; according to the
- `Toolbox::userErrorHandlerNormal()`
- `Transfer::transferComputerSoftwares()`
- `Reservation::displayReservationDay()`
- `Reservation::displayReservationsForAnItem()`
## [9.5.1] unreleased
## [9.5.0] 2020-07-07
......
<?php
/**
* ---------------------------------------------------------------------
* GLPI - Gestionnaire Libre de Parc Informatique
* Copyright (C) 2015-2020 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/>.
* ---------------------------------------------------------------------
*/
/**
* @since 0.85
*/
include ('../inc/includes.php');
header("Content-Type: text/html; charset=UTF-8");
Html::header_nocache();
Session::checkCentralAccess();
// Make a select box
ReservationItem::ajaxDropdown($_POST);
<?php
/**
* ---------------------------------------------------------------------
* GLPI - Gestionnaire Libre de Parc Informatique
* Copyright (C) 2015-2020 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/>.
* ---------------------------------------------------------------------
*/
include ('../inc/includes.php');
Session::checkCentralAccess();
if (!isset($_REQUEST["action"])) {
exit;
}
if ($_REQUEST["action"] == "get_events") {
header("Content-Type: application/json; charset=UTF-8");
echo json_encode(Reservation::getEvents($_REQUEST));
exit;
}
if ($_REQUEST["action"] == "get_resources") {
header("Content-Type: application/json; charset=UTF-8");
echo json_encode(Reservation::getResources());
exit;
}
if ($_REQUEST["action"] == "update_event") {
$result = Reservation::updateEvent($_REQUEST);
echo json_encode(['result' => $result]);
exit;
}
Html::header_nocache();
header("Content-Type: text/html; charset=UTF-8");
if ($_REQUEST["action"] == "add_reservation_fromselect") {
$reservation = new Reservation;
$reservation->showForm(0, [
'item' => [(int) $_REQUEST['id']],
'begin' => $_REQUEST['start'],
'end' => $_REQUEST['end'],
]);
}
Html::ajaxFooter();
.reservation-panel {
.reservation-header {
bottom: 15px;
.reservation-icon {
vertical-align: middle;
}
.item-name {
display: inline-block;
}
.view-all {
margin-left: 15px;
}
}
}
.reservations-planning {
width: 90%;
margin: 15px auto 0;
&.tabbed {
width: 950px;
}
a .fc-time, .fc-title {
color: initial;
}
.defaultDate {
background: #e3fce8;
}
}
......@@ -38,14 +38,16 @@ Session::checkRight("reservation", ReservationItem::RESERVEANITEM);
$rr = new Reservation();
if (Session::getCurrentInterface() == "helpdesk") {
if (isset($_REQUEST['ajax'])) {
Html::header_nocache();
Html::popHeader(__('Simplified interface'));
} else if (Session::getCurrentInterface() == "helpdesk") {
Html::helpHeader(__('Simplified interface'), $_SERVER['PHP_SELF'], $_SESSION["glpiname"]);
} else {
Html::header(Reservation::getTypeName(Session::getPluralNumber()), $_SERVER['PHP_SELF'], "tools", "reservationitem");
}
if (isset($_POST["update"])) {
list($begin_year,$begin_month) = explode("-", $_POST['resa']["begin"]);
Toolbox::manageBeginAndEndPlanDates($_POST['resa']);
if (Session::haveRight("reservation", UPDATE)
|| (Session::getLoginUserID() == $_POST["users_id"])) {
......@@ -54,8 +56,7 @@ if (isset($_POST["update"])) {
$_POST['begin'] = $_POST['resa']["begin"];
$_POST['end'] = $_POST['resa']["end"];
if ($rr->update($_POST)) {
Html::redirect($CFG_GLPI["root_doc"]."/front/reservation.php?reservationitems_id=".
$_POST['_item']."&mois_courant=$begin_month&annee_courante=$begin_year");
Html::back();
}
}
......@@ -73,7 +74,6 @@ if (isset($_POST["update"])) {
"$reservationitems_id&mois_courant=$begin_month&annee_courante=$begin_year");
} else if (isset($_POST["add"])) {
$all_ok = true;
$reservationitems_id = 0;
if (empty($_POST['users_id'])) {
$_POST['users_id'] = Session::getLoginUserID();
......@@ -119,26 +119,14 @@ if (isset($_POST["update"])) {
Event::log($newID, "reservation", 4, "inventory",
sprintf(__('%1$s adds the reservation %2$s for item %3$s'),
$_SESSION["glpiname"], $newID, $reservationitems_id));
} else {
$all_ok = false;
}
}
}
}
} else {
$all_ok = false;
}
if ($all_ok) {
$toadd = "";
// Only one reservation : move to correct month
if (count($_POST['items']) == 1) {
$toadd = "?reservationitems_id=$reservationitems_id";
$toadd .= "&mois_courant=".intval($begin_month);
$toadd .= "&annee_courante=".intval($begin_year);
}
Html::redirect($CFG_GLPI["root_doc"] . "/front/reservation.php$toadd");
}
Html::back();
} else if (isset($_GET["id"])) {
if (!isset($_GET['begin'])) {
$_GET['begin'] = date('Y-m-d H:00:00');
......@@ -153,7 +141,9 @@ if (isset($_POST["update"])) {
}
}
if (Session::getCurrentInterface() == "helpdesk") {
if (isset($_REQUEST['ajax'])) {
Html::popFooter();
} else if (Session::getCurrentInterface() == "helpdesk") {
Html::helpFooter();
} else {
Html::footer();
......
......@@ -35,7 +35,7 @@ include ('../inc/includes.php');
Session::checkLoginUser();
if (!isset($_GET["reservationitems_id"])) {
$_GET["reservationitems_id"] = '';
$_GET["reservationitems_id"] = 0;
}
if (Session::getCurrentInterface() == "helpdesk") {
......@@ -44,7 +44,7 @@ if (Session::getCurrentInterface() == "helpdesk") {
Html::header(Reservation::getTypeName(Session::getPluralNumber()), $_SERVER['PHP_SELF'], "tools", "reservationitem");
}
Reservation::showCalendar($_GET["reservationitems_id"]);
Reservation::showCalendar((int) $_GET["reservationitems_id"], $_REQUEST);
if (Session::getCurrentInterface() == "helpdesk") {
Html::helpFooter();
......
......@@ -585,7 +585,6 @@ class CommonGLPI {
* @return boolean true
**/
static function displayStandardTab(CommonGLPI $item, $tab, $withtemplate = 0, $options = []) {
switch ($tab) {
// All tab
case -1 :
......@@ -1218,6 +1217,11 @@ class CommonGLPI {
// $options must contains the id of the object, and if locked by manageObjectLock will contains 'locked' => 1
ObjectLock::manageObjectLock(get_class($this), $options);
// manage custom options passed to tabs
if (isset($_REQUEST['tab_params']) && is_array($_REQUEST['tab_params'])) {
$options += $_REQUEST['tab_params'];
}
$this->showNavigationHeader($options);
if (!self::isLayoutExcludedPage() && self::isLayoutWithMain()) {
......
......@@ -467,6 +467,8 @@ $dashboard_libs = [
'charts', 'clipboard'
];
$reservations_libs = ['fullcalendar', 'reservations'];
$CFG_GLPI['javascript'] = [
'central' => [
'central' => array_merge([
......@@ -493,6 +495,7 @@ $CFG_GLPI['javascript'] = [
'knowbaseitemtranslation' => ['tinymce'],
'reminder' => ['tinymce'],
'remindertranslation' => ['tinymce'],
'reservationitem' => $reservations_libs,
],
'management' => [
'datacenter' => [
......@@ -518,6 +521,14 @@ $CFG_GLPI['javascript'] = [
'self-service' => ['tinymce', 'photoswipe']
];
// push reservations libs to reservations itemtypes (they shoul in asset sector)
foreach ($CFG_GLPI['reservation_types'] as $reservation_type) {
$CFG_GLPI['javascript']['assets'][strtolower($reservation_type)] = array_merge(
$CFG_GLPI['javascript']['assets'][strtolower($reservation_type)] ?? [],
$reservations_libs
);
}
//Maximum time, in miliseconds a saved search should not exeed
//so we count it on display (using automatic mode).
$CFG_GLPI['max_time_for_count'] = 200;
......
......@@ -1458,6 +1458,7 @@ class Dropdown {
$params['showItemSpecificity'] = '';
$params['emptylabel'] = self::EMPTY_VALUE;
$params['used'] = [];
$params['ajax_page'] = $CFG_GLPI["root_doc"]."/ajax/dropdownAllItems.php";
if (is_array($options) && count($options)) {
foreach ($options as $key => $val) {
......@@ -1488,7 +1489,7 @@ class Dropdown {
$show_id = Html::cleanId("show_".$params['items_id_name'].$rand);
Ajax::updateItemOnSelectEvent($field_id, $show_id,
$CFG_GLPI["root_doc"]."/ajax/dropdownAllItems.php", $p);
$params['ajax_page'], $p);
echo "<br><span id='$show_id'>&nbsp;</span>\n";
......@@ -1501,7 +1502,7 @@ class Dropdown {
echo "});</script>\n";
$p["idtable"] = $params['default_itemtype'];
Ajax::updateItem($show_id, $CFG_GLPI["root_doc"]. "/ajax/dropdownAllItems.php", $p);
Ajax::updateItem($show_id, $params['ajax_page'], $p);
}
}
return $rand;
......
......@@ -914,6 +914,7 @@ class Html {
if (isset($url['query'])) {
parse_str($url['query'], $parameters);
unset($parameters['forcetab']);
unset($parameters['tab_params']);
$new_query = http_build_query($parameters);
return str_replace($url['query'], $new_query, $url_in);
}
......@@ -1266,6 +1267,11 @@ class Html {
Html::requireJs('fullcalendar');
}
if (in_array('reservations', $jslibs)) {
echo Html::scss('css/reservations');
Html::requireJs('reservations');
}
if (in_array('gantt', $jslibs)) {
echo Html::css('public/lib/jquery-gantt.css');
Html::requireJs('gantt');
......@@ -4354,7 +4360,6 @@ JS;
$link .= " class='pointer' ";
}
}
$btlabel = htmlentities($btlabel, ENT_QUOTES, 'UTF-8');
$action = " submitGetLink('$action', {" .implode(', ', $javascriptArray) ."});";
if (is_array($confirm) || strlen($confirm)) {
......@@ -6433,6 +6438,8 @@ JAVASCRIPT;
break;
case 'photoswipe':
$_SESSION['glpi_js_toload'][$name][] = 'public/lib/photoswipe.js';
case 'reservations':
$_SESSION['glpi_js_toload'][$name][] = 'js/reservations.js';
break;
default:
$found = false;
......
This diff is collapsed.
......@@ -49,8 +49,10 @@ class ReservationItem extends CommonDBChild {
const RESERVEANITEM = 1024;
public $get_item_to_display_tab = false;
public $showdebug = false;
public $get_item_to_display_tab = false;
public $showdebug = false;
public $taborientation = 'horizontal';
/**
......@@ -305,17 +307,30 @@ class ReservationItem extends CommonDBChild {
//Switch reservation state
if ($ri->fields["is_active"]) {
Html::showSimpleForm(static::getFormURL(), 'update', __('Make unavailable'),
['id' => $ri->fields['id'],
'is_active' => 0]);
Html::showSimpleForm(
static::getFormURL(),
'update',
"<i class='fas fa-toggle-on'></i>&nbsp;".__('Make unavailable'),
[
'id' => $ri->fields['id'],
'is_active' => 0
]
);
} else {
Html::showSimpleForm(static::getFormURL(), 'update', __('Make available'),
['id' => $ri->fields['id'],
'is_active' => 1]);
Html::showSimpleForm(
static::getFormURL(),
'update',
"<i class='fas fa-toggle-off'></i>&nbsp;".__('Make available'),
[
'id' => $ri->fields['id'],
'is_active' => 1
]
);
}
echo '</td><td>';
Html::showSimpleForm(static::getFormURL(), 'purge', __('Prohibit reservations'),
Html::showSimpleForm(static::getFormURL(), 'purge',
"<i class='fas fa-ban'></i>&nbsp;".__('Prohibit reservations'),
['id' => $ri->fields['id']], '', '',
[__('Are you sure you want to return this non-reservable item?'),
__('That will remove all the reservations in progress.')]);
......@@ -323,7 +338,8 @@ class ReservationItem extends CommonDBChild {
echo "</td>";
} else {
echo "<td class='center'>";
Html::showSimpleForm(static::getFormURL(), 'add', __('Authorize reservations'),
Html::showSimpleForm(static::getFormURL(), 'add',
"<i class='fas fa-check'></i>&nbsp;".__('Authorize reservations'),
['items_id' => $item->getID(),
'itemtype' => $item->getType(),
'entities_id' => $item->getEntityID(),
......@@ -404,8 +420,13 @@ class ReservationItem extends CommonDBChild {
} else {
echo "<div id='makesearch' class='center firstbloc'>".
"<a class='pointer' onClick=\"javascript:showHideDiv('viewresasearch','','','');".
"showHideDiv('makesearch','','','')\">";
"<a class='vsubmit pointer' href='reservation.php?reservationitems_id=0'>
<i class='far fa-calendar'></i>&nbsp;
".__("View calendar for all items")."
</a>
<a class='vsubmit pointer' onClick=\"javascript:showHideDiv('viewresasearch','','','');".
"showHideDiv('makesearch','','','')\">
<i class='fas fa-search'></i>&nbsp;";
echo __('Find a free item in a specific period')."</a></div>\n";
echo "<div id='viewresasearch' style=\"display:none;\" class='center'>";
......@@ -504,7 +525,16 @@ class ReservationItem extends CommonDBChild {
echo "<div id='nosearch' class='center'>";
echo "<form name='form' method='GET' action='".Reservation::getFormURL()."'>";
echo "<table class='tab_cadre_fixehov'>";
echo "<tr><th colspan='".($showentity?"5":"4")."'>".self::getTypeName(1)."</th></tr>\n";
echo "<tr>";
echo "<th style='width: 30px;'>".Html::getCheckAllAsCheckbox('nosearch')."</th>";
echo "<th>".self::getTypeName(Session::getPluralNumber())."</th>";
echo "<th>".__("Location")."</th>";
echo "<th>".__("Comment")."</th>";
if ($showentity) {
echo "<th>".__("Entity")."</th>";
}
echo "<th style='width: 50px;'>".__("Booking calendar")."</th>";
echo "</tr>";
foreach ($CFG_GLPI["reservation_types"] as $itemtype) {
if (!($item = getItemForItemtype($itemtype))) {
......@@ -592,9 +622,12 @@ class ReservationItem extends CommonDBChild {
$iterator = $DB->request($criteria);
while ($row = $iterator->next()) {
echo "<tr class='tab_bg_2'><td>";
echo "<input type='checkbox' name='item[".$row["id"]."]' value='".$row["id"]."'>".
"</td>";
echo "<tr><td>";
echo Html::getCheckbox([
'name' => "item[".$row["id"]."]",
'value' => $row["id"],
]);
echo "</td>";
$typename = $item->getTypeName();
if ($itemtype == 'Peripheral') {
$item->getFromDB($row['items_id']);
......@@ -605,25 +638,32 @@ class ReservationItem extends CommonDBChild {
$item->fields["peripheraltypes_id"]);
}
}
echo "<td><a href='reservation.php?reservationitems_id=".$row['id']."'>".
sprintf(__('%1$s - %2$s'), $typename, $row["name"])."</a></td>";
echo "<td><a href='".$itemtype::getFormURLWithId($row['items_id'])."&forcetab=Reservation$1'>".
sprintf(__('%1$s - %2$s'), $typename, $row["name"]).
"</a></td>";
echo "<td>".Dropdown::getDropdownName("glpi_locations", $row["location"])."</td>";
echo "<td>".nl2br($row["comment"])."</td>";
if ($showentity) {
echo "<td>".Dropdown::getDropdownName("glpi_entities", $row["entities_id"]).
"</td>";
}
echo "</tr>\n";
echo "<td class='center'><a href='reservation.php?reservationitems_id=".$row['id']."'>
<i class='far fa-calendar-plus fa-2x pointer' title=\"".__s("Reserve this item")."\"></i>
</a></td>";
echo "</tr>";
$ok = true;
}
}
if ($ok) {
echo "<tr class='tab_bg_1 center'><td colspan='".($showentity?"5":"4")."'>";
echo "<tr class='tab_bg_1'>";
echo "<th><img src='".$CFG_GLPI["root_doc"]."/pics/arrow-left.png'></th>";
echo "<th colspan='".($showentity?"5":"4")."'>";
if (isset($_POST['reserve'])) {
echo Html::hidden('begin', ['value' => $_POST['reserve']["begin"]]);
echo Html::hidden('end', ['value' => $_POST['reserve']["end"]]);
}
echo "<input type='submit' value=\""._sx('button', 'Add')."\" class='submit'></td></tr>\n";
echo Html::submit("<i class='fas fa-lg fa-calendar-plus'></i>&nbsp;"._sx('button', 'Book'));
echo "</th></tr>";
}
echo "</table>\n";
......@@ -863,4 +903,60 @@ class ReservationItem extends CommonDBChild {
static function getIcon() {
return Reservation::getIcon();
}
/**
* Display a dropdown with only reservable items
*
* @param array $post with these options
* - idtable: itemtype of items to show
* - name: input name
*
* @return void
*/
static function ajaxDropdown(array $post) {
global $DB;
if ($post['idtable'] && class_exists($post['idtable'])) {
$itemtype = $post['idtable'];
$item_table = $itemtype::getTable();
$resi_table = ReservationItem::getTable();
$result = $DB->request([
'SELECT' => [
"$resi_table.id",
"$item_table.name"
],
'FROM' => $item_table,
'INNER JOIN' => [
$resi_table => [
'ON' => [
$resi_table => 'items_id',
$item_table => 'id',
['AND' => ["$resi_table.itemtype" => $itemtype]],
]
]
],
'WHERE' => [
"$resi_table.is_active" => 1,
"$item_table.is_deleted" => 0,
"$item_table.is_template" => 0
]
]);
if ($result->count() == 0) {
echo __('No reservable item !');
} else {
$items = [];
foreach ($result as $row) {
$name = $row['name'];
if (empty($name)) {
$name = $row['id'];
}
$items[$row['id']] = $name;
}
Dropdown::showFromArray($post['name'], $items);
}
}
}
}
......@@ -1414,4 +1414,8 @@ class Session {
public static function getActiveEntity() {
return $_SESSION['glpiactive_entity'];
}
public static function getActiveEntityRecursive() {
return $_SESSION['glpiactive_entity_recursive'];
}
}
......@@ -3154,7 +3154,7 @@ HTML;
}
//get Hsl
$base_L = $base_S = [0.35, 0.5, 0.65];
$base_L = $base_S = [0.6, 0.65, 0.7];
$H = $hash % 359;
$hash = intval($hash / 360);
$S = $base_S[$hash % count($base_S)];
......
/* global FullCalendar, FullCalendarLocales */
var Reservations = function() {
this.is_all = true;
this.id = 0;
this.rand = '';
this.dom_id = '';
this.calendar = null;
this.license_key = null;
this.currentv = null;
this.defaultDate = null;
var my = this;