Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
4455f27
Fix fatal error on installer when AT_CONTENT_DIR is undefined
akshyin-v2 Jul 1, 2026
6744774
Fix fatal error: get_magic_quotes_gpc() removed in PHP 7+
akshyin-v2 Jul 1, 2026
2268503
Document mbstring requirement and add install hint in installer
akshyin-v2 Jul 1, 2026
83986c1
Fix PHP 8 warnings during installation
akshyin-v2 Jul 1, 2026
6b9c165
Fix TypeError: count() on non-countable query result in PHP 8
akshyin-v2 Jul 1, 2026
b6428d9
Fix undefined constant MYSQL_ASSOC in queryDButf8 on PHP 8
akshyin-v2 Jul 1, 2026
a4b8d97
Fix TypeError: vsprintf() expects array, string given in queryDButf8
akshyin-v2 Jul 1, 2026
de84275
Fix empty database name causing mysqli_sql_exception during install
akshyin-v2 Jul 1, 2026
7dc6229
Prevent empty database name from causing fatal mysqli exception
akshyin-v2 Jul 1, 2026
144f50e
Fix null db_name in queryDButf8 when $db is not a global
akshyin-v2 Jul 1, 2026
33e137e
Fix undefined array key warning and MySQL strict mode date error
akshyin-v2 Jul 1, 2026
69eacec
Fix MySQL strict mode: last_login NULL rejected in members INSERT
akshyin-v2 Jul 1, 2026
8f09b34
Fix PHP 8 undefined array key and null warnings across installer
akshyin-v2 Jul 1, 2026
0136c19
Fix PHP 8 installer crashes: syntax errors, mysqli hangs, undefined v…
akshyin-v2 Jul 1, 2026
401694e
Fix PHP 8 fatal errors: undefined DEFAULT_CHARSET constant, curly bra…
akshyin-v2 Jul 1, 2026
2388f56
Fix PHP 8 runtime errors: undefined constants, missing isset guards, …
akshyin-v2 Jul 1, 2026
0ac5258
Fix PHP 8 fatal error and warnings in preferences/profile modules
akshyin-v2 Jul 1, 2026
7ab456e
Add modern Bootstrap 5 theme; bundle Bootstrap + Icons locally
akshyin-v2 Jul 1, 2026
8a2ed79
Fix Bootstrap/legacy collisions; restyle all content; make modern def…
akshyin-v2 Jul 1, 2026
ace6914
Merge branch 'modern-bootstrap-theme'
akshyin-v2 Jul 1, 2026
2e3f191
Modernize login page with Bootstrap 5 components
akshyin-v2 Jul 1, 2026
3ceb39f
Fix empty $_SESSION['lang'] causing all _AT() labels to render as [term]
akshyin-v2 Jul 1, 2026
888591f
Disable AT_DEVEL dev mode to stop leaking config/session to the page
akshyin-v2 Jul 1, 2026
03d7b1a
Enhance login page look and feel
akshyin-v2 Jul 1, 2026
2c17fde
Soften login page palette from blue/black to warm + sage
akshyin-v2 Jul 1, 2026
30074e1
Rebrand modern theme to the LearnerChoice (sky + orange) design system
akshyin-v2 Jul 1, 2026
8eb7bec
Bundle Inter font locally instead of loading from Google Fonts
akshyin-v2 Jul 1, 2026
296b7b2
Fix course creation failure on empty release_date/end_date
akshyin-v2 Jul 1, 2026
d5d736c
Allow NULL release_date/end_date on courses table
akshyin-v2 Jul 1, 2026
cafb612
Fix PHP 8 fatal in ContentManager::initContent() for empty courses
akshyin-v2 Jul 1, 2026
9afd420
Fix PHP 8 fatal: remove obsolete __autoload() fallback in gameme
akshyin-v2 Jul 1, 2026
5f7edb0
Fix PHP 8 named-param fatal in gameme DAO::toArrayObject()
akshyin-v2 Jul 1, 2026
f059dae
Fix gameme event-counter insert on PHP 8/strict MySQL
akshyin-v2 Jul 1, 2026
b3f6b01
Fix PHP 8 fatal + warnings blocking registration page
akshyin-v2 Jul 1, 2026
a873bec
Redesign registration page to match the LearnerChoice card system
akshyin-v2 Jul 1, 2026
1a8ec3c
Allow '0000-00-00' date sentinel by relaxing session sql_mode
akshyin-v2 Jul 2, 2026
9d6d854
Fix PHP 8 vsprintf ValueError from spurious DB_QUERY errors
akshyin-v2 Jul 2, 2026
49435c9
Fix 'last_login cannot be null' on member creation
akshyin-v2 Jul 2, 2026
9a6a0d6
Fix PHP 8 fatal from bareword constants in registration
akshyin-v2 Jul 2, 2026
43bccc2
PHP 8.3 compability issue
akshyin-v2 Jul 2, 2026
551e9ad
Fix PHP 8.3 warnings and fatal errors across registration, modules, a…
akshyin-v2 Jul 2, 2026
6e903a1
Fix security vulnerabilities: arbitrary file upload, path traversal, …
akshyin-v2 Jul 2, 2026
5d2d05a
Fix additional security issues: open redirect, path traversal, stored…
akshyin-v2 Jul 2, 2026
9893ccd
Fix CSRF bypass, open redirect, and stored XSS in footer templates
akshyin-v2 Jul 2, 2026
0b9e7ff
Fix reflected XSS in file manager forms
akshyin-v2 Jul 2, 2026
3107077
Fix NULL time in admin_log INSERT causing Column 'time' cannot be nul…
akshyin-v2 Jul 2, 2026
db6c788
Fix PHP 8 fatal errors: create_function, split, eregi
akshyin-v2 Jul 2, 2026
bc2d545
Add missing exit after header() redirects
akshyin-v2 Jul 2, 2026
ed5487c
Guard implode() against non-array input to prevent PHP 8 TypeError
akshyin-v2 Jul 2, 2026
8502082
Add is_uploaded_file() guard before file_get_contents on uploads
akshyin-v2 Jul 2, 2026
af0b74d
Guard set_magic_quotes_runtime() calls with function_exists()
akshyin-v2 Jul 2, 2026
50ae35f
Fix PHP 8 fatal: queryDB called with string instead of array
akshyin-v2 Jul 2, 2026
4cacc9b
Update documentation for PHP 8.3+ and MySQL strict mode compatibility
akshyin-v2 Jul 2, 2026
86a8495
Fix PHP 8 warnings in enrollment and mysql_connect
akshyin-v2 Jul 2, 2026
c95135b
Add is_array() guards to foreach loops over queryDB results
akshyin-v2 Jul 2, 2026
01094df
Fix AT_admin_log schema: add DEFAULT CURRENT_TIMESTAMP
akshyin-v2 Jul 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,27 @@ Two accessibility features in the system are text alternatives for all visual el

