Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
d538af3
Place logo and search components
frank-hee Mar 11, 2025
bfad80a
Merge branch 'develop/bespoke' into feature/TD-5276-update-nhs-header
paulstevendev Apr 30, 2025
9624415
TD-5276: Implement NHS header updates and untrack compiled assets
paulstevendev Jun 17, 2025
d0a5c47
TD-5276: Further refinements related to header updates
paulstevendev Jun 17, 2025
bf83abd
TD-5575: Add SCORM content inline with fullscreen option, and related…
paulstevendev Jun 17, 2025
c61a293
Merge pull request #18 from TechnologyEnhancedLearning/feature/TD-527…
paulstevendev Jun 17, 2025
89ca537
Update api call to use Moodle theme base URL
paulstevendev Jun 18, 2025
ddfdb6e
Merge pull request #19 from TechnologyEnhancedLearning/bugfix/FixAPIPath
paulstevendev Jun 18, 2025
31a96a2
Merge branch 'develop/bespoke' into feature/TD-5575-Display-SCORM-con…
paulstevendev Jun 18, 2025
6a75582
Merge pull request #20 from TechnologyEnhancedLearning/feature/TD-557…
paulstevendev Jun 19, 2025
63bbe02
Update logo link in header to point to main Learning Hub instance
paulstevendev Jun 19, 2025
f10cccc
Merge pull request #21 from TechnologyEnhancedLearning/bugfix/Correct…
paulstevendev Jun 19, 2025
7604f6a
Update footer links to use base url from theme settings
paulstevendev Jun 19, 2025
526fbc0
Merge pull request #22 from TechnologyEnhancedLearning/bugfix/make-fo…
paulstevendev Jun 19, 2025
5bd5e7a
Feature: update footer link functionality to use dynamic path correctly
paulstevendev Jul 8, 2025
cf91af9
Feature: Implement updates for logout process
paulstevendev Jul 8, 2025
7726254
Feature: update code to hide adapt exit button to include legacy
paulstevendev Jul 8, 2025
d3550ae
Hide enrolment icons
paulstevendev Jul 8, 2025
3cb2a11
Implement autosuggest functionality
paulstevendev Jul 17, 2025
0b5dd6a
Stop tracking generated build files
paulstevendev Jul 30, 2025
7225148
Merge pull request #23 from TechnologyEnhancedLearning/feature/TD-579…
paulstevendev Jul 30, 2025
4ff327f
Add amd/build/ to gitignore
paulstevendev Jul 30, 2025
93154ad
Merge branch 'feature/TD-5794-Add-Autosuggest-Functionality' into dev…
paulstevendev Jul 30, 2025
793ed16
Ensure SCORM content has background when full screen
paulstevendev Jul 30, 2025
e8b6a83
Merge pull request #24 from TechnologyEnhancedLearning/feature/TD-586…
paulstevendev Jul 30, 2025
8660b9e
Merge branch 'develop/bespoke' into feature/fix-footer-links
paulstevendev Jul 30, 2025
35aee8d
Merge pull request #25 from TechnologyEnhancedLearning/feature/fix-fo…
paulstevendev Jul 30, 2025
a823502
Remove redundant css
paulstevendev Jul 30, 2025
32c8c88
Merge branch 'develop/bespoke' into feature/update-adapt-exit-button-…
paulstevendev Jul 30, 2025
5d3b2aa
Merge pull request #26 from TechnologyEnhancedLearning/feature/update…
paulstevendev Jul 30, 2025
ea86204
Merge branch 'develop/bespoke' into feature/hide-enrolment-icons
paulstevendev Jul 30, 2025
e48c626
Merge branch 'develop/bespoke' into feature/hide-enrolment-icons
paulstevendev Jul 30, 2025
5c496bf
Merge pull request #27 from TechnologyEnhancedLearning/feature/hide-e…
paulstevendev Jul 30, 2025
9d5328b
Merge branch 'develop/bespoke' into feature/update-logout
paulstevendev Jul 30, 2025
c604194
Merge pull request #28 from TechnologyEnhancedLearning/feature/update…
paulstevendev Jul 30, 2025
ad8dd28
Update code to deal with change in response format
paulstevendev Aug 14, 2025
dc94729
Merge pull request #29 from TechnologyEnhancedLearning/feature/update…
paulstevendev Aug 14, 2025
266cf82
Fix alignment issue of breadcrumb and session title
paulstevendev Aug 14, 2025
5aca23d
Merge pull request #30 from TechnologyEnhancedLearning/feature/TD-580…
paulstevendev Aug 14, 2025
2b3810f
Refactor full screen functionality
paulstevendev Aug 21, 2025
3371e56
Merge pull request #31 from TechnologyEnhancedLearning/feature/TD-574…
paulstevendev Aug 22, 2025
fd0e181
Update to self enrolment page layout
frank-hee Aug 26, 2025
90cc5bc
Merge branch 'develop/bespoke' into feature/TD-5780-self-enrolment-pa…
frank-hee Sep 1, 2025
a813b10
Merge pull request #32 from TechnologyEnhancedLearning/feature/TD-578…
frank-hee Sep 1, 2025
fc16a75
hide activity icons
frank-hee Sep 5, 2025
00a02b4
override negative margin on mobile nav
frank-hee Sep 5, 2025
fcfac66
add navigation theme files and styling
frank-hee Sep 12, 2025
ba3f7c9
removed commented scss rules
frank-hee Sep 12, 2025
794ffca
removed commented mustache code
frank-hee Sep 12, 2025
e8a5682
Merge pull request #33 from TechnologyEnhancedLearning/feature/TD-580…
paulstevendev Sep 16, 2025
cb58a0e
fix merge conflict
frank-hee Sep 16, 2025
a9d941b
Merge branch 'develop/bespoke' into feature/TD-5733-mobile-title-spac…
paulstevendev Sep 16, 2025
7327584
Merge pull request #34 from TechnologyEnhancedLearning/feature/TD-573…
paulstevendev Sep 16, 2025
42abe02
Merge branch 'develop/bespoke' into feature/TD-5818-mobile-navigation…
paulstevendev Sep 16, 2025
9afc758
Merge pull request #35 from TechnologyEnhancedLearning/feature/TD-581…
paulstevendev Sep 16, 2025
4f74638
Add and hook up Admin URL theme property
paulstevendev Sep 19, 2025
8c1bb3c
Increase width of course page white area and restrict content
paulstevendev Sep 22, 2025
5bc12f2
Rename .github/workflows/pull_request_template.md to .github/pull_req…
paulstevendev Sep 23, 2025
1805a99
Update course section expander to nhsuk version
frank-hee Sep 23, 2025
2c2cf9a
Update to prevent side menu overlapping content on larger screen
paulstevendev Sep 23, 2025
ff1785b
indentation fix on summarytext
frank-hee Sep 23, 2025
f731dab
Merge pull request #36 from TechnologyEnhancedLearning/feature/TD-623…
paulstevendev Sep 24, 2025
b22c633
fix foxus state on expander headings
frank-hee Sep 24, 2025
414f4d9
fix on open expander focus colour
frank-hee Sep 24, 2025
6030397
Merge pull request #38 from TechnologyEnhancedLearning/feature/TD-573…
paulstevendev Sep 24, 2025
475b580
override boost focus styles with NHSUK styles
frank-hee Sep 24, 2025
6fb8df2
Merge pull request #39 from TechnologyEnhancedLearning/feature/TD-627…
paulstevendev Sep 24, 2025
78de9fa
Merge branch 'develop/bespoke' into feature/TD-5807-Content-area-seem…
frank-hee Sep 24, 2025
0fd78b9
Merge pull request #37 from TechnologyEnhancedLearning/feature/TD-580…
frank-hee Sep 24, 2025
619be09
Add admin url theme setting strings
paulstevendev Sep 29, 2025
5b94b87
Ensure admin link opens in a new window
paulstevendev Sep 29, 2025
c8869d2
Merge pull request #40 from TechnologyEnhancedLearning/feature/TD-628…
paulstevendev Sep 29, 2025
ce92970
Prevent unwanted horizontal scrolling of certified users page
paulstevendev Sep 29, 2025
1333647
Remove redundant scss
paulstevendev Sep 29, 2025
916b886
Merge pull request #41 from TechnologyEnhancedLearning/feature/TD-621…
paulstevendev Sep 29, 2025
ff83451
remove surplus course navigation
frank-hee Oct 3, 2025
52ae1d8
Merge pull request #42 from TechnologyEnhancedLearning/feature/TD-630…
paulstevendev Oct 6, 2025
79f6768
Use theme property for search action URL prefix
paulstevendev Oct 13, 2025
6f63af8
Merge pull request #43 from TechnologyEnhancedLearning/bugfix/TD-6340…
frank-hee Oct 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
node_modules/
vendor/
/.vs
/.vs

