From 395246e02ba7f49300991bb35f6700541308109c Mon Sep 17 00:00:00 2001 From: Riedler Date: Sat, 4 Oct 2025 23:25:33 +0200 Subject: [PATCH 01/27] feat: setting for sentry DSN preliminary work for adding the sentry API to the plugin --- lbplanner/classes/enums/SETTINGS.php | 4 ++++ lbplanner/settings.php | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/lbplanner/classes/enums/SETTINGS.php b/lbplanner/classes/enums/SETTINGS.php index 84d236bb..4665bdb0 100644 --- a/lbplanner/classes/enums/SETTINGS.php +++ b/lbplanner/classes/enums/SETTINGS.php @@ -41,4 +41,8 @@ class SETTINGS extends Enum { * Key for the setting for how long a course should be expired for until it counts as outdated and gets culled. */ const COURSE_OUTDATERANGE = 'course_outdaterange'; + /** + * Key for the setting for where sentry events should be sent to. + */ + const SENTRY_DSN = 'sentry_dsn'; } diff --git a/lbplanner/settings.php b/lbplanner/settings.php index 65a84832..c32a48c3 100644 --- a/lbplanner/settings.php +++ b/lbplanner/settings.php @@ -57,4 +57,12 @@ ); $outdaterangesett->set_min_duration(0); $settings->add($outdaterangesett); + + $sentrydsnsett = new admin_setting_configtext( + 'local_lbplanner/'.SETTINGS::SENTRY_DSN, + 'Sentry DSN (data source name), for where to send error debugging info to.', + '', + PARAM_TEXT + ); + $settings->add($sentrydsnsett); } From 379508d7e10de2e4341f20f9303690192c21c774 Mon Sep 17 00:00:00 2001 From: Riedler Date: Sun, 5 Oct 2025 14:26:37 +0200 Subject: [PATCH 02/27] feat: added sentry to dependencies + composer setup --- .gitignore | 3 +- composer.json | 16 ++ composer.lock | 291 +++++++++++++++++++++++++++++++++++ lbplanner/thirdpartylibs.xml | 15 ++ 4 files changed, 324 insertions(+), 1 deletion(-) create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 lbplanner/thirdpartylibs.xml diff --git a/.gitignore b/.gitignore index b1203584..b760de7b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ moodle *.zip lbplanner.dart/test/env.dart example -.idea \ No newline at end of file +.idea +lbplanner/vendor \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 00000000..6a5d7cea --- /dev/null +++ b/composer.json @@ -0,0 +1,16 @@ +{ + "require": { + "sentry/sentry": "^4.16", + "php": ">=8.1" + }, + "replace": { + "symfony/deprecation-contracts": "*", + "guzzlehttp/psr7": "*", + "psr/http-factory": "*", + "psr/http-message": "*", + "ralouphie/getallheaders": "*" + }, + "config": { + "vendor-dir": "lbplanner/vendor" + } +} \ No newline at end of file diff --git a/composer.lock b/composer.lock new file mode 100644 index 00000000..a1fc2d27 --- /dev/null +++ b/composer.lock @@ -0,0 +1,291 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "fa263e582fac5015e85c61aa6c050786", + "packages": [ + { + "name": "jean85/pretty-package-versions", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/4d7aa5dab42e2a76d99559706022885de0e18e1a", + "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.1.0", + "php": "^7.4|^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "jean85/composer-provided-replaced-stub-package": "^1.0", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^7.5|^8.5|^9.6", + "rector/rector": "^2.0", + "vimeo/psalm": "^4.3 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Jean85\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alessandro Lai", + "email": "alessandro.lai85@gmail.com" + } + ], + "description": "A library to get pretty versions strings of installed dependencies", + "keywords": [ + "composer", + "package", + "release", + "versions" + ], + "support": { + "issues": "https://github.com/Jean85/pretty-package-versions/issues", + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.1" + }, + "time": "2025-03-19T14:43:43+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "sentry/sentry", + "version": "4.16.0", + "source": { + "type": "git", + "url": "https://github.com/getsentry/sentry-php.git", + "reference": "c5b086e4235762da175034bc463b0d31cbb38d2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/c5b086e4235762da175034bc463b0d31cbb38d2e", + "reference": "c5b086e4235762da175034bc463b0d31cbb38d2e", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "guzzlehttp/psr7": "^1.8.4|^2.1.1", + "jean85/pretty-package-versions": "^1.5|^2.0.4", + "php": "^7.2|^8.0", + "psr/log": "^1.0|^2.0|^3.0", + "symfony/options-resolver": "^4.4.30|^5.0.11|^6.0|^7.0" + }, + "conflict": { + "raven/raven": "*" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.4", + "guzzlehttp/promises": "^2.0.3", + "guzzlehttp/psr7": "^1.8.4|^2.1.1", + "monolog/monolog": "^1.6|^2.0|^3.0", + "phpbench/phpbench": "^1.0", + "phpstan/phpstan": "^1.3", + "phpunit/phpunit": "^8.5|^9.6", + "symfony/phpunit-bridge": "^5.2|^6.0|^7.0", + "vimeo/psalm": "^4.17" + }, + "suggest": { + "monolog/monolog": "Allow sending log messages to Sentry by using the included Monolog handler." + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Sentry\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sentry", + "email": "accounts@sentry.io" + } + ], + "description": "PHP SDK for Sentry (http://sentry.io)", + "homepage": "http://sentry.io", + "keywords": [ + "crash-reporting", + "crash-reports", + "error-handler", + "error-monitoring", + "log", + "logging", + "profiling", + "sentry", + "tracing" + ], + "support": { + "issues": "https://github.com/getsentry/sentry-php/issues", + "source": "https://github.com/getsentry/sentry-php/tree/4.16.0" + }, + "funding": [ + { + "url": "https://sentry.io/", + "type": "custom" + }, + { + "url": "https://sentry.io/pricing/", + "type": "custom" + } + ], + "time": "2025-09-22T13:38:03+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v7.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/0ff2f5c3df08a395232bbc3c2eb7e84912df911d", + "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v7.3.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-08-05T10:16:07+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.1" + }, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/lbplanner/thirdpartylibs.xml b/lbplanner/thirdpartylibs.xml new file mode 100644 index 00000000..44051736 --- /dev/null +++ b/lbplanner/thirdpartylibs.xml @@ -0,0 +1,15 @@ + + + + vendor + Sentry API SDK + An error reporting library. + 4.16 + MIT + + https://github.com/getsentry/sentry-php + + 2012 Functional Software, Inc. dba Sentry + + + \ No newline at end of file From 1a5e448b7a7be756fd214e5aeb074fac1c70e8a1 Mon Sep 17 00:00:00 2001 From: Riedler Date: Sun, 5 Oct 2025 15:21:56 +0200 Subject: [PATCH 03/27] fix: settings off-by-one --- lbplanner/settings.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lbplanner/settings.php b/lbplanner/settings.php index c32a48c3..361d7831 100644 --- a/lbplanner/settings.php +++ b/lbplanner/settings.php @@ -60,7 +60,8 @@ $sentrydsnsett = new admin_setting_configtext( 'local_lbplanner/'.SETTINGS::SENTRY_DSN, - 'Sentry DSN (data source name), for where to send error debugging info to.', + 'Sentry DSN', + 'for where to send error debugging info to.', '', PARAM_TEXT ); From 04233ce74fdcc457b4b6b8f1206ffde7a67c5d20 Mon Sep 17 00:00:00 2001 From: Riedler Date: Thu, 9 Oct 2025 19:49:45 +0200 Subject: [PATCH 04/27] feat: initial impl of sentry callback --- lbplanner/classes/helpers/config_helper.php | 8 +++ lbplanner/classes/helpers/sentry_helper.php | 73 +++++++++++++++++++++ lbplanner/lib.php | 62 +++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 lbplanner/classes/helpers/sentry_helper.php create mode 100644 lbplanner/lib.php diff --git a/lbplanner/classes/helpers/config_helper.php b/lbplanner/classes/helpers/config_helper.php index 37a1ff96..7105f856 100644 --- a/lbplanner/classes/helpers/config_helper.php +++ b/lbplanner/classes/helpers/config_helper.php @@ -115,4 +115,12 @@ public static function get_slot_futuresight(): int { public static function get_course_outdatedrange(): int { return get_config('local_lbplanner', SETTINGS::COURSE_OUTDATERANGE); } + + /** + * Get the Sentry DSN - for where to send error debugging info to. + * @return string the sentry DSN + */ + public static function get_sentry_dsn(): string { + return get_config('local_lbplanner', SETTINGS::SENTRY_DSN); + } } diff --git a/lbplanner/classes/helpers/sentry_helper.php b/lbplanner/classes/helpers/sentry_helper.php new file mode 100644 index 00000000..250c05a6 --- /dev/null +++ b/lbplanner/classes/helpers/sentry_helper.php @@ -0,0 +1,73 @@ +. + +namespace local_lbplanner\helpers; + +use Throwable; + +use local_lbplanner\helpers\config_helper; + +require_once($CFG->dirroot.'/local/lbplanner/vendor/autoload.php'); + +/** + * Provides helper methods for sentry logging + * + * @package local_lbplanner + * @subpackage helpers + * @copyright 2025 necodeIT + * @license https://creativecommons.org/licenses/by-nc-sa/4.0/ CC-BY-NC-SA 4.0 International or later + */ +class sentry_helper { + /** + * Checks if moodle plugin is set to report exceptions to sentry + * @return bool whether sentry is to be used + */ + public static function enabled(): bool { + return strlen(config_helper::get_sentry_dsn()) > 0; + } + /** + * Initializes the sentry library for future use. + */ + public static function init(): void { + if (self::enabled()) { + $cfg = [ // TODO: look at all the config options + "dsn" => config_helper::get_sentry_dsn(), + "enable_tracing" => true, + "attach_stacktrace" => true, + ]; + \Sentry\init($cfg); + } + } + /** + * Checks if any errors happened since init() and reports the most recent one to sentry if so. + * @return whether any errors were found + */ + public static function report_last_err(): bool { + if (self::enabled()) { + return \Sentry\captureLastError() !== null; + } + return false; + } + /** + * Reports an error to sentry + * @param Throwable $err the error + */ + public static function report_err(Throwable $err): void { + if (self::enabled()) { + \Sentry\captureException($err); + } + } +} \ No newline at end of file diff --git a/lbplanner/lib.php b/lbplanner/lib.php new file mode 100644 index 00000000..e11e566d --- /dev/null +++ b/lbplanner/lib.php @@ -0,0 +1,62 @@ +. + +/** + * Defines callbacks for moodle + * + * @package local_lbplanner + * @copyright 2025 NecodeIT + * @license https://creativecommons.org/licenses/by-nc-sa/4.0/ CC-BY-NC-SA 4.0 International or later + */ + +use local_lbplanner\helpers\sentry_helper; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Callback for any webservices that get called by external actors. + * We use this to catch whenever anything from us is being called, and do sentry setup and error reporting. + * @param stdClass $externalfunctioninfo external function info @see external_api::external_function_info(); + * @param array $params the raw(ish) parameters that are going to get passed to the function implementing the API call + * @return mixed Either whatever the API call returned, or false if we don't wish to override anything. + */ +function local_lbplanner_override_webservice_execution(stdClass $externalfunctioninfo, array $params): mixed { + // Only override calling our own functions. + if ($externalfunctioninfo->component === 'local_lbplanner') { + sentry_helper::init(); + // Actually calling the function (since we're overriding this part, duh). + try { + $callable = [$externalfunctioninfo->classname, $externalfunctioninfo->methodname]; + $result = call_user_func_array($callable, $params); + + // Report if call_user_func_array itself had some kind of issue. + if ($result === false) { + $params_s = var_export($params, true); + throw new \coding_exception( + "webservice override: call_user_func_array returned with false at " + .$externalfunctioninfo->classname."::".$externalfunctioninfo->methodname."(".$params_s.");" + ); + } + + return $result; + } catch (\Throwable $e) { + sentry_helper::report_err($e); + throw $e; + } + } + + return false; +} \ No newline at end of file From f1227b9b2d9be0bb72a2753b75fcf7a8ced1d500 Mon Sep 17 00:00:00 2001 From: Riedler Date: Thu, 9 Oct 2025 19:50:39 +0200 Subject: [PATCH 05/27] fix: fixed minimum php version at 8.1.0 --- .kateproject | 2 +- composer.json | 7 +++++-- composer.lock | 19 +++++++++++-------- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/.kateproject b/.kateproject index ca3bca28..5f9c799c 100644 --- a/.kateproject +++ b/.kateproject @@ -7,7 +7,7 @@ "settings": { "intelephense": { "environment": { - "phpVersion": "8.0.0" + "phpVersion": "8.1.0" } } } diff --git a/composer.json b/composer.json index 6a5d7cea..8afc1a79 100644 --- a/composer.json +++ b/composer.json @@ -11,6 +11,9 @@ "ralouphie/getallheaders": "*" }, "config": { - "vendor-dir": "lbplanner/vendor" + "vendor-dir": "lbplanner/vendor", + "platform": { + "php": "8.1.0" + } } -} \ No newline at end of file +} diff --git a/composer.lock b/composer.lock index a1fc2d27..9a1ffa0b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "fa263e582fac5015e85c61aa6c050786", + "content-hash": "0c2c42c4c9cc556c76976c609d182626", "packages": [ { "name": "jean85/pretty-package-versions", @@ -207,20 +207,20 @@ }, { "name": "symfony/options-resolver", - "version": "v7.3.3", + "version": "v6.4.25", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d" + "reference": "d28e7e2db8a73e9511df892d36445f61314bbebe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/0ff2f5c3df08a395232bbc3c2eb7e84912df911d", - "reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/d28e7e2db8a73e9511df892d36445f61314bbebe", + "reference": "d28e7e2db8a73e9511df892d36445f61314bbebe", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.1", "symfony/deprecation-contracts": "^2.5|^3" }, "type": "library", @@ -254,7 +254,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.3.3" + "source": "https://github.com/symfony/options-resolver/tree/v6.4.25" }, "funding": [ { @@ -274,7 +274,7 @@ "type": "tidelift" } ], - "time": "2025-08-05T10:16:07+00:00" + "time": "2025-08-04T17:06:28+00:00" } ], "packages-dev": [], @@ -287,5 +287,8 @@ "php": ">=8.1" }, "platform-dev": {}, + "platform-overrides": { + "php": "8.1.0" + }, "plugin-api-version": "2.6.0" } From dc600a8f57bc6fdda8d29f28473459bb6c24c077 Mon Sep 17 00:00:00 2001 From: Riedler Date: Thu, 9 Oct 2025 19:51:15 +0200 Subject: [PATCH 06/27] docs: setup and packaging instructions --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index a63646b0..74c7f279 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,29 @@ # LB Planer Plugin This repository contains the source code for the LB Planer Moodle plugin. Api endpoints are documented [here](https://necodeit.github.io/lb_planner_docs/moodle/index.html) + +## Setup + +### Bundled Dependencies + +Run `composer up` here to download dependencies to the vendor folder. +Note that dependencies already present in moodle are excluded from the tree. + +### Dev Env + +You can do this multiple ways, but we personally have a directory structure like so: + +``` +-root ← top folder. any name works + |-plugin ← this folder + |-moodle ← folder with moodle in it + |-local ← moodle's plugin folder + |-lbplanner ← symlink to the lbplanner folder + |-modcustomfields ← a dependency of ours (get at https://gitlab.com/adapta/moodle-local_modcustomfields/) +``` + +Using [kate](https://kate-editor.org/) with [intelephense](https://intelephense.com/) is recommended. + +## Packaging + +Zip the lbplanner folder. as shrimple as that. \ No newline at end of file From cb9a82bbef9ca9a80c71914c9675da644ed1ca9c Mon Sep 17 00:00:00 2001 From: Riedler Date: Thu, 9 Oct 2025 21:07:11 +0200 Subject: [PATCH 07/27] fix: add gitkeep to vendor dir so it sticks around --- lbplanner/vendor/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 lbplanner/vendor/.gitkeep diff --git a/lbplanner/vendor/.gitkeep b/lbplanner/vendor/.gitkeep new file mode 100644 index 00000000..e69de29b From 9f03299e0908a737018eaae0c2c258feb064583a Mon Sep 17 00:00:00 2001 From: Riedler Date: Thu, 9 Oct 2025 21:39:12 +0200 Subject: [PATCH 08/27] chore: pacified moodle codechecker --- lbplanner/classes/helpers/sentry_helper.php | 6 ++++-- lbplanner/lib.php | 8 +++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lbplanner/classes/helpers/sentry_helper.php b/lbplanner/classes/helpers/sentry_helper.php index 250c05a6..da59b74f 100644 --- a/lbplanner/classes/helpers/sentry_helper.php +++ b/lbplanner/classes/helpers/sentry_helper.php @@ -20,6 +20,8 @@ use local_lbplanner\helpers\config_helper; +defined('MOODLE_INTERNAL') || die(); + require_once($CFG->dirroot.'/local/lbplanner/vendor/autoload.php'); /** @@ -43,7 +45,7 @@ public static function enabled(): bool { */ public static function init(): void { if (self::enabled()) { - $cfg = [ // TODO: look at all the config options + $cfg = [ // TODO: look at all the config options. "dsn" => config_helper::get_sentry_dsn(), "enable_tracing" => true, "attach_stacktrace" => true, @@ -70,4 +72,4 @@ public static function report_err(Throwable $err): void { \Sentry\captureException($err); } } -} \ No newline at end of file +} diff --git a/lbplanner/lib.php b/lbplanner/lib.php index e11e566d..651adbbd 100644 --- a/lbplanner/lib.php +++ b/lbplanner/lib.php @@ -24,8 +24,6 @@ use local_lbplanner\helpers\sentry_helper; -defined('MOODLE_INTERNAL') || die(); - /** * Callback for any webservices that get called by external actors. * We use this to catch whenever anything from us is being called, and do sentry setup and error reporting. @@ -44,10 +42,10 @@ function local_lbplanner_override_webservice_execution(stdClass $externalfunctio // Report if call_user_func_array itself had some kind of issue. if ($result === false) { - $params_s = var_export($params, true); + $paramsstring = var_export($params, true); throw new \coding_exception( "webservice override: call_user_func_array returned with false at " - .$externalfunctioninfo->classname."::".$externalfunctioninfo->methodname."(".$params_s.");" + .$externalfunctioninfo->classname."::".$externalfunctioninfo->methodname."(".$paramsstring.");" ); } @@ -59,4 +57,4 @@ function local_lbplanner_override_webservice_execution(stdClass $externalfunctio } return false; -} \ No newline at end of file +} From 9fb99879ce138dbf6d1f57a759761da6abbf90c5 Mon Sep 17 00:00:00 2001 From: Riedler Date: Thu, 9 Oct 2025 21:49:30 +0200 Subject: [PATCH 09/27] chore: pacified PHPDoc checker --- lbplanner/lib.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbplanner/lib.php b/lbplanner/lib.php index 651adbbd..9b4106d6 100644 --- a/lbplanner/lib.php +++ b/lbplanner/lib.php @@ -27,7 +27,7 @@ /** * Callback for any webservices that get called by external actors. * We use this to catch whenever anything from us is being called, and do sentry setup and error reporting. - * @param stdClass $externalfunctioninfo external function info @see external_api::external_function_info(); + * @param stdClass $externalfunctioninfo external function info { @see external_api::external_function_info() } * @param array $params the raw(ish) parameters that are going to get passed to the function implementing the API call * @return mixed Either whatever the API call returned, or false if we don't wish to override anything. */ From 0ff367f5a41889cb932abc525a7e9b30a9c69942 Mon Sep 17 00:00:00 2001 From: Riedler Date: Thu, 9 Oct 2025 21:55:42 +0200 Subject: [PATCH 10/27] chore: kill yourself --- lbplanner/lib.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbplanner/lib.php b/lbplanner/lib.php index 9b4106d6..70866082 100644 --- a/lbplanner/lib.php +++ b/lbplanner/lib.php @@ -27,7 +27,7 @@ /** * Callback for any webservices that get called by external actors. * We use this to catch whenever anything from us is being called, and do sentry setup and error reporting. - * @param stdClass $externalfunctioninfo external function info { @see external_api::external_function_info() } + * @param stdClass $externalfunctioninfo external function info {@see external_api::external_function_info()} * @param array $params the raw(ish) parameters that are going to get passed to the function implementing the API call * @return mixed Either whatever the API call returned, or false if we don't wish to override anything. */ From 073e6343588eb03a5af65afa1e7e609c1b0d9a47 Mon Sep 17 00:00:00 2001 From: Riedler Date: Wed, 29 Oct 2025 15:42:47 +0100 Subject: [PATCH 11/27] feat: set release and environment for sentry --- .github/workflows/package-and-attach-to-release.yml | 4 ++++ lbplanner/classes/helpers/sentry_helper.php | 4 +++- lbplanner/version.php | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/package-and-attach-to-release.yml b/.github/workflows/package-and-attach-to-release.yml index 41c28a80..a5419bac 100644 --- a/.github/workflows/package-and-attach-to-release.yml +++ b/.github/workflows/package-and-attach-to-release.yml @@ -16,6 +16,10 @@ jobs: - name: Checkout uses: actions/checkout@v5.0.0 + - name: Set environment to prod + run: | + sed -i "s/set_config('sentry_environment', 'develop', 'local_lbplanner')/set_config('sentry_environment', 'production', 'local_lbplanner')/g" "$TARGET_FOLDER/version.php" + - name: Extract version id: version run: | diff --git a/lbplanner/classes/helpers/sentry_helper.php b/lbplanner/classes/helpers/sentry_helper.php index da59b74f..12fe6e9a 100644 --- a/lbplanner/classes/helpers/sentry_helper.php +++ b/lbplanner/classes/helpers/sentry_helper.php @@ -45,10 +45,12 @@ public static function enabled(): bool { */ public static function init(): void { if (self::enabled()) { - $cfg = [ // TODO: look at all the config options. + $cfg = [ "dsn" => config_helper::get_sentry_dsn(), "enable_tracing" => true, "attach_stacktrace" => true, + "release" => 'lbplanner@' . get_config('local_lbplanner', 'release'), + "environment" => get_config('local_lbplanner', 'sentry_environment'), ]; \Sentry\init($cfg); } diff --git a/lbplanner/version.php b/lbplanner/version.php index 29a582a1..17e0d721 100644 --- a/lbplanner/version.php +++ b/lbplanner/version.php @@ -35,3 +35,4 @@ ]; set_config('release', $plugin->release, 'local_lbplanner'); +set_config('sentry_environment', 'develop', 'local_lbplanner'); // NOTE: gets set to 'production' by CI for release. From 6645bcef9ce362a896fe0fd1721fee3ec9476cc1 Mon Sep 17 00:00:00 2001 From: Riedler Date: Wed, 29 Oct 2025 16:11:45 +0100 Subject: [PATCH 12/27] chore: removed psr/log from vendor folder it's already included in moodle proper --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 8afc1a79..b031b618 100644 --- a/composer.json +++ b/composer.json @@ -8,6 +8,7 @@ "guzzlehttp/psr7": "*", "psr/http-factory": "*", "psr/http-message": "*", + "psr/log": "*", "ralouphie/getallheaders": "*" }, "config": { From b40a60734aa82b07f27b9a5e14a5680094c4325f Mon Sep 17 00:00:00 2001 From: Riedler Date: Wed, 29 Oct 2025 16:15:04 +0100 Subject: [PATCH 13/27] feat: get dependencies when making dist package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit this *should* work, but it's really hard to test, so… we'll see next release --- .github/workflows/package-and-attach-to-release.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/package-and-attach-to-release.yml b/.github/workflows/package-and-attach-to-release.yml index a5419bac..0f8efb78 100644 --- a/.github/workflows/package-and-attach-to-release.yml +++ b/.github/workflows/package-and-attach-to-release.yml @@ -26,7 +26,16 @@ jobs: version=$(grep -oP '\$plugin->version\s*=\s*\K[0-9]+' "$TARGET_FOLDER/version.php") echo "version=$version" >> $GITHUB_OUTPUT - + + - name: Setup PHP ${{ matrix.php }} + uses: shivammathur/setup-php@v2 + with: # see gha.dist.yml + php-version: '8.1' + coverage: none + - name: Get php libs + run: | + composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist + - name: Create ZIP file run: | ZIP_NAME="${MOODLE_INSTALL_DIR}_${PACKAGE_NAME}_${VERSION}.zip" From ce8bc28daa911348a3934ca9509f2ae40d3f62cb Mon Sep 17 00:00:00 2001 From: Riedler Date: Wed, 29 Oct 2025 22:13:38 +0100 Subject: [PATCH 14/27] feat: first draft of sentry tracing --- lbplanner/classes/helpers/sentry_helper.php | 74 +++++++++++++++++++++ lbplanner/lib.php | 2 + 2 files changed, 76 insertions(+) diff --git a/lbplanner/classes/helpers/sentry_helper.php b/lbplanner/classes/helpers/sentry_helper.php index 12fe6e9a..fef5f7ac 100644 --- a/lbplanner/classes/helpers/sentry_helper.php +++ b/lbplanner/classes/helpers/sentry_helper.php @@ -19,6 +19,8 @@ use Throwable; use local_lbplanner\helpers\config_helper; +use Sentry\SentrySdk; +use Sentry\Tracing\{Span, SpanContext, Transaction, TransactionContext}; defined('MOODLE_INTERNAL') || die(); @@ -33,6 +35,11 @@ * @license https://creativecommons.org/licenses/by-nc-sa/4.0/ CC-BY-NC-SA 4.0 International or later */ class sentry_helper { + /** + * Remembers what spans have been in use. + */ + private static array $spans = []; + /** * Checks if moodle plugin is set to report exceptions to sentry * @return bool whether sentry is to be used @@ -48,6 +55,7 @@ public static function init(): void { $cfg = [ "dsn" => config_helper::get_sentry_dsn(), "enable_tracing" => true, + "traces_sample_rate" => 0.2, "attach_stacktrace" => true, "release" => 'lbplanner@' . get_config('local_lbplanner', 'release'), "environment" => get_config('local_lbplanner', 'sentry_environment'), @@ -55,6 +63,72 @@ public static function init(): void { \Sentry\init($cfg); } } + /** + * Does a bunch of setup for measuring transaction duration. + * @param string $name name of the transaction to start + * @param string $op the operation this transaction is for + * @param ?array $data an assocarr of data to record for this transaction, or null + * @return ?Transaction the transaction that got started, or null if disabled + */ + public static function transaction_start(string $name, string $op, ?array $data = null): ?Transaction { + if (self::enabled()) { + if (SentrySdk::getCurrentHub()->getSpan() !== null) { + throw new \coding_exception('tried to start a new sentry transaction when there\'s already a span set'); + } + $ctx = TransactionContext::make() + ->setName($name) + ->setOp($op); + if ($data !== null) { + $ctx = $ctx->setData($data); + } + $transaction = \Sentry\startTransaction($ctx); + SentrySdk::getCurrentHub()->setSpan($transaction); + self::$spans[(string)$transaction->getSpanId()] = $transaction; + return $transaction; + } + return null; + } + /** + * Marks a transaction as ended + * @param ?Transaction $transaction the transaction to end + */ + public static function transaction_end(?Transaction $transaction): void { + self::span_end($transaction); // Transactions are just special spans. + } + /** + * Does a bunch of setup for measuring span duration. + * @param string $op the operation this span is for + * @return ?Span the span that got started, or null if disabled + */ + public static function span_start(string $op): ?Span { + if (self::enabled()) { + $ctx = SpanContext::make() + ->setOp($op); + $parent = SentrySdk::getCurrentHub()->getSpan(); + $span = $parent->startChild($ctx); + self::$spans[(string)$span->getSpanId()] = $span; + return $span; + } + return null; + } + /** + * Marks a span as ended + * @param ?Span $span the span to end + */ + public static function span_end(?Span $span): void { + if ($span !== null) { + $span->finish(); + $parentid = $span->getParentSpanId(); + if ($parentid === null) { + // Probably a transaction. No parent, thus no new active span. + $parent = null; + } else { + // Set currently active span to parent span (may be the transaction). + $parent = self::$spans[(string)$parentid]; + } + SentrySdk::getCurrentHub()->setSpan($parent); + } + } /** * Checks if any errors happened since init() and reports the most recent one to sentry if so. * @return whether any errors were found diff --git a/lbplanner/lib.php b/lbplanner/lib.php index 70866082..7899cfc7 100644 --- a/lbplanner/lib.php +++ b/lbplanner/lib.php @@ -38,7 +38,9 @@ function local_lbplanner_override_webservice_execution(stdClass $externalfunctio // Actually calling the function (since we're overriding this part, duh). try { $callable = [$externalfunctioninfo->classname, $externalfunctioninfo->methodname]; + $transaction = sentry_helper::transaction_start(...$callable); $result = call_user_func_array($callable, $params); + sentry_helper::transaction_end($transaction); // Report if call_user_func_array itself had some kind of issue. if ($result === false) { From 347417ff0f299f6d1303687be82198aaccdf5c5c Mon Sep 17 00:00:00 2001 From: Riedler Date: Wed, 29 Oct 2025 22:14:07 +0100 Subject: [PATCH 15/27] chore: relicensed lib.php to pallasys --- lbplanner/lib.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbplanner/lib.php b/lbplanner/lib.php index 7899cfc7..e2a0b40f 100644 --- a/lbplanner/lib.php +++ b/lbplanner/lib.php @@ -18,7 +18,7 @@ * Defines callbacks for moodle * * @package local_lbplanner - * @copyright 2025 NecodeIT + * @copyright 2025 Pallasys * @license https://creativecommons.org/licenses/by-nc-sa/4.0/ CC-BY-NC-SA 4.0 International or later */ From 38b0cb8cac9ca0948f896dd1796642b32391c12e Mon Sep 17 00:00:00 2001 From: Riedler Date: Wed, 29 Oct 2025 23:05:36 +0100 Subject: [PATCH 16/27] chore: less funky docstring, to pacify phpdoc checker --- lbplanner/classes/helpers/sentry_helper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbplanner/classes/helpers/sentry_helper.php b/lbplanner/classes/helpers/sentry_helper.php index fef5f7ac..539d56bb 100644 --- a/lbplanner/classes/helpers/sentry_helper.php +++ b/lbplanner/classes/helpers/sentry_helper.php @@ -67,7 +67,7 @@ public static function init(): void { * Does a bunch of setup for measuring transaction duration. * @param string $name name of the transaction to start * @param string $op the operation this transaction is for - * @param ?array $data an assocarr of data to record for this transaction, or null + * @param ?array $data an assocarr of data to record for this transaction, or null * @return ?Transaction the transaction that got started, or null if disabled */ public static function transaction_start(string $name, string $op, ?array $data = null): ?Transaction { From d0b54a90fe64543d0fdd746bf2b7dd93c61fe4ad Mon Sep 17 00:00:00 2001 From: Riedler Date: Wed, 29 Oct 2025 23:31:39 +0100 Subject: [PATCH 17/27] chore: pacify code checker --- lbplanner/classes/helpers/sentry_helper.php | 5 ++--- lbplanner/lib.php | 7 ++++++- lbplanner/settings.php | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lbplanner/classes/helpers/sentry_helper.php b/lbplanner/classes/helpers/sentry_helper.php index 539d56bb..70f01ba1 100644 --- a/lbplanner/classes/helpers/sentry_helper.php +++ b/lbplanner/classes/helpers/sentry_helper.php @@ -17,14 +17,13 @@ namespace local_lbplanner\helpers; use Throwable; - use local_lbplanner\helpers\config_helper; use Sentry\SentrySdk; use Sentry\Tracing\{Span, SpanContext, Transaction, TransactionContext}; defined('MOODLE_INTERNAL') || die(); -require_once($CFG->dirroot.'/local/lbplanner/vendor/autoload.php'); +require_once($CFG->dirroot . '/local/lbplanner/vendor/autoload.php'); /** * Provides helper methods for sentry logging @@ -36,7 +35,7 @@ */ class sentry_helper { /** - * Remembers what spans have been in use. + * @var array $spans Remembers what spans have been in use. */ private static array $spans = []; diff --git a/lbplanner/lib.php b/lbplanner/lib.php index e2a0b40f..a04b0fe3 100644 --- a/lbplanner/lib.php +++ b/lbplanner/lib.php @@ -47,7 +47,12 @@ function local_lbplanner_override_webservice_execution(stdClass $externalfunctio $paramsstring = var_export($params, true); throw new \coding_exception( "webservice override: call_user_func_array returned with false at " - .$externalfunctioninfo->classname."::".$externalfunctioninfo->methodname."(".$paramsstring.");" + . $externalfunctioninfo->classname + . "::" + . $externalfunctioninfo->methodname + . "(" + . $paramsstring + . ");" ); } diff --git a/lbplanner/settings.php b/lbplanner/settings.php index 361d7831..b1e3eafb 100644 --- a/lbplanner/settings.php +++ b/lbplanner/settings.php @@ -59,7 +59,7 @@ $settings->add($outdaterangesett); $sentrydsnsett = new admin_setting_configtext( - 'local_lbplanner/'.SETTINGS::SENTRY_DSN, + 'local_lbplanner/' . SETTINGS::SENTRY_DSN, 'Sentry DSN', 'for where to send error debugging info to.', '', From 22cd3c7083db907d63f147030fd8fd109f87d6c1 Mon Sep 17 00:00:00 2001 From: Riedler Date: Thu, 30 Oct 2025 02:31:24 +0100 Subject: [PATCH 18/27] feat: well-placed sentry tracing --- lbplanner/classes/helpers/course_helper.php | 7 ++++++ lbplanner/classes/helpers/modules_helper.php | 2 ++ lbplanner/classes/helpers/plan_helper.php | 2 ++ lbplanner/classes/helpers/sentry_helper.php | 17 ++++++++++++-- lbplanner/classes/helpers/slot_helper.php | 24 ++++++++++++++++++++ lbplanner/lib.php | 2 +- 6 files changed, 51 insertions(+), 3 deletions(-) diff --git a/lbplanner/classes/helpers/course_helper.php b/lbplanner/classes/helpers/course_helper.php index 5e49aa06..2ee61718 100644 --- a/lbplanner/classes/helpers/course_helper.php +++ b/lbplanner/classes/helpers/course_helper.php @@ -88,6 +88,10 @@ public static function get_eduplanner_courses(bool $onlyenrolled): array { global $DB, $USER; $userid = $USER->id; + $sentryspan = sentry_helper::span_start(__FUNCTION__, ['onlyenrolled' => $onlyenrolled]); + + // TODO: rewrite this where it asks the DB for all lbp courses, and then for any mdlcourses that aren't in lbpc. + $lbptag = core_tag_tag::get_by_name(core_tag_collection::get_default(), self::EDUPLANNER_TAG, strictness:MUST_EXIST); $courseexpireseconds = config_helper::get_course_outdatedrange(); $courseexpiredate = (new DateTimeImmutable("{$courseexpireseconds} seconds ago"))->getTimestamp(); @@ -177,6 +181,9 @@ public static function get_eduplanner_courses(bool $onlyenrolled): array { $fetchedcourse->set_mdlcourse($mdlcourse); array_push($results, $fetchedcourse); } + + $sentryspan->setData(['count_out', count($results)]); + sentry_helper::span_end($sentryspan); return $results; } diff --git a/lbplanner/classes/helpers/modules_helper.php b/lbplanner/classes/helpers/modules_helper.php index f121918d..6660d6e6 100644 --- a/lbplanner/classes/helpers/modules_helper.php +++ b/lbplanner/classes/helpers/modules_helper.php @@ -207,6 +207,7 @@ public static function get_module_status(module $module, int $userid, ?int $plan public static function get_all_modules_by_course(int $courseid, bool $ekenabled): array { global $DB; + $sentryspan = sentry_helper::span_start(__FUNCTION__, ["ekenabled" => $ekenabled]); $cmodules = $DB->get_records( self::COURSE_MODULES_TABLE, [ @@ -227,6 +228,7 @@ public static function get_all_modules_by_course(int $courseid, bool $ekenabled) array_push($modules, $module); } + sentry_helper::span_end($sentryspan); return $modules; } } diff --git a/lbplanner/classes/helpers/plan_helper.php b/lbplanner/classes/helpers/plan_helper.php index 50919e75..fa82bb1f 100644 --- a/lbplanner/classes/helpers/plan_helper.php +++ b/lbplanner/classes/helpers/plan_helper.php @@ -146,6 +146,7 @@ public static function check_edit_permissions(int $planid, int $userid): bool { */ public static function get_deadlines(int $planid): array { global $DB; + $sentryspan = sentry_helper::span_start(__FUNCTION__); $dbdeadlines = $DB->get_records(self::DEADLINES_TABLE, ['planid' => $planid]); @@ -159,6 +160,7 @@ public static function get_deadlines(int $planid): array { ]; } + sentry_helper::span_end($sentryspan); return $deadlines; } diff --git a/lbplanner/classes/helpers/sentry_helper.php b/lbplanner/classes/helpers/sentry_helper.php index 70f01ba1..92f0ef28 100644 --- a/lbplanner/classes/helpers/sentry_helper.php +++ b/lbplanner/classes/helpers/sentry_helper.php @@ -39,12 +39,21 @@ class sentry_helper { */ private static array $spans = []; + /** + * This cache is needed because the value is read fairly often and determining it is somewhat expensive. + * @var bool $isenabled cache for enabled/disabled state of sentry reporting. + */ + private static ?bool $isenabled = null; + /** * Checks if moodle plugin is set to report exceptions to sentry * @return bool whether sentry is to be used */ public static function enabled(): bool { - return strlen(config_helper::get_sentry_dsn()) > 0; + if (self::$isenabled === null) { + self::$isenabled = strlen(config_helper::get_sentry_dsn()) > 0; + } + return self::$isenabled; } /** * Initializes the sentry library for future use. @@ -97,12 +106,16 @@ public static function transaction_end(?Transaction $transaction): void { /** * Does a bunch of setup for measuring span duration. * @param string $op the operation this span is for + * @param ?array $data an assocarr of data to record for this span, or null * @return ?Span the span that got started, or null if disabled */ - public static function span_start(string $op): ?Span { + public static function span_start(string $op, ?array $data = null): ?Span { if (self::enabled()) { $ctx = SpanContext::make() ->setOp($op); + if ($data !== null) { + $ctx = $ctx->setData($data); + } $parent = SentrySdk::getCurrentHub()->getSpan(); $span = $parent->startChild($ctx); self::$spans[(string)$span->getSpanId()] = $span; diff --git a/lbplanner/classes/helpers/slot_helper.php b/lbplanner/classes/helpers/slot_helper.php index d2f94a7c..8d77d0bc 100644 --- a/lbplanner/classes/helpers/slot_helper.php +++ b/lbplanner/classes/helpers/slot_helper.php @@ -97,6 +97,8 @@ class slot_helper { */ public static function get_all_slots(): array { global $DB; + $sentryspan = sentry_helper::span_start(__FUNCTION__); + $slots = $DB->get_records(self::TABLE_SLOTS, []); $slotsobj = []; @@ -104,6 +106,7 @@ public static function get_all_slots(): array { array_push($slotsobj, slot::from_db($slot)); } + sentry_helper::span_end($sentryspan); return $slotsobj; } @@ -117,6 +120,7 @@ public static function get_all_slots(): array { */ public static function get_vintage_time_slots(string $vintage, int $today, int $range): array { global $DB; + $sentryspan = sentry_helper::span_start(__FUNCTION__, ['range' => $range]); if ($range < 7) { $valid = []; @@ -140,6 +144,7 @@ public static function get_vintage_time_slots(string $vintage, int $today, int $ array_push($slotsobj, slot::from_db($slot)); } + sentry_helper::span_end($sentryspan); return $slotsobj; } @@ -151,6 +156,7 @@ public static function get_vintage_time_slots(string $vintage, int $today, int $ */ public static function get_supervisor_slots(int $supervisorid): array { global $DB; + $sentryspan = sentry_helper::span_start(__FUNCTION__); $slots = $DB->get_records_sql( 'SELECT slot.* FROM {' . self::TABLE_SLOTS . '} as slot ' . @@ -164,6 +170,7 @@ public static function get_supervisor_slots(int $supervisorid): array { array_push($slotsobj, slot::from_db($slot)); } + sentry_helper::span_end($sentryspan); return $slotsobj; } @@ -175,6 +182,8 @@ public static function get_supervisor_slots(int $supervisorid): array { */ public static function get_slots_by_room(string $room): array { global $DB; + $sentryspan = sentry_helper::span_start(__FUNCTION__); + $slots = $DB->get_records(self::TABLE_SLOTS, ['room' => $room]); $slotsobj = []; @@ -182,6 +191,7 @@ public static function get_slots_by_room(string $room): array { array_push($slotsobj, slot::from_db($slot)); } + sentry_helper::span_end($sentryspan); return $slotsobj; } @@ -271,6 +281,8 @@ public static function get_reservations_for_user(int $userid): array { * @return reservation[] reservations that pass */ public static function filter_reservations_for_recency(array $reservations): array { + $sentryspan = sentry_helper::span_start(__FUNCTION__, ['count_in' => count($reservations)]); + $goodeggs = []; foreach ($reservations as $reservation) { if (!$reservation->is_outdated()) { @@ -278,6 +290,8 @@ public static function filter_reservations_for_recency(array $reservations): arr } } + $sentryspan->setData(['count_out' => count($goodeggs)]); + sentry_helper::span_end($sentryspan); return $goodeggs; } @@ -308,6 +322,8 @@ public static function get_filters_for_slot(int $slotid): array { * @return slot[] the filtered slot array */ public static function filter_slots_for_user(array $allslots, \stdClass $user): array { + $sentryspan = sentry_helper::span_start(__FUNCTION__, ['count_in' => count($allslots)]); + $mycourses = course_helper::get_eduplanner_courses(true); $mycourseids = []; foreach ($mycourses as $course) { @@ -332,6 +348,9 @@ public static function filter_slots_for_user(array $allslots, \stdClass $user): break; } } + + $sentryspan->setData(['count_out' => count($slots)]); + sentry_helper::span_end($sentryspan); return $slots; } @@ -342,6 +361,8 @@ public static function filter_slots_for_user(array $allslots, \stdClass $user): * @return slot[] the filtered slot array */ public static function filter_slots_for_time(array $allslots, int $range): array { + $sentryspan = sentry_helper::span_start(__FUNCTION__, ['count_in' => count($allslots)]); + if ($range === 7) { return $allslots; } @@ -357,6 +378,9 @@ public static function filter_slots_for_time(array $allslots, int $range): array array_push($slots, $slot); } } + + $sentryspan->setData(['count_out' => count($slots)]); + sentry_helper::span_end($sentryspan); return $slots; } diff --git a/lbplanner/lib.php b/lbplanner/lib.php index a04b0fe3..74deb9d9 100644 --- a/lbplanner/lib.php +++ b/lbplanner/lib.php @@ -38,7 +38,7 @@ function local_lbplanner_override_webservice_execution(stdClass $externalfunctio // Actually calling the function (since we're overriding this part, duh). try { $callable = [$externalfunctioninfo->classname, $externalfunctioninfo->methodname]; - $transaction = sentry_helper::transaction_start(...$callable); + $transaction = sentry_helper::transaction_start(...$callable, $params); $result = call_user_func_array($callable, $params); sentry_helper::transaction_end($transaction); From 17b19648b0a398aa7bb6f73cd53f588f5d390b56 Mon Sep 17 00:00:00 2001 From: Riedler Date: Thu, 30 Oct 2025 02:36:55 +0100 Subject: [PATCH 19/27] fix: oops, syntax err at the very end --- lbplanner/lib.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbplanner/lib.php b/lbplanner/lib.php index 74deb9d9..0f62b059 100644 --- a/lbplanner/lib.php +++ b/lbplanner/lib.php @@ -38,7 +38,7 @@ function local_lbplanner_override_webservice_execution(stdClass $externalfunctio // Actually calling the function (since we're overriding this part, duh). try { $callable = [$externalfunctioninfo->classname, $externalfunctioninfo->methodname]; - $transaction = sentry_helper::transaction_start(...$callable, $params); + $transaction = sentry_helper::transaction_start($callable[0], $callable[1], $params); $result = call_user_func_array($callable, $params); sentry_helper::transaction_end($transaction); From 290fe54b8362407fbcfadb31687792af7ea77b7e Mon Sep 17 00:00:00 2001 From: Riedler Date: Sat, 1 Nov 2025 16:42:54 +0100 Subject: [PATCH 20/27] fix: remove duplicate & broken data from sentry call --- lbplanner/lib.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbplanner/lib.php b/lbplanner/lib.php index 0f62b059..07eaea0e 100644 --- a/lbplanner/lib.php +++ b/lbplanner/lib.php @@ -38,7 +38,7 @@ function local_lbplanner_override_webservice_execution(stdClass $externalfunctio // Actually calling the function (since we're overriding this part, duh). try { $callable = [$externalfunctioninfo->classname, $externalfunctioninfo->methodname]; - $transaction = sentry_helper::transaction_start($callable[0], $callable[1], $params); + $transaction = sentry_helper::transaction_start($callable[0], $callable[1]); $result = call_user_func_array($callable, $params); sentry_helper::transaction_end($transaction); From 0281035018f093b1ee59660154b990cd60d51641 Mon Sep 17 00:00:00 2001 From: Riedler Date: Sat, 1 Nov 2025 16:46:20 +0100 Subject: [PATCH 21/27] fix: assocarr instead of indarr in sentry setData --- lbplanner/classes/helpers/course_helper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbplanner/classes/helpers/course_helper.php b/lbplanner/classes/helpers/course_helper.php index 2ee61718..6cd9ca63 100644 --- a/lbplanner/classes/helpers/course_helper.php +++ b/lbplanner/classes/helpers/course_helper.php @@ -182,7 +182,7 @@ public static function get_eduplanner_courses(bool $onlyenrolled): array { array_push($results, $fetchedcourse); } - $sentryspan->setData(['count_out', count($results)]); + $sentryspan->setData(['count_out' => count($results)]); sentry_helper::span_end($sentryspan); return $results; } From e713104fe0ef41fa38a1e9d0c2e007892e51dd81 Mon Sep 17 00:00:00 2001 From: Riedler Date: Sat, 1 Nov 2025 16:46:53 +0100 Subject: [PATCH 22/27] chore: put in another span for perf tracking --- lbplanner/services/modules/get_all_modules.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lbplanner/services/modules/get_all_modules.php b/lbplanner/services/modules/get_all_modules.php index dd06e8d9..09de5d57 100644 --- a/lbplanner/services/modules/get_all_modules.php +++ b/lbplanner/services/modules/get_all_modules.php @@ -17,7 +17,7 @@ namespace local_lbplanner_services; use core_external\{external_api, external_function_parameters, external_multiple_structure, external_value}; -use local_lbplanner\helpers\{modules_helper, plan_helper, course_helper}; +use local_lbplanner\helpers\{modules_helper, plan_helper, course_helper, sentry_helper}; use local_lbplanner\model\module; /** @@ -71,7 +71,11 @@ public static function get_all_modules(bool $ekenabled): array { $modules ); } - return array_map(fn(module $m) => $m->prepare_for_api_personal($USER->id, $planid), $modules); + + $sentryspan = sentry_helper::span_start('prepare_module_array_for_api_personal', ['amount' => count($modules)]); + $result = array_map(fn(module $m) => $m->prepare_for_api_personal($USER->id, $planid), $modules); + sentry_helper::span_end($sentryspan); + return $result; } /** From fa6be266e4db1d71366fd459846a139bfe3e8e74 Mon Sep 17 00:00:00 2001 From: Riedler Date: Sat, 1 Nov 2025 19:00:34 +0100 Subject: [PATCH 23/27] chore: made more keys into global constants to protect from misspelling errors, and to have a semi-central place where all the keys are listed --- .../package-and-attach-to-release.yml | 2 +- lbplanner/classes/enums/ENVIRONMENT.php | 44 +++++++++++++++++++ lbplanner/classes/enums/SETTINGS.php | 20 ++++++++- lbplanner/classes/helpers/config_helper.php | 6 +-- lbplanner/classes/helpers/sentry_helper.php | 7 ++- lbplanner/version.php | 7 ++- 6 files changed, 77 insertions(+), 9 deletions(-) create mode 100644 lbplanner/classes/enums/ENVIRONMENT.php diff --git a/.github/workflows/package-and-attach-to-release.yml b/.github/workflows/package-and-attach-to-release.yml index 0f8efb78..64f7d90b 100644 --- a/.github/workflows/package-and-attach-to-release.yml +++ b/.github/workflows/package-and-attach-to-release.yml @@ -18,7 +18,7 @@ jobs: - name: Set environment to prod run: | - sed -i "s/set_config('sentry_environment', 'develop', 'local_lbplanner')/set_config('sentry_environment', 'production', 'local_lbplanner')/g" "$TARGET_FOLDER/version.php" + sed -i "s/set_config(CONFKEY::SENTRY_ENV, ENVIRONMENT::DEV, 'local_lbplanner')/set_config(CONFKEY::SENTRY_ENV, ENVIRONMENT::PROD, 'local_lbplanner')/g" "$TARGET_FOLDER/version.php" - name: Extract version id: version diff --git a/lbplanner/classes/enums/ENVIRONMENT.php b/lbplanner/classes/enums/ENVIRONMENT.php new file mode 100644 index 00000000..7eda5640 --- /dev/null +++ b/lbplanner/classes/enums/ENVIRONMENT.php @@ -0,0 +1,44 @@ +. + +/** + * software environments + * + * @package local_lbplanner + * @subpackage enums + * @copyright 2025 Pallasys + * @license https://creativecommons.org/licenses/by-nc-sa/4.0/ CC-BY-NC-SA 4.0 International or later + */ + +namespace local_lbplanner\enums; + +// TODO: revert to native enums once we migrate to php8. + +use local_lbplanner\polyfill\Enum; + +/** + * Software environments + */ +class ENVIRONMENT extends Enum { + /** + * Key for the production environment. + */ + const PROD = 'production'; + /** + * Key for the development environment. + */ + const DEV = 'development'; +} diff --git a/lbplanner/classes/enums/SETTINGS.php b/lbplanner/classes/enums/SETTINGS.php index fefa648f..26414a50 100644 --- a/lbplanner/classes/enums/SETTINGS.php +++ b/lbplanner/classes/enums/SETTINGS.php @@ -30,9 +30,19 @@ use local_lbplanner\polyfill\Enum; /** - * The keys for plugin settings + * The keys for plugin settings/configs */ class SETTINGS extends Enum { + /** + * Key for the release version. + * NOTE: This is a constant! Do not set outside version.php under ANY circumstances! + */ + const V_RELEASE = 'release'; + /** + * Key for the full version number in $plugin->version. + * NOTE: This is a constant! Do not set outside version.php under ANY circumstances! + */ + const V_FULLNUM = 'release_fullnum'; /** * Key for the setting for how many days into the future a student should be able to reserve a slot. */ @@ -45,4 +55,12 @@ class SETTINGS extends Enum { * Key for the setting for where sentry events should be sent to. */ const SENTRY_DSN = 'sentry_dsn'; + /** + * Key for the sentry environment. + */ + const SENTRY_ENV = 'sentry_environment'; + /** + * Key for the custom field category ID. + */ + const CF_CATID = 'categoryid'; } diff --git a/lbplanner/classes/helpers/config_helper.php b/lbplanner/classes/helpers/config_helper.php index 309397cf..b2b63343 100644 --- a/lbplanner/classes/helpers/config_helper.php +++ b/lbplanner/classes/helpers/config_helper.php @@ -43,7 +43,7 @@ public static function add_customfield(): void { $handler = mod_handler::create(); $categoryid = $handler->create_category('LB Planer'); - set_config('categoryid', $categoryid, 'local_lbplanner'); + set_config(SETTINGS::CF_CATID, $categoryid, 'local_lbplanner'); $categorycontroller = category_controller::create($categoryid, null, $handler); $categorycontroller->save(); @@ -77,7 +77,7 @@ public static function add_customfield(): void { public static function remove_customfield(): void { $handler = mod_handler::create(); $catid = self::get_category_id(); - unset_config('categoryid', 'local_lbplanner'); + unset_config(SETTINGS::CF_CATID, 'local_lbplanner'); if ($catid !== -1) { $catcontroller = category_controller::create($catid, null, $handler); $handler->delete_category($catcontroller); @@ -89,7 +89,7 @@ public static function remove_customfield(): void { * @return int the category id if it is set, -1 otherwise */ public static function get_category_id(): int { - $catid = get_config('local_lbplanner', 'categoryid'); + $catid = get_config('local_lbplanner', SETTINGS::CF_CATID); if ($catid === false) { return -1; } else { diff --git a/lbplanner/classes/helpers/sentry_helper.php b/lbplanner/classes/helpers/sentry_helper.php index 92f0ef28..4fa9567e 100644 --- a/lbplanner/classes/helpers/sentry_helper.php +++ b/lbplanner/classes/helpers/sentry_helper.php @@ -17,6 +17,7 @@ namespace local_lbplanner\helpers; use Throwable; +use local_lbplanner\enums\{ENVIRONMENT, SETTINGS}; use local_lbplanner\helpers\config_helper; use Sentry\SentrySdk; use Sentry\Tracing\{Span, SpanContext, Transaction, TransactionContext}; @@ -60,13 +61,15 @@ public static function enabled(): bool { */ public static function init(): void { if (self::enabled()) { + $env = get_config('local_lbplanner', SETTINGS::SENTRY_ENV); + $release = get_config('local_lbplanner', SETTINGS::V_RELEASE); $cfg = [ "dsn" => config_helper::get_sentry_dsn(), "enable_tracing" => true, "traces_sample_rate" => 0.2, "attach_stacktrace" => true, - "release" => 'lbplanner@' . get_config('local_lbplanner', 'release'), - "environment" => get_config('local_lbplanner', 'sentry_environment'), + "release" => 'lbplanner@' . $release, + "environment" => $env, ]; \Sentry\init($cfg); } diff --git a/lbplanner/version.php b/lbplanner/version.php index 8fb6d641..89f7924b 100644 --- a/lbplanner/version.php +++ b/lbplanner/version.php @@ -24,6 +24,8 @@ defined('MOODLE_INTERNAL') || die(); +use local_lbplanner\enums\{ENVIRONMENT, SETTINGS}; + $plugin->requires = 2024042200.00; // Require Moodle >=4.4.0. $plugin->maturity = MATURITY_BETA; $plugin->component = 'local_lbplanner'; @@ -34,5 +36,6 @@ 'local_modcustomfields' => 2023110600, ]; -set_config('release', $plugin->release, 'local_lbplanner'); -set_config('sentry_environment', 'develop', 'local_lbplanner'); // NOTE: gets set to 'production' by CI for release. +set_config(SETTINGS::V_RELEASE, $plugin->release, 'local_lbplanner'); +set_config(SETTINGS::V_FULLNUM, $plugin->version, 'local_lbplanner'); +set_config(SETTINGS::SENTRY_ENV, ENVIRONMENT::DEV, 'local_lbplanner'); // NOTE: gets set to 'production' by CI for release. From 07cd6aa45cee635dbf3edcaff7e49c98337b159a Mon Sep 17 00:00:00 2001 From: Riedler Date: Sat, 1 Nov 2025 19:02:00 +0100 Subject: [PATCH 24/27] feat: tell sentry FULL version number in dev env --- lbplanner/classes/helpers/sentry_helper.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lbplanner/classes/helpers/sentry_helper.php b/lbplanner/classes/helpers/sentry_helper.php index 4fa9567e..7419db55 100644 --- a/lbplanner/classes/helpers/sentry_helper.php +++ b/lbplanner/classes/helpers/sentry_helper.php @@ -63,6 +63,9 @@ public static function init(): void { if (self::enabled()) { $env = get_config('local_lbplanner', SETTINGS::SENTRY_ENV); $release = get_config('local_lbplanner', SETTINGS::V_RELEASE); + if ($env === ENVIRONMENT::DEV) { + $release .= '.' . get_config('local_lbplanner', SETTINGS::V_FULLNUM); + } $cfg = [ "dsn" => config_helper::get_sentry_dsn(), "enable_tracing" => true, From 48bbf973afcafd177f997f01d4f04a6565e33fbb Mon Sep 17 00:00:00 2001 From: Riedler Date: Thu, 6 Nov 2025 20:40:03 +0100 Subject: [PATCH 25/27] fix: set int./ext. paths for sentry --- lbplanner/classes/helpers/sentry_helper.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lbplanner/classes/helpers/sentry_helper.php b/lbplanner/classes/helpers/sentry_helper.php index 7419db55..59e0eac6 100644 --- a/lbplanner/classes/helpers/sentry_helper.php +++ b/lbplanner/classes/helpers/sentry_helper.php @@ -68,6 +68,8 @@ public static function init(): void { } $cfg = [ "dsn" => config_helper::get_sentry_dsn(), + 'in_app_include' => [realpath(__DIR__.'/../..')], + 'in_app_exclude' => [realpath(__DIR__.'/../../vendor')], "enable_tracing" => true, "traces_sample_rate" => 0.2, "attach_stacktrace" => true, From 420efcdf51ab235f0f358a64e27fbe7ba6ae1a3b Mon Sep 17 00:00:00 2001 From: Riedler Date: Thu, 6 Nov 2025 20:44:09 +0100 Subject: [PATCH 26/27] chore: appease code checker --- lbplanner/classes/helpers/sentry_helper.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lbplanner/classes/helpers/sentry_helper.php b/lbplanner/classes/helpers/sentry_helper.php index 59e0eac6..34173649 100644 --- a/lbplanner/classes/helpers/sentry_helper.php +++ b/lbplanner/classes/helpers/sentry_helper.php @@ -68,8 +68,8 @@ public static function init(): void { } $cfg = [ "dsn" => config_helper::get_sentry_dsn(), - 'in_app_include' => [realpath(__DIR__.'/../..')], - 'in_app_exclude' => [realpath(__DIR__.'/../../vendor')], + 'in_app_include' => [realpath(__DIR__ . '/../..')], + 'in_app_exclude' => [realpath(__DIR__ . '/../../vendor')], "enable_tracing" => true, "traces_sample_rate" => 0.2, "attach_stacktrace" => true, From 8b26322fb2c2753036b6adaf88f5ab66e58db7e7 Mon Sep 17 00:00:00 2001 From: Riedler Date: Mon, 10 Nov 2025 16:21:28 +0100 Subject: [PATCH 27/27] fix: exclude moodle from being marked "in-app" in sentry --- lbplanner/classes/helpers/sentry_helper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbplanner/classes/helpers/sentry_helper.php b/lbplanner/classes/helpers/sentry_helper.php index 34173649..a796b407 100644 --- a/lbplanner/classes/helpers/sentry_helper.php +++ b/lbplanner/classes/helpers/sentry_helper.php @@ -69,7 +69,7 @@ public static function init(): void { $cfg = [ "dsn" => config_helper::get_sentry_dsn(), 'in_app_include' => [realpath(__DIR__ . '/../..')], - 'in_app_exclude' => [realpath(__DIR__ . '/../../vendor')], + 'in_app_exclude' => [realpath(__DIR__ . '/../../vendor'), '/'], "enable_tracing" => true, "traces_sample_rate" => 0.2, "attach_stacktrace" => true,