The Sqlite Plugin is for Grav CMS. The shortcode [sql-table]
and the Form actions sql-insert
and sql-update
are provided to interact with an Sqlite3 database.
Installing the Sqlite plugin can be done in one of two ways. The GPM (Grav Package Manager) installation method enables you to quickly and easily install the plugin with a simple terminal command, while the manual method enables you to do so via a zip file.
The simplest way to install this plugin is via the Grav Package Manager (GPM) through your system's terminal (also called the command line). From the root of your Grav install type:
bin/gpm install sqlite
This will install the Sqlite plugin into your /user/plugins
directory within Grav. Its files can be found under /your/site/grav/user/plugins/sqlite
.
To install this plugin, just download the zip version of this repository and unzip it under /your/site/grav/user/plugins
. Then, rename the folder to sqlite
. You can find these files on GitHub or via GetGrav.org.
You should now have all the plugin files under
/your/site/grav/user/plugins/sqlite
NOTE: This plugin is a modular component for Grav which requires Grav and the Error, Form, ShortcodeCore and Problems to operate.
The plugin also requires that the SQLite3 extension is available with the version of php operating on your site.
An adminstrator must create a directory for the database and place within it the sqlite3 database file. It is recommended that the directory is user/data/sqlite
(see configuration).
Before configuring this plugin, you should copy the user/plugins/sqlite/sqlite.yaml
to user/config/plugins/sqlite.yaml
and only edit that copy.
Here is the default configuration and an explanation of available options:
enabled: true
database_route: data/sqlite
database_name: db.sqlite3
extra_security: false
logging: false
all_logging: false # this option only becomes active when logging is True
error_logging: false # this option only becomes active when logging is True and all_logging is False
select_logging: false
insert_logging: false
update_logging: false
enabled
turns on the plugin for the whole site. Iffalse
, then making it active on a page will have no effect.database_route
is the Grav route (relative to the 'user' subdirectory) to the location of theSQLite3
database.database_name
is the full name (typically with the extension .sqlite3) of the database file. It is the responsibility of the site developer/maintainer to create the database.extra_security
enables a more paranoid setting. Whentrue
, a page may only contain an [sql-table] shortcode if the page header explicitly allows for on. (See below for onpage configuration when option is enabled.)logging
when false, nothing extra happens. Whentrue
, SQL related data is logged to a file calledsqlite.txt
in the directory given bydatabase_route
. If however there is an error in settingdatabase_route
, then the directory isuser/data/sqlite
.
SUGGESTION: If the DataManager plugin is installed and the default route is retained, then the SQL logs can be viewed from the Admin panel.
all_logging
only become active whenlogging
is enabled. If true, then all stanzas and errors are recorded.error_logging
only becomes active whenlogging
is enabled andall_logging
is not enabled.select_logging
only becomes active whenlogging
is enabled andall_logging
is not enabled.insert_logging
only becomes active whenlogging
is enabled andall_logging
is not enabled.update_logging
only becomes active whenlogging
is enabled andall_logging
is not enabled.
NOTE: The database must exist. If it does not, then an error is generated.
logging
should not be used in production settings as it writes to the hard drive, slowing performance.
- Shortcodes can be enabled separately using the
shortcode-core
configuration. To disable shortcodes being used on all pages, but only used on selected pages, configure the shortcode-core plugin inside the Admin panel withenabled=true
andactive=false
. Then on each page where shortcodes are used, include in the front section of the page:
shortcode-core:
active: true
- When
extra_security
is enabled, then on a page in which the[sql-table]
shortcode may be used, the page header must contain
sqliteSelect: allow
A shortcode and a Form action are provided.
[sql-table]
to generate a table (or json string) from data in the database- the
sql-insert
action for a Form is used to move data from a form to the database. - the
sql-update
action for a Form is used to update an existing row of data in the database.
When the plugin is first initialised, it verifies that the database exists. If it does not exist, then every instance of the [sql-table]
shortcode is replaced with an error message and the Form generates an error message when the submit button is pressed.
NOTE: Although as many errors as possible are trapped, it should be remembered that GRAV uses redirects extensively, eg., in login forms or sequential forms, which means the error messages may be overwritten. If this happens, then set
error_logging
to true whilst debugging.
In the page content the shortcode is used as follows:
[sql-table]SELECT stanza[/sql-table]
If extra_security
is not enabled, or extra_security
is enabled ** AND ** the page header contains the field sqliteSelect: allow
, then
the plugin then generates an html table (or json, see below) with the headers as returned by the select stanza, and the body containing the row data. (In the remainder of this documentation, it is assumed that extra_security
is NOT enabled.)
The SELECT stanza can be complex referring to multiple tables in the database. An SQLite3 query will return a table of rows with the same number of elements, which will fit into a simple HTML table.
Since the data would normally be updated, it is recommended that the page header contains:
cache-enabled: false
The [sql-table]...[/sql-table]
stanza can be embedded in other shortcodes, such as ScrolledTableShortcode plugin, or configured with the Tablesorter plugin.
Assuming that:
- The default plugin configuration is not changed
- The file
<path to Grav>/user/data/db.sqlite3
exists - The database has a table
people
, which in turn has the fieldsname
surname
telephone
gender
Then the following code and sql stanza (standard SQLite3 allows new lines and indentation for clarity)
[sql-table]
SELECT name, surname, telephone, gender
FROM people
LIMIT 4
[/sql-table]
will be rendered something like
<table>
<thead>
<tr>
<th>name</th><th>surname</th><th>telephone</th><th>gender</th>
</tr>
</thead>
<tbody>
<tr>
<td>xyz</td><td>qwe</td><td>1234</td><td>Male</td>
</tr>
<tr>
<td>xyz1</td><td>qwe</td><td>1234</td><td>Male</td>
</tr>
<tr>
<td>xyz2</td><td>qwe</td><td>1234</td><td>Male</td>
</tr>
<tr>
<td>xyz3</td><td>qwe</td><td>1234</td><td>Male</td>
</tr>
</tbody>
</table>
It is also possible for the SELECT stanza in the xxx.md file to contain a Twig variable. For example:
[sql-table]
SELECT name, surname, telephone, gender
FROM people
LIMIT {{ userinfo.lines }}
[/sql-table]
For this to work, Twig processing must be enabled, viz., in the page header there should be the line
process:
twig: true
The following options are allowed:
- class
- id
- hidden
- json
Provided so that a css
class can be added to the table. Thus
[sql-table class=SomeName]SELECT stanza[/sql-table]
will be rendered as
<table class="SomeName">
...
</table>
Provided so that an id
can be added to the table. Thus
[sql-table id=SomeId]SELECT stanza[/sql-table]
will be rendered as
<table id="SomeId">
...
</table>
hidden
If a list of column names is provided, then the column will not be displayed. The column names must be the same as the column names returned by the SQL stanza, eg.,
[sql-table hidden="id idnum"]SELECT row-id as id, Passport as idnum, name, surname, telephone from people
[/sql-table]
Notes:
- Column names are matched using
\s+
or whitespace.
This means that the column heads must be a single word (including_
). This can be done by usingAS
to rename column names in theSELECT
statement. Since the column headers are to be hidden, it does not matter what they look like. - Shortcode parameters must be in double quotes
"..."
not single quotes'...'
.
Column hiding is accomplished by adding a style="display: none;"
to the relevant <th>
and <td>
elements. Consequently, the data still exists in the HTML table, and so can be scrubbed or viewed by looking at the page source.
However, the intent of this option is to make the data available for use by JS or Jquery functions, or for updating the SQL database, but for it not to be immediately visible. For example, in order to update a row (a feature to be added), a row-id will be needed, but usually it is irrelevant for the user to see the row-id.
Sometimes data is required as a json
string, eg., to include as data for other shortcodes, rather than as an HTML <table>
.
For this purpose, the json option is provided:
[sql-table json]SELECT stanza[/sql-table]
The json string will be an array []
of hash elements {}
, one hash for each row of the table.
The keys of the hash are the names of the columns in the SELECT stanza. For example,
[sql-table json]
SELECT strftime('%H:%m',time,'unixepoch','localtime') as key, latitude as lat, longitude as lng
FROM tracking
WHERE id=1
[/sql-table]
This assumes a table of latitude and longitude readings over time in unix seconds for units with a given id.
This will be rendered by the sqlite-plugin
as
[
{ "key": "20:10", "lat": 123.012, "lng": 22.1234},
{ "key": "20:20", "lat": 123.546, "lng": 22.112}
]
When this option is used, the values of the other options class
, id
, or hidden
are ignored because they only have significance for an HTML <table>
.
A GRAV form is created within the page as described by the GRAV documentation. However, the process
list contains the word sql-insert
.
In the page header, assuming the page is form.md
form:
name: Input data
method: POST
fields:
- name: name # the value here must be the same as a field in the database
label: Name of person
type: text
- name: surname
label: Surname of the person
type: text
- name: telephone
type: text
label: Telephone number of the person
- name: gender
type: select
label: Person's gender
options:
male: male
female: female
process:
- sql-insert: # this is the crucial one
table: people # this must match the table the data is being added to
- redirect: showdata # this is optional (see note below)
buttons:
- type: submit
value: Add person to database
- type: reset
value: Reset
reset: true # this is advised to prevent the same data being added multiple times.
When the submit button is pressed, the following stanza is sent to the database:
INSERT INTO people (name, surname, telephone, gender) VALUES (...)
The form plugin offers considerable flexibility for validating data before being sent to the database.
NOTE1: No further validation of the data is carried out by the plugin.
In the example above, the process list has a redirect to another slug. This is optional. However, if the data is added correctly, it can be viewed using an [sql-table]
shortcode with an appropriate SELECT
stanza. The redirect action replaces the Form so that if, as recommended, the reset
option is set to true
, returning to the Form will set to default the fields, thus preventing inadvertent data duplication.
If there is an error (non-unique data, or incorrect fields), the redirect
action is short-circuited.
A GRAV form is created within the page as described by the GRAV documentation. However, the process
list contains the word sql-update
.
In the page header, assuming the page is form.md
form:
name: Input data
method: POST
fields:
- name: telephone
type: text
label: Telephone number of the person
- name: status
type: select
label: Club membership
options:
ordinary: Ordinary
VIP: VIP
Senior: Senior
- name: where # a mandatory option (note lower case only). It is the full WHERE expression
type: hidden # this is a where FIELD
content: ' row-id = "3" ' # the single quotes are needed to ensure the double quotes are included
process:
- sql-update: # this is the crucial one
table: people # this must match the table the data is being added to
where: ' row-id = "3" ' # an alternative to the where field.
# a where field takes precedence over a where parameter
- redirect: showdata # this is optional
buttons:
- type: submit
value: Update person to database
- type: reset
value: Reset
reset: true # this is advised to prevent the same data being added multiple times.
NOTE: It is mandatory to provide
where
data, either as a form field, or a process attribute.
When the submit button is pressed, the following stanza is sent to the database:
UPDATE people
SET telephone = <telephone> ,
status = <status>
WHERE row-id = "3"
Here <...> is the value given in the Form for the relevant field.
It is possible to include a Twig variable in the WHERE
data, eg.,
form:
process:
- sql-update:
table: people
where: ' row-id = "{{ userinfo.userid }}" '
Then it is possible to use another mechanism, such as the persistent-data
plugin, to arrange for a Twig variable to contain the necessary information.
For this to work, Twig processing in headers needs to be set for the site.
The following fields are defined for these two Form processes:
table
- This is mandatory, and is the table to which the sql stanza is applied.where
- This is mandatory forsql-update
& ignored forsql-insert
.ignore
- This is optional for both. It is followed by an array of field names that are not included in the stanza. Eg.
form:
process:
- sql-insert:
table: people
ignore:
- status
Security is an issue because a sql-insert
and sql-update
form actions allows a
page user to modify an existing database, and therefore corrupt it - at the very least by adding unnecessary data.
The website designer should therefore make sure that Forms with sql-insert
and sql-update
actions are only available on Grav pages that are protected.
For example, using the Login
plugin, only users with certain privileges or belonging to certain groups can be allowed in.
Alternatively, using the Private
plugin, a password can be created for the page.
Some plugins allow for authorised users to modify content in the frontend. This would allow a user to add an [sql-table]
within the markdown content of a page, and thus to access data on a website database. In order to allow a website designer to protect against such an accidental or malicious intrusion, the extra_security
option is provided in the sqlite
plugin configuration. It is false
by default, to allow for backward compatibility. (See above for more information about usage.)
The following is part of a page to show how to combine sqlite
and the datatable
shortcode, together with jQuery code to update a database.
When the page is generated, the Form plugin creates the html of a <form id="xxx">
where xxx is the name of the form in the header. Then the outer shortcode is called, which calls the inner shortcodes. The outer shortcode is a [datatables]
, which initialises a DataTables object and links to the DataTables jQuery. The DataTables query limits the number rows on the page, provides ordering and search functionality. When a row is selected, it also provides the funactionality for extracting the data from the row on a column by column basis.
The [datatables]
shortcode expects to have an html <table>
as its content. This is provided by the [sql-table]
shortcode.
In order to provide for JQuery code that can be triggered by clicking on row, and so transfering data from the table to the form, a [dt-script]
shortcode is added. When a row is first selected, the class selected is added to the row, which can then be rendered differently. When the row is clicked again, it is deselected.
When the submit
button is clicked on the form, the data in the fields, which has been transfered from the DataTable, is proceessed by the sql-update
form action, and the database is updated.
The following code would be in form.md
file.
---
title: Alter User data
form:
name: alter-client-form # this is the name given to the form and is the select for jQuery code.
fields:
- name: name # this field is for displaying the selected client
label: Client
type: display
content: undefined
- name: client # this field is needed in the redirect page to report on the changes
type: hidden
- name: client_id # this is the PRIMARY key
type: hidden
- name: telephone # a field that is to be altered depending on input
label: 'Telephone # [ eg. 1234 5678 ]'
type: text
validate:
pattern: '[0-9]{4}\s[0-9]{4}'
help: Telephone should be like: 1234 5678
placeholder: 1234 5678
- name: idserial
label: 'HKID # eg. A123456(7), use capital letters'
type: text
validate:
pattern: '[A-Z]{1,2}[0-9]{6}\([0-9A]\)'
help: Should be like: A123456(7)
placeholder: A123456(7)
- name: dtest
label: 'Driving Test result'
type: text
validate:
pattern: '[0-2]\|.+?\|2[0-9]{3}-[0-9]{2}-[0-9]{2}'
- name: where # this is essential for an UPDATE stanza.
type: hidden
- name: type
label: Client registration type
type: select
options:
Ordinary: Ordinary
VIP: VIP
Senior: Senior
Support: Support
Blacklist: Blacklist
buttons:
- type: submit
value: "Alter Client Data"
- type: reset
value: Reset
process:
- sql-update:
table: clients # the table that is to be updated
ignore:
- client # we wanted the client field for the redirect, but we want to exclude it from the UPDATE stanza
- redirect: operations/alteruser/info # at this route, there is an file with twig to display the updated info
reset: true
cache_enable: false
---
# Clients
[datatables]
[sql-table hidden="client_id"]
SELECT client_id, name || " " || upper( surname ) as client, telephone, type, idserial as 'HKID #', dtest as 'Driver Status'
FROM clients
/* This is a simple SELECT statement. client_id is the PRIMARY key and replaces row-id.
This short code will generate a table with five columns (the client_id column is hidden) */
[/sql-table]
[dt-script]
/* the dt-script shortcode is part of the datatables plugin. It is included as part of jQuery function
** that initialises the datatables jQuery plugin.
** It is jQuery code.
** The variable 'selector' is generated by the [datatables] shortcode.
*/
var table = $(selector).DataTable();
$(selector + ' tbody').on( 'click', 'tr', function () {
/* the function is triggered when a row of the table is selected with a mouse click.
** the class selected is styled by css associated with Datatables.
*/
if ( $(this).hasClass('selected') ) {
// if a row is already selected when clicked, it is deselected
// the data transfered when a row is selected is re-initialised.
$(this).removeClass('selected');
$('#alter-client-form input[name="data[where]"]').val('');
/* the form name is defined in the page header
** GRAV Form generates input elements with the attribute
** name=data[xxxx] where xxxx is the name of the field in the form definition
*/
$('#alter-client-form input[name="data[client]"]').val('');
$('#alter-client-form input[name="data[telephone]"]').val('');
$('#alter-client-form select[name="data[type]"]').val('Ordinary');
$('#alter-client-form input[name="data[idserial]"]').val('');
$('#alter-client-form input[name="data[dtest]"]').val('');
$('#alter-client-form div:first-of-type div:nth-of-type(2) div').html('undefined');
/* This selector refers to where the Display field is generated in the FORM.
*/
}
else {
table.$('tr.selected').removeClass('selected');
$(this).addClass('selected');
var rd = table.row('.selected').data();
// get a row using a function provided by DataTables
$('#alter-client-form input[name="data[where]"]').val('client_id=' + rd[0]);
/* this is vital for an UPDATE. It is provided as the WHERE clause.
** Here we get the client_id from the selected row of the table. It is in the
** first (zeroth) column
*/
$('#alter-client-form input[name="data[client]"]').val(rd[1]);
// transfer data from the DataTable to the form input elements
$('#alter-client-form input[name="data[telephone]"]').val(rd[2]);
$('#alter-client-form select[name="data[type]"]').val(rd[3]);
$('#alter-client-form input[name="data[idserial]"]').val(rd[4]);
$('#alter-client-form input[name="data[dtest]"]').val(rd[5]);
// Transfer data to the display field to show the Name of the selected client
$('#alter-client-form div:first-of-type div:nth-of-type(2) div').html(rd[1]);
}
} );
$('#alter-client-form').on('reset', function(e) {
setTimeout( function() {
// a reset button is provided, so when clicked re-initialise for form
table.$('tr.selected').removeClass('selected');
$('#alter-client-form input[name="data[where]"]').val('');
$('#alter-client-form input[name="data[client]"]').val('');
$('#alter-client-form input[name="data[telephone]"]').val('');
$('#alter-client-form input[name="data[idserial]"]').val('');
$('#alter-client-form input[name="data[dtest]"]').val('');
$('#alter-client-form select[name="data[type]"]').val('Ordinary');
$('#alter-client-form div:first-of-type div:nth-of-type(2) div').html('undefined');
});
});
[/dt-script]
[/datatables]
For bug fixes, thanks to
- Matt Marsh @marshmn
- @dlannan
- @hughbri - for extra security
- Internationalise. Add more languages to
langages.yaml