Upgrade Guide

IMPORTANT — password reset (14.0.0+). Starting with version 14.0.0 the password encryption algorithm changed and all passwords must be reset. The default password for admin is 1234 and its encoding is in install/db_data.sql. Create a new administrator (level 10) and delete the default admin user as soon as possible.

Always back up your database and files before upgrading. If this is a fresh install instead, see the Installation page.

Upgrading to the 17 release

This release modernises TCExam. In addition to the general process below, note the following one-time changes.

PHP and dependencies

  • PHP >= 8.2 is now required (tested on 8.2 / 8.3 / 8.4).

  • The previously bundled PHP libraries (TCPDF, PHPMailer, phpCAS, RADIUS) and the 27 MB fonts/ directory have been removed. The app now installs them via Composer and generates assets on demand:

    composer install     # installs vendor/, generates the PDF fonts (post-install hook)
    make lang            # (optional) pre-builds the translation caches
    

    Without composer install the application will not run.

  • Generated artifacts are no longer tracked in git: regenerate the PDF fonts with make fonts and the language caches with make lang (the language caches are also rebuilt lazily on first request).

  • A Dockerfile and docker-compose.yml are provided (make up) — see the Installation page.

Security-relevant changes

  • K_RANDOM_SECURITY. The shipped value is now a placeholder and the installer generates a unique random secret per install. The secret is used to generate session IDs, client fingerprints, CSRF tokens and SSO passwords. If shared/config/tce_general_constants.php still has the placeholder or the old hardcoded default ('mkTzxf8WwUxwvj6w'), set a fresh random value:

    php -r "echo bin2hex(random_bytes(32)), PHP_EOL;"
    
  • Register-globals emulation removed. The legacy block in shared/config/tce_config.php that auto-created $variables from posted form fields has been removed; controllers now read $_POST explicitly. Existing installs keep working unchanged whether or not their live shared/config/tce_config.php still contains that block. To gain the security benefit (closing the variable-injection vector), delete the “parse posted variables” foreach loop from your shared/config/tce_config.php, matching the shipped default.

See the Notes about security page for the full list of hardening changes.

Uploaded files, CSRF and backups

