PHPackages                             unlikelysource/filecms-core - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [File &amp; Storage](/categories/file-storage)
4. /
5. unlikelysource/filecms-core

ActiveLibrary[File &amp; Storage](/categories/file-storage)

unlikelysource/filecms-core
===========================

File-based content management system. Does not require a database.

v0.3.19(1w ago)112511Apache-2.0PHPPHP &gt;=8

Since Jan 3Pushed 1w ago1 watchersCompare

[ Source](https://github.com/dbierer/filecms-core)[ Packagist](https://packagist.org/packages/unlikelysource/filecms-core)[ Docs](https://unlikelysource.com/filecms)[ RSS](/packages/unlikelysource-filecms-core/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (10)Dependencies (4)Versions (41)Used By (1)

FileCMS (v0.3.21)
=================

[](#filecms-v0321)

Simple PHP framework that builds HTML files from HTML widgets.

- Includes a class that can generate and validate CAPTCHAs (uses the GD extension).
- Includes the CKEditor 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!
- Very fast and flexible.
- Once you've got it up and running, just upload HTML snippets and/or modify the configuration file.
- IMPORTANT: minimum version is PHP 8!

License: Apache v2

Critical Updates
----------------

[](#critical-updates)

**2026-08-06: Important Update!!!**

### Multi-byte (UTF-8) Support in Validation and Filter

[](#multi-byte-utf-8-support-in-validation-and-filter)

`Common\Security\Validation::alpha()` / `alnum()` previously used `ctype_alpha()` / `ctype_alnum()`, which only recognize ASCII and silently reject valid non-Latin text (Khmer, Thai, Arabic, accented Latin, etc.). They now use a Unicode-aware regex (`\p{L}`, `\p{N}`, `\p{M}`) instead, so any script validates correctly -- including combining-mark scripts like Khmer, where vowel signs and the "coeng" subscript marker are separate codepoints from the base letter, not letters themselves.

`Validation::notTooLong()` / `notTooShort()` and `Filter::truncate()` previously measured/sliced by byte count (`strlen()` / `substr()`), which over-counts multi-byte characters and can corrupt a UTF-8 string if the cut lands mid-character. They now use `mb_strlen()` / `mb_substr()` instead.

This adds a hard dependency on the `mbstring` PHP extension (declared in `composer.json` as `ext-mbstring`). It ships with virtually every PHP install, but confirm it's enabled (`php -m | grep mbstring`) before upgrading.

**Behavior change to be aware of:** `notTooLong` / `notTooShort` size limits are now measured in characters, not bytes. A multi-byte string that previously failed a `size` check purely because of its byte count (while actually under the limit by character count) will now correctly pass; conversely, if you were relying on the old byte-based check as a proxy for storage size, re-check it against the new character-based one.

If you subclassed `Validation` or `Filter` to override an individual method (e.g. a custom `alnum()`), note that `runValidators()` / `runFilters()` now dispatch via `static::` instead of `self::`, so your override will actually be called where it previously would have been silently bypassed.

**2026-07-26: Important Update!!!**

### Passwords Now Hashed in Config

[](#passwords-now-hashed-in-config)

The latest version of `filecms-core` now expects the passwords stored in `src/config.php` to be hashed using Bcrypt. Run `get_password_hash.sh PLAIN_TEXT_PASSWORD` (or `vendor/unlikelysource/filecms-core/get_password_hash.sh PLAIN_TEXT_PASSWORD`) and copy and paste the output into the `$config['SUPER']['password']` key and any `$config['SUPER']['alt_logins']` keys you've added. You can also run this PHP code from the command line to obtain the hashed version of your password (substitute your password in place of "PLAIN\_TEXT\_PASSWORD"):

```
php -r "echo password_hash('PLAIN_TEXT_PASSWORD', PASSWORD_BCRYPT) . PHP_EOL;"

```

### CAPTCHA Changes

[](#captcha-changes)

Have a look at the updated **CAPTCHA** documentation (scroll further down to see it). Ten new config parameters have been added to make the CAPTCHA more difficult for automated hacking systems to crack. Copy the recommended settings from this documentation page, and adjust as needed.

CK Editor Replacement
---------------------

[](#ck-editor-replacement)

**2026-07-18: Important Update!!!**Run `vendor/unlikelysource/filecms-core/tinymce_upgrade_2026_07.sh`

Upgrading From filecms-core v2.x to v3.x
----------------------------------------

[](#upgrading-from-filecms-core-v2x-to-v3x)

Composer versions `0.2.*` of this package are referred to in commit history and elsewhere as "v2.x"; `0.3.*` (the line this README's version number, above, belongs to) is "v3.x". If your site was originally built against a `0.2.*` release and you're bringing it up to the current `0.3.*` release, there are several breaking changes to work through -- they're listed here in the order you're likely to hit them.

### 1. PHP 8 is now required

[](#1-php-8-is-now-required)

As of `v0.3.9`, the minimum PHP version is PHP 8 (v2.x supported PHP &gt;=7.4). Confirm your server's PHP version before doing anything else -- everything below assumes PHP 8+.

### 2. Authentication moved from files to native PHP sessions

[](#2-authentication-moved-from-files-to-native-php-sessions)

`v0.3.1` removed `Common\Security\Profile::getAuthFileName()`, `Profile::build()`, and the `DEFAULT_AUTH_DIR` / `DEFAULT_AUTH_PREFIX` / `AUTH_FILE_TTL` constants. In v2.x, a successful login wrote an auth file to disk (under an `AUTH_DIR` config key) that was checked on subsequent requests; v3.x stores that same information in `$_SESSION` instead and never touches the filesystem for it.

- Remove the `AUTH_DIR` config key if your `config.php` still has one -- it's no longer read
- Make sure `session_start()` is being called (the `filecms-website` skeleton's `bootstrap.php` already does this) and that PHP's session save path is writable
- Clean up (or just ignore) any leftover auth files sitting in your old `AUTH_DIR` -- they're now dead weight, not a security-relevant artifact, since nothing reads them anymore

### 3. Passwords must be `password_hash()` hashes, not plaintext

[](#3-passwords-must-be-password_hash-hashes-not-plaintext)

`v0.3.16` changed login verification (`Profile::authenticate()`) to compare the submitted password against a stored hash with `password_verify()`, rather than comparing plaintext strings. **This is the change most likely to silently break login on an in-place upgrade** -- if `SUPER.password` (or any `SUPER.alt_logins.*.password`) is still a plaintext string after upgrading, no password will ever verify against it, and the account becomes unable to log in with no obvious error.

For every account, generate a hash and replace the plaintext value in `config.php` using this script:

```
./get_password_hash.sh PLAIN_TEXT_PLAIN_TEXT_PASSWORD
# or
vendor/unlikelysource/filecms-core/get_password_hash.sh PLAIN_TEXT_PASSWORD`

```

```
'SUPER' => [
    'username' => 'admin',
    'password' => '$2y$12$...',   // output of the command above, not the plaintext password
    'alt_logins' => [
        'editor' => [
            'username' => 'editor',
            'password' => '$2y$12$...',   // same here
        ],
    ],
],

```

`alt_logins` itself (multiple named accounts, each with its own username/password) has been part of the config schema since the original v2-to-v3 transition, but its verification lived in `filecms-website`'s `login.phtml` template as a hand-rolled comparison; `v0.3.16` moved that logic into `Profile::authenticate()` in core. If your site has its own copy of that old comparison code, you can retire it in favor of the shared method.

### 4. CKEditor replaced by TinyMCE

[](#4-ckeditor-replaced-by-tinymce)

If your v2.x site is still running the original CKEditor integration, follow the [CK Editor Replacement](#ck-editor-replacement) instructions below (`v0.3.10` introduced the TinyMCE migration script). The `SUPER.ckeditor` config key becomes `SUPER.tinymce`.

### 5. CAPTCHA hardening (recommended, not required)

[](#5-captcha-hardening-recommended-not-required)

`v0.3.17` reworked `Common\Image\Captcha` to render the whole phrase as a single distorted image instead of one clean image per character, which is substantially harder for automated (OCR) systems to read -- see the [CAPTCHA](#captcha) section below for the full explanation and config keys. This is backward compatible: a v2.x-era `config.php` without the new `font_files` / `overlap_min` / `overlap_max` / `wave_x_amplitude` / `wave_y_amplitude` keys will keep working with built-in defaults, but you won't get the new hardening until you add them.

### Checklist

[](#checklist)

1. Confirm your installation is running PHP &gt;= 8
2. `composer update unlikelysource/filecms-core`
3. Remove the `AUTH_DIR` config key if you have it set; confirm sessions work
4. Regenerate every `SUPER.password` / `SUPER.alt_logins.*.password` as a `password_hash()` hash (using `vendor/unlikelysource/filecms-core/get_password_hash.sh`)
5. Migrate CKEditor to TinyMCE, if not already done (using `vendor/unlikelysource/filecms-core/tinymce_upgrade_2026_07.sh`)
6. Add the new `CAPTCHA` config keys to opt into the hardened rendering
7. Test login (including any `alt_logins` accounts) end-to-end before considering the upgrade complete

CK Editor Replacement
---------------------

[](#ck-editor-replacement-1)

Run this from the root of your filecms-website-based project (the directory that contains `composer.json`, `src`, `templates` and, after `composer install`, `vendor`):

```
vendor/unlikelysource/filecms-core/tinymce_upgrade_2026_07.sh

```

This script does the following:

1. Backs up `templates/super/edit.phtml`, `src/upload.php` and `src/config/config.php` (adds a `.bak` suffix)
2. Adds `tinymce/tinymce` to `composer.json` and installs it
3. Copies the TinyMCE assets into `public/tinymce`
4. Downloads the updated `templates/super/edit.phtml` and `src/upload.php`

Two manual steps remain afterward:

1. In `src/config/config.php`, rename the `'SUPER' => 'ckeditor'` key to `'tinymce'` (keep the existing `width` and `height` values):

```
'tinymce' => [ 'width' => '100%', 'height' => 400 ],

```

2. If you had customized `templates/super/edit.phtml` or `src/upload.php`, re-apply those customizations by comparing against the `.bak` files just created, then remove the `.bak` files once you're satisfied.

Website Installation
--------------------

[](#website-installation)

### Automated Installation

[](#automated-installation)

To perform an automated installation, run the following command, where `/path/to/website` is the directory path to your new website:

1. Install Composer (see [https://getcomposer.org/doc/00-intro.md](https://getcomposer.org/doc/00-intro.md%5D)
2. 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.

Basic website config
--------------------

[](#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
------------------------

[](#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 Docker and docker-compose
----------------------------------------------

[](#to-run-locally-using-docker-and-docker-compose)

### Windows

[](#windows)

Install [Docker Desktop for Windows](https://hub.docker.com/editions/community/docker-ce-desktop-windows)

Open the Power Shell (some commands don't work in the regular command prompt)

To bring the docker container online, run this command:

```
cd \path\to\website
admin up

```

To stop the container do this:

```
admin down

```

To open a command shell into the container:

```
admin shell

```

### Linux / Mac

[](#linux--mac)

Install Docker + docker-compose:

- Mac
    - Install [Docker Desktop for Mac](https://hub.docker.com/editions/community/docker-ce-desktop-mac)
- Linux
    - Install [Docker](https://docs.docker.com/engine/install/)
    - Install [docker-compose](https://docs.docker.com/compose/install/#install-compose-on-linux-systems)

Open a terminal window (Terminal Application)

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

[](#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
---------------------------

[](#bootstrap-and-document-root)

Set the website document root to `/public`

- The central point of entry is `/public/index.php`
- There is a file `.htaccess` that controls URL rewriting
- If you are using *nginx* you will need to incorporate the same logic into your primary config file
- `/public/index.php` first 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 `/src` be sure to update `composer.json` and refresh Composer autoloading:

```
composer dump-autoload

```

Here is a summary of the three key constants defined by `/bootstrap.php`. Change as needed.

ConstantDefaultDescriptionBASE\_DIRSame directory as `bootstrap.php`Project rootHTML\_DIR`/templates/site`Location of HTML snippetsSRC\_DIR`/src`Location of source codePre-Processing
--------------

[](#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
---------

[](#templates)

By default templates are stored in `/templates/site`. You can alter this in the config file.

### Config File

[](#config-file)

Default: `/src/config/config.php`

- Delimiter: `DELIM` defaults to `%%`
- "Cards" `CARDS` defaults to `cards`
    - Represents the subdirectory under which view renderer expects to file HTML "cards"

### Layout

[](#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

[](#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_DIR` in the file `/bootstrap.php`.

### Cards

[](#cards)

Important: each `%%CARD%%` directive you add **must** be on its own line!

#### Auto-Populate All Cards

[](#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

[](#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

[](#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
-------------

[](#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

[](#super-config-key)

Example configuration for super user:

```
// other config not shown
'SUPER' => [
    'username'  => 'REPL_SUPER_NAME',  // fill in your username here
    // use `vendor/unlikelysource/filecms-core/get_password_hash.sh NEW_PLAIN_TEXT_PASSWORD` to get the hashed value to store here
    // you can also run `vendor/unlikelysource/filecms-core/get_password_hash.sh`
    '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_PLAIN_TEXT_PASSWORD` to get the hashed value to store here:
            // you can also run `vendor/unlikelysource/filecms-core/get_password_hash.sh`
            '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

KeyExplanationusernameSuper user login namepasswordSuper user login password hash (using `./get_password_hash.sh`)attemptsMaximum number of failed login attempts. If this number is exceeded, a random third authentication field is required for login.validationSet of key:value pairs randomly selected each time you login. Values can be in the form of an array.alt\_loginsAdditional usernames and password hashesmessageMessage that displayed if login failsprofileArray of `$_SERVER` keys that form the super user's profile once logged inlogin\_fieldsField names drawn from your `login.phtml` login formvalidationYou 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\_extOnly files with an extension on this list can be edited.ckeditorDefault width and height of the CKeditor screensuper\_\*Settings pertaining to the location of the super admin user URL, templates and menuContact Form
------------

[](#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.php` using the `COMPANY_EMAIL` key
- CAPTCHA configuration can be done in `/src/config/config.php` using the `CAPTCHA` key
- The same CAPTCHA is also used to protect `/super/login`

### CAPTCHA

[](#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

KeyExplanationinput\_tag\_nameName of the `$_POST` field expected to hold the phrase the user typed insess\_hash\_keyName of the `$_SESSION` key holding the `password_hash()` of the correct phrasefont\_fileFallback font, used if `font_files` is emptyfont\_filesPool of fonts randomized per character; more variety makes it harder for an OCR model trained on a single fontimg\_dirDirectory the generated CAPTCHA PNG is written to (must be web-accessible)num\_bytesCAPTCHA phrase length is `num_bytes * 2` hex charactersrotate\_min / rotate\_maxDegrees of random rotation applied to each characteroverlap\_min / overlap\_maxPixels shaved off each character's horizontal advance so adjacent glyphs touch/overlapline\_min / line\_maxRange for the random count of background noise linesdot\_min / dot\_maxRange for the random count of foreground noise dotswave\_x\_amplitude / wave\_y\_amplitudeMax pixel displacement of the horizontal/vertical wave distortion applied to the finished imageImport Feature
--------------

[](#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 `FALSE` if you do not wish this feature to be available.
- `IMPORT::delim_start`
    - tells the importer where to start cutting out content from the HTML source
    - default: &lt;body&gt;
- `IMPORT::delim_stop`
    - tells the importer where to stop cutting out content from the HTML source
    - default: &lt;/body&gt;
- `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`
    - `params` : array of parameters the callback expects
    - `description` : shows up when you run `/super/import`After logging in as the admin user, go to `/super/import`.

Transform Feature
-----------------

[](#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 `FALSE` if you do not wish this feature to be available.
- `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
------

[](#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

[](#statistical-methods)

The following methods are available for your use:

#### Clicks::get(string $click\_fn) : array

[](#clicksgetstring-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

[](#clicksget_by_page_by_daystring-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

[](#clicksget_by_pathstring-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
---

[](#csv)

You can use a CSV file just like a database using the new `FileCMS\Common\Data\Csv` class

### public function getItemsFromCsv($key\_field = NULL) : array

[](#public-function-getitemsfromcsvkey_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

[](#public-function-writerowtocsvarray-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

[](#public-function-finditemincsvstring-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-&gt;lines
- If found, sets $this-&gt;pos to the line number of the row found in $this-&gt;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

[](#public-function-updaterowincsvstring-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

### public static function array2csv(array $data) : string

[](#public-static-function-array2csvarray-data--string)

- This writes an array to CSV
- Credits:
- @param array $data : data to be written
- @return string $csv\_string

Change Log
----------

[](#change-log)

### tag: v0.2.2 / v0.2.3

[](#tag-v022--v023)

- 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

[](#tag-v021)

- 2022-02-16 DB: Updated tests + removed user key from Common\\Security\\Profile

### tag: v0.2.2

[](#tag-v022)

- 2022-02-13 DB: Fixed bug whereby you can never login

### tag: v0.2.4

[](#tag-v024)

2022-05-12 DB:

- Created `Email::trustedSend()` that allows you to directly call the core email send function
- Refactored `Email::confirmAndSend()` to call `trustedSend()`
- Added `$debug` option to facilitate testing and debugging
    - If set `TRUE` the email is not actually sent, and a `PHPMailer` instance is set to `Email::$phpMailer`
- Added `FileCMSTest\Common\Contact\EmailTest` test class

### tag: v0.2.5

[](#tag-v025)

- `FileCMS\Common\Contact\Email::trustedSend()`
    - Fixed bug whereby you were only allowed to send a string to `$cc` and `$bcc`
    - These inputs now allowed `mixed` types (expected string|array)
- Updated `FileCMSTest\Common\Contact\EmailTest` and `FileCMSTest\Common\Security\ProfileTest`
- `FileCMS\Common\Import\Import`
    - Added message if URL not found
    - Wrapped `file_get_contents($url)` call in `try` / `catch` to prevent expected errors from messing up test results

### tag: v0.2.6

[](#tag-v026)

- `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

[](#tag-v028)

- `FileCMS\Common\Contact\Email::confirmAndSend()`
    - Removed CAPTCHA verification logic and put into new `AntiSpam` class
- `FileCMS\Common\Contact\AntiSpam`
    - Added static function `verifyCaptcha($config)`

### tag: v0.2.9

[](#tag-v029)

- `FileCMS\Common\Security\Profile::verify()`
    - Removed type-hint from method signature for backward compatibility

### tag: v0.2.10

[](#tag-v0210)

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

[](#tag-v0211)

Date: Sun Jul 10 12:50:24 2022 +0700 FileCMS\\Common\\Stats\\Clicks: Added new column

- `add()` includes `json_encode($_GET)`
- `raw_get()` does `json_decode()` on new column
- Updated `CLICK_HEADERS`

### tag: v0.2.12

[](#tag-v0212)

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

[](#tag-v0213)

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 &lt;table&gt; structure with optional CSS classes for table, tr, th and td
- `render_as_div()` produces table structure using &lt;div class="row"&gt; and &lt;div class="col"&gt;

### tag: v0.3.0

[](#tag-v030)

Date: Thu Nov 3 11:09:53 2022 +0700 Added FileCMS\\Common\\Data\\Csv

- See documentation above for method information

### tag: v0.3.1

[](#tag-v031)

FileCMS\\Common\\Security\\Profile

- Removed the following methods:
    - `getAuthFileName()`
    - `build()`
- Removed the following constants:
    - `DEFAULT_AUTH_DIR`
    - `DEFAULT_AUTH_PREFIX`
    - `AUTH_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 `$_SERVER` keys
- `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

[](#tag-v032)

`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

[](#tag-v033)

`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 &gt; the count of the CSV headers, just appends empty strings
    - If your headers are &lt; the count of the CSV headers, adds fake headers `Header_1`, `Header_2`, etc.
- Also updated tests:
    - `Common\Data\CsvTest`
    - `Common\Security\ProfileTest`

### tag: v0.3.4

[](#tag-v034)

Fixed bad `sprintf()` call in `FileCMS\Common\Data\Csv::getItemsFromCsv()`

### tag: v0.3.5

[](#tag-v035)

Arghhhh ... struggling with git

### tag: v0.3.6

[](#tag-v036)

#### `FileCMS\Common\Data\BigCsv`

[](#filecmscommondatabigcsv)

- New class
- Handles files of any size
- Doesn't use `file()`
- Low memory consumption
- Not as fast as `Csv`

#### `FileCMS\Common\Data\CsvTrait`

[](#filecmscommondatacsvtrait)

- Hold common constants and methods
- Used by `Csv` and `BigCsv`
- Added new method `array_combine_whatever()`
    - If header count === data count runs `array_combine()`
    - If header count &lt; data count starts creating headers `header_01`, `header_02` etc.
    - If header count &gt; data count just assigns the headers to the data items and drops remaining headers

#### `FileCMS\Common\Data\Csv`

[](#filecmscommondatacsv)

- Refactored slightly to use `CsvTrait`
- Added flag `$all` to `findItemInCSV()`
    - If set `FALSE` (default) only returns 1st match
    - If set `TRUE` returns all matching rows

### tag: v0.3.7

[](#tag-v037)

#### `FileCMS\Common\Data\*`

[](#filecmscommondata)

- Moved `CsvTrait::array2csv()` and `array_combine_whatever()` to `FileCMS\Common\Generic\Functions`
- Moved remaining `CsvTrait` functionality to `CsvBase`
- Removed `CsvTrait`
- Refactored `Csv` and `BigCsv` to extend `CsvBase`

#### `FileCMS\Common\Generic\Functions`

[](#filecmscommongenericfunctions)

```
public static function array2csv(array $data) : string

```

- Writes an array to CSV
- Credits:

```
public static function array_combine_whatever(array $headers, array $data, string $prefix = '') : array

```

- Does the equivalent of `array_combine()` even if `count($headers)` doesn't match `count($data)`

### tag: v0.3.8

[](#tag-v038)

#### `FileCMS\Common\View\Html`

[](#filecmscommonviewhtml)

- Modified to accept a layout file with a `phtml` extension
    - Invokes `ob_start()` and does a PHP `require` on the layout file
    - Runs layout as a PHP script
    - Allows you to automate things like the copyright date (e.g. `
