diff --git a/app/controllers/resources/ajax.php b/app/controllers/resources/ajax.php
new file mode 100644
index 0000000000000000000000000000000000000000..6ff3942f734391a90ebbf5dfb937c0cd7fc58b59
--- /dev/null
+++ b/app/controllers/resources/ajax.php
@@ -0,0 +1,659 @@
+<?php
+
+/**
+ * ajax.php - contains Resources_AjaxController
+ *
+ * This program 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.
+ *
+ * @author      David Siegfried <ds.siegfried@gmail.com>
+ * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
+ */
+
+class Resources_AjaxController extends AuthenticatedController
+{
+    public function toggle_marked_action($request_id)
+    {
+        $request = \ResourceRequest::find($request_id);
+
+        if (!$request) {
+            throw new Exception('Resource request object not found!');
+        }
+
+        $current_user = \User::findCurrent();
+
+        if ($request->isReadOnlyForUser($current_user)) {
+            throw new \AccessDeniedException();
+        }
+
+        //Switch to the next marking state or return to the unmarked state
+        //if the next marking state would be after the last defined
+        //marking state.
+        $request->marked = ($request->marked + 1) % \ResourceRequest::MARKING_STATES;
+        $request->store();
+
+        $this->render_json($request->toArray());
+    }
+
+    public function get_resource_booking_intervals_action($booking_id)
+    {
+        $booking = \ResourceBooking::find($booking_id);
+        if (!$booking) {
+            throw new Exception('Resource booking object not found!');
+        }
+
+        $resource = $booking->resource->getDerivedClassInstance();
+        if (!$resource->bookingPlanVisibleForUser(\User::findCurrent())) {
+            throw new \AccessDeniedException();
+        }
+
+        //Get begin and end:
+        $begin_str = \Request::get('begin');
+        $end_str   = \Request::get('end');
+        $begin     = null;
+        $end       = null;
+        if ($begin_str && $end_str) {
+            //Try the ISO format first: YYYY-MM-DDTHH:MM:SS±ZZ:ZZ
+            $begin = \DateTime::createFromFormat(\DateTime::RFC3339, $begin_str);
+            $end   = \DateTime::createFromFormat(\DateTime::RFC3339, $end_str);
+            if (!($begin instanceof \DateTime) || !($end instanceof \DateTime)) {
+                $tz = new \DateTime();
+                $tz = $tz->getTimezone();
+                //Try the ISO format without timezone:
+                $begin = \DateTime::createFromFormat('Y-m-d\TH:i:s', $begin_str, $tz);
+                $end   = \DateTime::createFromFormat('Y-m-d\TH:i:s', $end_str, $tz);
+            }
+        }
+
+        $sql      = "booking_id = :booking_id ";
+        $sql_data = ['booking_id' => $booking->id];
+        if ($begin instanceof \DateTime && $end instanceof \DateTime) {
+            $sql               .= "AND begin >= :begin AND end <= :end ";
+            $sql_data['begin'] = $begin->getTimestamp();
+            $sql_data['end']   = $end->getTimestamp();
+        }
+        if (\Request::submitted('exclude_cancelled_intervals')) {
+            $sql .= "AND takes_place = '1' ";
+        }
+        $sql       .= "ORDER BY begin ASC, end ASC";
+        $intervals = \ResourceBookingInterval::findBySql($sql, $sql_data);
+
+        $result = [];
+        foreach ($intervals as $interval) {
+            $result[] = $interval->toRawArray();
+        }
+
+        $this->render_json($result);
+    }
+
+    public function toggle_takes_place_field_action($interval_id)
+    {
+        $interval = \ResourceBookingInterval::find($interval_id);
+        if (!$interval) {
+            throw new Exception('ResourceBookingInterval object not found!');
+        }
+
+        //Get the resource and check the permissions of the user:
+        $resource = $interval->resource;
+        if (!$resource) {
+            throw new Exception('ResourceBookingInterval not linked with a resource!');
+        }
+
+        $resource = $resource->getDerivedClassInstance();
+
+        if (!$resource->userHasPermission(\User::findCurrent(), 'autor', [$interval->begin, $interval->end])) {
+            throw new Exception('You do not have sufficient permissions to modify the interval!');
+        }
+
+        if (
+            !$interval->takes_place
+            && $resource->isAssigned(new \DateTime('@' . $interval->begin), new \DateTime('@' . $interval->end))
+        ) {
+            throw new Exception('Already booked');
+        }
+        //Switch the takes_place field:
+        $interval->takes_place = $interval->takes_place ? '0' : '1';
+
+        if ($interval->store()) {
+            $this->render_json([
+                'takes_place' => $interval->takes_place
+            ]);
+        } else {
+            throw new Exception('Error while storing the interval!');
+        }
+    }
+
+    public function get_semester_booking_plan_action($resource_id)
+    {
+        $resource = \Resource::find($resource_id);
+        if (!$resource) {
+            throw new Exception('Resource object not found!');
+        }
+
+        $resource = $resource->getDerivedClassInstance();
+
+        $current_user = User::findCurrent();
+
+        if (!$resource->bookingPlanVisibleForUser($current_user)) {
+            throw new AccessDeniedException();
+        }
+
+        $display_requests     = Request::get('display_requests');
+        $display_all_requests = Request::get('display_all_requests');
+
+        $begin = new \DateTime();
+        $end   = new \DateTime();
+
+        $semester_id = Request::get('semester_id');
+
+        $semester = $semester_id ? Semester::find($semester_id) : Semester::findCurrent();
+        if (!$semester) {
+            throw new Exception('No semester found!');
+        }
+
+        if (Request::get('semester_timerange') !== 'fullsem') {
+            $begin->setTimestamp($semester->vorles_beginn);
+            $end->setTimestamp($semester->vorles_ende);
+        } else {
+            $begin->setTimestamp($semester->beginn);
+            $end->setTimestamp($semester->ende);
+        }
+
+        //Get parameters:
+        $booking_types = Request::getArray('booking_types');
+
+        $begin_timestamp = $begin->getTimestamp();
+        $end_timestamp   = $end->getTimestamp();
+
+        //Get the event data sources:
+        $bookings = ResourceBooking::findByResourceAndTimeRanges(
+            $resource,
+            [
+                [
+                    'begin' => $begin_timestamp,
+                    'end'   => $end_timestamp
+                ]
+            ],
+            $booking_types
+        );
+
+        $requests = [];
+        if ($display_all_requests || $display_requests) {
+            $requests_sql = "JOIN seminar_cycle_dates AS scd USING (metadate_id)
+                             WHERE resource_id = :resource_id
+                               AND closed = 0";
+            $requests_sql_params = [
+                'begin'       => $begin_timestamp,
+                'end'         => $end_timestamp,
+                'resource_id' => $resource->id
+            ];
+            if (!$display_all_requests) {
+                $requests_sql .= "AND user_id = :user_id ";
+                $requests_sql_params['user_id'] = $current_user->id;
+            }
+
+            $requests = \ResourceRequest::findBySql(
+                $requests_sql,
+                $requests_sql_params
+            );
+        }
+
+        $merged_objects = [];
+        $meta_dates     = [];
+
+        foreach ($bookings as $booking) {
+            $booking->resource  = $resource;
+            $irrelevant_booking = $booking->getRepetitionType() !== 'weekly'
+                                  && (
+                                      !\Request::get('display_single_bookings')
+                                      || $booking->end < strtotime('today')
+                                  );
+            if ($booking->getAssignedUserType() === 'course' && in_array($booking->assigned_course_date->metadate_id, $meta_dates)) {
+                $irrelevant_booking = true;
+            };
+            if (!$irrelevant_booking) {
+                //It is an booking with repetitions that has to be included
+                //in the semester plan.
+                if (in_array($booking->getRepetitionType(), ['single', 'weekly'])) {
+                    $event_list = $booking->convertToEventData(
+                        [
+                            ResourceBookingInterval::build(
+                                [
+                                    'interval_id' => md5(uniqid()),
+                                    'begin'       => $booking->begin - $booking->preparation_time,
+                                    'end'         => $booking->end
+                                ]
+                            )
+                        ],
+                        $current_user
+                    );
+                } else {
+                    $event_list = $booking->getFilteredEventData(null, null, null, strtotime('today'), $end_timestamp);
+                }
+                foreach ($event_list as $event_data) {
+                    if ($booking->getAssignedUserType() === 'course' && $booking->assigned_course_date->metadate_id) {
+                        $index = sprintf(
+                            '%s_%s_%s',
+                            $booking->assigned_course_date->metadate_id,
+                            $event_data->begin->format('NHis'),
+                            $event_data->end->format('NHis')
+                        );
+                        $meta_dates[] = $booking->assigned_course_date->metadate_id;
+                    } else {
+                        $index = sprintf(
+                            '%s_%s_%s',
+                            $booking->id,
+                            $event_data->begin->format('NHis'),
+                            $event_data->end->format('NHis')
+                        );
+                    }
+
+                    //Strip some data that cannot be used effectively in here:
+                    $event_data->api_urls = [];
+                    $event_data->editable = false;
+
+                    $merged_objects[$index] = $event_data;
+                }
+            }
+        }
+
+        $relevant_request = false;
+        foreach ($requests as $request) {
+            if ($request->cycle instanceof \SeminarCycleDate) {
+                $cycle_dates = $request->cycle->getAllDates();
+                foreach ($cycle_dates as $cycle_date) {
+                    $relevant_request = $semester->beginn <= $cycle_date->date
+                        && $semester->ende >= $cycle_date->date;
+                    if ($relevant_request) {
+                        //We have found a date for the current semester
+                        //that makes the request relevant.
+                        break;
+                    }
+                }
+                if (!$relevant_request) {
+                    continue;
+                }
+                $event_data_list = $request->getFilteredEventData(
+                    $current_user->id
+                );
+
+                foreach ($event_data_list as $event_data) {
+                    $index = sprintf(
+                        '%s_%s_%s',
+                        $request->metadate_id,
+                        $event_data->begin->format('NHis'),
+                        $event_data->end->format('NHis')
+                    );
+
+                    //Strip some data that cannot be used effectively in here:
+                    $event_data->view_urls = [];
+                    $event_data->api_urls  = [];
+
+                    $merged_objects[$index] = $event_data;
+                }
+            }
+        }
+
+        //Convert the merged events to Fullcalendar events:
+        $data = [];
+        foreach ($merged_objects as $obj) {
+            $data[] = $obj->toFullCalendarEvent();
+        }
+
+        $this->render_json($data);
+    }
+
+    public function get_booking_plan_action($resource_id)
+    {
+        $resource = Resource::find($resource_id);
+        if (!$resource) {
+            throw new Exception('Resource object not found!');
+        }
+
+        $resource = $resource->getDerivedClassInstance();
+
+        $current_user  = User::findCurrent();
+        $nobody_access = true;
+
+        if ($current_user instanceof User) {
+            $nobody_access = false;
+            if (!$resource->bookingPlanVisibleForUser($current_user)) {
+                throw new AccessDeniedException();
+            }
+        } else if ($resource instanceof Room) {
+            if (!$resource->bookingPlanVisibleForUser($current_user)) {
+                throw new AccessDeniedException();
+            }
+        }
+        $user_is_resource_user = $current_user && $resource->userHasPermission($current_user);
+
+        $display_requests = $current_user && Request::bool('display_requests');
+        $display_all_requests = Request::bool('display_all_requests');
+
+        if ($display_all_requests && !$user_is_resource_user) {
+            //The user is not allowed to see all requests.
+            throw new AccessDeniedException();
+        }
+
+        $begin_date = Request::get('start');
+        $end_date   = Request::get('end');
+        if (!$begin_date || !$end_date) {
+            //No time range specified.
+            throw new Exception('The parameters "start" and "end" are missing!');
+        }
+
+        $begin = DateTime::createFromFormat(DateTime::RFC3339, $begin_date);
+        $end   = DateTime::createFromFormat(DateTime::RFC3339, $end_date);
+
+        if (!($begin instanceof DateTime) || !($end instanceof DateTime)) {
+            $begin = new DateTime();
+            $end   = new DateTime();
+            //Assume the local timezone and use the Y-m-d format:
+            $date_regex = '/[0-9]{4}-(0[1-9]|1[0-2])-([0-2][0-9]|3[0-1])/';
+            if (preg_match($date_regex, $begin_date)) {
+                //$begin is specified in the date format YYYY-MM-DD:
+                $begin_str = explode('-', $begin_date);
+                $begin->setDate(
+                    intval($begin_str[0]),
+                    intval($begin_str[1]),
+                    intval($begin_str[2])
+                );
+                $begin->setTime(0, 0, 0);
+            } else {
+                $begin->setTimestamp($begin_date);
+            }
+            //Now we do the same for $end_timestamp:
+            if (preg_match($date_regex, $end_date)) {
+                //$begin is specified in the date formay YYYY-MM-DD:
+                $end_str = explode('-', $end_date);
+                $end->setDate(
+                    intval($end_str[0]),
+                    intval($end_str[1]),
+                    intval($end_str[2])
+                );
+                $end->setTime(23, 59, 59);
+            } else {
+                $end->setTimestamp($end_date);
+            }
+        }
+
+        //Get parameters:
+        $booking_types = [];
+        if (!$nobody_access) {
+            $booking_types = explode(',', Request::get('booking_types'));
+        }
+
+        $begin_timestamp = $begin->getTimestamp();
+        $end_timestamp   = $end->getTimestamp();
+
+        //Get the event data sources:
+        $bookings = ResourceBooking::findByResourceAndTimeRanges(
+            $resource,
+            [
+                [
+                    'begin' => $begin_timestamp,
+                    'end'   => $end_timestamp
+                ]
+            ],
+            $booking_types
+        );
+        $requests = [];
+        if ($display_all_requests) {
+            $requests = ResourceRequest::findByResourceAndTimeRanges(
+                $resource,
+                [
+                    [
+                        'begin' => $begin_timestamp,
+                        'end'   => $end_timestamp
+                    ]
+                ],
+                0
+            );
+        } else if ($display_requests) {
+            //Get the users own request only:
+            $requests = ResourceRequest::findByResourceAndTimeRanges(
+                $resource,
+                [
+                    [
+                        'begin' => $begin_timestamp,
+                        'end'   => $end_timestamp
+                    ]
+                ],
+                0,
+                [],
+                'user_id = :user_id',
+                ['user_id' => $current_user->id]
+            );
+        }
+
+        $objects    = array_merge($bookings, $requests);
+        $event_data = Studip\Fullcalendar::createData($objects, $begin_timestamp, $end_timestamp);
+
+        if ($nobody_access) {
+            //For nobody users, the code stops here since
+            //nobody users are not allowed to include additional objects.
+            $this->render_json($event_data);
+            return;
+        }
+
+        //Check if there are additional objects to be displayed:
+        $additional_objects        = Request::getArray('additional_objects');
+        $additional_object_colours = Request::getArray('additional_object_colours');
+        if ($additional_objects) {
+            foreach ($additional_objects as $object_class => $object_ids) {
+                if (
+                    !is_a($object_class, SimpleORMap::class, true)
+                    || !is_a($object_class, Studip\Calendar\EventSource::class, true)
+                ) {
+                    continue;
+                }
+
+                $special_colours = [];
+                if ($additional_object_colours[$object_class]) {
+                    $special_colours = $additional_object_colours[$object_class];
+                }
+
+                $additional_objects = $object_class::findMany($object_ids);
+                foreach ($additional_objects as $additional_object) {
+                    $event_data = $additional_object->getFilteredEventData(
+                        $current_user->id,
+                        null,
+                        null,
+                        $begin,
+                        $end
+                    );
+
+                    if ($special_colours) {
+                        foreach ($event_data as $data) {
+                            $data->text_colour       = $special_colours['fg'];
+                            $data->background_colour = $special_colours['bg'];
+                            $data->editable          = false;
+                            $event_data[]            = $data->toFullcalendarEvent();
+                        }
+                    }
+                }
+            }
+        }
+        $this->render_json($event_data);
+    }
+
+    public function get_clipboard_semester_plan_action($clipboard_id = null)
+    {
+        if (!$clipboard_id) {
+            throw new Exception('ID of clipboard has not been provided!');
+        }
+
+        $clipboard = Clipboard::find($clipboard_id);
+
+        if (!empty($_SESSION['selected_clipboard_id'])) {
+            $clipboard = \Clipboard::find($_SESSION['selected_clipboard_id']);
+        }
+        if (!$clipboard) {
+            throw new Exception('Clipboard object not found!');
+        }
+        $current_user = User::findCurrent();
+
+        //Permission check:
+        if ($clipboard->user_id !== $current_user->id) {
+            throw new \AccessDeniedException();
+        }
+
+        $display_requests     = Request::bool('display_requests');
+        $display_all_requests = Request::bool('display_all_requests');
+
+        $begin = new DateTime();
+        $end   = new DateTime();
+
+        $semester_id = Request::get('semester_id');
+        $semester    = $semester_id ? Semester::find($semester_id) : Semester::findCurrent();
+
+        if (!$semester) {
+            throw new Exception('No semester found!');
+        }
+
+        if (Request::get('semester_timerange') === 'vorles') {
+            $begin->setTimestamp($semester->vorles_beginn);
+            $end->setTimestamp($semester->vorles_ende);
+        } else {
+            $begin->setTimestamp($semester->beginn);
+            $end->setTimestamp($semester->ende);
+        }
+
+        $rooms = Room::findMany($clipboard->getAllRangeIds('Room'));
+
+        //Get parameters:
+        $booking_types = Request::getArray('booking_types');
+
+        //Get the event data sources:
+        $plan_objects = [];
+
+        foreach ($rooms as $room) {
+            if ($room->bookingPlanVisibleForuser($current_user)) {
+                $plan_objects = array_merge(
+                    $plan_objects,
+                    ResourceManager::getBookingPlanObjects(
+                        $room,
+                        [
+                            [
+                                'begin' => $begin->getTimestamp(),
+                                'end'   => $end->getTimestamp()
+                            ]
+                        ],
+                        $booking_types,
+                        $display_all_requests ? 'all' : $display_requests
+                    )
+                );
+            }
+        }
+
+        $merged_objects   = [];
+        $meta_dates       = [];
+        $relevant_request = false;
+        foreach ($plan_objects as $plan_object) {
+            if ($plan_object instanceof ResourceBooking) {
+                $irrelevant_booking = $plan_object->getRepetitionType() !== 'weekly'
+                                      || (
+                                          $plan_object->getAssignedUserType() === 'course'
+                                          && in_array($plan_object->assigned_course_date->metadate_id, $meta_dates)
+                                      );
+                if ($irrelevant_booking) {
+                    continue;
+                }
+
+                //It is a booking with repetitions that has to be included
+                //in the semester plan.
+
+                $real_begin = $plan_object->begin;
+                if ($plan_object->preparation_time > 0) {
+                    $real_begin -= $plan_object->preparation_time;
+                }
+                $event_data = $plan_object->convertToEventData(
+                    [
+                        ResourceBookingInterval::build(
+                            [
+                                'interval_id' => md5(uniqid()),
+                                'begin'       => $real_begin,
+                                'end'         => $plan_object->end
+                            ]
+                        )
+                    ],
+                    $current_user
+                );
+
+                //Merge event data from the same booking that have the
+                //same weekday and begin and end time into one event.
+                //If no repetition interval is set and the booking belongs
+                //to a course date, use the corresponding metadate ID or the
+                //course date ID in the index. Otherwise use the booking's
+                //ID (specified by event_data->object_id).
+                foreach ($event_data as $event) {
+                    if ($plan_object->getAssignedUserType() === 'course') {
+                        $index = sprintf(
+                            '%s_%s_%s',
+                            $plan_object->assigned_course_date->metadate_id,
+                            $event->begin->format('NHis'),
+                            $event->end->format('NHis')
+                        );
+                        $meta_dates[] = $plan_object->assigned_course_date->metadate_id;
+                    } else {
+                        $index = sprintf(
+                            '%s_%s_%s',
+                            $plan_object->id,
+                            $event->begin->format('NHis'),
+                            $event->end->format('NHis')
+                        );
+                    }
+
+                    //Strip some data that cannot be used effectively in here:
+                    $event->api_urls = [];
+
+                    $merged_objects[$index] = $event;
+                }
+            } else if ($plan_object instanceof ResourceRequest) {
+                if ($plan_object->cycle instanceof SeminarCycleDate) {
+                    $cycle_dates = $plan_object->cycle->getAllDates();
+                    foreach ($cycle_dates as $cycle_date) {
+                        $relevant_request = $semester->beginn <= $cycle_date->date
+                            && $semester->ende >= $cycle_date->date;
+                        if ($relevant_request) {
+                            //We have found a date for the current semester
+                            //that makes the request relevant.
+                            break;
+                        }
+                    }
+                    if (!$relevant_request) {
+                        continue;
+                    }
+                    $event_data_list = $plan_object->getFilteredEventData(
+                        $current_user->id
+                    );
+
+                    foreach ($event_data_list as $event_data) {
+                        $index = sprintf(
+                            '%s_%s_%s',
+                            $plan_object->metadate_id,
+                            $event_data->begin->format('NHis'),
+                            $event_data->end->format('NHis')
+                        );
+
+                        //Strip some data that cannot be used effectively in here:
+                        $event_data->view_urls = [];
+                        $event_data->api_urls  = [];
+
+                        $merged_objects[$index] = $event_data;
+                    }
+                }
+            }
+        }
+
+        //Convert the merged events to Fullcalendar events:
+        $data = [];
+        foreach ($merged_objects as $obj) {
+            $data[] = $obj->toFullCalendarEvent();
+        }
+
+        $this->render_json($data);
+    }
+}
diff --git a/app/views/resources/print/clipboard_rooms.php b/app/views/resources/print/clipboard_rooms.php
index f8e7434a0f28aa047cabf855132936de1f19d69c..3d035a11fa32cae4b9b716c52be7e1a8ad343e59 100644
--- a/app/views/resources/print/clipboard_rooms.php
+++ b/app/views/resources/print/clipboard_rooms.php
@@ -171,8 +171,7 @@
                     'eventSources' => [
                         [
                             'url' => URLHelper::getURL(
-                                'api.php/resources/resource/'
-                                . $room->id . '/booking_plan'
+                                'dispatch.php/resources/ajax/get_semester_booking_plan/' . $room->id
                             ),
                             'method' => 'GET',
                             'extraParams' => [
diff --git a/app/views/resources/print/individual_booking_plan.php b/app/views/resources/print/individual_booking_plan.php
index d8c60867d115fdc41904534dc940b5c39f51f976..630f306127becce4350567cff1a75846189053ed 100644
--- a/app/views/resources/print/individual_booking_plan.php
+++ b/app/views/resources/print/individual_booking_plan.php
@@ -12,7 +12,7 @@
             'eventSources' => [
                 [
                     'url' => URLHelper::getURL(
-                        'api.php/resources/resource/' . $resource->id . '/booking_plan'
+                        'dispatch.php/resources/ajax/get_semester_booking_plan/' . $resource->id
                     ),
                     'method' => 'GET',
                     'extraParams' => [
diff --git a/app/views/resources/resource/booking_plan.php b/app/views/resources/resource/booking_plan.php
index ced660e773cd1dbef27f7dc9a4e04c6d4068b4d5..b9400dc63436eca1579162517db1b62c8f86bcfa 100644
--- a/app/views/resources/resource/booking_plan.php
+++ b/app/views/resources/resource/booking_plan.php
@@ -19,8 +19,7 @@
         'eventSources' => [
             [
                 'url' => URLHelper::getLink(
-                    'api.php/resources/resource/'
-                  . htmlReady($resource->id) . '/booking_plan'
+                    'dispatch.php/resources/ajax/get_semester_booking_plan/' . $resource->id
                 ),
                 'method' => 'GET',
                 'extraParams' => [
diff --git a/app/views/resources/room_planning/booking_plan.php b/app/views/resources/room_planning/booking_plan.php
index 16b76e5ecc9f3fb35604e357ee737398c8aca229..312d9a3dcf8526303da050217583e09a228991f3 100644
--- a/app/views/resources/room_planning/booking_plan.php
+++ b/app/views/resources/room_planning/booking_plan.php
@@ -76,7 +76,7 @@
             'eventSources' => [
                 [
                     'url' => URLHelper::getURL(
-                        'api.php/resources/resource/' . $resource->id . '/booking_plan'
+                        'dispatch.php/resources/ajax/get_booking_plan/' . $resource->id
                     ),
                     'method' => 'GET',
                     'extraParams' => [
diff --git a/app/views/resources/room_planning/semester_plan.php b/app/views/resources/room_planning/semester_plan.php
index 607891d42d7e0f3e31564f0d6d22ead98f2d5c32..2816328590d98d209fd62204ccbf36f5e9702680 100644
--- a/app/views/resources/room_planning/semester_plan.php
+++ b/app/views/resources/room_planning/semester_plan.php
@@ -103,10 +103,7 @@
             'eventSources' => [
                 [
                     'url' => URLHelper::getURL(
-                        sprintf(
-                            'api.php/resources/resource/%s/semester_plan',
-                            htmlReady($resource->id)
-                        )
+                        'dispatch.php/resources/ajax/get_semester_booking_plan/' . $resource->id
                     ),
                     'method' => 'GET',
                     'extraParams' => [
diff --git a/app/views/resources/room_request/planning.php b/app/views/resources/room_request/planning.php
index aeebb655c0b849bd1e48c45c91c47141a1df1e46..0653043278618a375c1da502b91d71fab5b6d421 100644
--- a/app/views/resources/room_request/planning.php
+++ b/app/views/resources/room_request/planning.php
@@ -82,10 +82,7 @@
             'eventSources'       => [
                 [
                     'url'         => URLHelper::getURL(
-                        sprintf(
-                            'api.php/resources/resource/%s/semester_plan',
-                            htmlReady($resource->id)
-                        )
+                        'dispatch.php/resources/ajax/get_booking_plan/' . $resource->id
                     ),
                     'method'      => 'GET',
                     'extraParams' => [
diff --git a/app/views/room_management/planning/index.php b/app/views/room_management/planning/index.php
index c5d7d5fa250261d33713ff7b07a45cd6e294b210..4bdee329bdf3a7d394e8535f109aacaef0705676 100644
--- a/app/views/room_management/planning/index.php
+++ b/app/views/room_management/planning/index.php
@@ -56,8 +56,7 @@
             'eventSources' => [
                 [
                     'url' => URLHelper::getLink(
-                        'api.php/room_clipboard/'
-                      . htmlReady($clipboard->id) . '/booking_plan'
+                        'dispatch.php/resources/ajax/get_clipboard_semester_plan/' . $clipboard->id
                     ),
                     'method' => 'GET',
                     'extraParams' => [
diff --git a/app/views/room_management/planning/semester_plan.php b/app/views/room_management/planning/semester_plan.php
index f5b9c94eb74570ad499b1cc1208d3b1e6fe0f4d6..58e8febb6b505e7bc129b35a3600ed6395210521 100644
--- a/app/views/room_management/planning/semester_plan.php
+++ b/app/views/room_management/planning/semester_plan.php
@@ -61,8 +61,7 @@
             'eventSources' => [
                 [
                     'url' => URLHelper::getLink(
-                        'api.php/room_clipboard/'
-                      . htmlReady($clipboard->id) . '/semester_plan'
+                        'dispatch.php/resources/ajax/get_clipboard_semester_plan/' . $clipboard->id
                     ),
                     'method' => 'GET',
                     'extraParams' => [
diff --git a/resources/assets/javascripts/lib/resources.js b/resources/assets/javascripts/lib/resources.js
index 0c2980e1a7a342aee6eff9fdfe02514a367b8510..3287b42d6af4d01ec84ec74f901237c71b728bb4 100644
--- a/resources/assets/javascripts/lib/resources.js
+++ b/resources/assets/javascripts/lib/resources.js
@@ -1,10 +1,9 @@
-import { $gettext } from '../lib/gettext';
+import {$gettext} from '../lib/gettext';
 
 class Resources
 {
 
-    static addUserToPermissionList(user_id, table_element)
-    {
+    static addUserToPermissionList(user_id, table_element) {
         if (!user_id || !table_element) {
             return;
         }
@@ -37,7 +36,7 @@ class Resources
                 }
             }
         }
-        var insert_function = function(user_id = null, username = null) {
+        var insert_function = function (user_id = null, username = null) {
             var new_row = jQuery(template_row).clone(true);
             jQuery(new_row).removeClass('invisible');
             jQuery(new_row).removeClass('resource-permission-list-template');
@@ -137,7 +136,7 @@ class Resources
 
         STUDIP.api.GET(
             `user/${user_id}`
-        ).done(function(data) {
+        ).done(function (data) {
             var username = data.name.family
                 + ', '
                 + data.name.given;
@@ -147,17 +146,16 @@ class Resources
             if (data.name.suffix) {
                 username += ' ' + data.name.suffix;
             }
-            username += ' (' + data.name.username +')'
+            username += ' (' + data.name.username + ')'
                 + ' (' + data.perms + ')';
             insert_function(user_id, username);
-        }).fail(function() {
+        }).fail(function () {
             insert_function(user_id);
         });
     }
 
 
-    static addCourseUsersToPermissionList(course_id, table_element)
-    {
+    static addCourseUsersToPermissionList(course_id, table_element) {
         if (!course_id || !table_element) {
             return;
         }
@@ -171,7 +169,7 @@ class Resources
                     limit: 1000000
                 }
             }
-        ).done(function(data) {
+        ).done(function (data) {
             for (var attribute in data.collection) {
                 var user_id = data.collection[attribute].member.id;
                 STUDIP.Resources.addUserToPermissionList(
@@ -183,8 +181,7 @@ class Resources
     }
 
 
-    static removeUserFromPermissionList(html_node)
-    {
+    static removeUserFromPermissionList(html_node) {
         if (!html_node) {
             return;
         }
@@ -208,8 +205,7 @@ class Resources
     //Room search related methods:
 
 
-    static addSearchCriteriaToRoomSearchWidget(select_node)
-    {
+    static addSearchCriteriaToRoomSearchWidget(select_node) {
         if (!select_node) {
             return;
         }
@@ -331,13 +327,13 @@ class Resources
                 jQuery(date_inputs[1]).attr('name', option_value + '_end_date');
                 jQuery(date_inputs[0]).val(
                     now.getFullYear() + '-'
-                        + (now.getMonth() + 1) + '-'
-                        + (now.getDate() + 1)
+                    + (now.getMonth() + 1) + '-'
+                    + (now.getDate() + 1)
                 );
                 jQuery(date_inputs[1]).val(
                     now.getFullYear() + '-'
-                        + (now.getMonth() + 1) + '-'
-                        + (now.getDate() + 2)
+                    + (now.getMonth() + 1) + '-'
+                    + (now.getDate() + 2)
                 );
             } else {
                 //One date field, two time fields.
@@ -348,8 +344,8 @@ class Resources
                 jQuery(date_inputs[0]).attr('name', option_value + '_date');
                 jQuery(date_inputs[0]).val(
                     now.getFullYear() + '-'
-                        + (now.getMonth() + 1) + '-'
-                        + (now.getDate() + 1)
+                    + (now.getMonth() + 1) + '-'
+                    + (now.getDate() + 1)
                 );
             }
 
@@ -381,8 +377,7 @@ class Resources
     }
 
 
-    static removeSearchCriteriaFromRoomSearchWidget(icon_node)
-    {
+    static removeSearchCriteriaFromRoomSearchWidget(icon_node) {
         if (!icon_node) {
             return;
         }
@@ -412,8 +407,7 @@ class Resources
     }
 
 
-    static submitRoomSearchWidgetForm(input_node)
-    {
+    static submitRoomSearchWidgetForm(input_node) {
         if (!input_node) {
             return;
         }
@@ -431,8 +425,7 @@ class Resources
     //Resource request related methods:
 
 
-    static addPropertyToRequest(event)
-    {
+    static addPropertyToRequest(event) {
         var select = jQuery(event.target).siblings('select.requestable-properties-select')[0];
         if (!select) {
             return;
@@ -486,48 +479,32 @@ class Resources
     //ResourceBookingInterval methods:
 
 
-    static toggleBookingIntervalStatus(event)
-    {
+    static toggleBookingIntervalStatus(event) {
         event.preventDefault();
-        var li = jQuery(event.target).parents('tr')[0];
-        if (!li) {
-            //Something is wrong with the HTML.
-            return;
-        }
-        var interval_id = jQuery(li).data('interval_id');
-        if (!interval_id) {
+        let button = event.target.closest('button');
+        let intervalId = button.dataset.interval_id;
+
+        if (!intervalId) {
             return;
         }
 
-        STUDIP.api.POST(
-            `resources/booking_interval/${interval_id}/toggle_takes_place`
-        ).done(function(data) {
-            if (data['takes_place'] === undefined) {
-                //Something went wrong: do nothing.
-                return;
-            }
+        const url = STUDIP.URLHelper.getURL(`dispatch.php/resources/ajax/toggle_takes_place_field/${intervalId}`);
+        fetch(url)
+            .then(response => response.json())
+            .then(response => {
+                if (response['takes_place'] === undefined) {
+                    //Something went wrong: do nothing.
+                    return;
+                }
 
-            if (data['takes_place'] === '1') {
-                //Switch on the icons and text for the "takes place"
-                //status and switch off the other ones:
-                jQuery(li).find('.takes-place-revive').addClass('invisible');
-                jQuery(li).find('.takes-place-delete').removeClass('invisible');
-                jQuery(li).find('.booking-list-interval-date').removeClass('not-taking-place');
-            } else {
-                //Do the opposite of the if-block above:
-                jQuery(li).find('.takes-place-delete').addClass('invisible');
-                jQuery(li).find('.takes-place-revive').removeClass('invisible');
-                jQuery(li).find('.booking-list-interval-date').addClass('not-taking-place');
-            }
-        });
+                const cell = button.closest('td');
+                cell.previousElementSibling.classList.toggle('not-taking-place');
+                cell.querySelectorAll('button').forEach(node => node.classList.toggle('invisible'));
+            });
     }
 
 
-    //Methods for the resource category form:
-
-
-    static addResourcePropertyToTable(event)
-    {
+    static addResourcePropertyToTable(event) {
         var select = jQuery(event.target).siblings('select')[0];
         if (!select) {
             //Something is wrong with the HTML
@@ -607,22 +584,20 @@ class Resources
     //Methods for opening or closing of ressource tree elements:
 
 
-    static toggleTreeNode(treenode)
-    {
+    static toggleTreeNode(treenode) {
         var arr = treenode.children("img");
         if (arr.hasClass('rotated')) {
             arr.attr('style', 'transform: rotate(0deg)');
         } else {
             arr.attr('style', 'transform: rotate(90deg)');
         }
-        arr.toggleClass('rotated') ;
+        arr.toggleClass('rotated');
         treenode.children(".resource-tree").children("li").toggle();
     }
 
 
-    static moveTimeOptions(bookingtype_val)
-    {
-        if(bookingtype_val === 'single') {
+    static moveTimeOptions(bookingtype_val) {
+        if (bookingtype_val === 'single') {
             $(".time-option-container").hide();
             $(".block-booking-item").hide();
             $(".repetition-booking-item").hide();
@@ -632,7 +607,7 @@ class Resources
         } else {
             var time_options = $(".time-option-container");
             $(".time-option-container").detach();
-            if(bookingtype_val === 'block') {
+            if (bookingtype_val === 'block') {
                 $("#BlockBookingFieldset").prepend(time_options);
 
                 $("#BlockEndLabel").show();
@@ -657,83 +632,81 @@ class Resources
     //Fullcalendar specialisations:
 
 
-    static updateEventUrlsInCalendar(calendar_event)
-    {
-        if (!calendar_event) {
+    static updateEventUrlsInCalendar(calendarEvent) {
+        if (!calendarEvent) {
             return;
         }
 
-        STUDIP.api.GET(
-            `resources/booking/${calendar_event.extendedProps.studip_parent_object_id}/intervals`,
-            {
-                data: {
-                    begin: STUDIP.Fullcalendar.toRFC3339String(calendar_event.start),
-                    end: STUDIP.Fullcalendar.toRFC3339String(calendar_event.end)
+        let parentObjectId = calendarEvent.extendedProps.studip_parent_object_id;
+        let begin = STUDIP.Fullcalendar.toRFC3339String(calendarEvent.start);
+        let end = STUDIP.Fullcalendar.toRFC3339String(calendarEvent.end);
+
+        const url = STUDIP.URLHelper.getURL(
+            `dispatch.php/resources/ajax/get_resource_booking_intervals/${parentObjectId}`,
+            {begin, end}
+        );
+        fetch(url)
+            .then(response => response.json())
+            .then(response => {
+                if (!response || response.length === 0) {
+                    return;
                 }
-            }
-        ).done(function (data) {
-            if (!data || data.length === 0) {
-                return;
-            }
-            var new_interval_id = data[0].interval_id;
-            calendar_event.setExtendedProp('studip_object_id', new_interval_id);
-            if (new_interval_id) {
-                var move_url = calendar_event.extendedProps.studip_api_urls['move'];
-                var resize_url = calendar_event.extendedProps.studip_api_urls['resize'];
-                move_url = move_url.replace(
-                    /&interval_id=([0-9a-f]{32})/,
-                    '&interval_id=' + new_interval_id
-                );
-                resize_url = resize_url.replace(
-                    /&interval_id=([0-9a-f]{32})/,
-                    '&interval_id=' + new_interval_id
-                );
-                var studip_api_urls = calendar_event.extendedProps.studip_api_urls;
-                studip_api_urls['move'] = move_url;
-                studip_api_urls['resize'] = resize_url;
-                calendar_event.setExtendedProp('studip_api_urls', studip_api_urls);
-            }
-        });
+                let newIntervalId = response[0].intervalId;
+                calendarEvent.setExtendedProp('studip_object_id', newIntervalId);
+                if (newIntervalId) {
+                    let moveUrl = calendarEvent.extendedProps.studip_api_urls['move'];
+                    let resizeUrl = calendarEvent.extendedProps.studip_api_urls['resize'];
+                    moveUrl = moveUrl.replace(
+                        /&interval_id=([0-9a-f]{32})/,
+                        '&interval_id=' + newIntervalId
+                    );
+                    resizeUrl = resizeUrl.replace(
+                        /&interval_id=([0-9a-f]{32})/,
+                        '&interval_id=' + newIntervalId
+                    );
+                    let studipApiUrls = calendarEvent.extendedProps.studip_api_urls;
+                    studipApiUrls['move'] = moveUrl;
+                    studipApiUrls['resize'] = resizeUrl;
+                    calendarEvent.setExtendedProp('studip_api_urls', studipApiUrls);
+                }
+            })
     }
 
 
-    static resizeEventInRoomGroupBookingPlan(info)
-    {
+    static resizeEventInRoomGroupBookingPlan(info) {
         STUDIP.Fullcalendar.defaultResizeEventHandler(info);
         STUDIP.Resources.updateEventUrlsInCalendar(info.event);
     }
 
-    static dropEventInRoomGroupBookingPlan(info)
-    {
+    static dropEventInRoomGroupBookingPlan(info) {
         STUDIP.Fullcalendar.defaultDropEventHandler(info);
         STUDIP.Resources.updateEventUrlsInCalendar(info.event);
     }
 
-    static toggleRequestMarked(source_node)
-    {
-        if (!source_node) {
+    static toggleRequestMarked(sourceNode) {
+        if (!sourceNode) {
             return;
         }
 
-        var request_id = jQuery(source_node).data('request_id');
-        if (!request_id) {
+        let requestId = sourceNode.dataset.request_id
+
+        if (!requestId) {
             return;
         }
 
-        STUDIP.api.POST(
-            `resources/request/${request_id}/toggle_marked`
-        ).done(function(data) {
-            jQuery(source_node).attr('data-marked', data.marked);
-            jQuery(source_node).parent().attr('data-sort-value', data.marked);
-            jQuery(source_node).parents('table.request-list').trigger('update');
-        });
+        fetch(STUDIP.URLHelper.getURL(`dispatch.php/resources/ajax/toggle_marked/${requestId}`))
+            .then(response => response.json())
+            .then(response => {
+                sourceNode.dataset.marked = response.marked;
+                sourceNode.parentNode.dataset.sortValue = response.marked;
+                sourceNode.closest('table.request-list').dispatchEvent(new Event('update'));
+            })
     }
 
-    static bookAllCalendarRequests()
-    {
-        var calendarSektion = $('*[data-resources-fullcalendar="1"]')[0];
-        if (calendarSektion) {
-            var calendar = calendarSektion.calendar;
+    static bookAllCalendarRequests() {
+        let calendarSection = $('*[data-resources-fullcalendar="1"]')[0];
+        if (calendarSection) {
+            let calendar = calendarSection.calendar;
             if (calendar) {
                 if (!$('#loading-spinner').length) {
                     jQuery('#content').append(
@@ -746,14 +719,14 @@ class Resources
                         )
                     );
                 }
-                $('.fc-request-event').each(function(){
-                    var objectData = $(this).data();
-                    var existingRequestEvent = calendar.getEventById(objectData.eventId);
+                $('.fc-request-event').each(function () {
+                    let objectData = $(this).data();
+                    let existingRequestEvent = calendar.getEventById(objectData.eventId);
                     if (existingRequestEvent) {
-                        var bookingURL = 'dispatch.php/resources/room_request/quickbook/'
-                                       + objectData.eventRequest +'/'
-                                       + objectData.eventResource +'/'
-                                       + objectData.eventMetadate;
+                        let bookingURL = 'dispatch.php/resources/room_request/quickbook/'
+                            + objectData.eventRequest + '/'
+                            + objectData.eventResource + '/'
+                            + objectData.eventMetadate;
                         jQuery.ajax(
                             STUDIP.URLHelper.getURL(bookingURL),
                             {
@@ -780,10 +753,8 @@ Resources.definedResourceClasses = [
 ];
 
 
-class Messages
-{
-    static selectRoom(room_id, room_name)
-    {
+class Messages {
+    static selectRoom(room_id, room_name) {
         if (!room_id) {
             return;
         }
@@ -806,30 +777,29 @@ class Messages
         jQuery(selection_area).append(new_room);
     }
 }
+
 Resources.Messages = Messages;
 
 
-class BookingPlan
-{
-    static insertEntry(new_entry, date, begin_hour, end_hour)
-    {
+class BookingPlan {
+    static insertEntry(new_entry, date, begin_hour, end_hour) {
         //Get the resource-ID from the current URL:
-        var results = window.location.href.match(
-                /dispatch.php\/resources\/resource\/booking_plan\/([a-z0-9]{1,32})/
+        let results = window.location.href.match(
+            /dispatch.php\/resources\/resource\/booking_plan\/([a-z0-9]{1,32})/
         );
         if (results.length === 0) {
             //No resource-ID found.
             jQuery(new_entry).remove();
             return;
         }
-        var resource_id = results[1];
+        let resource_id = results[1];
 
         //Now we re-format the time from begin_hour and end_hour.
         //In case the data-dragged attribute is set for the
         //calendar entry we just add two hours to the start time
         //to get the end time.
 
-        var dragged = jQuery(new_entry).data('dragged');
+        let dragged = jQuery(new_entry).data('dragged');
         if (dragged) {
             end_hour = begin_hour + 2;
         }
@@ -840,7 +810,7 @@ class BookingPlan
             end_hour += ':00';
         }
 
-        var result = STUDIP.Dialog.fromURL(
+        let result = STUDIP.Dialog.fromURL(
             STUDIP.URLHelper.getURL(
                 'dispatch.php/resources/booking/add/' + resource_id,
                 {
@@ -853,6 +823,7 @@ class BookingPlan
         );
     }
 }
+
 Resources.BookingPlan = BookingPlan;