Skip to content

Tried claude to make t to run in latest version of PHP. #203

Open
akshyin-v2 wants to merge 56 commits into
atutor:masterfrom
akshyin-v2:master
Open

Tried claude to make t to run in latest version of PHP. #203
akshyin-v2 wants to merge 56 commits into
atutor:masterfrom
akshyin-v2:master

Conversation

@akshyin-v2

Copy link
Copy Markdown

statisfied with the improvement. Let me know if there is ok.

akshyin-v2 and others added 30 commits July 1, 2026 07:32
In PHP 8+, referencing an undefined constant is a fatal error.
During installation, config.inc.php does not yet exist, so AT_CONTENT_DIR
is undefined when constants.inc.php is loaded from not_installed.php.

Guard WORKSPACE_FILE_PATH and AT_BACKUP_DIR definitions with
defined('AT_CONTENT_DIR') checks to prevent the installer from crashing.
get_magic_quotes_gpc() was removed in PHP 7.0 and causes a fatal error in PHP 8.
Guard the call with function_exists() so the installer and runtime proceed
correctly on modern PHP versions where magic quotes no longer exist.
- Add a 'System Requirements' section to README.md listing PHP extensions
  including mbstring, which is strongly recommended and required for upgrades.
- Update the installer warning message to show the Debian/Ubuntu command
  for installing php-mbstring, making it easier for users to resolve.
- queryDButf8: give defaults to $sanitize and $db so optional params are
  not declared before required ones (deprecated in PHP 8).
- common.inc.php: guard $msg and $savant with isset() checks; fix undefined
  array key $_POST['old_path'].
- header.php: guard $step with isset() before comparison.
- index.php: prevent redefining MYSQLI_ENABLED by adding !defined() guard.
In sqlutility.class.php:200, queryDB() was called for a CREATE TABLE
statement with oneRow=false, returning a boolean/resource rather than an
array. count() on a non-array throws TypeError in PHP 8.

Replace count() > 0 with  !== false to correctly check
query success for DDL statements.
MYSQL_ASSOC was removed in PHP 8 along with the old mysql extension.
Change the default $array_type in queryDButf8() from MYSQL_ASSOC to
MYSQLI_ASSOC to match queryDB() and prevent a fatal Error.
In install.inc.php:462, queryDButf8() was called with $db_name (a string)
as the $params argument, but vsprintf() inside queryDButf8() requires an
array. Wrap $db_name in array() to match the correct call pattern used on
line 466.
Add validation in step2.php to ensure required database fields
(db_host, db_port, db_login, db_name) are not empty before calling
create_and_switch_db(). Prevents 'Incorrect database name' errors
when the form is submitted with missing values.
- Add an early guard in create_and_switch_db() to return a clear error when
  $db_name is empty instead of letting MySQL throw a fatal exception.
- Harden step2.php to only process database setup when $_POST['db_name']
  is actually present (not just when $_POST['submit'] exists). This prevents
  the installer from crashing when first loading step 2 after agreeing to terms.
In the installer, $db is passed as a local parameter, but $addslashes (set to
my_add_null_slashes) relies on global $db which is null in that context. This
causes real_escape_string to receive null, producing an empty string, which
makes vsprintf generate SHOW CREATE DATABASE  and MySQL rejects it.

In queryDButf8(), since we already have $db as a parameter, bypass the
$addslashes closure and call $db->real_escape_string() directly. This
removes the dependency on global state and fixes the installer crash.

Also removes temporary debug logging.
- install.php:22: ['new_version'] may not be set on Step 3, causing
  an undefined array key warning. Guard with isset() and provide fallback.

- install.inc.php:172: INSERT into members used '0000-00-00' as dob, which
  MySQL 5.7+ / MariaDB rejects under strict mode (NO_ZERO_DATE). Replace
  with '1900-01-01' so the account creation succeeds.
The installer's INSERT into %smembers set last_login to NULL, but the
schema defines last_login as TIMESTAMP NOT NULL. Under MySQL strict mode
this throws a 'Column cannot be null' error. Use NOW() instead of NULL
to match the admins INSERT and satisfy the NOT NULL constraint.
- install/index.php: Guard gd_info() JPG/JPEG Support keys with isset().
- install/install.php: Guard $_POST['step'] and $_POST['submit'] with isset().
- install/include/step2.php: Guard $_POST['db_login'] and $_POST['db_password']
  before htmlspecialchars() to avoid null deprecation warnings.