# Compiled CSS files
css/*.min.css
css/*.min.css.map

# Ignore the entire build folder
/amd/build/
111 changes: 111 additions & 0 deletions ajax/search_suggestions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php
require_once(__DIR__ . '/../../../config.php');
require_once($CFG->libdir . '/filelib.php');
require_login(); // Ensures full session context, like in core_renderer

header('Content-Type: application/json');

// Function to handle JSON error response and exit.
// This centralizes error handling and ensures consistent output.
function send_json_error($message, $status = 'error', $http_status_code = 500) {
error_log("theme_nhse: send_json_error - Status: " . $status . ", Message: " . $message);
http_response_code($http_status_code);
echo json_encode(['status' => $status, 'message' => $message]);
exit;
}

try {
global $USER, $DB;
$theme_identifier = 'theme_nhse';

// Get the .NET application base URL theme property
$dotnet_base_url = get_config($theme_identifier, 'dotnet_base_url');
// Ensure the .NET application base URL ends with a slash if it's not empty
if (!empty($dotnet_base_url) && substr($dotnet_base_url, -1) !== '/') {
$dotnet_base_url .= '/';
}

// Get the API Base URL theme property
$api_base_url = get_config($theme_identifier, 'api_base_url');
// Ensure the API base URL ends with a slash if it's not empty
if (empty($api_base_url)) {
send_json_error('API Base URL is not configured in theme settings.', 'error', 400);
}
if (substr($api_base_url, -1) !== '/') {
$api_base_url .= '/';
}

// Retrieve the OIDC token
$accesstoken = null;
$token_record = $DB->get_record('auth_oidc_token', ['username' => $USER->username]);
// IMPORTANT: Only access $token->token if $token is not false (i.e., a record was found)
if ($token_record) {
$accesstoken = $token_record->token; // Assuming the actual token is in the 'token' column
} else {
send_json_error(
'No OIDC token found for user.', // The message for the client and log
'error', // The status field in the JSON response
500 // The HTTP status code (e.g., 500 Internal Server Error)
);
exit; // Exit if no token is found and you want to indicate an error
}

} catch (Throwable $e) {
send_json_error(
'An unexpected error occurred: ' . $e->getMessage(),
'exception_error',
500
);
exit;
}

$searchterm = optional_param('query', '', PARAM_TEXT);

$api_endpoint_path = 'Search/GetAutoSuggestionResult/';
$url_for_search = $api_base_url . $api_endpoint_path . urlencode($searchterm);


try
{
$curl = new \curl();
// Set Authorization header
$options = [
'HTTPHEADER' => [
'Authorization: Bearer ' . $accesstoken,
'Accept: application/json'
]
];
$response = $curl->get($url_for_search, null, $options);
$result = json_decode($response, true);

if (json_last_error() !== JSON_ERROR_NONE) {
$json_error_msg = json_last_error_msg();
error_log("theme_nhse: JSON decoding error: " . $json_error_msg);
send_json_error(
'API response could not be decoded: ' . $json_error_msg,
'json_decode_error',
500 // Internal Server Error as the server received unparseable data
);
}

echo json_encode([
'status' => 'ok',
'test_url' => $url_for_search,
'accesstoken' => $accesstoken,
'api_raw_response' => $response, // Add the raw string response
'api_decoded_result' => $result // Add the decoded array/object response
]);
exit;


} catch (Exception $e) {
error_log("theme_nhse: CURL Exception caught for URL: " . $url_for_search . " Message: " . $e->getMessage()); // Use $url_for_search for clarity
send_json_error(
'Failed to connect to API: ' . $e->getMessage(), // Message for client and log
'curl_error', // Specific status for cURL issues
500 // HTTP 500 Internal Server Error for network/server-side issues
);
}



194 changes: 194 additions & 0 deletions amd/src/autosuggest.js

Large diffs are not rendered by default.

251 changes: 251 additions & 0 deletions classes/output/core_renderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,257 @@
*/
class core_renderer extends \theme_boost\output\core_renderer
{

// You can declare it explicitly for IDE clarity, but the parent usually does this.
// protected $page;

public function __construct(\moodle_page $page, $target) {
// This line is CRUCIAL. It calls the parent constructor,
// which sets up $this->page for the current renderer instance.
parent::__construct($page, $target);
}

/**
* Returns the data context for the global theme header, fetching from API.
* This method is called by {{# output.get_global_header_data }} in Mustache.
*
* @return \stdClass An object containing data for the header.
*/
public function get_global_header_data() {
global $PAGE; // Keep $PAGE just in case for future use/context, though not strictly needed for this API call
global $DB, $USER; // Declare $DB and $USER as global
global $SESSION;

$context = new \stdClass();
$context->customnavigation = []; // Default to empty array in case of API issues

// Fetch token for current user
$token = null; // Initialize to null
$tokenNew = null; // Initialize to null
$accesstoken = null; // Initialize to null
$response_content = false; // Initialize to false

// --- NEW: Get the configured .NET application base URL ---
$dotnet_base_url = get_config('theme_nhse', 'dotnet_base_url');

if (!empty($dotnet_base_url) && substr($dotnet_base_url, -1) !== '/') {
$dotnet_base_url .= '/';
}

// Add dotnet_base_url to the context for Mustache template ---
$context->dotnet_base_url = $dotnet_base_url;
error_log("DEBUG NHSE: get_global_header_data: dotnet_base_url = " . $context->dotnet_base_url); // ADD THIS LINE
// Add sesskey to the context for Mustache template ---
$context->sessionKey = sesskey();

// --- NEW: Get the configured API Base URL ---
$api_base_url = get_config('theme_nhse', 'api_base_url');
// Ensure the API base URL ends with a slash if it's not empty
if (!empty($api_base_url) && substr($api_base_url, -1) !== '/') {
$api_base_url .= '/';
}

if (empty($api_base_url)) {
// Log an error and return early if the API URL is not configured
error_log("theme_nhse: ERROR: LH OpenAPI Base URL is not configured in theme settings. Cannot fetch navigation data.");
return $context; // Return empty context if API URL is missing
}

// --- NEW: Add login status to the context ---
// isloggedin() is a Moodle core function that returns true if a user is logged in.
$context->is_user_logged_in = isloggedin();

// --- NEW: Add 'Site Administration' link if user is an admin ---
// Check if the user has the capability to configure the site (i.e., is an admin)
if (has_capability('moodle/site:config', \context_system::instance())) {
$admin_link = new \stdClass();
$admin_link->title = "Site administration";
$admin_link->url = new \moodle_url('/admin/search.php'); // Use the observed URL
$admin_link->hasnotification = false;
$admin_link->notificationcount = 0;
$admin_link->openInNewTab = false;

// Add this admin link to your customnavigation array
// This ensures it gets rendered by your {{#customnavigation}} block in Mustache
$context->customnavigation[] = $admin_link;
error_log("theme_nhse: Added Site Administration link to custom navigation.");
}


if (isloggedin()) { // Only try to fetch token if a user is logged in
$token = $DB->get_record('auth_oidc_token', ['username' => $USER->username]);
if ($token) {
error_log("theme_nhse: Found OIDC token for user {$USER->username}.");
// You would then use $token->accesstoken to construct your bearer token
$accesstoken = $token->token;
error_log("theme_nhse: accesstoken {$accesstoken}");

} else {
error_log("theme_nhse: No OIDC token found for user {$USER->username}.");
}
} else {
error_log("theme_nhse: User not logged in, skipping OIDC token fetch.");
}

// --- The rest of your existing API call logic ---
$api_endpoint_path = 'User/GetLHUserNavigation';
$url = $api_base_url . $api_endpoint_path;



try
{
$curl = new \curl();
// Set Authorization header
$options = [
'HTTPHEADER' => [
'Authorization: Bearer ' . $accesstoken,
'Accept: application/json'
]
];
$response = $curl->get($url, null, $options);
$result = json_decode($response, true);
// Log the raw response and the decoded result
error_log("theme_nhse: API Response (raw): " . $response);
error_log("theme_nhse: API Response (decoded): " . print_r($result, true));
// Check for JSON decoding errors
if (json_last_error() !== JSON_ERROR_NONE) {
error_log("theme_nhse: JSON decoding error: " . json_last_error_msg());
}
} catch (Exception $e)
{
debugging('CURL error: ' . $e->getMessage(), DEBUG_DEVELOPER);
error_log("theme_nhse: CURL Exception caught for URL: " . $url . " Message: " . $e->getMessage());
}

// --- Conditional Block for Processing Response (after try-catch) ---
// We use $result here, which will be null if there was an API error or JSON decoding issue.
if (is_array($result) && !empty($result)) {
// JSON decoded successfully and is an array with content
error_log("theme_nhse: JSON decoded successfully. Processing " . count($result) . " items.");
$processed_links = [];
foreach ($result as $item) {
// Check 'visible' property from API (only include if true or not set)
// Assuming 'visible' being absent or true means it should be shown

// --- NEW: Skip item if title is "Sign Out" ---
// We use trim() to handle any potential leading/trailing whitespace.
if (isset($item['title']) && trim($item['title']) === 'Sign Out') {
error_log("theme_nhse: Skipping 'Sign Out' link from API response.");
continue; // Skip to the next item in the loop
}


if (!isset($item['visible']) || $item['visible'] === true) {
$processed_item = new \stdClass();
$processed_item->title = $item['title'] ?? 'Untitled'; // Default title if missing
$processed_item->openInNewTab = $item['openInNewTab'] ?? false;
// Handle URLs - convert to moodle_url if internal, keep as string if external
if (isset($item['url']) && !empty($item['url'])) {
$item_url_path = $item['url']; // Store the raw URL from the API
// --- NEW: Override URL for 'Admin' link with theme setting ---
if (trim($processed_item->title) === 'Admin') {
$admin_url = get_config('theme_nhse', 'admin_url');
if (!empty($admin_url)) {
$processed_item->url = $admin_url;
$processed_item->openInNewTab = true;
error_log("theme_nhse: Overriding 'Admin' URL with theme setting: {$processed_item->url}");
} else {
// Fallback to original logic if theme setting is not configured
error_log("theme_nhse: Admin URL theme setting not found, falling back to API URL.");
}
// Check if it's an absolute URL (starts with http/s or //)
} else if (strpos($item_url_path, 'http') === 0 || strpos($item_url_path, '//') === 0) {
$processed_item->url = $item_url_path; // Use the absolute URL as is
error_log("theme_nhse: Processing absolute URL: {$processed_item->url}");
}
// If it's a relative URL AND we have a configured .NET base URL, prepend it
else if (!empty($dotnet_base_url)) {
// Prepend .NET base URL, ensuring no double slashes by trimming leading slash from item_url_path
$processed_item->url = $dotnet_base_url . ltrim($item_url_path, '/');
error_log("theme_nhse: Redirecting relative link '{$item_url_path}' to .NET domain: {$processed_item->url}");
}
// Fallback: If it's a relative URL but no .NET base URL is configured,
// treat it as a Moodle internal URL.
else {
$processed_item->url = new \moodle_url($item_url_path);
error_log("theme_nhse: .NET base URL not configured, processing relative link '{$item_url_path}' as Moodle internal.");
}
} else {
// Default to Moodle home if URL is missing or empty
$processed_item->url = new \moodle_url('/');
error_log("theme_nhse: Item has empty or missing URL, defaulting to Moodle home.");
}
$processed_item->hasnotification = $item['hasNotification'] ?? false;
$processed_item->notificationcount = $item['notificationCount'] ?? 0;

$processed_links[] = $processed_item;
} else {
// Log items that are not visible
error_log("theme_nhse: Item not visible and filtered out: " . ($item['title'] ?? 'N/A') . " (visible: " . ($item['visible'] ? 'true' : 'false') . ")");
}
}

// Add API processed links AFTER the static admin link, so it appears first if you want.
// If you want admin link to appear last, change this to array_unshift or prepend it.
$context->customnavigation = array_merge($context->customnavigation, $processed_links);
//$context->customnavigation = array_unshift($context->customnavigation, $processed_links);

error_log("theme_nhse: Processed links for display: " . print_r($context->customnavigation, true));


// Log the final processed links (for debugging)
error_log("theme_nhse: Processed links for display: " . print_r($processed_links, true));
} else {
// This block handles cases where:
// 1. $result is null (due to JSON decoding error or CURL exception)
// 2. $result is not an array (e.g., API returned a non-array JSON like a string or object)
// 3. $result is an empty array
$json_error_msg = (json_last_error() !== JSON_ERROR_NONE) ? json_last_error_msg() : 'N/A';
error_log("theme_nhse: Failed to process API response. Response was empty, not an array, or an error occurred. JSON Error: " . $json_error_msg);
error_log("theme_nhse: Raw API response (if available): " . ($response ?: 'No response content'));
}

// NEW: Require your autosuggest JavaScript module
$this->page->requires->js_call_amd('theme_nhse/autosuggest', 'init');

// You can remove this for now, or keep it, it won't hurt
// $this->page->requires->js_init_call('M.cfg.userid = ' . $USER->id . ';');


return $context;
}

public function get_footer_data(): \stdClass {
$context = new \stdClass();

$dotnet_base_url = get_config('theme_nhse', 'dotnet_base_url');
if (!empty($dotnet_base_url) && substr($dotnet_base_url, -1) !== '/') {
$dotnet_base_url .= '/';
}

$context->dotnet_base_url = $dotnet_base_url;

return $context;

}

public function standard_head_html() {
$output = parent::standard_head_html();

// Inject dotnet_base_url into M.cfg globally
$dotnet_base_url = get_config('theme_nhse', 'dotnet_base_url');

if (!empty($dotnet_base_url)) {
$output .= '<script>';
$output .= 'M.cfg.dotnet_base_url = ' . json_encode($dotnet_base_url) . ';';
$output .= '</script>';
}

return $output;
}


/**
* Wrapper for header elements.
*
Expand Down
26 changes: 0 additions & 26 deletions css/nhse.min.css

This file was deleted.

1 change: 0 additions & 1 deletion css/nhse.min.css.map

This file was deleted.

Loading