unlikelysource / filecms-website
File-based content management system. Does not require a database.
Package info
github.com/dbierer/filecms-website
Language:CSS
pkg:composer/unlikelysource/filecms-website
Requires
- php: >=8
- phpmailer/phpmailer: >=6.1
- tinymce/tinymce: >=8.0
- unlikelysource/filecms-core: >=0.3.17
README
CK Editor Replacement
2026-07-18: Important Update!!! The CK Editor system has been monetized and completely changed. We've decided to replace it with Tiny MCE, which is self-hosted here under its free/GPL community license (no CK Editor style monetization or cloud API key required).
If you have an existing site built on this framework, to switch over:
- From your project root (the directory containing
composer.json, and thesrcandtemplatesfolders), run:
./tinymce_upgrade_2026_07.sh
It backs up the affected files, adds the tinymce/tinymce composer package, copies its assets into public/tinymce, and downloads the updated templates/super/edit.phtml and src/upload.php.
2. In src/config/config.php, rename the SUPER => ckeditor key to tinymce (keep the existing width/height values). This is a manual step since config.php is typically customized per site.
3. If you had customized edit.phtml or upload.php, reapply your changes using the .bak files the script creates as a reference, then delete the .bak files.
No changes to unlikelysource/filecms-core are required for this upgrade -- everything needed to talk to Tiny MCE's image upload handler lives in this repo's src/upload.php.
Overview
Simple PHP framework that builds HTML files from HTML widgets.
- Includes a class that can generate and validate CAPTCHAs (uses the GD extension).
- Includes a self-hosted Tiny MCE (GPL edition) for full-featured editing.
- Includes an email contact form that uses PHPMailer.
- Is able to import single files from a legacy website, or can do bulk import
- Includes a complete set of transformation filters that can be applied during import, or afterwards
- Entirely file-based: does not require a database!
- Includes classes that let you use a CSV file just like a database
- Very fast and flexible.
- Once you've got it up and running, just upload HTML snippets and/or modify the configuration file.
- Works on PHP 8.0+
License: Apache v2
Website Installation
Automated Installation
To perform an automated installation, run the following command, where /path/to/website is the directory path to your new website:
- Install Composer (see https://getcomposer.org/doc/00-intro.md
- Run the following command:
composer create-project unlikelysource/filecms-website /path/to/website
This single command clones the repository, installs unlikelysource/filecms-core and its dependencies (PHPMailer, TinyMCE), and copies the TinyMCE assets into public/tinymce -- no further manual steps are required.
Manual Installation
- Clone the
filecms-websiterepository to the project root of your new website.
- If you have
gitinstalled run this command from a command prompt / terminal window:
git clone https://github.com/dbierer/filecms-website.git /path/to/website
- If you don't have
gitinstalled, just download the ZIP file and unzip into/path/to/website:
cd /path/to/website
wget https://github.com/dbierer/filecms-website/archive/refs/tags/0.2.5.zip
unzip 0.2.5.zip
- Use composer to install
unlikelysource/filecms-coreand dependencies (e.g. PHPMailer and TinyMCE)
wget https://getcomposer.org/download/latest-stable/composer.phar
php composer.phar self-update
php composer.phar install
composer install also copies the TinyMCE assets into public/tinymce automatically via the post-install-cmd script in composer.json -- no separate copy step is needed.
Basic website config
All references are from /path/to/website
- Primary config file:
/src/config/config.php - Bootstrap file:
/bootstrap.php - Pre-processing code:
/src/processing.php
Additional documentation on these three follows.
To Run Locally Using PHP
From this directory, run the following command:
cd /path/to/website
php -S localhost:8888 -t public
To Run Locally Using podman and podman-compose
Windows
Install Windows Subsystem for Linux Open a command prompt and go into WSL
wsl
If you have multiple Linux clients installed, go into the folder for that client
- See: https://learn.microsoft.com/en-us/windows/wsl/basic-commands From inside a WSL Linux guest, continue with Linux / Mac instructions.
Linux / Mac
Open a terminal window (Terminal Application)
- Install Podman:
- Install podman-compose:
- To bring the docker container online, run this command:
cd /path/to/website
./admin.sh up
- To stop the container do this:
./admin.sh down
- To open a command shell into the container:
./admin.sh shell
Browser Access
To access from your browser:
http://localhost:8888/
- Or, if your IP addressing is working:
http://10.10.10.10/
Bootstrap and Document Root
Set the website document root to /public
- The central point of entry is
/public/index.php - There is a file
.htaccessthat controls URL rewriting - If you are using nginx you will need to incorporate the same logic into your primary config file
/public/index.phpfirst loads the bootstrap file/bootstrap.php- This file defines three key constants used throughout the program (summarized in the table shown next)
- The bootstrap file also loads the Composer autoloader
- If you add your own classes under
/srcbe sure to updatecomposer.jsonand refresh Composer autoloading:
composer dump-autoload
Here is a summary of the three key constants defined by /bootstrap.php. Change as needed.
| Constant | Default | Description |
|---|---|---|
| BASE_DIR | Same directory as bootstrap.php |
Project root |
| HTML_DIR | /templates/site |
Location of HTML snippets |
| SRC_DIR | /src |
Location of source code |
Pre-Processing
Before the final HTML view is rendered, /public/index.php includes /src/processsing.php.
In this file you can include any pre-processing you need done.
- The request URL is available as the variable
$uri - This is where the admin URL (e.g.
/super) is captured and sent to processing
Templates
By default templates are stored in /templates/site. You can alter this in the config file.
Config File
Default: /src/config/config.php
- Delimiter:
DELIMdefaults to%% - "Cards"
CARDSdefaults tocards- Represents the subdirectory under which view renderer expects to file HTML "cards"
Layout
The overall website look-and-feel is in a single HTML file, by default in /templates/layout/layout.phtml.
- The view rendered by requests is injected into the layout by replacing
%%CONTENTS%%.
HTML
You can create HTML snippets designed to fit into layout.phtml any place in the designated HTML directory.
- Be sure to set the constant
HTML_DIRin the file/bootstrap.php.
Cards
Important: each %%CARD%% directive you add must be on its own line!
Auto-Populate All Cards
To get an HTML file to auto-populate with cards use this syntax:
DELIM+DIR+DELIM
Example: you have a subdirectory off HTML_DIR named projects and you want to load all HTML card files under the cards folder:
%%PROJECTS%%
Auto-Populate Specific Number of Cards
To only load a certain (random) number of cards, use =.
Example: you have a subdirectory off HTML_DIR named features and you want to load 3 random HTML card files under the cards folder:
%%FEATURES=3%%
Auto-Populate Specified Cards in a Certain Order
For each card, only use the base filename, no extension (i.e. do not add .html).
Example: you have a directory HTML_DIR/blog/cards with files one.html, two.html, three.html, etc.
You want the cards to be loaded in the order one.html, two.html, three.html, etc.:
%%BUNDLES=one,two,three,etc.%%
Editing Pages
By default, if you enter the URL /super/login you're prompted to login as a super user.
Configure the username, password and secondary authentication factors in: /src/config/config.php under the SUPER config key.
SUPER config key
Example configuration for super user:
// other config not shown
'SUPER' => [
'username' => 'REPL_SUPER_NAME', // fill in your username here
// use `get_password_hash.sh NEW_PASSWORD` to get the hashed value to store here:
'password' => '$2y$12$N57MR.2KWUMyNtdjrv7X4ejAl/5XgyPFIUH2TCbCLhbUxbSGIut9q', // hash for 'REPL_SUPER_PWD'
/*
* extra login validation fields
* change key/value pairs as desired
* add as many as you want
* they're selected at random when asked to login
*/
'validation' => [
// if value is array, authentication needs to use "in_array()"
'City' => ['London','Tokyo'],
'Postal Code' => 'NW1 6XE',
'Last Name' => ['Holmes','Lincoln'],
],
'alt_logins' => [
'REPL_OTHER_NAME' => [
'username' => 'REPL_OTHER_NAME', // fill in alt username here
// use `get_password_hash.sh NEW_PASSWORD` to get the hashed value to store here:
'password' => '$2y$12$ytOLGb9SRaFppla4MnExtuRFzhDDn0WitMD7AD4uMEqlT9fJpLuEa', // hash for 'REPL_OTHER_PWD'
],
// add others as needed
],
'attempts' => 3,
'message' => 'Sorry! Unable to login. Please contact your administrator',
// reserved for future use:
'allowed_ip' => ['10.0.0.0/24','192.168.0.0/24'],
// array of $_SERVER keys to store in session if authenticated
'profile' => ['REMOTE_ADDR','HTTP_ACCEPT_LANGUAGE'],
// change the values to reflect the names of fields in your login.phtml form
'login_fields' => [
'name' => 'name',
'password' => 'password',
'other' => 'other',
'phrase' => 'phrase', // CAPTCHA phrase
],
// only files with these extensions can be edited
'allowed_ext' => ['html','htm'],
'ckeditor' => [
'width' => '100%',
'height' => 400,
],
'super_url' => '/super', // IMPORTANT: needs to be a subdir off the "super_dir" setting
'super_dir' => BASE_DIR . '/templates', // IMPORTANT: needs to have a subdir === "super_url" setting
'super_menu' => BASE_DIR . '/templates/layout/super_menu.html',
'backup_dir' => BASE_DIR . '/backups',
'backup_cmd' => BASE_DIR . 'zip -r %%BACKUP_FN%% %%BACKUP_SRC%%',
],
// other config not shown
Here's a breakdown of the SUPER config keys
| Key | Explanation |
|---|---|
| username | Super user login name |
| password | Super user login password hash (using get_password_hash.sh) |
| attempts | Maximum number of failed login attempts. If this number is exceeded, a random third authentication field is required for login. |
| validation | Set of key:value pairs randomly selected each time you login. Values can be in the form of an array. |
| alt_logins | Additional usernames and password hashes |
| message | Message that displayed if login fails |
| profile | Array of $_SERVER keys that form the super user's profile once logged in |
| login_fields | Field names drawn from your login.phtml login form |
| validation | You can specify as many of these as you want. If the login attemp exceeds attempts, the SimpleHtml framework will automatically add a random field drawn from this list. |
| allowed_ext | Only files with an extension on this list can be edited. |
| ckeditor | Default width and height of the CKeditor screen |
| super_* | Settings pertaining to the location of the super admin user URL, templates and menu |
Contact Form
The skeleton app includes under /templates a file contact.phtml that implements an email contact form with a CAPTCHA
- Uses the PHPMailer package
- Configuration can be done in
/src/config/config.phpusing theCOMPANY_EMAILkey - CAPTCHA configuration can be done in
/src/config/config.phpusing theCAPTCHAkey - The same CAPTCHA is also used to protect
/super/login
CAPTCHA
FileCMS\Common\Image\Captcha::writeImages() renders the whole phrase as a single distorted image rather than one clean image per character. Rendering each character separately hands an automated reader its segmentation step for free -- it doesn't even need to figure out where one character ends and the next begins. Instead:
- Each character is drawn directly onto one shared canvas, using a randomly chosen font (from
font_files), size, rotation and baseline - Characters are placed with overlapping ("negative kerning") spacing, so adjacent glyphs touch or overlap
- Background/foreground noise (lines and dots) is colored close to the text's own color range instead of fully random, so it can't be stripped out by simple color thresholding
- The finished image is warped with a 2D wave distortion (
FileCMS\Common\Image\Strategy\Wave)
Example configuration:
'CAPTCHA' => [
'input_tag_name' => 'phrase',
'sess_hash_key' => 'hash',
'font_file' => SRC_DIR . '/fonts/FreeSansBold.ttf',
'font_files' => [
SRC_DIR . '/fonts/FreeSansBold.ttf',
SRC_DIR . '/fonts/FreeSansBoldOblique.ttf',
SRC_DIR . '/fonts/FreeSerifBold.ttf',
SRC_DIR . '/fonts/FreeSerifBoldItalic.ttf',
SRC_DIR . '/fonts/FreeMonoBold.ttf',
SRC_DIR . '/fonts/FreeMonoBoldOblique.ttf',
],
'img_dir' => BASE_DIR . '/public/img/captcha',
'num_bytes' => 3, // each byte == 2 characters
'rotate_min' => -40, // degrees, per character
'rotate_max' => 40,
// pixels shaved off each character's advance so adjacent glyphs
// touch/overlap -- denies clean per-character segmentation
'overlap_min' => 9,
'overlap_max' => 17,
'line_min' => 20, // count of background noise lines
'line_max' => 40,
'dot_min' => 40, // count of foreground noise dots
'dot_max' => 70,
// amplitude (pixels) of the 2D wave distortion applied to the
// finished image
'wave_x_amplitude' => 2,
'wave_y_amplitude' => 1,
],
Here's a breakdown of the CAPTCHA config keys
| Key | Explanation |
|---|---|
| input_tag_name | Name of the $_POST field expected to hold the phrase the user typed in |
| sess_hash_key | Name of the $_SESSION key holding the password_hash() of the correct phrase |
| font_file | Fallback font, used if font_files is empty |
| font_files | Pool of fonts randomized per character; more variety makes it harder for an OCR model trained on a single font |
| img_dir | Directory the generated CAPTCHA PNG is written to (must be web-accessible) |
| num_bytes | CAPTCHA phrase length is num_bytes * 2 hex characters |
| rotate_min / rotate_max | Degrees of random rotation applied to each character |
| overlap_min / overlap_max | Pixels shaved off each character's horizontal advance so adjacent glyphs touch/overlap |
| line_min / line_max | Range for the random count of background noise lines |
| dot_min / dot_max | Range for the random count of foreground noise dots |
| wave_x_amplitude / wave_y_amplitude | Max pixel displacement of the horizontal/vertical wave distortion applied to the finished image |
Import Feature
You can enable the import feature by setting the IMPORT::enable config key to TRUE.
The importer itself is at /templates/site/super/import.phtml.
Selected transformation filters can be applied to one or more pages during the import process.
Here are some notes on config file settings under the IMPORT config key:
IMPORT::enable- Set this value to
FALSEif you do not wish this feature to be available.
- Set this value to
IMPORT::delim_start- tells the importer where to start cutting out content from the HTML source
- default: <body>
IMPORT::delim_stop- tells the importer where to stop cutting out content from the HTML source
- default: </body>
IMPORT::trusted_src- list of one or more prefixes from "trusted" sources for import
- allows you to limit where imports can be taken from
- in case you get hacked, this prevents attackers from importing malicious from their own sites
IMPORT::import_file_field- this file must be in JSON format
- name of the file upload field used in the form
- you can upload a list of URLs to import followed by a list of transforms to apply
- the URLs key is 'URLS'
- the 'IMPORT' key lets you override any Import configuration including the transforms to apply during import
IMPORT::transform- sub-array of transforms to make available to the importer
callback: anything that's callable- if your own PHP function or anonymous function, signature must match
SimpleHtml\Transform\TransformInterface
- if your own PHP function or anonymous function, signature must match
params: array of parameters the callback expectsdescription: shows up when you run/super/importAfter logging in as the admin user, go to/super/import.
Transform Feature
You can apply transformation filters on existing pages.
The importer itself is at /templates/site/super/import.phtml.
Included transformation classes are located in /src/Transform.
You can add your own by simply extending FileCMS\Common\Transform\Base.
After logging in as the admin user, go to /super/transform.
Here are some notes on config file settings under the TRANSFORM config key:
TRANSFORM::enable- Set this value to
FALSEif you do not wish this feature to be available.
- Set this value to
TRANSFORM::backup_dir- Directory where backups will be placed prior to transformation
TRANSFORM::transform_dir- Directory where transform classes are found
TRANSFORM::transform_file_field- Name of the form field that is used if you want to upload a set of transforms
Clicks
A class FileCMS\Common\Stats\Clicks was added as of version 0.2.1.
Records the following information into a CSV file:
- URL
- Date
- Time
- IP address
- Referrer
- 1
The "1" can be used in a spreadsheet to create totals by any of the other fields.
After logging in as the admin user, go to
/super/clicks.
Statistical Methods
The following methods are available for your use:
Clicks::get(string $click_fn) : array
Returns an array keyed and sorted by URL, with hit grand totals.
Clicks::get_by_page_by_day(string $click_fn) : array
Returns an array keyed and sorted by URL + Y-m-d, with hit totals for each day
Clicks::get_by_path(string $click_fn, string $path) : array
Returns the same as get_by_page_by_day() except that it filters results based on $path.
Use this to return stats on URLs such as /practice/dr_tom/.
CSV
You can use a CSV file just like a database using either the FileCMS\Common\Data\Csv or FileCMS\Common\Data\BigCsv classes
FileCMS\Common\Data\Csv- Use this for small CSV files (less than 10 MB in size)
- Fast performance
- Uses a lot of memory
FileCMS\Common\Data\BigCsv- Use this for large CSV files (greater than 10 MB in size)
- Moderate performance
- Uses very little memory
- If your CSV file is greater than 10 MB in size use this class instead of
FileCMS\Common\Data\Csv
public function getItemsFromCsv($key_field = NULL) : array
- Gets list of items from CSV
- @param string|array $key_field : header(s) to use as key; leave blank for numeric array
- @return array $select :
[key => value]; key === practice_key; value = $row
public function writeRowToCsv(array $post, array $csv_fields = []) : bool
- Writes row to CSV
- @param array $post : normally sanitized $_POST
- @param array $csv_fields : array of CSV headers; leave blank if headers not used
- @return bool : TRUE if entry made OK
public function findItemInCSV(string $search, bool $case = FALSE, bool $first = TRUE) : array
- Finds key in CSV file
- Assumes first row is headers unless $first === FALSE
- Stores contents of CSV file in $this->lines
- If found, sets $this->pos to the line number of the row found in $this->lines
- @param string $search : any value that might be in the CSV file
- @param bool $case : TRUE: case sensitive; FALSE:
[default]case insensitive search - @param bool $first_row : TRUE
[default]: first row is headers; FALSE: first row is data - @return array
public function updateRowInCsv(string $search, array $data, array $csv_fields = [], bool $case = FALSE) : bool
- Updates row in CSV file
- If you don't supply $csv_fields, assumes no headers
- If no headers, update does delete and then insert
- @param string $search : any value that might be in the CSV file
- @param array $data : array of items to update
- @param array $csv_fields : array of fields names; leave blank if you don't use headers
- @param bool $case : TRUE: case sensitive; FALSE:
[default]case insensitive search - @return bool : TRUE if entry made OK
FileCMS Change Log
tag: v0.2.2 / v0.2.3
- 2022-04-22 DB: Finished testing modifications to Profile
- 2022-04-18 DB: Updated tests
- 2022-02-17 DB: Added option to prevent %%CARDS%% tags from being overwritten + implemented messages marker replacement for static HTML pages + expanded tests
tag: v0.2.1
- 2022-02-16 DB: Updated tests + removed user key from Common\Security\Profile
tag: v0.2.2
- 2022-02-13 DB: Fixed bug whereby you can never login
tag: v0.2.4
2022-05-12 DB:
- Created
Email::trustedSend()that allows you to directly call the core email send function - Refactored
Email::confirmAndSend()to calltrustedSend() - Added
$debugoption to facilitate testing and debugging- If set
TRUEthe email is not actually sent, and aPHPMailerinstance is set toEmail::$phpMailer
- If set
- Added
FileCMSTest\Common\Contact\EmailTesttest class
tag: v0.2.5
FileCMS\Common\Contact\Email::trustedSend()- Fixed bug whereby you were only allowed to send a string to
$ccand$bcc - These inputs now allowed
mixedtypes (expected string|array)
- Fixed bug whereby you were only allowed to send a string to
- Updated
FileCMSTest\Common\Contact\EmailTestandFileCMSTest\Common\Security\ProfileTest FileCMS\Common\Import\Import- Added message if URL not found
- Wrapped
file_get_contents($url)call intry/catchto prevent expected errors from messing up test results
tag: v0.2.6
FileCMS\Common\Contact\Email::trustedSend()- Fixed bug whereby PHPMailer was always set to SMTP regardless of config settings
- Updated
FileCMSTest\Common\Contact\EmailTest- Added tests to see if PHPMailer instance is set to "smtp" or "mail"
tag: v0.2.8
FileCMS\Common\Contact\Email::confirmAndSend()- Removed CAPTCHA verification logic and put into new
AntiSpamclass
- Removed CAPTCHA verification logic and put into new
FileCMS\Common\Contact\AntiSpam- Added static function
verifyCaptcha($config)
- Added static function
tag: v0.2.9
FileCMS\Common\Security\Profile::verify()- Removed type-hint from method signature for backward compatibility
tag: v0.2.10
Date: Sat Jun 25 16:42:03 2022 +0700
- 2022-06-25 DB: Enhancing security in Common\Contact\Email
- 2022-06-21 DB: Minor fix to Common\Security\Profile::verify()
tag: v0.2.11
Date: Sun Jul 10 12:50:24 2022 +0700 FileCMS\Common\Stats\Clicks: Added new column
add()includesjson_encode($_GET)raw_get()doesjson_decode()on new column- Updated
CLICK_HEADERS
tag: v0.2.12
Date: Thu Aug 18 10:33:15 2022 +0700 Modified FileCMS\Common\Stats\Clicks to track all URLs but allow users to add list of URLs to be ignored
tag: v0.2.13
Date: Thu Sep 8 09:54:26 2022 +0700 FileCMS\Common\Stats\Clicks:
- Added
get_by_page_by_month() - Fixed
get_by_path()FileCMS\Common\View\Table: - New class
- Renders multi-dimensional array data
render_table()produces <table> structure with optional CSS classes for table, tr, th and tdrender_as_div()produces table structure using <div class="row"> and <div class="col">
tag: v0.3.0
Date: Thu Nov 3 11:09:53 2022 +0700 Added FileCMS\Common\Data\Csv
- See documentation above for method information
tag: v0.3.1
FileCMS\Common\Security\Profile
- Removed the following methods:
getAuthFileName()build()
- Removed the following constants:
DEFAULT_AUTH_DIRDEFAULT_AUTH_PREFIXAUTH_FILE_TTL
- Added these constants:
PROFILE_KEY = __CLASS__;PROFILE_DEF_SRC = 'HTTP_USER_AGENT';
Profile::init()- Revised to make backwards compatible
- Always adds
$_SERVER[Profile::PROFILE_DEF_SRC]to profile - If profile config keys are present, also adds these to profile
- All keys must be valid
$_SERVERkeys
Profile::verify()- Revised to make backwards compatible
- Added config file as 2nd argument
- Always checks value of
$_SESSION[Profile::PROFILE_KEY][Profile::PROFILE_DEF_SRC] - If profile config keys are present, also confirms these values match
tag: v0.3.2
FileCMS\Common\Data\Csv
- If CSV file doesn't exist, first Csv instance creates it
- If array of headers are supplied, first instance writes headers
- Added new method
deleteRowInCsv() - Slightly refactored
updateRowInCsv()but functionality is the same
tag: v0.3.3
FileCMS\Common\Data\Csv
writeRowToCsv()- If you already have headers in the CSV file, this method will now allow you to write a row without using headers as the 2nd argument
getItemsFromCsv()- Now allows you to read rows even if header count doesn't match
- If your headers are > the count of the CSV headers, just appends empty strings
- If your headers are < the count of the CSV headers, adds fake headers
Header_1,Header_2, etc.
- Also updated tests:
Common\Data\CsvTestCommon\Security\ProfileTest
tag: v0.3.4
Fixed bad sprintf() call in FileCMS\Common\Data\Csv::getItemsFromCsv()
tag: v0.3.5
Arghhhh ... struggling with git
tag: v0.3.6
FileCMS\Common\Data\BigCsv
- New class
- Handles files of any size
- Doesn't use
file() - Low memory consumption
- Not as fast as
Csv
FileCMS\Common\Data\CsvTrait
- Hold common constants and methods
- Used by
CsvandBigCsv - Added new method
array_combine_whatever()- If header count === data count runs
array_combine() - If header count < data count starts creating headers
header_01,header_02etc. - If header count > data count just assigns the headers to the data items and drops remaining headers
- If header count === data count runs
FileCMS\Common\Data\Csv
- Refactored slightly to use
CsvTrait - Added flag
$alltofindItemInCSV()- If set
FALSE(default) only returns 1st match - If set
TRUEreturns all matching rows
- If set
tag: v0.3.7
FileCMS\Common\Data\*
- Moved
CsvTrait::array2csv()andarray_combine_whatever()toFileCMS\Common\Generic\Functions - Moved remaining
CsvTraitfunctionality toCsvBase - Removed
CsvTrait - Refactored
CsvandBigCsvto extendCsvBase
FileCMS\Common\Generic\Functions
public static function array2csv(array $data) : string
- Writes an array to CSV
- Credits: https://stackoverflow.com/questions/13108157/php-array-to-csv
public static function array_combine_whatever(array $headers, array $data, string $prefix = '') : array
- Does the equivalent of
array_combine()even ifcount($headers)doesn't matchcount($data)
tag: v0.3.8
/templates/super/edit.phtml
- Replaced CK Editor with a self-hosted Tiny MCE, loaded from
/tinymce/tinymce.min.js(installed via thetinymce/tinymcecomposer package) - Generates a one-time token (stored in
$_SESSION['token']) and passes its hash to Tiny MCE'simages_upload_urlso/super/uploadcan verify the request came from this form
/src/config/config.php
- Renamed the
SUPER=>ckeditorkey totinymce
/src/upload.php
- Checks the token generated by
edit.phtml; if it doesn't check out, redirects to the main page - Reshapes the response from
FileCMS\Common\File\Upload::handle()into the{location: url}format Tiny MCE's image upload handler expects, and returns HTTP 400 with{error: message}on failure
composer.json
- Added
tinymce/tinymceas a dependency
tag: v0.3.15
FileCMS\Common\File\Upload (filecms-core)
- Fixed
handle()ignoring the requested field name at the finalmove_uploaded_file()step, which always failed uploads for any field name other than'upload'
src/upload.php
- Now calls
Upload::handle('file')to match the field name TinyMCE's built-in image upload handler actually sends, instead of'upload'
composer.json
- Bumped the
unlikelysource/filecms-coreconstraint to>=0.3.15
tag: v0.3.16
FileCMS\Common\Security\Profile (filecms-core)
- Added
Profile::authenticate(), which verifies login credentials against apassword_hash()-stored hash instead of a plaintext comparison
templates/super/login.phtml
- Replaced the plaintext
$name === $valid_name/$pwd === $valid_pwdchecks with a singleProfile::authenticate($config, $name, $pwd)call
src/config/config.php
SUPER.passwordandSUPER.alt_logins.*.passwordmust now bepassword_hash()bcrypt hashes, not plaintext
composer.json
- Bumped the
unlikelysource/filecms-coreconstraint to>=0.3.16
tag: v0.3.17
FileCMS\Common\Image\Captcha (filecms-core)
- Hardened the CAPTCHA against automated (OCR) reading: renders the whole phrase as a single distorted image (overlapping glyphs, randomized fonts, 2D wave distortion, color-correlated noise) instead of one clean image per character. See the CAPTCHA section above for details and the new
CAPTCHAconfig keys.
templates/super/login.phtml / templates/site/contact.phtml
- Updated the CAPTCHA
<img>styling:writeImages()now returns a single wide image instead of one 50px tile per character, so the fixedwidth:50pxstyle would have squeezed the whole phrase into 50px
src/config/config.php
CAPTCHA.rotate_min/rotate_maxdefault changed from -50/50 to -33/33;line_min/line_maxfrom 5/50 to 12/24;dot_min/dot_maxfrom 20/60 to 30/50- Added
font_files,overlap_min,overlap_max,wave_x_amplitude,wave_y_amplitude
composer.json
- Bumped the
unlikelysource/filecms-coreconstraint to>=0.3.17