- install/include/common.inc.php: Guard $_POST['step'.] with isset() in
  print_hidden(), and add missing $_defaults['email'] key.
- install/include/step3.php: Guard $_POST['action'], $_POST['upgrade_action'],
  and $_POST['just_social'] with isset() checks.
…ariables, and fsockopen deadlocks

   - Fix stray backslashes before $ variables causing parse errors in install/index.php and install/include/step2.php
   - Add missing initialization and type guards for $svn_data in install/include/step6.php to prevent TypeError on count(null)
   - Fix fsockopen self-deadlock in install/include/step4.php when running PHP built-in single-threaded server
   - Fix MySQL TIMESTAMP NOT NULL without default in schema causing strict mode INSERT failures
   - Update mysqli connection logic and add diagnostics for install hangs
   - Fix undefined array key and null value warnings in installer forms
…ce string offsets, and time() argument count
- Fix fatal error: ATutor.setcookie() is not valid PHP; use setcookie()
- Fix undefined $last_login when auto-login is disabled
- Fix undefined array key users/profile.php in profile_pictures module

Co-Authored-By: Claude <noreply@anthropic.com>
Refactor the themes/modern/ template system to deliver a modern
Bootstrap-based look & feel, with all CSS/JS/fonts stored locally
(no CDN).

- Bundle Bootstrap 5.3.8 (css + js bundle w/ Popper) and Bootstrap
  Icons 1.11.3 (css + woff2/woff) under themes/modern/lib/ for a
  fully offline install.
- Rewrite header/footer/index .tmpl.php to use Bootstrap 5
  components (navbar, nav nav-underline, breadcrumb, nav-pills,
  card grid, container-fluid, form-select, btn, pagination, alert,
  badge, bi-* icons). All legacy element IDs/classes are preserved so
  ATutor.js, scroll-to-fixed and subnav-toggle keep working.
- Rewrite modern-overrides.css to compose on top of Bootstrap,
  rebinding --bs-primary to the modern palette and mapping legacy
  IDs to Bootstrap design tokens, with a responsive breakpoint.
- Register the 'modern' theme in the atutor_schema.sql themes seed
  (it existed only on disk and never appeared in the picker) and
  bump theme_info.xml to "Modern (Bootstrap 5)" v1.1.

Legacy jQuery 1.6.1 / Infusion scripts are intentionally kept loaded
alongside Bootstrap 5, because ATutor.js, jquery-ui, jquery.switch,
jquery.cookie, jquery-scrolltofixed and TinyMCE all depend on jQuery;
removing it would break the app. Bootstrap 5 itself is jQuery-free.

The 15 legacy themes are untouched.

Co-Authored-By: Claude <noreply@anthropic.com>
…ault

The enhanced interface was not showing because Bootstrap's global .row
grid rules were clobbering ATutor's ~500 legacy .row form rows (turning
them into flex grids with gutter padding), and modern-overrides.css was
loaded before forms.css so legacy form styles won.

Fixes:
- Load order: modern-overrides.css now loads LAST (after styles.css and
  forms.css) so it is authoritative.
- Neutralise Bootstrap's .row and .row > * in modern-overrides.css so
  legacy form rows keep their block layout.
- Replace Bootstrap row/col-* grid classes in header.tmpl.php and
  index.tmpl.php with non-colliding custom classes (.at-content-grid,
  .at-side-col, .at-main-col, .at-card-grid, .at-card-item) so the
  refactored layout no longer depends on the colliding .row.
- Comprehensively restyle every legacy content class in
  modern-overrides.css (.input-form cards, fieldset.group_form /
  legend.group_form, .formfield, .button/.buttons, .required,
  .contentbox/.headingbox, .data tables, .paginator, .sort, .box,
  .contentrow, social/profile/photo widgets, etc.) so all 221
  sub-templates get a cohesive modern Bootstrap-consistent look at once.
- Make the modern theme the default desktop theme (status 2) and demote
  the legacy default to enabled (status 1). Default remains the Savant
  inheritance fallback, so legacy sub-templates still resolve.

Templates render verified via a Savant2 harness (31 KB output, all
Bootstrap CSS/JS/Icons + custom grids + nav/breadcrumb/footer present,
no new template-level errors).