## Installation

### System Requirements

- **PHP 8.0+** (tested up to PHP 8.3) with the following extensions:
- `mysqli` **(required; the old `mysql` extension was removed in PHP 7)**
- `mbstring` **(required for upgrades and multilingual content)**
- `zlib`
- `gd` (for image processing)
- `openssl` (for HTTPS and mail)
- **MySQL 5.7+** or **MariaDB 10.2+**
- MySQL strict mode (`STRICT_TRANS_TABLES`, `NO_ZERO_DATE`) is supported as of this release.
- A web server such as Apache or Nginx

To install missing PHP extensions on Debian/Ubuntu:
```bash
sudo apt-get install php-mbstring php-mysqli php-zlib php-gd php-openssl
```

> **Note:** This release drops support for PHP 5.x/7.x. If you are upgrading from an older ATutor version, ensure your server is running PHP 8.0 or later.

### Installing ATutor

To install ATutor, point your web browser to the installation directory where ATutor was extracted, and follow the Installer's instructions.
e.g. http://localhost/ATutor

Expand Down Expand Up @@ -46,6 +67,13 @@ For additional ATutor documentation, see the ATutor Handbook in ATutor, or at th

- Users:
https://atutor.github.io/atutor/handbook/users.html

## Compatibility & Known Issues

