Whoops \ Exception \ ErrorException
(E_WARNING)
Stack frames (9)
8
Whoops\Exception\ErrorException
…/public/local/lernhive/classes/dashboard/course_catalog_provider.php:620
7
default_error_handler
…/public/local/lernhive/classes/dashboard/course_catalog_provider.php:620
6
local_lernhive\dashboard\course_catalog_provider
course_to_offering
…/public/local/lernhive/classes/dashboard/course_catalog_provider.php:127
5
local_lernhive\dashboard\course_catalog_provider
get_offerings
…/public/local/lernhive/classes/hook_callbacks.php:132
4
local_lernhive\hook_callbacks
contribute_course_catalog_offerings
…/public/lib/classes/hook/manager.php:323
3
call_user_func
…/public/lib/classes/hook/manager.php:323
2
core\hook\manager
dispatch
…/public/local/lernhive/classes/dashboard/catalog_registry.php:49
1
local_lernhive\dashboard\catalog_registry
collect_offerings
…/public/local/lernhive/classes/output/catalog_page.php:117
0
local_lernhive\output\catalog_page
export_for_template
…/public/local/lernhive/catalog.php:112
/var/www/html/public/local/lernhive/classes/dashboard/course_catalog_provider.php
timecreated: !empty($course->timecreated) ? (int) $course->timecreated : null,
availability: $requiresapproval
? offering_data::AVAILABILITY_ON_REQUEST
: offering_data::AVAILABILITY_BOOKABLE,
score: !empty($markings['featured']) ? 40 : 10,
metaitems: $meta,
capacitystatus: $capacitystatus,
capacitylabel: $capacitylabel,
capacityfill: $capacityfill,
capacityisprogress: $isenrolled,
showcapacitybar: $isenrolled,
trainers: self::trainers_data($context),
bookmarkable: $userid > 0,
bookmarkcomponent: 'core_course',
bookmarkitemtype: 'course',
bookmarkitemid: (string) $course->id,
flags: $flags,
featured: !empty($markings['featured']),
ispublic: !empty($markings['publicvisible']),
ratingaverage: $rating && $rating->count > 0 ? (float) $rating->aggregate : null,
ratingcount: $rating ? (int) $rating->count : 0,
);
}
/**
* Build image-overlay flags (recommended / mandatory / paid+price) from a
* course's markings.
*
* @param array{recommended:bool,mandatory:bool,paid:bool,price:string} $markings
* @param bool $needsapproval
* @return array<int,array{label:string,tone:string,icon:string}>
*/
private static function flags_from_markings(array $markings, bool $needsapproval = false): array {
$flags = [];
if (!empty($markings['mandatory'])) {
$flags[] = [
'label' => get_string('flag_mandatory', 'local_lernhive'),
'tone' => 'mandatory',
'icon' => 'fa-exclamation',
];
More info
https://docs.moodle.org/502/en/error/moodle/generalexceptionmessage
/var/www/html/public/local/lernhive/classes/dashboard/course_catalog_provider.php
timecreated: !empty($course->timecreated) ? (int) $course->timecreated : null,
availability: $requiresapproval
? offering_data::AVAILABILITY_ON_REQUEST
: offering_data::AVAILABILITY_BOOKABLE,
score: !empty($markings['featured']) ? 40 : 10,
metaitems: $meta,
capacitystatus: $capacitystatus,
capacitylabel: $capacitylabel,
capacityfill: $capacityfill,
capacityisprogress: $isenrolled,
showcapacitybar: $isenrolled,
trainers: self::trainers_data($context),
bookmarkable: $userid > 0,
bookmarkcomponent: 'core_course',
bookmarkitemtype: 'course',
bookmarkitemid: (string) $course->id,
flags: $flags,
featured: !empty($markings['featured']),
ispublic: !empty($markings['publicvisible']),
ratingaverage: $rating && $rating->count > 0 ? (float) $rating->aggregate : null,
ratingcount: $rating ? (int) $rating->count : 0,
);
}
/**
* Build image-overlay flags (recommended / mandatory / paid+price) from a
* course's markings.
*
* @param array{recommended:bool,mandatory:bool,paid:bool,price:string} $markings
* @param bool $needsapproval
* @return array<int,array{label:string,tone:string,icon:string}>
*/
private static function flags_from_markings(array $markings, bool $needsapproval = false): array {
$flags = [];
if (!empty($markings['mandatory'])) {
$flags[] = [
'label' => get_string('flag_mandatory', 'local_lernhive'),
'tone' => 'mandatory',
'icon' => 'fa-exclamation',
];
/var/www/html/public/local/lernhive/classes/dashboard/course_catalog_provider.php
continue;
}
// Date-range facet ("Termine von/bis"): a date window asks for
// offerings *happening* inside it, so undated courses drop out
// while the filter is active.
if ($filter->from !== null || $filter->to !== null) {
$startdate = (int) ($course->startdate ?? 0);
if (
$startdate <= 0
|| ($filter->from !== null && $startdate < $filter->from)
|| ($filter->to !== null && $startdate > $filter->to)
) {
continue;
}
}
if (!self::matches_access($course, $filter, $context, $userid)) {
continue;
}
$offerings[] = self::course_to_offering($course, $context, $userid, $markings);
if (count($offerings) >= self::RESULT_LIMIT) {
break;
}
}
return $offerings;
}
/**
* Return course-specific catalogue facets.
*
* @param int $userid Viewer identifier.
* @return facet[] Available facets.
*/
public static function get_facets(int $userid): array {
$facets = [
new facet(
id: 'catalog_focus',
label: get_string('catalog_filter_group_focus', 'local_lernhive'),
type: facet::TYPE_TOGGLE,
/var/www/html/public/local/lernhive/classes/hook_callbacks.php
return;
}
if (($PAGE->theme->name ?? '') !== 'lernhive') {
return;
}
if (!level_manager::get_level_record($USER->id)) {
return;
}
$scope = level_manager::get_level($USER->id) <= 1 ? 'explorer' : 'lernhive';
$hook->add_attribute('data-lh-ui', $scope);
}
/**
* Contribute ordinary Moodle courses to the central LernHive catalogue.
*
* @param collect_offerings $hook
*/
public static function contribute_course_catalog_offerings(collect_offerings $hook): void {
$hook->add_offerings(course_catalog_provider::get_offerings($hook->userid, $hook->filter));
}
/**
* Contribute core catalogue facets.
*
* @param collect_facets $hook
*/
public static function contribute_course_catalog_facets(collect_facets $hook): void {
$hook->add_facets(course_catalog_provider::get_facets($hook->userid));
}
/**
* Register core catalogue sort options.
*
* @param collect_sort_options $hook Sort-option collection hook.
*/
public static function contribute_course_catalog_sort_options(collect_sort_options $hook): void {
// Core owns the built-in modes. Feature plugins may register more modes.
}
/var/www/html/public/lib/classes/hook/manager.php
if (empty($callbacks)) {
// Nothing is interested in this hook.
return $event;
}
foreach ($callbacks as $callback) {
// Note: PSR-14 states:
// If passed a Stoppable Event, a Dispatcher
// MUST call isPropagationStopped() on the Event before each Listener has been called.
// If that method returns true it MUST return the Event to the Emitter immediately and
// MUST NOT call any further Listeners. This implies that if an Event is passed to the
// Dispatcher that always returns true from isPropagationStopped(), zero listeners will be called.
// Ergo, we check for a stopped event before calling each listener, not afterwards.
if ($event instanceof StoppableEventInterface) {
if ($event->isPropagationStopped()) {
return $event;
}
}
call_user_func($callback, $event);
}
// Developers need to be careful to not create infinite loops in hook callbacks.
return $event;
}
/**
* Initialise list of all callbacks for each hook.
*/
private function init_standard_callbacks(): void {
global $CFG;
$this->allcallbacks = [];
$this->alldeprecations = [];
// @codeCoverageIgnoreStart
$shouldcache = $this->should_cache();
if ($shouldcache) {
$cache = $this->get_cache();
/var/www/html/public/lib/classes/hook/manager.php
if (empty($callbacks)) {
// Nothing is interested in this hook.
return $event;
}
foreach ($callbacks as $callback) {
// Note: PSR-14 states:
// If passed a Stoppable Event, a Dispatcher
// MUST call isPropagationStopped() on the Event before each Listener has been called.
// If that method returns true it MUST return the Event to the Emitter immediately and
// MUST NOT call any further Listeners. This implies that if an Event is passed to the
// Dispatcher that always returns true from isPropagationStopped(), zero listeners will be called.
// Ergo, we check for a stopped event before calling each listener, not afterwards.
if ($event instanceof StoppableEventInterface) {
if ($event->isPropagationStopped()) {
return $event;
}
}
call_user_func($callback, $event);
}
// Developers need to be careful to not create infinite loops in hook callbacks.
return $event;
}
/**
* Initialise list of all callbacks for each hook.
*/
private function init_standard_callbacks(): void {
global $CFG;
$this->allcallbacks = [];
$this->alldeprecations = [];
// @codeCoverageIgnoreStart
$shouldcache = $this->should_cache();
if ($shouldcache) {
$cache = $this->get_cache();
/var/www/html/public/local/lernhive/classes/dashboard/catalog_registry.php
* @package local_lernhive
* @copyright 2026 LernHive.de
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class catalog_registry {
/**
* Collect offerings for a user under the given filter.
*
* Sort order: relevance score descending, then start timestamp ascending
* (offerings without a start date land last among same-score items), then
* source-component for deterministic ordering.
*
* @param int $userid User id to render for; 0 renders the public
* (anonymous) catalogue and keeps only opted-in public offerings.
* @param search_filter $filter Resolved filter values.
* @return offering_data[] Sorted as documented above.
*/
public static function collect_offerings(int $userid, search_filter $filter, string $sortid = 'relevance'): array {
$hook = new collect_offerings($userid, $filter);
di::get(hook_manager::class)->dispatch($hook);
$offerings = $hook->get_offerings();
$offerings = self::apply_visibility_filters($userid, $offerings, $filter);
$option = self::sort_option($userid, $sortid);
usort($offerings, static function (offering_data $a, offering_data $b) use ($option): int {
$result = $option->compare($a, $b);
return $result !== 0 ? $result : [$a->sourcecomponent, $a->id] <=> [$b->sourcecomponent, $b->id];
});
return $offerings;
}
/**
* Collect built-in and provider-declared sort modes.
*
* @return sort_option[]
*/
public static function collect_sort_options(int $userid): array {
$hook = new collect_sort_options($userid);
/var/www/html/public/local/lernhive/classes/output/catalog_page.php
*/
private function url(array $params = []): \moodle_url {
$url = $this->baseurl
? new \moodle_url($this->baseurl)
: new \moodle_url('/local/lernhive/catalog.php');
foreach ($params as $name => $value) {
$url->param($name, $value);
}
return $url;
}
/**
* Export the catalogue template context.
*
* @param \renderer_base $output Renderer instance.
* @return array<string,mixed> Template context.
*/
public function export_for_template(\renderer_base $output): array {
$sortid = (string) ($this->rawparams['sort'] ?? 'relevance');
$offerings = catalog_registry::collect_offerings($this->userid, $this->filter, $sortid);
$sortoptions = catalog_registry::collect_sort_options($this->userid);
$selectedsort = catalog_registry::sort_option($this->userid, $sortid);
// Preserve any non-search params (e.g. format=, category=) across the
// search-submit so a click on the search button does not silently drop
// an active tag-driven filter.
$extras = [];
foreach ($this->rawparams as $name => $value) {
// Skip params that render as their own visible form fields.
if (in_array($name, ['q', 'datefrom', 'dateto', 'sort'], true) || !is_string($value) || $value === '') {
continue;
}
$extras[] = ['name' => $name, 'value' => $value];
}
// A GET form drops the query string of its action, so any param the
// hosting page needs for itself (e.g. index.php's `redirect=0`) has to
// ride along as a hidden field or the search submit would lose it.
if ($this->baseurl) {
foreach ($this->baseurl->params() as $name => $value) {
if (array_key_exists($name, $this->rawparams) || $value === '') {
/var/www/html/public/local/lernhive/catalog.php
// View switcher (cards | month) — shared with the full-view catalogue block,
// see `catalog_view`.
$togglebase = new moodle_url('/local/lernhive/catalog.php', $rawparams);
echo catalog_view::view_toggle($togglebase, $view);
if ($view === catalog_view::VIEW_MONTH) {
$monthhtml = catalog_view::month_html(
$OUTPUT,
$PAGE,
$togglebase,
$filter,
$rawparams,
$calendartime,
(int) ($rawparams['expand'] ?? 0)
);
$renderable = new catalog_page($userid, $filter, $rawparams, $monthhtml);
echo $OUTPUT->render_from_template(
'local_lernhive/catalog_page',
$renderable->export_for_template($OUTPUT)
);
} else {
$renderable = new catalog_page($userid, $filter, $rawparams);
echo $OUTPUT->render_from_template(
'local_lernhive/catalog_page',
$renderable->export_for_template($OUTPUT)
);
}
echo html_writer::end_div();
echo $OUTPUT->footer();
Environment & details:
| Key | Value |
| time | 3450121200 |
| view | month |
empty
empty
empty
| Key | Value |
| USER | stdClass Object ( [id] => 0 [mnethostid] => 1 [access] => Array ( [ra] => Array ( [/1] => Array ( [6] => 6 ) ) [time] => 1789315663 [rsw] => Array ( ) ) [enrol] => Array ( [enrolled] => Array ( ) [tempguest] => Array ( ) ) [sesskey] => hkDoKxbSbQ [preference] => Array ( ) ) |
| SESSION | stdClass Object ( [isnewsessioncookie] => 1 [cachestore_session] => Array ( [default_session-core/coursecat] => Array ( [__lastaccess__u0_1ff75ef7f4c6815020675ca3d84e8dae] => Array ( [0] => 1789315663 [1] => 1789315663 ) [u0_1ff75ef7f4c6815020675ca3d84e8dae_260b57ae1c3966b1910137be05eb286db0e0c867] => Array ( [0] => 1789315663.7924-6aa6ca4fc17594.95349379 [1] => 1789315663 ) [u0_1ff75ef7f4c6815020675ca3d84e8dae_0878690aecf796a687f345a5cf7ce17a758a13e6] => Array ( [0] => Array ( [2] => Array ( [name] => Admin User [path] => /2 ) [58] => Array ( [name] => Praxisbeispiele [path] => /58 ) [59] => Array ( [name] => Einstieg und Pflichtwissen [path] => /58/59 ) [60] => Array ( [name] => Zusammenarbeit und Führung [path] => /58/60 ) [61] => Array ( [name] => Kunden, Produkt und Daten [path] => /58/61 ) [62] => Array ( [name] => Seminarveranstaltungen [path] => /62 ) ) [1] => 1789315663 ) [u0_1ff75ef7f4c6815020675ca3d84e8dae_eae4bdad609813f1131741916a481c2b05ca4ee3] => Array ( [0] => Array ( ) [1] => 1789315663 ) [u0_1ff75ef7f4c6815020675ca3d84e8dae_f94cf60b9b9490154c92b9fc367aa7e1af8368de] => Array ( [0] => Array ( [0] => 2 [1] => 58 [2] => 62 ) [1] => 1789315663 ) ) [default_session-core/navigation_cache] => Array ( [__lastaccess__u0_1ff75ef7f4c6815020675ca3d84e8dae] => Array ( [0] => 1789315663 [1] => 1789315663 ) ) [default_session-theme_boost_union/flavours] => Array ( [__lastaccess__u0_1ff75ef7f4c6815020675ca3d84e8dae] => Array ( [0] => 1789315663 [1] => 0 ) [u0_1ff75ef7f4c6815020675ca3d84e8dae_lastinvalidation-ab95758982f7eb97b18eb12d60b3d0cb] => Array ( [0] => 1789315663.7924-6aa6ca4fc17594.95349379 [1] => 0 ) [u0_1ff75ef7f4c6815020675ca3d84e8dae_0-ab95758982f7eb97b18eb12d60b3d0cb] => Array ( [0] => [1] => 0 ) ) ) [theme_boost_union_menu_purgesessioncache] => 1 [theme_boost_union_prevlang] => en ) |
| Key | Value |
| HTTP_HOST | dev.lernhive.de |
| HTTP_USER_AGENT | Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; ClaudeBot/1.0; +claudebot@anthropic.com) |
| HTTP_ACCEPT | */* |
| HTTP_ACCEPT_ENCODING | gzip, br, zstd, deflate |
| HTTP_VIA | 2.0 Caddy |
| HTTP_X_FORWARDED_FOR | 216.73.216.75 |
| HTTP_X_FORWARDED_HOST | dev.lernhive.de |
| HTTP_X_FORWARDED_PROTO | https |
| HTTP_X_REAL_IP | 216.73.216.75 |
| PATH | /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin |
| LD_LIBRARY_PATH | /usr/local/instantclient |
| SERVER_SIGNATURE | <address>Apache/2.4.66 (Debian) Server at dev.lernhive.de Port 80</address> |
| SERVER_SOFTWARE | Apache/2.4.66 (Debian) |
| SERVER_NAME | dev.lernhive.de |
| SERVER_ADDR | 172.23.0.2 |
| SERVER_PORT | 443 |
| REMOTE_ADDR | 172.23.0.3 |
| DOCUMENT_ROOT | /var/www/html/public |
| REQUEST_SCHEME | http |
| CONTEXT_PREFIX | |
| CONTEXT_DOCUMENT_ROOT | /var/www/html/public |
| SERVER_ADMIN | webmaster@localhost |
| SCRIPT_FILENAME | /var/www/html/public/local/lernhive/catalog.php |
| REMOTE_PORT | 41264 |
| GATEWAY_INTERFACE | CGI/1.1 |
| SERVER_PROTOCOL | HTTP/1.1 |
| REQUEST_METHOD | GET |
| QUERY_STRING | time=3450121200&view=month |
| REQUEST_URI | /local/lernhive/catalog.php?time=3450121200&view=month |
| SCRIPT_NAME | /local/lernhive/catalog.php |
| PHP_SELF | /local/lernhive/catalog.php |
| REQUEST_TIME_FLOAT | 1789315663.7459 |
| REQUEST_TIME | 1789315663 |
| argv | Array ( [0] => time=3450121200&view=month ) |
| argc | 1 |
| HTTPS | on |
| Key | Value |
| MOODLE_DOCKER_DBUSER | moodle |
| MOODLE_DOCKER_DBNAME | moodle |
| MOODLE_DOCKER_TIMEOUT_FACTOR | 1 |
| HOSTNAME | f5ad226f3b16 |
| PHP_VERSION | 8.3.30 |
| APACHE_CONFDIR | /etc/apache2 |
| PHP_INI_DIR | /usr/local/etc/php |
| GPG_KEYS | 1198C0117593497A5EC5C199286AF1F9897469DC C28D937575603EB4ABB725861C0779DC5C0A9DE4 AFD8691FDAEDF03BDF6E460563F15A9B715376CA |
| PHP_LDFLAGS | -Wl,-O1 -pie |
| MOODLE_DOCKER_RUNNING | 1 |
| PWD | /var/www/html |
| APACHE_DOCUMENT_ROOT | /var/www/html/public |
| MOODLE_DOCKER_WEB_PORT | 127.0.0.1:8000 |
| APACHE_LOG_DIR | /var/log/apache2 |
| LANG | C |
| PHP_SHA256 | 67f084d36852daab6809561a7c8023d130ca07fc6af8fb040684dd1414934d48 |
| TARGETPLATFORM | linux/amd64 |
| MOODLE_DOCKER_DBTYPE | pgsql |
| MOODLE_DOCKER_WEB_HOST | localhost |
| APACHE_PID_FILE | /var/run/apache2/apache2.pid |
| PHPIZE_DEPS | autoconf dpkg-dev file g++ gcc libc-dev make pkg-config re2c |
| MOODLE_DOCKER_BROWSER | firefox |
| PHP_URL | https://www.php.net/distributions/php-8.3.30.tar.xz |
| APACHE_RUN_GROUP | www-data |
| APACHE_LOCK_DIR | /var/lock/apache2 |
| SHLVL | 0 |
| PHP_CFLAGS | -fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 |
| MOODLE_DOCKER_DBPASS | m@0dl3ing |
| LD_LIBRARY_PATH | /usr/local/instantclient |
| APACHE_RUN_DIR | /var/run/apache2 |
| APACHE_ENVVARS | /etc/apache2/envvars |
| APACHE_RUN_USER | www-data |
| PATH | /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin |
| PHP_ASC_URL | https://www.php.net/distributions/php-8.3.30.tar.xz.asc |
| PHP_CPPFLAGS | -fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 |
0. Whoops\Handler\PrettyPageHandler
1. Whoops\Handler\CallbackHandler