Co-Authored-By: Claude <noreply@anthropic.com>
Rewrite themes/modern/login.tmpl.php to use Bootstrap 5 (cards,
card-header/card-body, form-control, form-label, btn btn-primary /
btn-outline-primary, d-grid, d-flex responsive layout, Bootstrap
Icons) for a modern login look. All PHP logic preserved: sha1
password challenge (sha-1factory.js + encrypt_password + session
token), hidden form fields, AT_EMAIL_CONFIRMATION notice, and
registration gating. Added a missing isset() guard on $_GET['course']
(PHP 8 notice fix).

Add `global $_config;` in the template so the "New User" registration
card reliably renders -- Savant2 renders templates in method scope, so
the global $_config (built by vitals.inc.php) is not auto-visible and
the original if($_config['allow_registration']==1) evaluated against an
undefined var, hiding the card on the legacy login page.

Add a login-specific rule block to modern-overrides.css so the
fieldset.group_form inside the login cards does not render as a
nested box (it is meant for collapsible sections elsewhere).

Validated via a Savant2 render harness (production-realistic scope):
both cards render, all Bootstrap component classes present, sha1 JS
and session token flow through, hidden fields emitted, zero PHP
notices/warnings.

Co-Authored-By: Claude <noreply@anthropic.com>
The Language class used a PHP4-style constructor (function Language(...)).
PHP 8.3 no longer invokes methods named the same as the class as a
constructor, so every Language object was created with an empty $code.
getMyLanguage() then saved an empty code into $_SESSION['lang'], and _AT()
queried language_code="" -> no rows matched -> it fell back to printing
sprintf('[ %s ]', $term) for every label (login, register, returning_user,
AT_FEEDBACK_LOGOUT, etc.).

Rename the constructor to __construct() and update the one explicit caller
(LanguageEditor::__construct called $this->Language(); now parent::__construct()).

Co-Authored-By: Claude <noreply@anthropic.com>
AT_DEVEL was set to 1, which gates several page-rendered diagnostics:
the footer debug box (TIME USED, TABLE_PREFIX, DB_NAME, VERSION, SESSION:,
CONFIGURATION) that print_r's $_SESSION and $_config into the HTML, the
(term_name) suffixes appended to feedback messages, and raw SQL/DB errors
in failed-query messages. None of these gate required functionality.

Set AT_DEVEL to 0. Console error_log diagnostics are independent of AT_DEVEL
and remain intact.

Co-Authored-By: Claude <noreply@anthropic.com>
- Responsive two-card grid (side-by-side on md+, stacked on mobile)
- Gradient card headers with circular icon chips
- Input-group icons for login and password fields
- Password show/hide toggle (type=button so it never submits; sha1
  encrypt_password() flow unchanged)
- "Forgot your password?" link inline with the password label
- Course-context banner when ?course= is set
- Hover lift/shadow micro-interactions; registration button fills on hover
- Secure-login footer note with site name

All functional pieces preserved: encrypt_password() sha1 logic, hidden
fields (form_login_action, form_course_id, form_password_hidden, p),
field names, form action, autofocus via $onload.

Co-Authored-By: Claude <noreply@anthropic.com>
The default site chrome (near-black top bar, blue header gradient, blue
nav states) plus the blue login cards/buttons made the login page feel
heavy and high-contrast. Add an at-login-page body class (set in
header.tmpl.php when PHP_SELF is login.php) and scope a calmer palette
to it in modern-overrides.css:

- body background: soft warm cream-to-sage gradient
- top bar: muted dark sage-slate instead of near-black
- header bar + nav active states: muted sage-teal instead of blue
- login card primary header + button: sage-teal
- registration card header: warm sand with a sage icon chip
- borders, muted text, input icon chips recoloured to warm/sage tones

Scoped to the login page only; the rest of the site keeps its default
colours.

Co-Authored-By: Claude <noreply@anthropic.com>
Adapt the design tokens from freelearnerchoice.org and apply them to the
modern (default) theme. Because the theme is token-driven, remapping the
:root variables recolours the whole site at once.

