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.8(1y ago)1921Apache-2.0PHPPHP &gt;=8

Since Jan 3Pushed 1y 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 1mo ago

READMEChangelog (10)Dependencies (2)Versions (30)Used By (0)

FileCMS
=======

[](#filecms)

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

Initial Installation
--------------------

[](#initial-installation)

1. Clone the `filecms-website` repository to the project root of your new website.

- If you have `git` installed 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 `git` installed, just download the ZIP file from:

```
https://github.com/dbierer/filecms-website/archive/refs/heads/main.zip

```

- And unzip into `/path/to/website`

2. Use composer to install `unlikelysource/filecms-core` and 3rd party source code (e.g. PHPMailer)

```
cd /path/to/website
wget https://getcomposer.org/download/latest-stable/composer.phar
php composer.phar self-update
php composer.phar install

```

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
    'password'  => 'REPL_SUPER_PWD',   // fill in your password here
    /*
     * 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
            'password'  => 'REPL_OTHER_PWD',   // fill in alt password here
        ],
        // 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 passwordattemptsMaximum 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 passwordsmessageMessage 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

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