- **PHP 8.3:** This release includes extensive PHP 8 compatibility fixes for removed functions (`create_function`, `split`, `eregi`, `each`, `set_magic_quotes_runtime`, etc.), strict typing errors, and MySQL strict mode.
- **MySQL Strict Mode:** Previously caused fatal errors on date columns and null defaults. Resolved by replacing bareword constants, using `NOW()` for timestamps, and guarding `NULL` inserts.
- **Third-party libraries:** Bundled libraries (nusoap, phpmailer, phpseclib, PEAR XML_HTMLSax) have been patched for PHP 8 survival. Some legacy libraries still emit deprecation warnings but are functional.
- **Security hardening:** Recent commits fixed reflected XSS, CSRF bypass, open redirect, path traversal, arbitrary file upload, stored XSS, SSRF/LFI, and missing auth checks.

## Developers

Expand Down
139 changes: 0 additions & 139 deletions admin/config_template.php

This file was deleted.

6 changes: 3 additions & 3 deletions admin/error_logging.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
}

if (is_dir($dir_ . '/' . $file)) {
$logdirs{$file} = $file; // store the day log dir
$logdirs[$file] = $file; // store the day log dir
}
}
closedir($dir); // clean it up
Expand Down Expand Up @@ -99,7 +99,7 @@
if (strpos($file, 'profile') !== false) { // found a profile, store its md5 key identifier
$tmp_ = substr($file, strpos($file, '_') + 1);
$tmp_ = substr($tmp_, 0, strpos($tmp_, '.log.php'));
$log_profiles{$file} = $tmp_;
$log_profiles[$file] = $tmp_;
}

}
Expand Down Expand Up @@ -142,7 +142,7 @@
closedir($dir);

// store the amount of bugs associated with profile
$log_profiles_bug_count{$val}[$val_] = $count;
$log_profiles_bug_count[$val][$val_] = $count;
}
$log_profiles = array();
}
Expand Down
10 changes: 5 additions & 5 deletions admin/error_logging_bundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
$date = substr($work, 0, strpos($work, ':'));
$id = substr($work, strpos($work, ':') + 1);
/* Parse the variable */
$profiles{$id} = $date;
$profiles[$id] = $date;
}
}

Expand Down Expand Up @@ -71,7 +71,7 @@

// any files mathcing the $elem key correspond to this profile
if (strpos($file, $elem) !== false) {
$store_some{$dir_ . '/'. $val . '/' . $file} = $file;
$store_some[$dir_ . '/'. $val . '/' . $file] = $file;
}

}
Expand Down Expand Up @@ -173,7 +173,7 @@
}

if (is_dir($dir_ . '/' . $file)) {
$logdirs{$file} = $file; // store the day log dir
$logdirs[$file] = $file; // store the day log dir
}
}
closedir($dir); // clean it up
Expand Down Expand Up @@ -206,7 +206,7 @@
if (strpos($file, 'profile') !== false) { // found a profile, store its md5 key identifier
$tmp_ = substr($file, strpos($file, '_') + 1);
$tmp_ = substr($tmp_, 0, strpos($tmp_, '.log.php'));
$log_profiles{$file} = $tmp_;
$log_profiles[$file] = $tmp_;
}

}
Expand Down Expand Up @@ -249,7 +249,7 @@
closedir($dir);

// store the amount of bugs associated with profile
$log_profiles_bug_count{$val}[$val_] = $count;
$log_profiles_bug_count[$val][$val_] = $count;
}
$log_profiles = array();
}
Expand Down
6 changes: 3 additions & 3 deletions admin/error_logging_details.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
}

if (strpos($file, $key) !== false) { // found a bug associated with our profile key
$delete_store{$file} = $file;
$delete_store[$file] = $file;
} else {
$cnt++;
}
Expand Down Expand Up @@ -115,7 +115,7 @@
}

if (strpos($file, $key) !== false) { // found a bug associated with our profile key
$log_profile_bugs{$file} = $file;
$log_profile_bugs[$file] = $file;
}

}
Expand Down Expand Up @@ -177,7 +177,7 @@
}