Design tokens (themes/modern/modern-overrides.css :root):
- --at-primary:  #2563eb -> #0EA5E9 (sky)
- --at-accent:    #0ea5e9 -> #f97316 (warm orange CTA) + hover/light/rgb
- --at-dark:      #0f172a -> #0C4A6E (deep navy, softer than near-black)
- --at-bg:        #f4f6fb -> #F0F9FF (light sky)
- --at-text:      #1e293b -> #075985; new --at-text-head #0C4A6E
- radii 8/14px, new --at-font Inter stack

Chrome & components:
- Header band: signature deep-navy -> sky gradient (var(--at-dark) ->
  --at-dark-2 -> --at-primary), replacing the flat indigo->cyan.
- Top bar uses the new navy; jump menu recoloured to match.
- Headings navy + 700 + -0.01em; body uses Inter.
- Login primary card header sky gradient; registration card header
  warmed to a soft orange tint with an orange icon chip.

Inter font loaded via Google Fonts in header.tmpl.php (falls back to
system-ui offline). A new at-login-page body class is added on the login
page (basename(PHP_SELF) === 'login.php') for future scoping.

Warm-orange CTA on key primary actions (matches the reference site):
- New reusable .at-cta class (orange bg/border + glow, hover lift).
- Applied to: login submit, registration "login" + create-account "save",
  password reminder submit, password change submit.
- Default .btn-primary/.button stay sky (brand) so the LMS is not
  overwhelmed with orange; only the key CTAs are orange.

The earlier login-only "sage" palette experiment is removed in favour of
this global sky+orange system.

Co-Authored-By: Claude <noreply@anthropic.com>
The LearnerChoice rebrand (prev commit) added an external Google Fonts
request for Inter, breaking the modern theme's fully-offline property.
Replace it with a local bundle, matching how Bootstrap/Bootstrap-Icons
are already bundled.

- themes/modern/lib/inter/: 7 variable-font woff2 files (one per
  unicode-range subset — latin, latin-ext, cyrillic, cyrillic-ext, greek,
  greek-ext, vietnamese; weights 300-800; ~218 KB total) plus a generated
  inter.css with @font-face blocks preserving the per-subset unicode-ranges
  so all scripts render. Files downloaded from Google Fonts' gstatic CDN.
- header.tmpl.php: $_inter_url = $_theme_url.'/lib/inter'; load
  <link href="$_inter_url/inter.css"> instead of the three Google Fonts
  links (preconnect x2 + stylesheet). No external request remains.

Verified: render of login.php references the local inter.css, zero
fonts.googleapis.com/gstatic references in head, header.tmpl.php lints
clean, all woff2 files have valid 'wOF2' magic.

Co-Authored-By: Claude <noreply@anthropic.com>
queryDB() runs vsprintf(), which converts PHP NULL to an empty string,
so a missing release_date/end_date produced release_date='' — rejected
by MySQL strict mode for datetime columns ('Incorrect datetime value').
Build the column fragment directly so an unset date emits the unquoted
NULL keyword instead of an empty string.

Co-Authored-By: Claude <noreply@anthropic.com>
The application assigns NULL when an instructor leaves the release/end
date blank, but the schema declared both columns datetime NOT NULL with
no default. With the insert now correctly emitting the NULL keyword
(prior commit), MySQL rejected it with 'Column release_date cannot be
null' (sqlstate 23000). Make both columns nullable to match the app's
optional-date semantics.

Co-Authored-By: Claude <noreply@anthropic.com>
initContent() is called right after a new course is created (before the
default welcome page is added), when the content table has no rows for
the course yet. $_menu stays empty, so count($_menu[0]) becomes
count(null) — a fatal TypeError on PHP 8 (only a notice on PHP 7).
The same crash hits any empty course on every page load, since
vitals.inc.php constructs ContentManager and calls initContent() when a
course context is active. Skip the ordering walk and set safe defaults
when there is no content.

Co-Authored-By: Claude <noreply@anthropic.com>
akshyin-v2 and others added 26 commits July 1, 2026 20:10
PHP 8 removed the __autoload() magic function; defining it (even inside a
dead else-branch for PHP < 5.1.2) raises a compile-time fatal
'__autoload() is no longer supported, use spl_autoload_register() instead'
when Autoload.php is included, 500-ing every page. SPL autoloading is
available in all supported PHP versions, so drop the version check and
register unconditionally.