These changes apply from version 17.3.0.

  • Media uploads (File Manager, media selector, question/answer/subject attachments) reject markup and script types (svgz, xml, xsl, xslt, xht, xhtml, htm, html, shtml, mht, mhtml, js, mjs, swf) even when they are listed in K_ALLOWED_UPLOAD_EXTENSIONS. Server script types (php*, phtml, phar, cgi, pl, py, sh, …) are rejected for every upload. File content is checked, inner dots in file names become underscores and existing files are never overwritten. File Manager rename follows the same rules, including the content check for the new extension; new directory names are restricted to [A-Za-z0-9_-]. The File Manager still lists existing markup files so that they can be deleted.

  • SVG uploads accept only plain, static SVG files. 'svg' is in the default K_ALLOWED_UPLOAD_EXTENSIONS; add it to admin/config/tce_config.php to enable SVG uploads on an existing install. Each uploaded or renamed SVG file must be well-formed UTF-8 XML with an <svg> root element and contain only static graphics. The upload is rejected if the file contains any of the following:

    • scripts, event handler attributes (onload, onclick, …), <foreignObject>, links (<a>), animation elements, or SVG fonts;
    • references to anything other than an element of the same file (#id) or an embedded PNG, JPEG or GIF image (data:image/png;base64,...), including in CSS url(), @import, image(), image-set(), cross-fade() and src();
    • entity declarations, an internal DTD subset, or processing instructions such as <?xml-stylesheet?>;
    • editor-specific elements or attributes (Inkscape, Sodipodi, Adobe Illustrator, Sketch, …);
    • an encoding other than UTF-8 (declared, or detected such as UTF-16), or gzip compression (.svgz).

    Save drawings as plain SVG before uploading them:

    • Inkscape: File > Save a Copy… and choose Plain SVG or Optimized SVG.
    • Adobe Illustrator: File > Export > Export As… and choose SVG, with Styling: Presentation Attributes or Internal CSS, Font: Convert to Outlines and Images: Embed. Do not use Save As with Preserve Illustrator Editing Capabilities.
    • Other editors: remove editor metadata with an optimizer such as SVGO or Scour.

    Existing SVG files are not checked again and are still displayed through <img>.

  • Imports (users, questions, SSL certificates) are read from the PHP temporary upload directory and are no longer copied into cache/. Keep 'xml', 'tsv', 'csv', 'txt', 'pfx', 'pem' and 'crt' in K_ALLOWED_UPLOAD_EXTENSIONS as needed by the importers. A custom question importer (admin/code/tce_import_custom.php) receives the temporary file path, which has no file name extension.

  • Previews and [object] tags display images with <img>, audio with <audio>, video with <video> and every other type as a download link. Uploaded files are no longer embedded with <object>.

  • CSRF: every POST request with form data or files to a page that loads the form helpers must carry a valid csrf_token; otherwise the request stops with HTTP status 403 before the submitted data is processed and before the page is displayed.

  • Backups: creating, restoring and downloading a backup requires the current user’s password. After 5 wrong passwords in the same session the backup actions are refused for 15 minutes. Users signed in through an SSL client certificate, CAS or Shibboleth are not asked for a password, because TCExam stores a generated password for these accounts at every login. Users signed in through HTTP Basic, RADIUS or LDAP enter the password of that system.

  • cache/ server configuration. The shipped cache/.htaccess serves the directory as passive content: X-Content-Type-Options: nosniff, a Content-Security-Policy with sandbox, Content-Disposition: attachment for markup types, and no script execution. It requires mod_headers and AllowOverride AuthConfig FileInfo (or All). Keep this file when copying an old cache/ folder (step 8 below). For nginx, add the equivalent rules to the server block:

    location ^~ /cache/ {
        location ~* \.(php[0-9]?|pht|phtml|phar|phps|cgi|pl|py|sh|asp|aspx|jsp)(\.|$) { deny all; }
        location ~* \.(svgz?|xml|xslt?|xht|xhtml|s?html?|mht|mhtml|m?js|swf)$ {
            add_header Content-Disposition "attachment" always;
            add_header X-Content-Type-Options "nosniff" always;
            add_header Content-Security-Policy "default-src 'none'; sandbox" always;
        }
        add_header X-Content-Type-Options "nosniff" always;
        add_header Content-Security-Policy "default-src 'none'; img-src 'self' data:; media-src 'self'; style-src 'unsafe-inline'; sandbox" always;
    }
    

    Adjust the /cache/ prefix if TCExam is installed in a sub-path. cache/index.php only redirects to the public area, so denying it is harmless.

  • Review existing content. Files uploaded before this release are not checked again:

    1. List markup and script files in the cache and review or delete them:

      find cache/ -type f \( -iname '*.svg' -o -iname '*.svgz' -o -iname '*.xml' -o -iname '*.xsl*' \
          -o -iname '*.htm*' -o -iname '*.xht*' -o -iname '*.mht*' -o -iname '*.js' -o -iname '*.mjs' \
          -o -iname '*.swf' -o -iname '*.php*' -o -iname '*.phtml' \) -not -path 'cache/lang/*' \
          -not -name index.php
      
    2. List file and directory names that contain characters outside [A-Za-z0-9_.-]:

      find cache/ -name '*[!A-Za-z0-9_.-]*'
      
    3. Delete leftover import files (*.tsv, *.csv, *.txt, *.xml) from the cache/ root.

    4. Search question, answer and subject text for [object] tags that reference non-media files:

      SELECT question_id FROM tce_questions
       WHERE question_description LIKE '%[object]%' OR question_explanation LIKE '%[object]%';
      SELECT answer_id FROM tce_answers
       WHERE answer_description LIKE '%[object]%' OR answer_explanation LIKE '%[object]%';
      SELECT subject_id FROM tce_subjects WHERE subject_description LIKE '%[object]%';
      
    5. If level 5 accounts could upload svg or xml files before this release, check admin/backup/ for unexpected backups and consider rotating administrator passwords.

Access control and the administration area

These changes apply from version 17.4.0.

  • Page levels. A page that does not set $pagelevel now requires the administrator level. The results PDF, the test PDF generator and the user results XML/JSON export require the results or tests operator level. Pages meant for anonymous users set $pagelevel = 0; explicitly; do the same in any customised page, including admin/code/tce_export_custom.php and admin/code/tce_import_custom.php.

  • Emailed PDF reports are generated in-process. The ?email= token parameter of tce_pdf_results.php has been removed, and the email job no longer requests admin/code/tce_pdf_results.php over HTTP, so K_PATH_HOST no longer needs to be reachable from the server and K_FILE_ALLOWED_HOSTS is not needed for reports.

  • Session ID. The session ID is read only from the PHPSESSID cookie. Links carrying a PHPSESSID URL parameter no longer resume a session.

  • Operator scope. Operators (non-administrators) see and change only results of the tests they are authorized for, including the “all tests” view and the bulk lock, unlock, extend-time and delete actions. Subjects, questions and answers can be added or moved only under a module, subject or question the operator is authorized for, and only authorized subjects can be added to a test. OMR imports are accepted only for authorized tests, and only when every question and answer on the sheet belongs to that test.

  • Log and backup directories. New admin/log/.htaccess and public/log/.htaccess files deny web access to the error logs, and admin/backup/.htaccess now uses the Apache 2.4 syntax (Require all denied). Keep these files when copying old directories. For nginx, add:

    location ^~ /admin/backup/ { deny all; }
    location ^~ /admin/log/ { deny all; }
    location ^~ /public/log/ { deny all; }
    
  • Isolating /admin/. See Isolating the administration area for a separate admin host, network restrictions and web server examples. Set K_PATH_URL_ADMIN when the admin area is served from its own host.

Exam pages, LaTeX, OTP and installer

These changes apply from version 17.5.0.

  • Exam navigation. The exam page displays and stores answers only for test logs of the examinee’s active attempt. Answers of earlier (locked or repeated) attempts can no longer be changed.
  • LaTeX in examinee text. [tex] in free-text answers and test comments is shown as text and is not compiled. Delete cache/latex_*.png after upgrading to remove images generated from examinee text. pdflatex now runs with openin_any=p openout_any=p and -no-shell-escape, and formulas using \pdf... primitives, \scantokens, \immediate or \shipout are rejected. See LaTeX rendering.
  • Names and test names containing HTML entities are displayed literally on the online users page, the results pages, the module editor and the public test list.
  • One-Time-Password. With K_OTP_LOGIN enabled, the code is checked against the secret key of the user who logs in. Users without a key cannot log in; a new key is generated when an administrator sets a new password in the user editor.
  • Test IP ranges are checked against the address of the current request instead of the address recorded at login.
  • CSRF tokens have a new format. Reload forms that were open during the upgrade.
  • User editor. Editors below the administrator level can only edit their own account and users with a lower level.
  • Installer. install/install.php refuses to run when shared/config/tce_db_config.php exists and accepts the installation only from a POST request. install/phpinfo.php has been removed. The Docker image denies web access to install/. Delete the install/ directory after installation as before.
  • Removed admin/code/tce_update.php. Upgrade as described in the general upgrade process.
  • LDAP. The user name is escaped before it is inserted into K_LDAP_FILTER.
  • CAS. Set the new K_CAS_SERVER_CA_CERT in shared/config/tce_cas.php to the path of a PEM file with the CA certificate(s) of the CAS server to validate its certificate. If it is empty or not defined, the CAS server certificate is not validated.
  • SSL client certificate login. With K_SSL_ENABLED, users with a valid, unexpired client certificate are logged in and replicated into the TCExam database. Before this release only expired certificates matched, so this login method did not work.
  • Session ID at login. A new session ID is issued at login and the session used before login is deleted.

New configuration constants

Compare your config files against the shipped *.default files and add the new constants:

FileNew constants
shared/config/tce_paths.phpK_FILE_ALLOWED_PATHS, K_FILE_ALLOWED_HOSTS (allow-lists for tc-lib-file safe file/URL access), K_PATH_URL_ADMIN (base URL of the administration area)
shared/config/tce_cas.php (CAS only)K_CAS_SERVICE_BASE_URL, K_CAS_SERVER_CA_CERT
shared/config/tce_pdf.phpTCPDF-only options removed; tc-lib-pdf options added: K_PDF_UNICODE, K_PDF_SUBSET_FONT, K_PDF_COMPRESS, K_PDF_MODE, K_PDF_ALLOWED_PATHS / K_PDF_ALLOWED_HOSTS / K_PDF_MAX_REMOTE_SIZE
shared/config/tce_user_registration.phpK_USRREG_ALLOWED_GROUPS

The simplest approach is to start from the new *.default files and re-apply your previous customisations.

General upgrade process

This is the general upgrade process for any TCExam version.

  1. Back up your entire existing database and the TCExam folder.

  2. Upgrade the database schema. If the new main version number (Y) differs from your installed main version number (X), execute the matching SQL file for your engine:

    install/upgrade/mysql/mysql_db_upgrade_XtoY.sql
    install/upgrade/postgresql/postgresql_db_upgrade_XtoY.sql
    install/upgrade/oracle/oracle_db_upgrade_XtoY.sql
    
  3. Rename the current folder (e.g. /var/www/tcexam → /var/www/tcexam.old).

  4. Extract the new version into the same location where it was previously installed (e.g. /var/www/tcexam).

  5. Install the PHP dependencies and generate the assets:

    composer install
    make lang
    
  6. Delete the tcexam/install folder.

  7. Update the configuration files. Manually edit them to match your previous installation’s values and add the new constants listed above. The configuration files live in:

    admin/config/
    public/config/
    shared/config/
    
  8. Migrate cached multimedia content (images, etc.) from your old cache/ folder to the new one.

  9. Set the correct file permissions for your web server / PHP environment (example for Apache running as www-data, installation in /var/www/tcexam):

    cd /var/www/tcexam
    chown -R www-data:www-data .
    find . -type f -exec chmod 544 {} \;
    find . -type d -exec chmod 755 {} \;
    find cache/ -type d -exec chmod 775 {} \;
    find cache/ -type f -exec chmod 664 {} \;
    
  10. Restore custom translations. If you use custom language files, replace shared/config/lang/language_tmx.xml and rebuild the caches with make lang (or delete the cache/lang/*.php files).

  11. Verify that TCExam is working correctly.

  12. Remove the old installation (tcexam.old) after confirming the upgrade succeeded.