if (strpos($file, $key) !== false) { // found a bug associated with our profile key
$delete_store{$file} = $file;
$delete_store[$file] = $file;
}

}
Expand Down
17 changes: 12 additions & 5 deletions bounce.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,15 @@ function get_groups($course_id) {
}
} elseif (!empty($_REQUEST['p'])) {
//For search
//p is a relative path, check that. #4773
if (strpos($_REQUEST['p'], 'http') !== false) {
//if not relative, reset it.
$_REQUEST['p'] = "";
}
//p is a relative path, check that. #4773
if (preg_match('#^(\/\/|[a-zA-Z][a-zA-Z0-9+.-]*:)#', $_REQUEST['p'])) {
//if not relative, reset it.
$_REQUEST['p'] = "";
}
$page = urldecode($_REQUEST['p']);
if (preg_match('#^(\/\/|[a-zA-Z][a-zA-Z0-9+.-]*:)#', $page)) {
$page = "";
}
} elseif (($_config['pretty_url'] > 0) && preg_match('/bounce.php\?course=([\d]+)$/', $_SERVER['REQUEST_URI'])==1) {
//for browse, and my start page url rewrite.
$page = url_rewrite($_SERVER['REQUEST_URI'], AT_PRETTY_URL_NOT_HEADER, true).'/index.php'; //force overwrite
Expand Down Expand Up @@ -292,6 +295,8 @@ function get_groups($course_id) {
$owner_id = $row['member_id'];
$_SESSION['packaging'] = $row['content_packaging'];

error_log('[ATUTOR-BOUNCE-DIAG] entry course='.$course.' access='.$row['access'].' valid_user='.var_export($_SESSION['valid_user']??null,true).' member_id='.var_export($_SESSION['member_id']??null,true).' owner_id='.$owner_id.' release='.var_export($row['u_release_date'],true).' end='.var_export($row['u_end_date'],true).' pretty_url='.var_export($_config['pretty_url']??null,true));

$_SESSION['groups'] = array();
unset($_SESSION['fs_owner_type']);
unset($_SESSION['fs_owner_id']);
Expand Down Expand Up @@ -388,6 +393,7 @@ function get_groups($course_id) {

$_SESSION['groups'] = get_groups($course);

error_log('[ATUTOR-BOUNCE-DIAG] PUBLIC-final course_id='.var_export($_SESSION['course_id']??null,true).' is_admin='.var_export($_SESSION['is_admin']??null,true).' member_id='.var_export($_SESSION['member_id']??null,true).' dest='.$dest);
header('Location: '.$dest, TRUE, 301);
exit;

Expand Down Expand Up @@ -450,6 +456,7 @@ function get_groups($course_id) {
header('Location: ./'.$page.'?f='.$addslashes($_GET['f']));
exit;
} /* else */
error_log('[ATUTOR-BOUNCE-DIAG] PROTECTED-final course_id='.var_export($_SESSION['course_id']??null,true).' is_admin='.var_export($_SESSION['is_admin']??null,true).' member_id='.var_export($_SESSION['member_id']??null,true).' dest=./'.$page);
header('Location: ./'.$addslashes($page));
exit;

Expand Down
12 changes: 10 additions & 2 deletions content.php
Original file line number Diff line number Diff line change
Expand Up @@ -281,9 +281,17 @@
// Mauro Donadio
//

$fp = @file_get_contents($content_row['text']);
$fp = false;
// Restrict file_get_contents to valid paths within AT_CONTENT_DIR to prevent SSRF/LFI
if ($content_row['text'] && file_exists($content_row['text'])) {
$real_text_path = realpath($content_row['text']);
$content_dir_real = realpath(AT_CONTENT_DIR);
if ($real_text_path !== false && $content_dir_real !== false && strpos($real_text_path, $content_dir_real) === 0) {
$fp = @file_get_contents($content_row['text']);
}
}
// just for AContent content
if(strstr($fp, 'AContentXX')){
if($fp && strstr($fp, 'AContentXX')){

//$fp = str_ireplace('<head>','<MAURO>',$fp);
//<base href="http://www.w3schools.com/images/" target="_blank" />
Expand Down
Loading