Co-Authored-By: Claude <noreply@anthropic.com>
ReflectionClass::newInstanceArgs() now treats associative-array string
keys as named constructor parameters (PHP 8.0+). The call passed
array('StdClass' => $data), but the model constructors declare $stdClass
(lowercase d), so PHP 8 raised 'Unknown named parameter $StdClass' and
500'd every page during module load. Pass $data positionally instead,
which matches the pre-PHP 8 behaviour (the string key was ignored then).

Co-Authored-By: Claude <noreply@anthropic.com>
Two issues in the gameme module, both surfaced when executeEvent() ran on
page load:

1. gm_user_events.points_counter (and event_counter) were int NOT NULL
   with no default. increaseEventCounter() inserts event_counter=1 but
   omits points_counter, so MySQL strict mode rejected the row with
   'Field points_counter doesn't have a default value' (errno 1364),
   500-ing the page. Add DEFAULT '0' to both counter columns
   (module.sql) and ALTER the live table.

2. PHP 8 emits an E_WARNING 'Undefined array key' for
   $this->eventsTriggers[$alias] when the event has no triggers; the
   following null check already handles that case, so use ?? null to
   silence the warning.

Co-Authored-By: Claude <noreply@anthropic.com>
- securimage.php: PHP 8 removed curly-brace string/array offset access
  ($this->code{$i}, $this->charset{...}, $code{$i}). This was a compile
  fatal that 500'd registration.php (CAPTCHA rendering). Use [] syntax.
- tile_search/module.php: $_config['transformable_uri'] is undefined
  when the Transformable service isn't configured; PHP 8 warns on the
  undefined array key during define(). Null-coalesce to ''.
- blogs/module.php: $_SESSION['is_admin'] may be unset; PHP 8 warns on
  the undefined key in the comparison. Null-coalesce to 0.

Co-Authored-By: Claude <noreply@anthropic.com>
Rebuild themes/modern/registration.tmpl.php with the same Bootstrap-5
card chrome as the recently-redesigned login page: an orange 'new user'
gradient header with an icon chip, grouped Required/Personal sections
shown as bordered fieldset cards, a 2-column CSS-grid field layout that
stacks on narrow viewports, input-group icon chips, a styled CAPTCHA
box (Bootstrap Icons replacing the old GIF links), and a right-aligned
action row (orange Save CTA + neutral Cancel). The returning-user login
form is restyled as a toggleable sky card.

Markup/CSS only — every form name, hidden input, field name/id, POST
echo, JS handler (encrypt_password, encrypt_password_login,
show_login_form), and conditional block (master list, admin account
status/picture, captcha, auto-enroll, logged-in confirm exit) is
preserved unchanged.

Add a scoped '8c. REGISTRATION PAGE' section to modern-overrides.css
reusing existing sky/orange tokens (no new palette values); rules are
namespaced under .at-register-* / .at-register-card.at-login-card so the
login page is unaffected.

Co-Authored-By: Claude <noreply@anthropic.com>
Registration (and profile create/edit) write dob='0000-00-00' when no date
of birth is supplied, but MySQL strict mode with NO_ZERO_DATE/NO_ZERO_IN_DATE
rejects it: 'Incorrect date value: 0000-00-00 for column dob'. The app's data
model uses '0000-00-00' as the 'no date' sentinel throughout — edit pages
read it back with explode('-', $row['dob']) and templates check
!= '0000-00-00' — so switching the column to NULL would require rewriting
~12 read/write/display sites and would surface PHP 8 explode(null) warnings.

Instead, align the session sql_mode with the app's existing design in the
single connection path (at_db_connect): drop only NO_ZERO_DATE and
NO_ZERO_IN_DATE, preserving every other server mode (notably
STRICT_TRANS_TABLES, which still catches genuinely empty values for NOT
NULL columns — the class fixed for courses.release_date/end_date). This
resolves dob and any other date column using the same sentinel, with no
per-file changes. Verified the adjusted mode accepts '0000-00-00' into a
date NOT NULL column and that the registration page still renders cleanly.

