Time for a new instance in my series "a simple web app as a single php file" (check for others here). This time I've made one to create digital compendiums: compilation of information organised as keywords referring to text and/or image, available as a single php file web app. Whether you want to keep track of your favourite coffee beans, are fed up to crawl the web garbages to find once again how to regex an email address, or simply believe in the dead internet theory and want to make your own secured source of whatever knowledge you need before it disappears, this app may interest you. Paper books are even better, sure, I do have a big pile on my desk. But the power of search and editing with a digital app can't be denied. Moreover, this app also comes with an import/export functionality, because knowledge is worth nothing if it can't be shared. The php file is available as a .tar.gz file here. As always, all you have to do is copy it somewhere in a php server like Apache, set the obfuscation parameter (cf below) and you're good to go.
Template for a single php file web app
All web apps of this series are based on the same model. Its first instance dates from 2023. It has improved little by little and I think now is a good timing to reintroduce it. If you want the boilerplate to make your own app then the current version is available here. The details about the app itself follows the introduction of the template.
The key points of this model are: one single php file with no dependencies or any assets, and an installation procedure as simple as copying a file and setting an obfuscation parameter. I also try as much as possible to keep these apps under 1k lines of code (a good part of it being the same template reused again and again), with a first working version generally written over a weekend. I take them as proof that there is really no need for all those bloated frameworks or AI for a human to create bare hand and in no time useful tools he/she entirely owns (both the data and the code).
The php file is divided into three sections: the php code, the html/css code, and the javascript code. The php section is as follow.
First of all I start the php session:
Then I make sure the error reporting is turned off to ensure it won't mess up with API calls (normally they will be catched, it's just an additional layer of safety net).
Next I make sure https is used to ensure the communication between the user and server will be encrypted. If the user tries to use http only the call is automatically redirected to https, except if the app is running on localhost, in which case it doesn't matter and simplify my life when I'm developping the app.
Now comes three global variables. The first one is used to obfuscate the filename of the SQlite database created by the app to save all the data. It should be set to a long password-like string of random alphanumerical characters. It avoids someone to access the database without using the app. The second one is the login for the guest account. If set, it allows to use a limited version of the app (what's "limited" depends on the app). And the third one is the app version. All the XXX and YYY are uppercase and lowercase versions of your app name.
Then comes the definition of the class implementing the app. The php section ends with the creation of an instance of this class, and based on the existence of the action parameter in the POST data I route between the methods API(), which process an API call, and Main(), which creates the DOM.
The class definition is as follow. First comes the class properties and their getters.
The constructor initialises the properties and the database connection. In the template the database definition contains only the table for the users. You can add the definition of other tables needed for your app in dbDef and they will be created automatically. The automatic creation of the database is done by trying first to open it with SQLITE3_OPEN_READWRITE. If it fails it means the database doesn't exist, then we can create it by using SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE and CreateDb() takes care of creating the tables as defined in dbDef.
The automatic database creation is done as follow. Loop on the database definition, create and execute the SQL command for each table creation. IF NOT EXISTS is used to be able to call that method more than once and create only non existing tables. It isn't useful in the template as it is now, and only left in preparation for future improvement of this model where an existing database would be automatically updated after dbDef is modified.
The destructor simply closes the connection to the database.
The Main() method checks if the user is logged in and set opMode accordingly. In the html section the DOM will be created based on this property: either a login interface, either the app interface.
The CheckLogin() method checks if the user is logged in, either because it has already logged in in the current session, or because it is providing credentials right now. If the guest account is set and it's the currently used account then the user is always considered logged in. If the user is providing credentials for login, they are checked against those saved in the database, or if there are no credentials yet then they are used to create the first (and single) user. The model currently allows for only one user (plus the eventual guest user). I plan to allow for several ones in the future once I find a solution that fits my taste. Upon login, a token is generated and used in API calls to ensure they come from a valid logged in user. Token also expires automatically after a given time.
The token verification is done as follow. For each API calls a login and token are provided by the user request. The token is checked against the one created for that user during login, and its expiration is checked at the same time. Here again, the guest user is considered to always have a valid token.
API calls are processed by the method API(). First of all the token is verified. Then the request is routed based on the action parameter. This is here where we eventually reject actions not allowed for a guest user. The result of a request is returned as a JSON encoded dictionary, which contains at least the actionResult entry, equal to ok if the request succeeded. The exit(0) at the end of the method ensures there is no other output that would mess up the JSON data.
The template also contains a dummy DoAction() method as a boilerplate with all the select/delete/insert/update SQL commands.
The html/css section is as follow.
The following css generally gives me 99% of all I need. I complete it as needed for each app.
The DOM of the app is contained in the divTop. According to the login status, a login interface or the app DOM is created. In both cases a header, message area and footer are also created. The header displays the app name. The message area displays various information to the user. The footer contains general information like the version, and links to logout and download the database if the user is logged in and not the guest. The DOM of the app can be created differently for the user or a guest by using <?php if($YYY->IsGuest() == false) :?>.