ILIAS  release_8 Revision v8.19
All Data Structures Namespaces Files Functions Variables Modules Pages
class.ilBookingPreferencesManager.php
Go to the documentation of this file.
1 <?php
2 
24 {
25  public const BOOKINGS_PER_USER_DEFAULT = 1;
26 
28  protected ?int $current_time;
29  protected int $bookings_per_user;
31 
32  public function __construct(
33  ilObjBookingPool $pool,
35  ?int $current_time = null,
36  int $bookings_per_user = self::BOOKINGS_PER_USER_DEFAULT
37  ) {
38  $this->current_time = ($current_time > 0)
39  ? $current_time
40  : time();
41  $this->pool = $pool;
42  $this->bookings_per_user = $bookings_per_user;
43  $this->book_repo = $book_repo;
44  }
45 
49  public function isGivingPreferencesPossible(): bool
50  {
51  return $this->pool->getScheduleType() === ilObjBookingPool::TYPE_NO_SCHEDULE_PREFERENCES &&
52  $this->pool->getPreferenceDeadline() > $this->current_time;
53  }
54 
58  public function isPreferenceDeadlineReached(): bool
59  {
60  return $this->pool->getScheduleType() === ilObjBookingPool::TYPE_NO_SCHEDULE_PREFERENCES &&
61  $this->pool->getPreferenceDeadline() < $this->current_time;
62  }
63 
71  public function storeBookings(
72  ilBookingPreferences $preferences,
73  ?array $booking_object_ids = null
74  ): void {
75  $bookings = $this->calculateBookings($preferences, $booking_object_ids);
76  $this->book_repo->storeBookings($this->pool->getId(), $bookings);
77  }
78 
82  public function readBookings(): array
83  {
84  $booking_object_ids = array_map(static function ($i) {
85  return $i["booking_object_id"];
86  }, ilBookingObject::getList($this->pool->getId()));
87  return $this->book_repo->getBookings($booking_object_ids);
88  }
89 
95  public function calculateBookings(
96  ilBookingPreferences $preferences,
97  ?array $booking_object_ids = null,
98  ?array $availability = null
99  ): array {
100  $preferences = $preferences->getPreferences();
101 
102  // we calculate if a) any preferences are given and b) the deadline is reached
103  /*if (!is_array($preferences) || count($preferences) == 0) {
104  throw new ilBookingCalculationException("No preferences given.");
105  }*/
106  if (!$this->isPreferenceDeadlineReached()) {
107  throw new ilBookingCalculationException("Preference deadline not reached.");
108  }
109 
110  if ($booking_object_ids === null) {
111  $booking_object_ids = array_map(function ($i) {
112  return $i["booking_object_id"];
113  }, ilBookingObject::getList($this->pool->getId()));
114  }
115 
116  if ($availability === null) {
117  $availability = [];
118  foreach ($booking_object_ids as $book_obj_id) {
119  $availability[$book_obj_id] = ilBookingReservation::getNumAvailablesNoSchedule($book_obj_id);
120  }
121  }
122 
123  // remove all objects from the preferences
124  // that are already not available anymore
125  // see bug 30204 (a tutor booked an object already before and made it unavailable)
126  foreach ($availability as $book_obj_id => $cnt) {
127  if ($cnt == 0) {
128  $preferences = $this->removeObjectFromPreferences($book_obj_id, $preferences);
129  }
130  }
131 
132  $bookings = [];
133 
134  $end_phase_one = false;
135 
136  // phase one: assign lowest popular items to random user
137  while (!$end_phase_one) {
138  $popularity = $this->calculatePopularity($booking_object_ids, $preferences);
139  $low_pop_book_obj_id = $this->getObjectWithLowestPopularity($popularity, $availability);
140  if ($low_pop_book_obj_id > 0) {
141  $user_ids = $this->getUsersForObject($preferences, $low_pop_book_obj_id);
142  if (count($user_ids) > 0) {
143  $user_id = $this->selectRandomEntry($user_ids);
144  $this->addBooking($bookings, $preferences, $availability, $user_id, $low_pop_book_obj_id);
145  }
146  } else {
147  $end_phase_one = true;
148  }
149  }
150 
151  $end_phase_two = false;
152 
153  // choose random user from and assign currently rarely assigned objects
154  while (!$end_phase_two) {
155  $random_user_id = $this->chooseRandomUserFromPreferences($preferences);
156  if ($random_user_id > 0) {
157  $rare_assigned_book_obj_id = $this->getMinimalAssignedEntryForUser($booking_object_ids, $bookings, $preferences[$random_user_id], $availability);
158  if ($rare_assigned_book_obj_id > 0) {
159  $this->addBooking($bookings, $preferences, $availability, $random_user_id, $rare_assigned_book_obj_id);
160  } else {
161  $preferences = $this->removeUserFromPreferences($random_user_id, $preferences);
162  }
163  } else {
164  $end_phase_two = true;
165  }
166  }
167  return $bookings;
168  }
169 
170  protected function addBooking(
171  array &$bookings,
172  array &$preferences,
173  array &$availability,
174  int $user_id,
175  int $book_obj_id
176  ): void {
177  $bookings[$user_id][] = $book_obj_id;
178  $availability[$book_obj_id]--;
179  if (count($bookings[$user_id]) >= $this->bookings_per_user) {
180  $preferences = $this->removeUserFromPreferences($user_id, $preferences);
181  } else {
182  $preferences = $this->removePreference($user_id, $book_obj_id, $preferences);
183  }
184  if ($availability[$book_obj_id] <= 0) {
185  $preferences = $this->removeObjectFromPreferences($book_obj_id, $preferences);
186  }
187  }
188 
193  protected function selectRandomEntry(array $items)
194  {
195  return $items[array_rand($items)];
196  }
197 
198 
203  protected function getUsersForObject(
204  array $preferences,
205  int $sel_obj_id
206  ): array {
207  $user_ids = [];
208  foreach ($preferences as $user_id => $obj_ids) {
209  foreach ($obj_ids as $obj_id) {
210  if ((int) $obj_id === $sel_obj_id) {
211  $user_ids[] = (int) $user_id;
212  }
213  }
214  }
215  return $user_ids;
216  }
217 
218 
222  protected function calculatePopularity(
223  array $booking_object_ids,
224  array $preferences
225  ): array {
226  $popularity = [];
227  foreach ($booking_object_ids as $book_obj_id) {
228  $popularity[$book_obj_id] = 0;
229  }
230  foreach ($preferences as $user_id => $bobj_ids) {
231  foreach ($bobj_ids as $bobj_id) {
232  ++$popularity[$bobj_id];
233  }
234  }
235 
236  return $popularity;
237  }
238 
239 
243  protected function getObjectWithLowestPopularity(
244  array $popularity,
245  array $availability
246  ): int {
247  asort($popularity, SORT_NUMERIC);
248  foreach ($popularity as $obj_id => $pop) {
249  if ($pop > 0 && $availability[$obj_id] > 0) {
250  return (int) $obj_id;
251  }
252  }
253  return 0;
254  }
255 
259  protected function removePreference(
260  int $user_id,
261  int $obj_id,
262  array $preferences
263  ): array {
264  if (is_array($preferences[$user_id])) {
265  $preferences[$user_id] = array_filter($preferences[$user_id], static function ($i) use ($obj_id) {
266  return ($i !== $obj_id);
267  });
268  }
269  return $preferences;
270  }
271 
275  protected function removeObjectFromPreferences(
276  int $obj_id,
277  array $preferences
278  ): array {
279  $new_preferences = [];
280  foreach ($preferences as $user_id => $obj_ids) {
281  $new_preferences[$user_id] = array_filter($obj_ids, static function ($i) use ($obj_id) {
282  return ($i !== $obj_id);
283  });
284  }
285  return $new_preferences;
286  }
287 
291  protected function removeUserFromPreferences(
292  int $user_id,
293  array $preferences
294  ): array {
295  if (is_array($preferences[$user_id])) {
296  unset($preferences[$user_id]);
297  }
298  return $preferences;
299  }
300 
305  array $preferences
306  ): ?int {
307  if (count($preferences) === 0) {
308  return null;
309  }
310  $user_ids = array_keys($preferences);
311  return $this->selectRandomEntry($user_ids);
312  }
313 
324  protected function getMinimalAssignedEntryForUser(
325  array $booking_object_ids,
326  array $bookings,
327  array $user_preferences,
328  array $availability
329  ): int {
330  // count the assignments per object
331  $count_assignments = [];
332  foreach ($booking_object_ids as $obj_id) {
333  $count_assignments[$obj_id] = 0;
334  }
335  foreach ($bookings as $user => $obj_ids) {
336  foreach ($obj_ids as $obj_id) {
337  $count_assignments[$obj_id]++;
338  }
339  }
340 
341  // sort the objects by number of assignments, return the first one being found in the user preferences
342  asort($count_assignments, SORT_NUMERIC);
343  foreach ($count_assignments as $obj_id => $cnt) {
344  // if no preferences left for user, even assign object outside preferences
345  // otherwise choose object from preferences
346  if ($availability[$obj_id] > 0 && (count($user_preferences) === 0 || in_array($obj_id, $user_preferences))) {
347  return (int) $obj_id;
348  }
349  }
350  return 0;
351  }
352 
353  public function hasRun() : bool
354  {
355  return $this->book_repo->hasRun($this->pool->getId());
356  }
357 
358  public function resetRun() : void
359  {
360  $this->book_repo->resetRun($this->pool->getId());
361  }
362 }
isGivingPreferencesPossible()
Can participants hand in preferences.
static getNumAvailablesNoSchedule(int $a_obj_id)
addBooking(array &$bookings, array &$preferences, array &$availability, int $user_id, int $book_obj_id)
getUsersForObject(array $preferences, int $sel_obj_id)
Get users for object.
getObjectWithLowestPopularity(array $popularity, array $availability)
Get an availabe object with lowest popularity > 0.
calculatePopularity(array $booking_object_ids, array $preferences)
Calculate popularity (number of preferences each object got from users)
storeBookings(ilBookingPreferences $preferences, ?array $booking_object_ids=null)
Calculate and store bookings.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
removeObjectFromPreferences(int $obj_id, array $preferences)
Remove an object from the preference array.
removeUserFromPreferences(int $user_id, array $preferences)
Remove user from preference array.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
selectRandomEntry(array $items)
Select a random entry of an array.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
getPreferences()
Get user preferences.
chooseRandomUserFromPreferences(array $preferences)
Choose random user from the preference array.
getMinimalAssignedEntryForUser(array $booking_object_ids, array $bookings, array $user_preferences, array $availability)
Get an available object within the preferences (if no preferences left, even outside of preferences) ...
__construct(ilObjBookingPool $pool, ilBookingPrefBasedBookGatewayRepository $book_repo, ?int $current_time=null, int $bookings_per_user=self::BOOKINGS_PER_USER_DEFAULT)
ilBookingPrefBasedBookGatewayRepository $book_repo
calculateBookings(ilBookingPreferences $preferences, ?array $booking_object_ids=null, ?array $availability=null)
Calculate bookings.
removePreference(int $user_id, int $obj_id, array $preferences)
Remove a preference from the preference array.
static getList(int $a_pool_id, string $a_title=null)
Get list of booking objects.
$i
Definition: metadata.php:41
isPreferenceDeadlineReached()
Can participants hand in preferences.