Co-Authored-By: Claude <noreply@anthropic.com>
execute_sql() raised a 'DB_QUERY' message (translation has 3 %s
placeholders) whenever a query returned a bool result with $oneRow=TRUE.
For a SUCCESSFUL INSERT/UPDATE/DELETE/REPLACE, $displayErrorMessage was
still just ['DB_QUERY', date] (1 argument) — the error/sql args are only
appended in the failure else-branch — so the queued message had 1 arg
vs. 3 placeholders. PHP 8's vsprintf throws ValueError for too few
arguments (PHP 7 silently substituted empty strings), fataling the page
when the message was later printed (e.g. on the next GET /registration.php).

- mysql_connect.inc.php: a bool/null result is a non-SELECT query; a
  failure (false/null) is already reported in the else-branch, and a
  success (true) must NOT raise a spurious 'unable to access the
  database' error. Drop the bogus addError for the oneRow+bool path.
- Message.class.php: pad the arguments array to the format's placeholder
  count before vsprintf, so any other translation/caller arg-count
  mismatch degrades gracefully instead of 500ing the page.

Co-Authored-By: Claude <noreply@anthropic.com>
members.last_login is TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, so
the explicit NULL inserted by registration (and admin create_user) was
rejected with 'Column last_login cannot be null'.

The app's 'never logged in' sentinel is the zero datetime, not NULL:
mods/_core/users/users.php filters never-logged-in users with
'last_login+0=0'. Insert '0000-00-00 00:00:00' instead, which (a) is
accepted now that the session sql_mode drops NO_ZERO_DATE and (b) keeps
last_login+0=0 so the admin filter still works. The non-confirmation
registration path already overwrites it with now() immediately after.

Co-Authored-By: Claude <noreply@anthropic.com>
PHP 8 made undefined constants a fatal Error (PHP 7 treated barewords like
LOGIN_SUCCESS_AUTO_ENROLL as the string itself). registration.php used
bareword message codes and bareword array keys outside of string
interpolation:

- array(AUTO_ENROLL_ALREADY_ENROLLED, ...) and array(LOGIN_SUCCESS_AUTO_ENROLL,
  ...) in addInfo/addFeedback (lines 43, 50, 335) — quote the message codes.
- $_POST[login] and $_SESSION[lang] outside strings (lines 341, 344) —
  quote the array keys. (Bareword keys inside the double-quoted INSERT SQL
  are fine; PHP interpolates them as strings.)

Co-Authored-By: Claude <noreply@anthropic.com>
…nd core

- registration.php: guard $_POST['gender'] with isset(); quote bareword
  $_config[pref_defaults] (PHP 8 fatal)
- auto_enroll_courses.inc.php: initialize $course_names and
  $course_registered_names outside the en_id block
- vital_funcs.inc.php: isset-guard $_SESSION['course_id'], 'is_admin',
  'privileges', 'cid_time', and 'member_id'
- login_functions.inc.php: null-coalesce $_POST['form_login']
- header.inc.php: isset-guard 'course_id' and 'is_admin' checks
- modern/header.tmpl.php: guard foreach over nav_courses when null
- 17 module files: isset-guard $_SESSION['is_admin'] before comparison
- helpme/module.php + helpme_js.php: initialize $helpme_total; guard
  'valid_user', 'course_id', 'member_id', and 'is_admin'
- gameme/module.php: guard 'valid_user'; early-return gamemeEnabled()
  when course_id is absent
- PhotoAlbum.class.php: initialize group_albums1, course_albums1,
  my_albums1, and rows_clean; isset-guard $_SESSION['groups']
  and 'member_id'

Co-Authored-By: Claude <noreply@anthropic.com>
…RCE, SQLi, SSRF/LFI, missing auth

- gameme/upload_level_icon.php, upload_badge.php: restrict uploads to images,
  validate with getimagesize(), use safe uniqid filenames, add auth check
- gameme/save_event.php, save_badge.php, save_level.php: add authenticate()
- get_course_icon.php: restore realpath-based path validation for custom/stock icons
- Module.class.php: replace eval() with call_user_func() to prevent RCE
- mysql_connect.inc.php: escape db_name in at_is_db() to prevent SQLi
- content.php: restrict file_get_contents to AT_CONTENT_DIR to prevent SSRF/LFI

Co-Authored-By: Claude <noreply@anthropic.com>
… XSS

