# includes\r
$(MKDIR) $(DESTDIR)/usr/share/iserv/www/inc/mod_room-reservation\r
$(LN) includes/*.inc $(DESTDIR)/usr/share/iserv/www/inc/mod_room-reservation/\r
- touch $(DESTDIR)/usr/share/iserv/www/inc/mod_room-reservation/config.inc\r
+ if [ ! -e $(DESTDIR)/usr/share/iserv/www/inc/mod_room-reservation/config.inc ] ; then touch $(DESTDIR)/usr/share/iserv/www/inc/mod_room-reservation/config.inc; fi\r
+ifeq ($(shell id -u),0)\r
+ chmod g+w $(DESTDIR)/usr/share/iserv/www/inc/mod_room-reservation/config.inc\r
+ chown www-data $(DESTDIR)/usr/share/iserv/www/inc/mod_room-reservation/config.inc\r
+endif\r
\r
# i18n\r
if [ ! -d $(DESTDIR)/usr/share/iserv/locale/de/ ]; then $(MKDIR) $(DESTDIR)/usr/share/iserv/locale/de/; fi\r
\r
# additional files\r
$(MKDIR) $(DESTDIR)/usr/share/iserv/modules/room-reservation\r
- $(LN) update-iserv1-iserv2.pl $(DESTDIR)/usr/share/iserv/modules/room-reservation\r
+ $(LN) maint/update-iserv1-iserv2.pl $(DESTDIR)/usr/share/iserv/modules/room-reservation\r
chmod +x $(DESTDIR)/usr/share/iserv/modules/room-reservation/update-iserv1-iserv2.pl\r
\r
# build doxygen documentation from source\r
\r
# install everything\r
install: sourcedoc links\r
-ifndef $(DESTDIR)\r
-# only useful if sql data is in the right place\r
- chmod g+w $(DESTDIR)/usr/share/iserv/www/inc/mod_room-reservation/config.inc\r
- chgrp www-data $(DESTDIR)/usr/share/iserv/www/inc/mod_room-reservation/config.inc\r
+ifeq ($(shell id -u),0)\r
chkdb -r\r
chkpriv\r
chklocale\r
<body>\r
<h1>Changelog</h1>
<h2>iserv-room-reservation-3.0</h2>
-<!-- TODO Releasedatum, Wiki-Seite verschieben, Links anpassen -->
<i>Release: <!-- FIXME Release --></i>
<ul>
<li>Umbenennung, um Kollisionen zu vermeiden</li>
<li>Zugriffssteuerung über Sonderrechte (Gruppen-Verwaltung)<br />
<b>Achtung:</b> Wenn eines der drei vom Raumbelegungsplan benutzten Sonderrechte keiner Gruppe
zugewiesen wird, besitzen alle Benutzer dieses Portalservers das entsprechende Sonderrecht!</li>
- <li>Räume werden in der Rechnerverwaltung angelegt <!-- FIXME und können in der Konfiguration zur
- Buchung freigegeben werden --></li>
+ <li>Räume werden in der Rechnerverwaltung angelegt und können in der Konfiguration zur
+ Buchung freigegeben werden</li>
<li>Komplette Neuentwicklung des Quellcodes auf Basis von PHP 5</li>
</ul></li>
<li>Alle Formulare werden innerhalb der Tabelle angezeigt, ebenso die
<li>Neu eingefügte Buchungen werden farblich markiert</li>
<li>Administrations-Seite zum Anlegen von periodischen Buchungen entfällt</li>
</ul></li>
-<!-- FIXME <li>Bearbeiten von Buchungen durch Admins bzw. jeweilige Besitzer möglich</li>-->
+<!-- TODO <li>Bearbeiten von Buchungen durch Admins bzw. jeweilige Besitzer möglich</li>-->
<li>Algorithmus zum Zeichnen der Buchungstabelle schlanker gemacht</li>
<li><a href='source/html/'>Dokumentation des Quellcodes</a></li>
<li>Konfiguration durch sinnvolle Vorgaben vereinfacht</li>
/** There is no such account */
define("MOD_ROOM_RESERVATION_ERROR_NO_SUCH_ACCOUNT", _c("room-reservation:".
"The specified account does not exist."));
+/** The room is not available for booking */
+define("MOD_ROOM_RESERVATION_ERROR_ROOM_NOT_WHITELISTED",
+ _c("room-reservation:This room is not available for booking."));
/**
* @}
*/
}
/** (array of strings) Additional CSS rules */
-$rsLocalCss = array();
+$GLOBALS["rrLocalCss"] = array();
/**
* Add CSS rules to the page
* @return void
*/
function rrAddCss($s) {
- rrDebug("rsAddCss: add \"$s\"");
- $GLOBALS["rsLocalCss"][] = $s;
+ rrDebug("rrAddCss: add \"$s\"");
+ $GLOBALS["rrLocalCss"][] = $s;
}
/**
- * Get CSS rules that have been added with rmAddCss()
+ * Get CSS rules that have been added with rrAddCss()
* @return string
*/
-function rrGetCSS() {
- rrDebug("rsGetCss: Local CSS is ".var_export($GLOBALS["rsLocalCss"], true));
- return implode("\n", $GLOBALS["rsLocalCss"]);
+function rrGetCss() {
+ rrDebug("rrGetCss: Local CSS is ".var_export($GLOBALS["rrLocalCss"], true));
+ return implode("\n", $GLOBALS["rrLocalCss"]);
}
function rrDebug($s, $bReturn = false) {
die();
}
- if($this->oRm->getRooms() == array()) {
- echo sprintf("<p>%s</p>\n", _c("room-reservation:No rooms have been ".
- "configured yet."));
- _PageBlue();
- die();
- }
-
Title(_c("room-reservation:Book rooms"));
// Form for room selection
$_SERVER["PHP_SELF"]);
echo sprintf("<input type='hidden' name='mod_roomReservationBookingTable".
"[date]' value='%d' />\n", $this->getStart());
- echo _c("room-reservation:Room:") . sprintf(" <select onchange=".
- "'document.forms[\"room\"].submit()' width='250' ".
- "name='mod_roomReservationBookingTable[room]'>\n", $this->getStart());
- $aor = $this->oRm->getRooms();
- foreach($aor as $or) {
- // note to myself: no qu() here, seems this is being done automagically
- echo sprintf("<option value='%s'%s>%s</option>\n", $or->getName(),
- ($or->getName() == $this->getRoom()) ? " selected='selected'" : "",
- $or->getName());
- }
- echo sprintf("</select> <%s value='%s' /></form><p />\n",
- $GLOBALS["stdbtn"], _("Change"));
+ // Show rooms only if it is whitelisted
+ try {
+ $aor = $this->oCfg->getWhitelistedRooms();
+ } catch(SQLException $e) {
+ trigger_error($e->getMessage());
+ }
+ if(count($aor) > 0) {
+ echo _c("room-reservation:Room:") . sprintf(" <select onchange=".
+ "'document.forms[\"room\"].submit()' width='250' ".
+ "name='mod_roomReservationBookingTable[room]'>\n", $this->getStart());
+ foreach($aor as $or) {
+ // note to myself: no qu() here, seems this is being done automagically
+ echo sprintf("<option value='%s'%s>%s</option>\n", $or->getName(),
+ ($or->getName() == $this->getRoom()) ? " selected='selected'" : "",
+ $or->getName());
+ }
+ echo sprintf("</select> <%s value='%s' /></form><p />\n",
+ $GLOBALS["stdbtn"], _("Change"));
+ } else {
+ printf("<p>%s</p>\n", _c("room-reservation:No rooms have been ".
+ "configured yet."));
+ return;
+ }
+
// Print line with next 5 or so weeks
$strSep = " | ";
$strLink = sprintf("<a href='%s?mod_roomReservationBookingTable[date]=%%d".
protected $strPostAccount;
/** (int) recurrence interval, POST data */
protected $nPostInterval;
+ /** (string) Array of error messages */
+ protected $asErrors = array();
/***************************************************************************/
/**
$this->oRm = $oRm;
$this->oBm = $oBm;
- $this->processRequestVariables();
+ try {
+ $this->processRequestVariables();
+ } catch(Exception $e) {
+ $this->asErrors[] = $e->getMessage();
+ }
$this->addCSS();
}
*/
/**
- * Process the REQUEST variables and preset the some variables
+ * Process the REQUEST variables and preset the some variables. Throws an
+ * exception if the room provided by the GET data is not allowed for booking
* @return void
+ * @throws Exception
*/
protected function processRequestVariables() {
// default values
- $aoRooms = $this->oRm->getRooms();
- if($aoRooms != array()) {
- $or = $aoRooms[0];
- $this->setRoom($or->getName());
+ $aoRooms = $this->oCfg->getWhitelistedRooms();
+ if(count($aoRooms) < 1) {
+ $this->setRoom("");
+ } else {
+ $this->setRoom($aoRooms[0]->getName());
}
$this->setDate(time());
$this->setAction(MOD_ROOM_RESERVATION_BT_ACTION_SHOW);
MOD_ROOM_RESERVATION_BT_ACTION_SHOW)))));
$this->setDate(isset($_GET["mod_roomReservationBookingTable"]["date"]) ?
intval($_GET["mod_roomReservationBookingTable"]["date"]) : time());
- $this->setRoom(isset($_GET["mod_roomReservationBookingTable"]["room"]) ?
- $_GET["mod_roomReservationBookingTable"]["room"] : "");
+ if(isset($_GET["mod_roomReservationBookingTable"]["room"])) {
+ $this->setRoom($_GET["mod_roomReservationBookingTable"]["room"]);
+ }
$this->setTsFirst(
isset($_GET["mod_roomReservationBookingTable"]["tsfirst"]) ?
intval($_GET["mod_roomReservationBookingTable"]["tsfirst"]) : 0);
/**
* Set the room of the requested booking or the room to be shown in the
- * booking table
+ * booking table. Throws an Exception if the room is not allowed for booking.
* @param $str (string)
+ * @throws Exception
*/
- protected function setRoom($str) { $this->strRoom = $str; }
+ protected function setRoom($str) {
+ // only allow whitelisted rooms
+ if($this->oCfg->isRoomWhitelisted($str)) {
+ $this->strRoom = $str;
+ } else {
+ throw new Exception(_c("room-reservation:This room is not available ".
+ "for booking."));
+ }
+ }
/**
* Set the reason of the requested booking
$strCss = <<<CSS
#mod_roomReservationBookingTable .msg { font-weight:800; }
#mod_roomReservationBookingTable td {
- vertical-align:middle;
- height:7em;
- border:1px solid white; padding:0.4em;
+ vertical-align: middle;
+ height: 5em;
+ border: 1px solid white;
+ padding:0.4em;
}
#mod_roomReservationBookingTable td.booking { background-color:#5276AB; }
#mod_roomReservationBookingTable td.new { background-color:#008015; }
throw new AccessException(MOD_ROOM_RESERVATION_ERROR_ACCESS_DENIED);
return;
}
-
+
+ // print error messages and return if there are any
+ if(count($this->asErrors) > 0) {
+ printf("<p class='err'>%s</p>", join("<br />\n", $this->asErrors));
+ return;
+ }
+
// Print the header with the days
$ncTs = sizeof($this->oCfg->getTimeslices());
$nDays = ($this->oCfg->isShowWeekend()) ? 7 : 5;
// print link to booking if the timeslice is later than now
$oTs = $this->oCfg->getTimeslice($nTs);
- $tsCur = strtotime(date("Y-m-d ", $ts) . date(" G:i",
+ // note: only the timeslices are in GMT!
+ $tsCur = strtotime(date("Y-m-d ", $ts) . gmdate(" G:i",
$oTs->getEnd()));
if($tsCur > time()) {
$strURL = $_SERVER["PHP_SELF"] .
($ob->getUid() == null and !$this->oCfg->userCanBook())) {
throw new AccessException(MOD_ROOM_RESERVATION_ERROR_ACCESS_DENIED);
}
-
+
+ // test if room is whitelisted
+ if(!$this->oCfg->isRoomWhitelisted($ob->getRoom())) {
+ throw new Exception(MOD_ROOM_RESERVATION_ERROR_ROOM_NOT_WHITELISTED);
+ }
+
$strWhere = null;
$strLog = "";
*/
require_once("sec/secure.inc");
+require_once("db.inc");
require_once("mod_room-reservation/functions.inc");
require_once("mod_room-reservation/mod_roomReservationTimeslice.inc");
+require_once("mod_room-reservation/mod_roomReservationRoomsManager.inc");
/**
* Determines if a privilege has been assigned
*/
public function flushTimeslices() { $this->aoTimeslices = array(); }
+ /**
+ * Add a room to the list of rooms who can be booked. Throws an SQLException
+ * in case of an error.
+ * @param $sRoom (string) The name of the room
+ * @throws SQLException, Exception
+ * @return void
+ */
+ public function whitelistRoom($sRoom) {
+ if(!$this->isRoomWhitelisted($sRoom)) {
+ $r = db_store("mod_roomreservation_roomswhitelist",
+ array("rrr_name" => $sRoom));
+ if(!$r) {
+ throw new SQLException(MOD_ROOM_RESERVATION_ERROR_SQL);
+ } else {
+ log_insert(sprintf("Raum „%s“ für Buchungen gesperrt", $sRoom));
+ }
+ }
+ }
+
+ /**
+ * Forbid bookings for a room. Throws an SQLException in case of an error.
+ * @param $sRoom The name of the room
+ * @throws SQLException
+ */
+ public function unWhitelistRoom($sRoom) {
+ $h = db_query("DELETE FROM mod_roomreservation_roomswhitelist WHERE ".
+ "rrr_name = $1;", $sRoom);
+ if(!$h) {
+ throw new SQLException(MOD_ROOM_RESERVATION_ERROR_SQL);
+ } else {
+ log_insert(sprintf("Raum „%s“ für Buchungen zur Verfügung gestellt",
+ $sRoom));
+ }
+ }
+
+ /**
+ * Determine if a room is allowed for booking. Throws an SQLException
+ * in case of an error.
+ * @param $sRoom (string) The name of the room
+ * @return bool
+ * @throws SQLException
+ */
+ public function isRoomWhitelisted($sRoom) {
+ $h = db_query("SELECT * FROM mod_roomreservation_roomswhitelist WHERE ".
+ "rrr_name=$1;", $sRoom);
+ if(!$h) {
+ throw new SQLException(MOD_ROOM_RESERVATION_ERROR_SQL);
+ }
+ return (pg_num_rows($h) > 0);
+ }
+
+ /**
+ * Get all rooms that are allowed for booking. Throws an SQLException
+ * in case of an error.
+ * @throws SQLException
+ * @return array of mod_roomReservationRoomsManager objects
+ */
+ public function getWhitelistedRooms() {
+ $aor = mod_roomReservationRoomsManager::getRooms();
+ $ar = array();
+ foreach($aor as $key => $or) {
+ if($this->isRoomWhitelisted($or->getName())) {
+ $ar[] = $or;
+ }
+ }
+ return $ar;
+ }
+
/**
* Show or hide the weekend
* @param $b (bool)
require_once("ctrl.inc");
require_once("mod_room-reservation/mod_roomReservationPage.inc");
require_once("mod_room-reservation/mod_roomReservationTimesliceListBox.inc");
+require_once("mod_room-reservation/mod_roomReservationRoomWhitelistListBox.inc");
/** @todo document */
class mod_roomReservationConfigPage extends mod_roomReservationPage {
protected $bPostShowWeekend;
protected $bPostShowLessons;
protected $asMessages = array();
+ protected $otlb;
+ protected $orwlb;
public function __construct(mod_roomReservationConfig &$oCfg) {
parent::__construct($oCfg);
-
+ $this->otlb = new mod_roomReservationTimesliceListBox($this->oCfg);
+ $this->orwlb = new mod_roomReservationRoomWhitelistListBox($this->oCfg);
$this->setTitle(_c("room-reservation:Configuration"));
$this->setIcon("mod_room-reservation_config");
}
printf("<p>%s</p>", nl2br(q(join("\n", $this->asMessages))));
}
+ // first column
echo "<table border='0' cellspacing='10' cellpadding='0'><tr>".
- "<td style='width:50%;'>\n"; // two rows
+ "<td style='width:50%;'>\n";
+
+ GroupBox(_c("room-reservation:Available rooms"), "host");
+ printf("<p>%s</p>", _c("room-reservation:The following rooms are ".
+ "available for booking:"));
+ echo "<div style='margin:8px;'>";
+ $this->orwlb->show();
+ echo "</div>\n";
+ _GroupBox();
GroupBox(_("Privileges"), "keys");
$asAdminGroups = rrPrivilegedGroups("mod_roomreservation_admin");
echo "</tr></table></p>\n";
_GroupBox();
+ // second column
+ echo "</td><td><!--second row-->\n";
+
+ GroupBox(_c("room-reservation:Periods"), "mod_room-reservation_timeslice");
+ printf("<p>%s</p>", _c("room-reservation:Here you can fill in the ".
+ "periods where bookings can be undertaken. A booking period can ".
+ "e. g. correspond to a lesson."));
+ echo "<div style='margin:8px;'>";
+ $this->otlb->show();
+ echo "</div>\n";
+ _GroupBox();
+
GroupBox(_c("room-reservation:Further options"), "manage");
printf("<div style='margin:8px;'><form action='%s' method='post'>".
"<table><tr>\n", $_SERVER["PHP_SELF"]);
"[submit]' value='%s' /></td>", $GLOBALS["stdbtn"], _("OK"));
echo "</tr></table></form></div>\n";
_GroupBox();
-
- // second row
- echo "</td><td><!--second row-->\n";
-
- GroupBox(_c("room-reservation:Periods"), "mod_room-reservation_timeslice");
- printf("<p>%s</p>", _c("room-reservation:Here you can fill in the ".
- "periods where bookings can be undertaken. A booking period can ".
- "e. g. correspond to a lesson."));
- echo "<div style='margin:8px;'>";
- $otlb = new mod_roomReservationTimesliceListBox($this->oCfg);
- $otlb->show();
- echo "</div>";
- _GroupBox();
-
+
echo "</td></tr></table>\n";
}
}
--- /dev/null
+<?php
+/**\r
+ * @file mod_roomReservationControl.inc\r
+ * Class that represents an abstract control\r
+ * @author Roland Hieber (roland.hieber@wilhelm-gym.net)\r
+ * @date 25.07.2008\r
+ * \r
+ * Copyright © 2007 Roland Hieber\r
+ *\r
+ * Permission is hereby granted, free of charge, to any person obtaining\r
+ * copy of this software and associated documentation files (the "Software"),\r
+ * to deal in the Software without restriction, including without limitation\r
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,\r
+ * and/or sell copies of the Software, and to permit persons to whom the\r
+ * Software is furnished to do so, subject to the following conditions:\r
+ * \r
+ * The above copyright notice and this permission notice shall be included in\r
+ * all copies or substantial portions of the Software.\r
+ *\r
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r
+ * THE SOFTWARE.\r
+ */\r
+
+require_once("quote.inc");
+require_once("functions.inc");
+\r
+/** @todo document */
+abstract class mod_roomReservationControl {
+ /** (array of strings) Errors that occur while processing the form */
+ protected $asMessages;
+ /** (mod_roomReservationConfig) Reference to the configuration object */
+ protected $oCfg;
+
+ /***************************************************************************/
+ /**
+ * @name Constructor
+ * @{
+ * Constructor
+ * @param $oCfg (reference to mod_roomReservationConfig) Reference to the
+ * configuration
+ * @return mod_roomReservationBookingTable
+ */
+ public function __construct(mod_roomReservationConfig &$oCfg) {
+ rrAddCss(".blue .treeview .err { color:red !important; }");
+ $this->oCfg = $oCfg;
+ $this->processRequestVariables();
+ }
+
+ /***************************************************************************/
+ /**
+ * @}
+ * @name Initialization
+ * @{
+ */
+
+ /**
+ * Process the REQUEST variables and preset the some variables
+ * @return void
+ */
+ protected function processRequestVariables() { }
+
+ /***************************************************************************/
+ /**
+ * @}
+ * @name Output
+ * @{
+ */
+
+ /**
+ * Get the messages that have been produced. Returns HTML.
+ * @return string
+ */
+ protected function getMessages() {
+ if(count($this->asMessages) > 0) {
+ return sprintf("<div class='err'>%s</p>\n",
+ nl2br(q(join("\n", $this->asMessages))));
+ }
+ }
+
+ /**
+ * Show the beginning of the control.
+ * @return void
+ */
+ protected function beginShow() { }
+
+ /**
+ * Show the control. Override this function to print your HTML code.
+ * @return void
+ */
+ protected abstract function doShow();
+
+ /**
+ * Show the end of the control.
+ * @return void
+ */
+ protected function endShow() { }
+
+ /**
+ * Show the full control. You don't need to override this function. Instead,
+ * override doShow().
+ * @return void
+ */
+ public function show() {
+ $this->beginShow();
+ $this->doShow();
+ $this->endShow();
+ }
+}
+?>
\ No newline at end of file
* @return void
*/
protected function beginShow() {
- html_header("<style type='text/css'>\n".rrGetCSS()."\n</style>\n");
+ html_header("<style type='text/css'>\n".rrGetCss()."\n</style>\n");
PageBlue(q($this->getTitle()), $this->getIcon());
// print error messages from the configuration
/**
* Show the end of the page.
- *
+ * @return void
*/
protected function endShow() {
_PageBlue();
*/
public function getLocation() { return $this->strLocation; }
+ /**
+ * Conversion to string
+ * @return string
+ */
+ public function __toString() { return $this->getName(); /* name is key */ }
/**@}*/
}
?>
--- /dev/null
+<?php
+/**\r
+ * @file mod_roomReservationRoomWhitelistListBox.inc\r
+ * List box that shows the rooms who can be booked\r
+ * @author Roland Hieber (roland.hieber@wilhelm-gym.net)\r
+ * @date 24.07.2008\r
+ * \r
+ * Copyright © 2007 Roland Hieber\r
+ *\r
+ * Permission is hereby granted, free of charge, to any person obtaining\r
+ * copy of this software and associated documentation files (the "Software"),\r
+ * to deal in the Software without restriction, including without limitation\r
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,\r
+ * and/or sell copies of the Software, and to permit persons to whom the\r
+ * Software is furnished to do so, subject to the following conditions:\r
+ * \r
+ * The above copyright notice and this permission notice shall be included in\r
+ * all copies or substantial portions of the Software.\r
+ *\r
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r
+ * THE SOFTWARE.\r
+ */\r
+
+require_once("mod_roomReservationControl.inc");
+require_once("mod_roomReservationRoomsManager.inc");
+
+/*****************************************************************************/
+/**
+ * @page roomwhitelistlistbox_actions Actions of a
+ * mod_roomReservationRoomWhitelistListBox instance
+ * @{
+ * The following constants describe the actions that a
+ * mod_roomReservationRoomWhitelistListBox instance can handle. They are used
+ * in processRequestVariables() to determine the action that should be done
+ * when the control is shown.
+ */
+/** Show the control (default action) */
+define("MOD_ROOM_RESERVATION_RWLB_ACTION_SHOW", 0);
+/** Show the addition form */
+define("MOD_ROOM_RESERVATION_RWLB_ACTION_ADD", 1);
+/** Process the addition form */
+define("MOD_ROOM_RESERVATION_RWLB_ACTION_SUBMITADD", 2);
+/** Show the deletion form */
+define("MOD_ROOM_RESERVATION_RWLB_ACTION_DELETE", 3);
+/** Process the deletion form */
+define("MOD_ROOM_RESERVATION_RWLB_ACTION_SUBMITDELETE", 4);
+/** @} */
+\r
+/** @todo document */
+class mod_roomReservationRoomWhitelistListBox
+ extends mod_roomReservationControl {
+ /**
+ * (array of integers) OIDs of the rows in the rooms table that were
+ * selected (POST data)
+ */
+ protected $anPostSelection = array();
+ /** (constant) Display mode, see @ref roomwhitelistlistbox_actions */
+ protected $cMode;
+
+ protected function processRequestVariables() {
+ // default values
+ $this->cMode = MOD_ROOM_RESERVATION_RWLB_ACTION_SHOW;
+
+ // POST data
+ if(isset($_POST["mod_roomReservationRoomWhitelistListBox"])) {
+ $aPost = $_POST["mod_roomReservationRoomWhitelistListBox"];
+ // mode
+ if(isset($aPost["action"])) {
+ if(isset($aPost["action"]["add"])) {
+ if($aPost["action"]["add"] == _("Add")) {
+ $this->cMode = MOD_ROOM_RESERVATION_RWLB_ACTION_ADD;
+ } elseif($aPost["action"]["add"] == _("OK")) {
+ $this->cMode = MOD_ROOM_RESERVATION_RWLB_ACTION_SUBMITADD;
+ }
+ } elseif(isset($aPost["action"]["delete"])) {
+ if($aPost["action"]["delete"] == _("Delete")) {
+ $this->cMode = MOD_ROOM_RESERVATION_RWLB_ACTION_DELETE;
+ } elseif($aPost["action"]["delete"] == _("OK")) {
+ $this->cMode = MOD_ROOM_RESERVATION_RWLB_ACTION_SUBMITDELETE;
+ }
+ }
+ }
+ // selection
+ if(isset($aPost["l"])) {
+ foreach($aPost["l"] as $nOid => $bChecked) {
+ if($bChecked) {
+ $this->anPostSelection[] = $nOid;
+ }
+ }
+ }
+ }
+
+ // process the forms
+ if($this->cMode == MOD_ROOM_RESERVATION_RWLB_ACTION_SUBMITADD) {
+ $h = db_query("SELECT name FROM rooms WHERE oid IN ".
+ qdb_arr($this->anPostSelection));
+ while($a = pg_fetch_array($h)) {
+ $this->oCfg->whitelistRoom($a["name"]);
+ }
+ }
+
+ if($this->cMode == MOD_ROOM_RESERVATION_RWLB_ACTION_SUBMITDELETE) {
+ $h = db_query("SELECT name FROM rooms WHERE oid IN ".
+ qdb_arr($this->anPostSelection));
+ while($a = pg_fetch_array($h)) {
+ $this->oCfg->unWhitelistRoom($a["name"]);
+ }
+ }
+ }
+
+ protected function doShow() {
+ echo "<form method='post'>";
+ TreeView(array(_("Room")));
+ switch($this->cMode) {
+ case MOD_ROOM_RESERVATION_RWLB_ACTION_DELETE:
+ $this->showDeleteForm();
+ break;
+ case MOD_ROOM_RESERVATION_RWLB_ACTION_ADD: $this->showAddForm(); break;
+ default:
+ case MOD_ROOM_RESERVATION_RWLB_ACTION_SHOW: $this->showForm(); break;
+ }
+ _TreeView();
+ echo "</form>\n";
+ }
+
+ /**
+ * Print the form if not delete nor add was requested
+ * @return void
+ */
+ protected function showForm() {
+ $aoRooms = $this->oCfg->getWhitelistedRooms();
+ // only show add button if there are still some unlisted rooms
+ if(count(mod_roomReservationRoomsManager::getRooms()) > count($aoRooms)) {
+ TreeViewLine(sprintf("<%s name='mod_roomReservationRoomWhitelistListBox".
+ "[action][add]' value='%s' /></form>", $GLOBALS["stdbtn"], _("Add")));
+ }
+ $this->showList($aoRooms);
+ // toolbar
+ printf("<tr><td class='tbbtm' colspan='%d'>", $GLOBALS["treeview_cols"]);
+ CheckCombo();
+ printf("<%s name='mod_roomReservationRoomWhitelistListBox[action]".
+ "[delete]' value='%s' />", $GLOBALS["stdbtn"], _("Delete"));
+ echo "</td></tr>\n";
+ }
+
+ /**
+ * Print the addition form
+ * @return void
+ */
+ protected function showAddForm() {
+ // only list rooms that are not already whitelisted
+ $aoRooms = array_diff(mod_roomReservationRoomsManager::getRooms(),
+ $this->oCfg->getWhitelistedRooms());
+ TreeViewSubtitle(_("Add"));
+ $this->showList($aoRooms);
+ TreeViewLine(sprintf("<p><%s name='mod_roomReservationRoomWhitelistList".
+ "Box[action][add]' value='%s' /> <%s name='mod_roomReservationRoom".
+ "WhitelistListBox[action][add]' value='%s' /></p>", $GLOBALS["stdbtn"],
+ _("OK"), $GLOBALS["stdbtn"], _("Cancel")));
+ }
+
+ /**
+ * Show the deletion form
+ * @return void
+ */
+ protected function showDeleteForm() {
+ // list rooms in selection
+ $aoRooms = array();
+ $h = db_query("SELECT name FROM rooms WHERE oid IN ".
+ qdb_arr($this->anPostSelection));
+ foreach($this->anPostSelection as $nOid) {
+ $aoRooms[] = mod_roomReservationRoomsManager::getRoomByOid($nOid);
+ }
+ TreeViewSubtitle(sprintf(_("Following %s will be deleted"),
+ _c("room-reservation:rooms")));
+ $this->showList($aoRooms, false);
+ TreeViewLine(sprintf("<p><%s name='mod_roomReservationRoomWhitelistList".
+ "Box[action][delete]' value='%s' /> <%s name='mod_roomReservationRoom".
+ "WhitelistListBox[action][delete]' value='%s' /></p>",
+ $GLOBALS["stdbtn"], _("OK"), $GLOBALS["stdbtn"], _("Cancel")));
+ }
+
+ /**
+ * Show the list items
+ * @param $aoRooms (array of mod_roomReservationRoom objects) List items
+ * @param $bCheckboxes (bool) Whether to show checkboxes
+ */
+ protected function showList($aoRooms, $bCheckboxes = true) {
+ if(count($aoRooms) > 0) {
+ foreach($aoRooms as $o) {
+ // fetch oid from SQL table
+ $nOid = pg_fetch_result(db_query("SELECT oid FROM ".
+ "rooms WHERE name = $1", $o->getName()), 0, "oid");
+ $sBox = $bCheckboxes ? sprintf("<%s id='box%d' name='mod_room".
+ "ReservationRoomWhitelistListBox[l][%d]' value='1'%s /><label ".
+ "for='box%d'>%s%s</label>", $GLOBALS["smlchk"], $nOid, $nOid,
+ @$this->anPostSelection[$nOid] ? " checked='checked'" : "", $nOid,
+ icon("host"), $o->getName()) :
+ sprintf("<input type='hidden' name='mod_roomReservationRoom".
+ "WhitelistListBox[l][%d]' value='1' />%s%s", $nOid, icon("host"),
+ $o->getName());
+ TreeViewLine($sBox);
+ }
+ } else {
+ TreeViewEmpty();
+ }
+ }
+}
+?>
\ No newline at end of file
}
*/
+ /**
+ * Get a room by its OID. Returns <tt>null</tt> if the room was not found.
+ * @param $nOid (integer) The OID of the room
+ * @return mod_roomReservationRoom
+ */
+ static function getRoomByOid($nOid) {
+ $o = null;
+ $h = db_query("SELECT * FROM rooms WHERE oid = $1;", $nOid);
+ if(pg_num_rows($h) > 0) {
+ $arResult = pg_fetch_array($h);
+ $o = new mod_roomReservationRoom($arResult["name"],
+ $arResult["room_no"], $arResult["floor"], $arResult["building"],
+ $arResult["location"]);
+ }
+ return $o;
+ }
+
+ /**
+ * Get a room by its name. Returns <tt>null</tt> if the room was not found.
+ * @param $sName (string) The name of the room
+ * @return mod_roomReservationRoom
+ */
+ static function getRoomByName($sName) {
+ $o = null;
+ $h = db_query("SELECT * FROM rooms WHERE name = $1;", $sName);
+ if(pg_num_rows($h) > 0) {
+ $arResult = pg_fetch_array($h);
+ $o = new mod_roomReservationRoom($arResult["name"],
+ $arResult["room_no"], $arResult["floor"], $arResult["building"],
+ $arResult["location"]);
+ }
+ return $o;
+ }
+
/**
* Get all rooms from the database
* @return array of mod_roomReservationRoom
*/
- function getRooms() {
+ static function getRooms() {
$aoReturn = array();
$hQuery = db_query("SELECT * FROM rooms ORDER BY name;");
while($arResult = pg_fetch_array($hQuery)) {
*/\r
require_once("ctrl.inc");
+require_once("mod_room-reservation/mod_roomReservationControl.inc");
require_once("mod_room-reservation/mod_roomReservationTimeslice.inc");
/*****************************************************************************/
/** @} */
\r
/** @todo document, add a delete confirmation */
-class mod_roomReservationTimesliceListBox /* extends mclControl */ {
+class mod_roomReservationTimesliceListBox extends mod_roomReservationControl {
- /** (mod_roomReservationConfig) Reference to the configuration object */
- protected $oCfg;
/**
* (constant) The action to be done (GET data).
* See @ref timeslicelistbox_actions.
protected $sNewBegin;
/** (string) The ending for a new timeslice (GET data) */
protected $sNewEnd;
- /** (array of strings) Errors that occur while processing the form */
- protected $asFormErrors;
/***************************************************************************/
/**
* Constructor
* @param $oCfg (reference to mod_roomReservationConfig) Reference to the
* configuration
- * @return mod_roomReservationBookingTable
+ * @return mod_roomReservationTimesliceListBox
*/
public function __construct(mod_roomReservationConfig &$oCfg) {
- $this->oCfg = $oCfg;
-
- $this->processRequestVariables();
+ parent::__construct($oCfg);
}
/***************************************************************************/
// Note: we want to handle the timestamps in GMT format, hence the "+0000"
if(strtotime($this->getNewBegin()." +0000") === false) {
- $this->asFormErrors[] = _c("room-reservation:The beginning time is ".
+ $this->asMessages[] = _c("room-reservation:The beginning time is ".
"invalid.");
$bErrors = true;
}
if(strtotime($this->getNewEnd()." +0000") === false) {
- $this->asFormErrors[] = _c("room-reservation:The ending time is ".
+ $this->asMessages[] = _c("room-reservation:The ending time is ".
"invalid.");
$bErrors = true;
}
$this->setNewBegin("");
$this->setNewEnd("");
} catch(Exception $e) {
- $this->asFormErrors[] = $e->getMessage();
+ $this->asMessages[] = $e->getMessage();
}
}
*/
/**
- * Show the control
+ * Actually show the control
* @return void
*/
- public function show() {
+ public function doShow() {
TreeView(array(_c("room-reservation:Begin"), _c("room-reservation:End")));
// addition form
printf("<form method='get'>");
hidden("mod_roomReservationTimesliceListBox[action]", "add");
TreeViewTitle(_("Add"));
- if(count($this->asFormErrors) > 0) {
- TreeViewLine(sprintf("<div class='err' style='color:red;'>%s</div>",
- nl2br(q(trim(join("\n", $this->asFormErrors))))));
+
+ $sMessages = $this->getMessages();
+ if(trim($sMessages) != "") {
+ TreeViewLine($sMessages);
}
+
TreeViewLine(array(sprintf("<%s name='mod_roomReservationTimesliceListBox".
"[begin]' value='%s' size='8'/>", $GLOBALS["stdedt"],
$this->getNewBegin()), sprintf("<%s name='".
msgstr "Administration des Raumbelegungsplans"
# Other things
+msgid "room-reservation:rooms"
+msgstr "room-reservation:Räume"
+
msgid "room-reservation:Access denied."
msgstr "room-reservation:Zugriff verweigert."
msgid "room-reservation:%d# week"
msgstr "room-reservation: %d. Woche"
+msgid "room-reservation:Available rooms"
+msgstr "room-reservation:Verfügbare Räume"
+
msgid "room-reservation:No rooms have been configured yet."
msgstr "room-reservation:Es wurden noch keine Räume eingerichtet."
+msgid "room-reservation:This room is not available for booking."
+msgstr "room-reservation:Dieser Raum steht nicht für Buchungen zu Verfügung."
+
msgid "room-reservation:Schedule of room bookings"
msgstr "room-reservation:Raumbelegungsplan"
"Gruppe mit Administrations-Sonderrecht kann ebenso implizit Buchungen "
"vornehmen und die Buchungstabelle einsehen."
+msgid "room-reservation:The following rooms are available for booking:"
+msgstr "room-reservation:Die folgenden Räume stehen für Buchungen zur "
+ "Verfügung:"
+
msgid "room-reservation:Here you can fill in the periods where bookings can "
"be undertaken. A booking period can e. g. correspond to a lesson."
msgstr "room-reservation:Hier die Zeitstunden der Buchungszeiträume eingeben. "
--- /dev/null
+#!/usr/bin/perl -w
+
+use strict;
+use warnings;
+use IServ::DB;
+use Time::Local;
+
+my $OLDCFG = "/old/opt/iserv/idesk/rooms/admin/config.inc.rpmsave";
+my $NEWCFG = "/usr/share/iserv/www/inc/mod_room-reservation/config.inc";
+
+my @tsbegin;
+my @tsend;
+my @tsbeginold;
+my @tsendold;
+my @allowedgroups;
+my @admingroups;
+my $restrictaccess = 0;
+my $showweekend = 0;
+my $showlessons = 1;
+
+# convert the config file
+open IN, "<", $OLDCFG or die "ERROR: old config file could not be opened: $!";
+while(<IN>) {
+ # skip unused variables
+ if(/\$cfgRooms\[(\"LogOnInsert\"|\'LogOnInsert\')\]/) {
+ } elsif(/\$cfgRooms\[(\"OldBookings\"|\'OldBookings\')\]/) {
+ } elsif(/\$cfgRooms\[(\"ShowClassEdit\"|\'ShowClassEdit\')\]/) {
+ } elsif(/\$cfgRooms\[(\"ClassEditText\"|\'ClassEditText\')\]/) {
+
+ # AbsTime is now named ShowLessons
+ } elsif(/\$cfgRooms\[(\"AbsTime\"|\'AbsTime\')\]\s*=\s*(true|false|0|1)\s*/) {
+ print "AbsTime is $2\n";
+ $showlessons = ($2 eq "true" or $2 eq "1") ? 1 : 0;
+
+ # ShowWeekend
+ } elsif(/\$cfgRooms\[(\"ShowWeekend\"|\'ShowWeekend\')\]\s*=\s*(true|false|0|1)/) {
+ print "ShowWeekend is $2\n";
+ $showweekend = ($2 eq "true" or $2 eq "1") ? 1 : 0;
+
+ # timeslice beginnings
+ } elsif(/\$cfgRooms\[(\"TimeslicesBegin\"|\'TimeslicesBegin\')\]\s*=\s*array\s*\(/) {
+ print "processing timeslice beginnings...\n";
+ while(<IN>) {
+ if(/\);/) {
+ last;
+ } else {
+ /\s*(\d)\s*=>\s*[\'\"](\d\d?):(\d\d)[\'\"]\s*/;
+ my $hr = $2;
+ $hr = "0$hr" if length $hr < 2;
+ push @tsbeginold, "$hr:$3:00";
+ push @tsbegin, Time::Local::timegm 0, $3, $hr, 1, 0, 1970;
+ print " found beginning: $hr:$3\n";
+ }
+ }
+
+ # timeslice endings
+ } elsif(/\$cfgRooms\[(\"TimeslicesEnd\"|\'TimeslicesEnd\')\]\s*=\s*array\s*\(/) {
+ print "processing timeslice endings...\n";
+ while(<IN>) {
+ if(/\);/) {
+ last;
+ } else {
+ /\s*(\d)\s*=>\s*[\'\"](\d\d?):(\d\d)[\'\"]\s*/;
+ my $hr = $2;
+ $hr = "0$hr" if length $hr < 2;
+ push @tsendold, "$hr:$3:00";
+ push @tsend, Time::Local::timegm 0, $3, $hr, 1, 0, 1970;
+ print " found ending: $hr:$3\n";
+ }
+ }
+
+ # add rooms to the database, if they do not exist yet
+ } elsif(/\$cfgRooms\[(\"Rooms\"|\'Rooms\')\]\s*=\s*array\s*\(/) {
+ print "processing rooms...\n";
+ while(<IN>) {
+ if(/\);/) {
+ last;
+ } else {
+ /\s*\d\s*=>\s*((\'([^\']*)\')|(\"([^\"]*)\"))\s*/;
+ my $sqlval = IServ::DB::Val $3;
+ if(IServ::DB::Rows "SELECT * FROM rooms WHERE name = $sqlval;") {
+ print " room '$3' found in database.\n";
+ } else {
+ print "NOTICE: room '$3' not found in database, adding it.\n";
+ IServ::DB::Put "rooms", { "name" => $3 } or die $!;
+ }
+ }
+ }
+
+ # convert old user rights to privileges
+ } elsif(/\$cfgRooms\[(\"RestrictAccess\"|\'RestrictAccess\')\]\s*=\s*(false|true|0|1)/) {
+ $restrictaccess = ($2 eq "true" or $2 eq "1") ? 1 : 0;
+
+ } elsif(/\$cfgRooms\[(\"AllowedGroups\"|\'AllowedGroups\')\]\s*=\s*array\s*\(/) {
+ print "processing allowed groups...\n";
+ while(<IN>) {
+ if(/\);/) {
+ last;
+ } else {
+ /\s*\d\s*=>\s*((\'([^\']*)\')|(\"([^\"]*)\"))\s*/;
+ my $name = IServ::DB::Val $3;
+ my @act = IServ::DB::GetArr "SELECT act FROM groups WHERE name=$name;";
+ if(@act) {
+ print " found group $name.\n";
+ push @allowedgroups, $act[0]{"act"};
+ } else {
+ print "NOTICE: group $name not found in database, ignoring it.\n";
+ }
+ }
+ }
+
+ } elsif(/\$cfgRooms\[(\"GroupsAdmin\"|\'GroupsAdmin\')\]\s*=\s*array\s*\(/) {
+ print "processing admin groups...\n";
+ while(<IN>) {
+ if(/\);/) {
+ last;
+ } else {
+ /\s*\d\s*=>\s*((\'([^\']*)\')|(\"([^\"]*)\"))\s*/;
+ my $name = IServ::DB::Val $3;
+ my @act = IServ::DB::GetArr "SELECT act FROM groups WHERE name=$name;";
+ if(@act) {
+ print " found group $name.\n";
+ push @admingroups, $act[0]{"act"};
+ } else {
+ print "NOTICE: group $name not found in database, ignoring it.\n";
+ }
+ }
+ }
+ }
+}
+
+print "converting old user rights to privileges...\n";
+# change the privilege names to the right ones
+if($restrictaccess) {
+ foreach(@allowedgroups) {
+ my $priv = "mod_roomreservation_view";
+ my $act = "$_";
+ my @act = IServ::DB::GetArr "SELECT act FROM privileges_assign ".
+ "WHERE privilege='$priv' AND act='$act';";
+ if(@act) {
+ print " group $_ is already allowed to see the bookings, ignoring it.\n";
+ } else {
+ IServ::DB::Put "privileges_assign", { "act" => $_,
+ "privilege" => $priv } or die $!;
+ print " allowed viewing for group '$_'\n";
+ }
+
+ $priv = "mod_roomreservation_book";
+ @act = "SELECT act FROM privileges_assign ".
+ "WHERE privilege='$priv' AND act='$act';";
+ if(@act) {
+ print " group $_ is already allowed to book, ignoring it.\n";
+ } else {
+ IServ::DB::Put "privileges_assign", { "act" => $_,
+ "privilege" => $priv } or die $!;
+ print " allowed booking for group '$_'\n";
+ }
+ }
+}
+foreach(@admingroups) {
+ my $priv = "mod_roomreservation_admin";
+ my $act = "$_";
+ my @act = IServ::DB::GetArr "SELECT act FROM privileges_assign ".
+ "WHERE privilege='$priv' AND act='$act';";
+ if(@act) {
+ print " group $_ has already adminship, ignoring it.\n";
+ } else {
+ IServ::DB::Put "privileges_assign", { "act" => $_,
+ "privilege" => $priv } or die $!;
+ print " allowed administration for group '$_'\n";
+ }
+}
+
+print "writing config file...\n";
+system "touch $NEWCFG";
+open OUT, ">", $NEWCFG or die "ERROR: new config file could not be opened: $!";
+print OUT "<?php\n\$this->flushTimeslices();\n";
+for(my $i = 0; $i <= $#tsbegin; $i++) {
+ print OUT "\$this->addTimeslice(new mod_roomReservationTimeslice($tsbegin[$i],$tsend[$i]));\n";
+}
+print OUT "\$this->setShowWeekend($showweekend);\n";
+print OUT "\$this->setShowLessons($showlessons);\n";
+print OUT "?>";
+close OUT;
+close IN;
+
+print "\nconverting the database, have a lot of fun...\n";
+open IN, "cat /old/rooms.sql | iconv -f utf8 -t utf8 |" or die
+ "ERROR: the database dump could not be opened: $!\n";
+
+# build hashes of the form "timestamp" => "timeslice" ("15:00:00" => "3")
+my $i = 0;
+my %tsbeginoldkeys;
+my %tsendoldkeys;
+foreach (@tsbeginold) {
+ $tsbeginoldkeys{$_} = $i;
+ $i++;
+}
+$i = 0;
+foreach (@tsendold) {
+ $tsendoldkeys{$_} = $i;
+ $i++;
+}
+
+# database conversion
+# insert old data and then convert them
+print "importing old bookings...\n";
+IServ::DB::Exec "CREATE TABLE mod_roomreservation_bookings_old (id INT NOT NULL PRIMARY KEY, room TEXT NOT NULL, date DATE NOT NULL, timebegin TIME NOT NULL, timeend TIME NOT NULL, act TEXT REFERENCES users(Act) ON DELETE CASCADE ON UPDATE CASCADE NOT NULL, class TEXT, reason TEXT NOT NULL, fixed BOOL);";
+while(<IN>) {
+ # rename the table
+ $_ =~ s/INSERT INTO rooms/INSERT INTO mod_roomreservation_bookings_old/;
+ IServ::DB::Exec($_);
+}
+
+print "converting the database...\n";
+foreach(IServ::DB::GetArr("SELECT * FROM mod_roomreservation_bookings_old;")) {
+ my %row = %{$_};
+ IServ::DB::Put "mod_roomreservation_bookings", { "rrb_room" => $row{"room"},
+ "rrb_date" => $row{"date"}, "rrb_tsfirst" =>
+ $tsbeginoldkeys{$row{"timebegin"}}, "rrb_tslast" =>
+ $tsendoldkeys{$row{"timeend"}}, "rrb_act" => $row{"act"}, "rrb_reason" =>
+ $row{"reason"}, "rrb_interval" => $row{"fixed"} };
+}
+
+# delete old tables
+IServ::DB::Exec "DROP TABLE mod_roomreservation_bookings_old;";
+IServ::DB::Exec "DROP TABLE rooms_archive;";
+
+print "done.\n";
+
--\r
--- Table with bookings for module iserv-room-reservation\r
+-- Table with rooms allowed for booking\r
+-- \r
+CREATE TABLE mod_roomreservation_roomswhitelist (\r
+ rrr_name TEXT UNIQUE REFERENCES rooms(name)\r
+ ON DELETE CASCADE\r
+ ON UPDATE CASCADE\r
+);\r
+\r
+--\r
+-- Table with bookings\r
--\r
CREATE TABLE mod_roomreservation_bookings (\r
rrb_uid SERIAL NOT NULL PRIMARY KEY, -- Unique ID \r
--\r
GRANT SELECT, INSERT, UPDATE, DELETE ON mod_roomreservation_bookings, mod_roomreservation_bookings_rrb_uid_seq TO webusr;\r
GRANT SELECT, INSERT, UPDATE, DELETE ON mod_roomreservation_bookings, mod_roomreservation_bookings_rrb_uid_seq TO webadm;\r
+GRANT SELECT ON mod_roomreservation_roomswhitelist TO webusr;\r
+GRANT SELECT, INSERT, UPDATE, DELETE ON mod_roomreservation_roomswhitelist TO webadm;\r