- bounce.php: strengthen open-redirect validation to block protocol-relative
  URLs (//evil.com) and non-HTTP schemes (javascript:, data:, ftp:)
- accessibility.php: use sanitized  in unlink() and hidden input to prevent
  path traversal; also sanitize cid in hidden form field
- custom_logo.php: escape custom_logo_alt_text and custom_logo_url with
  htmlspecialchars() before outputting in HTML attributes to prevent stored XSS

Co-Authored-By: Claude <noreply@anthropic.com>
- login_functions.inc.php: remove attacker-controlled CSRF token override
  (POST['token'] could set the session token to any value, bypassing CSRF
  protection entirely). Token is now generated server-side only.
- login_functions.inc.php: validate redirect target after login to block
  absolute URLs and protocol-relative URLs (open redirect via POST['p']).
- All 11 theme footer templates: escape custom_logo_url and
  custom_logo_alt_text with htmlspecialchars() before echoing into HTML
  attributes, preventing stored XSS from database/config values.

Co-Authored-By: Claude <noreply@anthropic.com>
- zip.php: escape pathext, file, popup, framed GET parameters with
  htmlspecialchars/intval before echoing into hidden form fields
- new.php: escape pathext REQUEST parameter
- rename.php: escape pathext, oldname, new_name with htmlspecialchars;
  cast framed/popup to int
- preview_top.php: use ENT_QUOTES when escaping file name to ensure single
  quotes are also encoded in href attributes

Co-Authored-By: Claude <noreply@anthropic.com>
…l error

write_to_log() was inserting NULL for the time column, which is NOT NULL.
Replace with NOW() so admin login logging works again.

Co-Authored-By: Claude <noreply@anthropic.com>
- Replace create_function() with closures/anonymous functions
- Replace split() with preg_split()
- Replace eregi() with preg_match('/i')

Co-Authored-By: Claude <noreply@anthropic.com>
Prevent continued script execution after redirects in nusoap WSDL
handler and group manual creation.

Co-Authored-By: Claude <noreply@anthropic.com>
Add is_array() checks before implode() on  checkbox arrays.
Prevents fatal error when form fields are manipulated or omitted.

Co-Authored-By: Claude <noreply@anthropic.com>
Prevents LFI if an attacker manipulates the uploaded file tmp_name
through crafted multipart requests or extract() abuse.

Co-Authored-By: Claude <noreply@anthropic.com>
Prevents PHP 8 fatal errors in phpmailer, nusoap, and PEAR Config.

Co-Authored-By: Claude <noreply@anthropic.com>
get_group_concat() passed '' as params to queryDB, but vsprintf()
expects an array. Causes TypeError: vsprintf(): Argument atutor#2 must be
of type array, string given.

Co-Authored-By: Claude <noreply@anthropic.com>
README.md:
- Raise minimum PHP to 8.0+ (tested up to 8.3)
- Replace 'mysql' with 'mysqli' as the required extension
- Promote mbstring from recommended to required
- Add note about MySQL strict mode support
- Add Compatibility & Known Issues section

documentation/admin/requirements_recommendations.php:
- Update PHP requirement from 5.0.2 to 8.0+
- Mark mysqli as required and mysql as removed
- Mark mbstring as required
- Remove obsolete safe_mode, register_globals, magic_quotes_*
- Update MySQL requirement to 5.7+/MariaDB 10.2+

documentation/developer/guidelines.html:
- Update portable code minimum from PHP 5.0.2 to 8.0
- List removed functions (mysql_*, eregi, split, each, create_function)

Co-Authored-By: Claude <noreply@anthropic.com>
- enrollment.inc.php: guard undefined $_GET['reset_filter'], $_GET['search']
- enrollment.inc.php: guard undefined $_POST['view_select'] with null coalescing
- enrollment.inc.php: initialize $page_string before string concatenation
- mysql_connect.inc.php: guard foreach() with is_array($params) in both
  queryDButf8() and create_sql() to prevent foreach on string fatal

Co-Authored-By: Claude <noreply@anthropic.com>
Prevents PHP 8 fatal errors when queryDB returns a non-array
(e.g., affected_rows integer on error paths) by guarding foreach
loops in core include files.

Co-Authored-By: Claude <noreply@anthropic.com>
- Update atutor_schema.sql: time column now defaults to CURRENT_TIMESTAMP
- Add upgrade SQL atutor_upgrade_2.2.4_to_2.2.5.sql for existing installs
- Simplify write_to_log() to omit explicit NOW() and rely on DB default

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant