paferalib

PHP Framework for Rapid Application Development

After a couple of years of hacking this in my spare time, I feel that my API is stable enough for other people to play around with this code as well. It's released under the GPL, so feel free to use it, fork it, or do anything else that you want as long as you send me any improvements.

Basics

Paferalib is a set of components for developing web applications in PHP. It has a URL resolver, object cacher, database modeler, and all of the other convenience tools that you need in 2016. It's designed for use on any host that supports PHP 5, mod_rewrite, and MySQL or SQLite (although complex applications will really benefit from MySQL). It's lightweight enough to be useful on free or cheap hosting packages where bandwidth and/or disk space is limited, and is decently fast even on a Raspberry Pi. It's also bandwidth optimized for mobile devices which may not have the fastest Internet connection, and separates components for easy caching. With 50-80% of a typical website's visitors using phones or tablets instead of computers, small and efficient beats fat and slow anyday!

The philosophy behind Paferalib is that since modern devices have more than enough processing power and JavaScript engines have advanced dramatically since Internet Explorer was holding back the advanced web, we can achieve maximum efficiency by using the server to only process data and render the page on the device using JavaScript.

The workflow is thus:

  1. The framework sends a basic page to the client.
  2. The page loads the needed JavaScript libraries.
  3. The page calls the server's API to retrieve data.
  4. The page renders the data to the user.

This lets the page render almost immediately because the original page is nothing more than a skeleton, while the content is loaded as necessary as the user browses, providing the fastest possible viewing experience and keeping the server load to a minimal level. My experiences show that even a cheap phone with 1GiB of RAM with Chrome or Firefox installed is adequate for pages with complex scripting.

To use Paferalib to its full power, you should be comfortable writing your own SQL queries, sending data out through JSON to the browser, and then using JavaScript to render the data to the user. If you're such a person, then give this a try. I hope that it'll help you achieve your goals easily and quickly.

Installation

You can install paferalib simply by downloading the zip file and unzipping it into your web server's www directory. Afterwards, you can point your browser to your website and setup your database, scripts, and so forth using the administration interface.

Directory Structure

Main Directory

The main structure is as follow:

.htaccess Rewrites short URLs
index.php Loads the library files and resolves the URL
apps All application code and resources live here
cache Caches compiled code, pages, and database queries
data User-created images, videos, etc...
libs External libraries
private Unreadable directory from the web; useful for security logs
paferalib Main library directory

Application Structure

Each application can contain the following directories

admin Administration pages for the app
api APIs accessible via JSON
css Stylesheets
js JavaScript files
models Database models
pages Webpages
plugins Plugins to be run after the page content has been generated
translations Translations in JSON format

Due to the rewrite rules in .htaccess, direct file access is limited. This is both for security and for convenience in using short URLs.

Short URLs

To shorten URLs and to save typing, resources for each application can be addressed using the following scheme:

apps/appname/pages/pagename /appname/pagename
apps/appname/css/filename /c/appname/filename
data/appname/filename /d/appname/filename
apps/appname/images/filename /i/appname/filename
apps/appname/js/filename /j/appname/filename
apps/appname/sounds/filename /s/appname/filename
apps/appname/videos/filename /v/appname/filename

Let's say you created an app called test, your main page would be at apps/test/pages/index.php and the URL would be /test/index or /test for short. I think most people would agree that the short form is much easier to type.

Helpful functions in paferalib/utils.php

utils.php contains general functions which can be used by any code without any additional dependencies. As such, it's useful to look through this file before creating your own utility functions.

UPPER_CASE, LOWER_CASE, NUMBERS, ALPHA, ALNUM
String constants for later functions.
MissingArgs($data, $neededargs)
Ensures that the array in $data has valid values for all keys in $neededargs. Useful for checking parameters in functions.
MakeSUID()
Returns a 32-bit nonzero integer. Used for Synchronization IDs within the database.
ToShortCode(), FromShortCode(), ToShortCodes(), FromShortCodes()

Converts SUIDs to short codes, which are six characters consisting of upper case letters, lower case letters, numbers, dash, and underscore. These characters were chosen because they are usable on almost all current filesystems.

The functions with s at the end work on arrays rather than individual values. This allows more than 32-bit numbers to be used.

UUID4()
An implementation of UUID4() using openssl.
ChooseOne(), Average(), Shuffle(), KeyFromValue($array, $value)

Functions that work on arrays.

ChooseOne() randomly choses an value from the given array.

Average() returns the numerical average of all values.

Shuffle() returns an array with rearranged ordering.

KeyFromValue() returns the key belonging to the given value.

Bound($value, $min, $max)
Returns a value which is not lower than $min nor higher than $max.
First($array)
Returns the first element.
Last($array)
Returns the last element.
DefaultValue($array, $keys)
Tests each key in $keys until it finds a value in $array. If it can't find anything, then it returns the first element. Useful for determining which language to use when given a IETF language code.
V($array, $key), IntV($array, $key, $default, $min, $max), FloatV(), StrV(), ArrayV(), TimeV()
Functions for getting a value out of an array regardless of whether $key actually exists or not. The typed versions are guaranteed to return empty values of that type if $key does not exist. Useful for validating user input.
SameArray($a, $b)
Returns true if these arrays have the exact same key: value pairs.
BlowFish($input, $rounds)
Encrypts $input using the blowfish algorithm for $rounds times.
CheckUpload($filename, $sizelimit)
Checks an uploaded file in the $_FILES array for errors. Returns the path to the uploaded file
CodeToUTF()
Converts a Unicode code point to UTF-8.
IDToPath(), IDToAlnumPath()
Converts a numerical ID such as 123456789 to a filesystem path such as 123/456/789.

Helpful functions in paferalib/webutils.php

These functions are used only in the web environment.

IncludeDir(path)
Includes all PHP files in path and all subdirectories.
RequireLogin($isapi = False)
Sends a guest user to the login page. Returns an error for APIs.
RequireGroup($groups, $isapi = False)
Denies the user unless he or she is in the groups marked by $groups. Returns an error for APIs.
LanguageTokens()
Extracts the languages from HTTP_ACCEPT_LANGUAGES into an array in order of preference.
BestTranslation($translations)

Given a set of translations, this will search for the best available translation in the following order:

  1. The user's current language code
  2. The user's language tokens
  3. Any available English languages
  4. The first translation in the list
SetupSession()
Internal library initialization. You should not need to call this directly.
SendFile( $path, $downloadfilename = '', $forcedownload = false, $blocksize = 1024 * 16 )
A version of PHP's readfile() function which supports HTTP byte ranges.

Editing pages

Hello, world!

The default application for all root-level URLs is called h, which is short for home. To make our traditional "Hello, world!" page, simply edit apps/h/pages/index.php to read

<p>Hello, world!</p>

and your website's main page will read "Hello, world!"

Linking pages

All pages in Paferalib are PHP scripts. Let's say that you wanted an about page, you would create apps/h/pages/about.php. You could then update index.php to link to this page by using

<p>Hello, world!</p> <p><a href="/about">About me</a></p>

Using the provided objects

Pages in Pafera are not in the global scope, but are actually inserted inside a function in resolver.php. This means that if you want to use any global variables, you need to write at the top of your page as in a function.

<?php global $globalvar;

Another consequence of this scope is that the Resolver will automatically include several useful variables for you as well.

$pathargs Any arguments passed via the URL but not by query string
$D The default database for the site
$R The resolver, which handles paths and files
$S The session object, which saves your variables into the database
$T The translator, which handles loading translations for various languages
$T_SYSTEM System translations for common tasks
$T_MAIN Translations for the site title, buttons, and other layout items

$pathargs

The $pathargs variable contains any extra parameters to the script passed via the URL.

For example, if you have a script at the URL /view and the URL is /view/0/100/icon, $pathargs would become

['0', '100', 'icon']

This is the equivalent positional parameters of a function

function View($start, $limit, $style)

just using the URL rather than calling a function directly.

$D

This is the default database for the site, which is initialized before it comes to your page. Paferalib supports using multiple databases simultaneously, which you may want to do if you're supporting multiple simultaneous users using SQLite to avoid locking down the whole database file down whenever a script wants to insert something. If you're using MySQL or anything else that locks only individual tables, it's unlikely that you'll use anything beyond this variable.

$R

The Resolver is the object that you will use to identify which app you're in. It also has convenience functions for images, scripts, and other resources that your app might use.

For example, if your app is called test and you have an image at apps/test/images/logo.png, you can either type

<img src="/i/test/logo.png" />

or you can use

<?=$R->IMG('logo.png')?>

to do the same thing without hardcoding the name of your app into your code?

Why would you not want to hardcode the name of your app into your code?

Paferalib supports app instances, which are copies of your app using different names implemented by Unix or NTFS soft links. Each app has the same code, but different data. If you want your billing department and your sales department to both have forums, you can use the same code under apps/billingforums and apps/salesforums to achieve this result.

It's also possible to have different code paths depending on the name of your app. For example, our file manager "share" doubles as a TV remote under the name "pitv." The additional features are activated by a code block that reads

if ($R->currentapp == "pitv") { ... }

The Resolver is also useful when including other scripts. Instead of writing

include('apps/test/libs/lib.php');

You can write

$R->IncludePHP('libs/lib.php');

to do the same thing or use

$R->IncludeDir('libs/lib1');

to include an entire directory of PHP files at the same time.

$S

The session object exists to save session data into a database rather than on the filesystem. This allows many web servers to share a database server and thus have user sessions available anywhere. You should probably not worry too much about this unless you're a big company with multiple A records for your domain name.

$T

Translations are natively built into Paferalib. On the application side, they're stored in JSON files in the translations directory and loadable by this object. On the database side, they're stored into a JSON field.

Let's say that you have an app called test with a translation file called main. You could then load these translations by calling

$T_TEST_MAIN = $T->Load('test/main'); print_r($T_TEST_MAIN);

$T_TEST_MAIN would then be an array of strings.

$T_SYSTEM

System-wide collection of strings useful on every page. Things like "Go," "Cancel," and "Back" to make your life easier.

$T_MAIN

Site-specific collection of strings. This should contain your site name and anything else that's useful on multiple pages.

Using the database

The database class is... strangely enough... called DB. The default instance can be found in $D. All site settings including database login information can be found in the JSON file private/pafera.cfg. It would be a good idea to ensure that you don't accidentally send or upload this file anywhere.

Paferalib's database supports all of the normal SQL operations and includes object linking, translations, tagging, properties, and many other convenient tools. Of course, the downside to using any type of generic tools is that you trade development time for execution speed, but we like to stick by the old "Make it work first, then optimize later" philosophy.

Flags

The database class has several flags which you may find useful.

DB::DEBUG
Prints all queries and results.
DB::SECURE
Activates ownership, default permissions, and access control lists. Note that both the database and the model must have this flag set in order to be used, so you can have some models which are secure and some models which are not. Once activated, you can set permissions on database rows just like on your filesystem for viewing, changing, creating, and deleting. Note that this comes with a significant performance penalty, so only use it in those cases where you absolutely need it.
DB::TRACK_CHANGES
Paferalib includes a changelog which keeps track of every creation, modification, or deletion to the system. Like the DB::SECURE flag, this is set on an individual model basis, and decreases performance. If you want to know that Carl from accounting updated Bob's invoice, this is for you. Just be aware that heavily modified websites can fill up your disk space *very* quickly!
DB::TRACK_VALUES
An enhanced version of DB::TRACK_CHANGES, this not only tracks changes but also the values which were changed. This allows you to have a form of version control where any object can be returned to an earlier state at any time, but again, will *rapidly* consume your disk space.
DB::PRODUCTION

Signals to the system that this is a production system, which will make the following changes.

  • Disables normal error displays
  • Use minified versions of all JavaScript and CSS files
  • Enables enhanced caching for code and data

This will also make it much harder to debug your code, which is why it can easily be switched on and off as needed.

Models

Paferalib does not hide its SQL backend. In fact, it puts it right in front of you and lets you write your own SQL queries to take advantage of your system.

Database models live in the models directory of your app. They take their name from the filename, are always lowercase, and support autoloading so that you don't have to include a file for every model that you want to use. Instead, the first time you create or search for a model is the time where it will be autoloaded.

A model file looks something like the following:

# file loginattempt.php <?php class templateclass extends ModelBase { public static $DESC = [ 'numsuids' => 1, 'flags' => 0, 'uniqueids' => ['phonenumber', 'place'], 'fields' => [ 'phonenumber' => ['TEXT NOT NULL'], 'place' => ['TEXT NOT NULL'], 'timestamp' => ['INT32 NOT NULL'], 'ipaddress' => ['INT32 NOT NULL'], 'flags' => ['INT32 NOT NULL'], ], 'indexes' => [ ['INDEX', 'ids'], ], ]; }

The special "templateclass" keyword is the name of the model. This will vary depending on the name of the app. If your app is named "test," this model will become "test_loginattempt," and you will create it using the line

$attempt = $D->Create('test_loginattempt');

For convenience, the keyword "templateapp" will be replaced by the name of the current app. Thus if you have a sibling model called "user," you can load it inside the loginattempt definition using the code

$user = $D->Create('templateapp_user');

The preferred way to use models in Paferalib is to place the database definition and any commonly used functions in the model itself, but any logic that is used only once should be placed within an API script. This keeps down the amount of code which needs to be loaded and parsed every time an object is used.

Model definition

The main definition is found in the static variable $DESC. This can have the following members:

flags
The default flags for the model, modifiable by the site administrator at run-time. This includes DB::SECURE, DB::TRACK_CHANGES, and DB::TRACK_VALUES.
numsuids

The term "SUID" stands for Synchronization Unique ID, and is an implementation of a random ID across the int32 address space for every insertion. In simpler terms, every time you insert an object into the database, it automatically gets an unused ID from -2147483648 to 2147483647 excluding zero. It allows for Bob, Jack, and Mary to all have their own copies of the database, add their own items to it, and then come back and easily merge their changes into the main database, which would be rather inconvenient on a system which used automatically incrementing IDs.

This can take any value above zero, but remember that IDs take up space. 1 is a simple 32-bit value, but 4 takes up 128 bits for every row of your model. Unless you really need to store more than four billion rows, 1 should be enough for everyday use.

uniqueids

There are three ways to identify a given row in Paferalib

  1. AUTO_INCREMENT ID
  2. SUIDs
  3. Unique IDs

Unique IDs is an array of field names containing what makes this row unique. For example, the loginattempt model can be uniquely identified by the phone number and place of the user trying to login, since different places can have the same phone number.

It is quite possible for a model to have all three ways of identifying, in which case the database will first use the unique IDs, then try to use the SUIDs, then finally use the AUTO_INCREMENT ID.

Note that using unique IDs automatically creates an unique index for the fields in the database as well for efficiency.

fields

The fields is the real definition of the SQL CREATE TABLE statement. It contains an array whose keys are the field names and the values are the field definitions. The definitions are an array in the form [type, validator, extra] with only the type required.

Available types are the common SQL types INT, FLOAT, TEXT, BLOB along with some custom Paferalib types:

DATETIME
Stored as a text string in ISO 8601 format. No Y2K or 2038 problems here!
INT8, INT16, INT32, INT64
Integer types which specify the number of bits. Available on pretty much all databases, unlike the more esoteric INT128 or BCD types.
JSON
Stored as a UTF-8 string which is automatically encoded to and from a JSON array.
TRANSLATION
Stored as an INT32 index to the real translations table in h_translations. Automatically loaded by the database at run-time to the field name plus a "s." For example, the field "username" would result in the translations array being stored in "usernames." Use the BestTranslation() function to extract the most appropiate translation for the current user.
SINGLETEXT, MULTITEXT
Text that should be only one single line long or will take up multiple lines. Used in HTML forms to indicate whether this should be a plain <input type="text"> or a <textarea> tag.
PASSWORD
Text that should not be shown outside of the system by any means. The JSON DB API automatically stores such fields as having the value "[hidden]," and HTML forms will render as <input type="password" />
PRIVATE
Adding this keyword will ensure that these variables will not be readable by anyone except administrators.
PROTECTED
Adding this keyword will ensure that these variables will not be readable by anyone except those who have the DB::VIEW_PROTECTED permission.
indexes

This is an array which allows you to specify individual indexes to be created for the table. It takes the form [indextype, indexfields] where indexfields is a string containing field names separated by commas.

For tables which are frequently queried, indexes can dramatically improve performance, but for tables which are frequently written to or updated, indexes can dramatically lower performance. It's suggested that you test your tables both with indexes and without indexes to find the best fit.

Operations

CREATE TABLE: $D->Create($modelname)

You typically will not need to issue a CREATE TABLE statement yourself unless your table is really complicated. A model named test_user will result in a table called test_users being automatically created on first use.

To create an object, simply pass its name to the $D->Create() method. The class will be autoloaded, initialized with default values, and returned to you.

$obj = $D->Create($model);

You should not create new models using the PHP new keyword unless you manually initialize the fields yourself using $D->ImportFields(). If you have not setup correct validators, it's quite possible for bad data to be written into your database when you try to save your object.

SELECT: $D->Load($modelname, $ids), $D->LoadMany($modelname, $ids)

Like most database abstraction layers, Paferalib has two ways to get an object: load it by ID or find it by criteria.

Loading an object can be done in many ways depending on the type of ID used.

# Load by SUID $obj = $D->Load('test_loginattempt', $suid1); # Load by named array $obj = $D->Load('test_loginattempt', ['suid1' => $suid1]); # Load by unique IDs $obj = $D->Load( 'test_loginattempt', [ 'phonenumber' => $phonenumber, 'place' => $place, ] );

All of these will produce a test_loginattempt object stored in $obj, throwing an exception if the current user does not have the view permission or if the object could not be found.

For efficiency, it is also possible to specify which fields to load and to provide an object to load into:

# Load only phonenumber $obj = $D->Load('test_loginattempt', $suid1, 'phonenumber'); # Load into existing object $D->Load('test_loginattempt', $suid1, '', $obj);

If you have a list of IDs, it is possible to load them all at once

# Loading from an array of IDs $objs = $D->LoadMany('test_loginattempt', $ids);

but this is inefficient. Since Paferalib does not know in advance what type of search to perform for each row, it results in a query for every object. You should perform the load yourself for greatest performance using a WHERE clause and using $D->ImportFields() to create your objects.

SELECT: $D->Find($modelname, $where, $params, $options)

Finding an object is probably about 99% of what most people use SQL for, and also can be the most complicated operation to do once you start doing JOINs and subselects and all of those fun features that DBAs specialize at.

Paferalib has portability and ease of use as two of its goals, so we do *not* include any database-specific functions. We use SQLite as a baseline, meaning that if it can be done in SQLite, it can probably be done in every other database as well. For most people, this won't make much of a difference, but if your app has performance-critical parts, you can always use a direct query with $D->Query() to optimize for your particular database. CREATE VIEW and the database's native query cache can also help for frequently used queries.

# Simplest form returns all rows $objs = $D->Find('test_loginattempt')->All(); # Using a WHERE clause with parameters is the normal use $objs = $D->Find( 'test_loginattempt', 'WHERE phonenumber = ?', $phonenumber )->All(); # ORDER BY can be written straight into the WHERE clause $objs = $D->Find( 'test_loginattempt', 'WHERE phonenumber = ? ORDER BY phonenumber', $phonenumber )->All(); # but LIMIT should be put into the options array due to chunking $objs = $D->Find( 'test_loginattempt', 'WHERE phonenumber = ? ORDER BY phonenumber', $phonenumber, [ 'start' => 100, 'limit' => 100 ] )->All();

DBResult

Like regular PDO queries return a SQL cursor, $D->Find() returns a DBResult class. The most commonly used style is to use a foreach loop to iterate over each row that $D->Find() returns, and DBResult is designed to support exactly such a use.

foreach ($D->Find( 'test_loginattempt', 'WHERE phonenumber = ? ORDER BY phonenumber', $phonenumber, [ 'start' => 100, 'limit' => 100 ] ) as $r ) { print $r->phonenumber; }

DBResult natively supports chunking, or reading a portion of rows at a time for processing to save memory. This means that if your database returns a million rows, DBResult will first fetch rows 0-999, then rows 1000-1999, and so forth. The chunk size defaults to 1000, and can be set using the 'chunksize' option.

DBResult also supports caching the returned objects to the webserver, thus vastly improving performance for subsequent queries with the same parameters. This can be enabled by setting the 'cachesize' option to the number of rows that you wish to be cached. The next time that you run the same query, DBResult will check to see if the number of rows in the table has changed. If the count is still the same, your objects will be loaded from disk rather than having to make another trip to the database server.

Like $D->Load(), $D->Find() supports retrieving only specific fields from the database to save processing time. You can set these as a string with each field separated by a comma in the 'fields' option.

It's also possible to retrieve only objects with certain permissions. For example, if you only wish to get objects which you have permission to change, you can set 'access' to DB::CAN_CHANGE and DBResult will return only those objects.

Things to Watch for with DBResult

The top mistake to make with DBResult is that it will not return all of the rows of your database by default. Instead, it will only return the first 1000 rows, which saves a lot of processing for most common operations. If you wish to get rows past the first 1000, make sure to set the 'limit' option to a large number.

Because of security, chunking, and the processing limit, getting a precise count from DBResult when you have a secure model is only possible if you retrieve *all* rows and then manually count how many rows you have. For secure models, it's quite possible that certain rows are not viewable by your user and thus will be skipped over. Models without security do not have this issue since all of their rows are public.

ModelBase

All Paferalib models must inherit from ModelBase in order to receive variable tracking and validation capabilities.

Method forwarding and chaining

ModelBase keeps track of which database this object came from and will forward most methods to that database. These methods are also normally chainable, so instead of doing

$obj = $D->Create('model'); $obj->Set(['property' => 'foo']); $D->Insert($obj);

You could write the equivalent as

$D->Create('model')->Set(['property' => 'foo'])->Insert();
Setting properties

It's important that you remember that properties cannot be set directly as in

$model->property = 'foo';

but must be set using the ModelBase->Set() method

$model->Set(['property' => 'foo']);

While this will seem awkward, ModelBase->Set() will keep track of which variables have changed and which variables have not. When it's time to update the object, calling ModelBase->Save() will result in a no-op if nothing has changed.

An additional benefit is that if you have setup your validators correctly, searching for, loading, and updating an object from a POST request can be as simple as

$D->Create('model')->Set($_REQUEST)->Replace();

Most objects will require more processing than this, but for models which do not require advanced validation, this can be very convenient.

Useful methods

ModelBase has several useful methods which you might find yourself using from time to time.

ModelBase->ToArray()
Returns all of the properties and values as an array. This method respects properties marked as PRIVATE or PROTECTED.
ModelBase->ToJSON()
Returns all of the properties and values as an array for conversion to and from JSON, ignoring all private or protected properties. Very useful when debugging.
ModelBase->Changed()
Returns whether this object has been changed.
ModelBase->OnLoad(), ModelBase->OnSave(), ModelBase->OnDelete(), ModelBase->PostSave()
Processing hooks called whenever their named events occur for further functionality. These come in handy if you're dealing with hierarchical trees or other special data structures which SQL does not cope with very easily.
ModelBase->CanChange(), ModelBase->CanDelete()
Convenience functions for checking what the current user can do.

$D->Save(), $D->Insert(), $D->Update(), $D->Replace()

One of the main frustrations with SQL is to decide what to do when you're trying to insert a row with the same ID as an existing row. The MySQL REPLACE keyword does a lot to help with this situation, but unfortunately is not supported by all databases, and thus not includable in Paferalib.

$D->Save()
This is the backend function to all of the other methods. This should not be called directly unless you're sure of what you're doing.
$D->Insert()
This will do the same thing as the SQL INSERT INTO clause, and will fail if a row exists with the same ID.
$D->Update()
This will do the same thing as the SQL UPDATE clause, and will do nothing if no row exists with the same ID.
$D->Replace()
As a compromise between the above functions, this method will first search for any existing rows, do an update if it finds one, or does an insert if it doesn't find one. The downside is that these searches take time, and this is substantially slower than calling $D->Insert() or $D->Update() yourself if you know for sure what you need to do.

With all of these functions, the action of saving an object involves converting all of its properties into formats suitable for the database, calling any necessary validators, and then sending the command to the database itself. All of these can result in exceptions being thrown, so be sure to wrap any save operations in an exception handler.

Any special handling for a model can be done by defining the functions ModelBase->OnSave() for before the save is done and ModelBase->PostSave() for after the save is done. If you want to convert any special types or launch any hooks, this is the place to do it.

$D->Delete()

In its simplest form, $D->Delete() will truncate the model's table

$D->Delete($modelname)

It can also delete only specific rows

$D->Delete($modelname, 'WHERE phonenumber = ?', $phonenumber)

Plain and simple is the description for this method.

$D->Link(), $D->Linked(), $D->Unlink(), $D->UnlinkMany()

Paferalib natively supports linking objects to each other in a many-to-many relationship. These links can have a type, an order, and a comment as to what the purpose of the link is. It's not uncommon to see code like

$bob = $D->Find('user', 'WHERE username = ?', 'Bob')[0]; $tom = $D->Find('user', 'WHERE username = ?', 'Tom')[0]; $jane = $D->Find('user', 'WHERE username = ?', 'Jane')[0]; $mary = $D->Find('user', 'WHERE username = ?', 'Mary')[0]; $bob->Link($tom, BOSS); $bob->Link([$jane, $mary], EMPLOYEES); $bob->Linked('user', BOSS); # Returns $tom $bob->Linked('user', EMPLOYEES); # Returns [$jane, $mary] in that order
Linking Caveats

Paferalib linking is unidirectional, so $bob -> $tom does not imply $tom -> $bob. In order to have both objects linking to each other, you need to link from both ends.

Linking can only be done by objects which are already saved. If you try to link an object that hasn't been saved to the database, an exception will be thrown.

Linking also only works within the same database. The links table does not keep track of which database an object came from, so do not try to link across databases. The results won't be what you expect!

Another limitation to linking is that your object must be uniquely identifiable, meaning that it must have an AUTO_INCREMENT ID, SUID, or unique IDs. If we cannot distinguish between objects, then we cannot return the right object.

$D->Begin(), $D->Commit(), $D->Rollback()

These are the same as the SQL BEGIN, COMMIT, and ROLLBACK commands. You may nest these multiple times without problems, as Paferalib will keep track of how many layers you have created.

$D->Sudo(), $D->Unsudo()

While Paferalib's default database security works wonderfully for limiting access via the JSON APIs, there will eventually come a time where you need to perform a task as an admin because your current user does not have the permissions to do what you need. $D->Sudo(), $D->Unsudo() will let you become an admin for a short period of time so that you can get things done and get back to work.

# Become admin $D->Sudo(); [Do work as admin]... # Go back to previous user $D->Unsudo()

$D->Debug(), $D->TrackValues(), $D->TrackChanges(), $D->TrackViews(), $D->UseSecurity()

Database flags are normally set when the database object is first created, but sometimes you just want to debug a short piece of code. In that case, these convenience functions serve to temporarily enable or disable flags for the time.

# Enable debugging $D->Debug(); [Code that needs to be debugged]... # Disable debugging $D->Debug(0);

$D->Date(), $D->Timestamp()

Convenience functions for handling time. We recommend always using UTC to handle all of your times. paferalib/utils.php has GMTToLocal() and LocalToGMT() to convert between server time and local time using a JavaScript cookie set by the browser. Non-browser apps must convert to UTC before using any times in APIs.

$currentdate = $D->Date(); $currenttimestamp = $D->Timestamp(); sleep(5); $thendate = $D->Date($currenttimestamp); $thentimestamp = $D->Date($currentdate);

$D->Access($model, $obj)

Convenience function to check exactly what type of access your current user has to this object. Returns a bitmap of permission flags such as DB::CAN_CHANGE and DB::CANNOT_DELETE.

For common use such as CHANGE or DELETE, it's easier just to use ModelBase->CanChange() and ModelBase->CanDelete() instead.

Quick config using $D[$key]

For database wide settings that don't depend on user, DB implements the ArrayAccess interface, which means that you can use it like a normal array with the database as a backend.

$D['script.timeout'] = 120; echo $D['script.timeout'];

All values are kept in the h_config class, and are loaded as needed to avoid impacting normal code execution.

$D->MaxID($model)

Convenience function to get the existing maximum ID for AUTO_INCREMENT ID models.

$D->Tag($objs, $tagname, $language), $D->UnTag($objs, $tagname, $language), $D->HasTag($model, $tagname, $language), $D->ListAllTags($model, $language)

Paferalib supports native tagging of objects. The tags are separated by language, so you can display only the tags relevant to your current user.

For efficiency, only models with numsuids = 1 are supported at the moment. This can easily change in the future, but I haven't had a need to add support for any complicated models yet.

# Get an array of employees $a = [ $D->Load('user', $bobid), $D->Load('user', $tomid), $D->Load('user', $janeid), ]; # Tag them as employees. Note that $language will default to # $D->language unless otherwise set. $D->Tag($a, 'employees'); # Further tag by gender $D->Tag([$a[0], $a[1]], 'male'); $D->Tag($a[2], 'female'); # Get a list of all employees $employees = $D->HasTag('user', 'employees'); # Get a list of all males $males = $D->HasTag('user', 'male'); # Get a list of all tags for this model $tags = $D->ListAllTags('user');

$D->SetProp($objs, $key, $value, $language), $D->GetProp($objs, $tagname)

Paferalib also supports setting properties on objects. You can think of these as adding columns to your database table, but without the space needed on every row. These should be used when very few objects require these values, otherwise it's easier just to put another field into your model itself.

# Get an array of employees $a = [ $D->Load('user', $bobid), $D->Load('user', $tomid), $D->Load('user', $janeid), ]; # Tag them as employees. Note that $language will default to # $D->language unless otherwise set. $D->SetProp($a[0], 'attitude', 'bossy'); foreach ($a as $u) { echo $u->username . ': ' . $D->GetProp($u, 'attitude') . "\n"; }

JavaScript API

Libraries

Although Paferalib is mostly original code on the PHP side since I couldn't find suitable code to do what I wanted, the JavaScript side includes many libraries by other people. As long as a library is small, light, and efficient, it's vastly easier to extend other people's work in JavaScript than it is to roll your own. If you compare the footprint of JQuery or JQuery Mobile to what we use here, you'll quickly realize the difference in space savings, which is quite important if you're on a phone using a 2G connection in rural areas.

As of right now, Paferalib uses the following libraries:

minified
A lightweight replacement for JQuery
interact
Nice SVG compatible library for drag and drop, pinch zoom, multi-touch rotation, and tapping
lazysizes
A high performance JavaScript image loader to load images only as needed to improve page rendering time
stacktrace
The JavaScript analog to the PHP debug_print_backtrace() function

Libraries which are useful for multiple apps should be added to apps/h/js to make sharing easier for your site. I'm thinking of adding a dependency based automatic downloading administration page along the lines of the AMD loaders but implemented in PHP. However, it's only on the to-do list for now.

Libraries written/compiled by me include

loader
A very light dynamic loader for adding scripts or stylesheets after the page has already been loaded. It supports both loading in order and simultaneous loading with a callback function when all files have been loaded.
paferalib
A collection of useful functions for all pages
paferadb

Lets you interface with the Paferalib database straight from JavaScript.

Unfortunately, I haven't figured out a way to safely and securely handle this level of access for all users, so as of right now, only the objects administration page really utilizes this library.

paferapage
The main collection of functions including feature detection, popups, button bars, and all of those nice UI elements. The namespace for these functions is P, as JQuery uses $.

If you enable the DB::PRODUCTION flags, all of these libraries get minified and compiled into one file at apps/h/js/all.js, meaning that it only takes one HTTP request to get all of them at once. As of right now, that file is 220KiB on my machine, whereas a full JQuery Mobile installation is easily two to three times the size. Of course, we don't have all of the functionality of JQuery + JQuery Mobile/JQuery UI either, but with 50-80% of current browsing done on phones and tablets, it's nice to keep things small and efficient.

Server variables

To ease development, h_page automatically inserts several variables that you can use in your scripts.

P.userid
The current user ID
P.groups
The current groups that the user belongs to
P.lang
An integer indicating the current language from DBTranslator
P.langcode
The IETF language code for the current session
P.baseurl
The server and path for the current website
P.currentapp
The name of the current Paferalib app

Useful functions from paferalib.js

Unlike the functions from paferapage.js, the functions in paferalib.js are in the global scope because they are useful in a variety of situations. Some of the more useful ones are as follows:

IsNumber(), IsLower(), IsUpper(), IsAlpha(), IsAlphaNum(), IsFunc(), IsEmpty(), IsString(), IsNum(), IsArray(), IsObject()
Tests types to see if they are the type required. The first four work on strings while the rest work on all types.
EncodeEntities()
Converts HTML characters to entities for use in XML attributes.
ToParams(obj)
Converts the data in obj to a query string format.
Merge(obj1, obj2, ...)
Merges all of the keys and values in the provided objects into one object in order.
Clone(objectorarray)
Clones (deep copy) an object or an array.
PrintArray(objectorarray)
An analog to the PHP print_r() function.
PrintTimeStamp(timestamp, onlydate)
Converts a timestamp into an ISO 8601 date. If timestamp is not provided, then use the current time. If onlydate is set, then ignore the time value.
SecondsToTime(seconds, precision)
Converts a number of seconds into a time format such as 00:16:34. If you need to deal with fractional seconds, you can set precision to the number of digits that you need.
GMTToLocal(time), LocalToGMT(time)
Converts back and forth between GMT and local time. time should be an ISO 8601 string or blank for the current time.
DisplayTime(time)
Formats an ISO 8601 string for display by getting rid of the time zone and the middle T.
Keys(array), Values(array)
Extracts the keys or values from an object as an array.
NestObjects(key1, key2, ... value)

A shortcut for setting values in multidimensional arrays. This allows you to write code such as

a = NestObjects('foods', 'fruits', 'apples', 16); console.log('You have ', a['foods']['fruits']['apples'], ' apples.');

without having to check the intermediary arrays.

Basename(array), Dirname(array)
Analogs of the equivalent PHP functions, basename will give you the filename component of a path while dirname will give you everything in front.
String.startswith(s), String.endswith(s), String.replaceAll(s1, s2, ignore), String.format()
Helpful functions for dealing with strings. String.format() is a JavaScript implementation of the Python string.format() method.
Array.remove(value), Array.contains(value)
Helpful functions for dealing with arrays.
Array.addtext(list, delimiter)

One of JavaScript's major annoyances is its lack of multiline strings. Until I finish the Pafera Universal Language, this function will help by adding a list of strings as a single string. Compare the following for readability and amount of typing required:

// Adding a long string of HTML to a list // String addition method ls.push( '<div class=Padded>' + 'This is a list', + '<ol>', + '<li>Item 1</li>' + '<li>Item 2</li>' + '<li>Item 3</li>' + '</div>' ); // String list method ls.addtext([ '<div class=Padded>', 'This is a list', '<ol>', '<li>Item 1</li>', '<li>Item 2</li>', '<li>Item 3</li>', '</div>' ]);

If you ask why I don't use backslashes, it's because I like code to have proper spacing, and using backslashes completely ruins the spacing when you look at it in an editor.

And for those of you who want to use backticks (template strings), please check support for Android and IE and you'll start crying. 8-(

Shuffle(array)
Returns a randomized array by shuffling its elements around.
SortArray(obj, ignorecase, level1, level2)
.Sorts a JavaScript object based upon its keys and returns a sorted array of [value, key] pairs. level1 and level2 allow sorting based upon a nested key such as obj['animals']['birds']['parrots'].
Strcmp(s1, s2, ignorecase)
Handy strcmp function for use in sorting lists of strings.
RandInt(min, max)
(You really need documentation for this???)
Range(min, max, step)
An analog for the python range() function when you need a list of numbers.
InRect(x, y, rect)
Tests whether x and y are coordinates within the rect. rect should have left, top, right, and bottom properties.
SetCookie(name, value, numdays, path, domain, secure)
A convenience function to set a cookie for this session. You can usually leave everything blank except name, value, and numdays.
Emit(element, event)
Creates an event emitted from element. Useful for custom events.

Useful properties and functions from paferapage.js

All properties and functions in paferapage.js live in the P namespace, so be sure to type P. in front of whatever you use.

P.lang, P.langcode
The current language ID and IETF language code.
P.mousex, P.mousey
The absolute coordinates of the last mouse move event.
P.screensize
Set to "small," "medium," or "large" depending on how many ems the screen is wide. You can adjust your layout accordingly.
P.viewportwidth, P.viewportheight
Convenient measurements saved on a resize event.
P.ischrome, P.isfirefox, P.issafari, P.isedge, P.useadvanced

Browser detection flags. Note that due to many browsers disguising their user-agent settings, more than one can be true.

P.useadvanced is a quick but convenient shortcut to figuring out whether you can enable advanced JavaScript functionality in your applications.

P.screenorientation
Set to "portrait" or "landscape" depending on the window's width to height ratio.
P.URL(path, app), P.APIURL(), P.CSSURL(), P.JSURL(), P.DataURL(), P.ImageURL(), P.SoundURL(), P.VideoURL()

These all take a path and an app name as arguments and returns a URL pointing to the desired resource. If app is not given, then it defaults to the current app.

// Load main.css and main.js for the current app in parallel _loader.Load([ P.CSSURL('main'), P.JSURL('main') ]); // Change an image $('.ButtonIcon').set('@src', P.ImageURL('tux.png')); // Play a sound P.Play('buzzer', P.SoundURL('buzzer.mp3'), {autoplay: 1});
P.Icon(name, app, classes, size), P.IconURL()

These two functions provide support for SVG sprites. The name parameter is a SVG view ID, while classes are additional classes to add to the image and size is the width and height in ems. P.IconURL() returns only the URL, while P.Icon() returns the full IMG tag.

// A call like P.Icon('House', 'h', 'HouseIcon', '2.5'); // will return <img src="/i/h/icons.svg#House" class="HouseIcon" width="2.5em" height="2.5em" />

To take advantage of this setup, your app should have a SVG file called icons.svg in its images directory and each icon should be defined by a view inside it such as

<view id="House" viewBox="0 0 100 100" />
P.BestTranslation(translations)
Like the PHP function of the same name, this returns the best available translation in the array passed in translations.
P.Target(event)
Returns the target of the event. Useful for detecting exactly which element was clicked upon.
P.TargetClass(event, class)

Returns the element belonging to class containing the target element. For example, if you have

<div class=Card> <div class=Title>Title</div> </div>

Calling P.TargetClass(event, '.Card') when the title is clicked would give you the parent div.Card element.

P.Selected(selector)
Returns the option that is currently displayed in a select element. You can then use option.value or option.text to get what you need.
P.Trace()
Prints the current call stack to the console for debugging.
P.Debug(msg)
Prints a debugging message to the bottom of the page. Handy on Android for debugging.
P.HTML(selector, list)
Sets selector.innerHTML to the text strings provided in list.
P.DoneTyping(selector, func, timeout)
Sets up an function to be called whenever the user has typed within a text input and has stopped stopping for the duration in timeout. By default, timeout is one second. Useful for form validation and for those of us who are too lazy to use a mouse to click on the action button.
P.OnClick(selector, buttonnum, clickfunc, doubleclickfunc, holdfunc)
Sets up functions to be called when the user clicks on, double clicks on, or long presses on selector. buttonnum can be LBUTTON, MBUTTON, or RBUTTON. Useful for mobile interactions.
P.AddHandler(eventname, func), P.ClearHandlers(eventname)
Adds a function to be called whenever eventname is received. As of right now, this is used only for resize events.
P.Loading(selector)
Places an animated loading GIF inside selector. Useful for indicating that user input has been received but not fully processed yet.
P.ScrollTo(selector)
An advanced form of the scrollIntoView() function which scrolls smoothly and uses the minimum amount of scrolling needed.
P.AbsoluteRect(selector)
Returns a rect containing the absolute coordinates of selector.
P.ViewPortRect()
Returns a rect containing the absolute coordinates of the current viewport.
P.EMSize(selector)
Returns the number of pixels that an em occupies in the selector. For the whole page, you can simply use P.emsize instead. Useful for scaling elements.
P.AutoWidth(selector)
Automatically arranges a series of items to fit the page width. Each item can have a different width set by the data-autowidth attribute, so item[0] can be 20ems wide, item[1] can be 10ems wide, and so forth. If the item also includes the data-autosquare attribute, the height will also be set.
P.SameWidth(selector, size, heightratio)
Like P.AutoWidth(), but makes everything the same width instead. If heightratio is set, then make each element width * heightratio tall.
P.SameMaxWidth(selector), P.SameMaxHeight(selector)

Sets all elements to the largest width found. Useful for aligning floating elements in grids.

P.SameMaxHeight() does the same thing, except that it sets height rather than width.

P.Popup(content, options), P.ErrorPopup(), P.ClosePopup(class), P.CloseThisPopup(selector)

The base function for all popups. Content should be a string or HTML content wrapped using the HTML() function. options include all Minified CSS attributes that should be applied to the popup itself plus some special fields:

parent
The element used to place the popup.
position
Can be set to "left," "top," or "right" to change the position of the popup relative to the parent.
noanimate
P.Popup() will normally animate the popup as it appears or disappears on the screen to better show where the popup came from. Set this to make the popup appear instantly.

P.ErrorPopup() is a shortcut to show an error dialog with bright yellow colors and a cute warning sign.

P.ClosePopup() will close the popup belonging to the specified class, or all popups if no class is given.

P.CloseThisPopup() will close the popup containing selector.

P.EditPopup(fields, onsuccess, options)

P.EditPopup() shows a popup for editing. This can be something as simple as a text control and as complex as an entire tax form. The custom field option gives a variety of options for formatting and extended functionality such as selecting and uploading images.

fields is an array containing fields for editing. Each field is an array of up to six elements containing the following:

  1. The name of the input field
  2. The type of the field. This can be singletext, multitext, int, float, datetime-local, select, or custom.
  3. The initial value of the field.
  4. The name to be displayed to the user.
  5. The placeholder text, options if this is a select field, or the class if this is a custom field.
  6. Any extra attributes for the input element.

On small screens, P.EditPopup() will render in fullscreen mode. On other screens, it will show up as a normal popup.

Being a complicated function, P.EditPopup() has many options.

formdiv
A selector for the form to be rendered into instead of the standard popup. You can use this to make several forms on a page all using the P.EditPopup() API.
formclass
Sets the class of the form for easy further manipulation.
cancelfunc
Overrides the normal function for the cancel button, which is to simply close the popup.
fullscreen
Overrides size detection and forces this to be a fullscreen form.
gobuttontext
Sets the text for the confirm action.
cancelbuttontext
Sets the text for the cancel action.
extrabuttons
HTML text for any extra controls you want to add to the bottom button bar.

onsuccess is a function receiving the form class, values, and the bottom results display.

// A simple dialog to get the user's name P.EditPopup( [ ['username', 'text', '', "What's your name?"] ], function(formclass, values, resultsdiv) { resultsdiv.fill('Your name is ' + values.username); } );
P.EditThis(selector, onsuccess, fields)
A convenience function for editing a single element's text such as a table cell. onsuccess and fields are not required, but can be used for more complicated processing.
P.ConfirmDeletePopup(objname, deletefunc)
A quick convenience function to handle the usual "Are you really sure that you want to delete this?" popup.
P.Tooltip(selector, func, delay)
A convenience function to display a tooltip whenever the user hovers over selector for delay milliseconds. func should return the content of the tooltip as a HTML string.
P.MakeButtonBar(selector, buttons)

This function is responsible for the responsive button bars that animate on click used extensively on pafera.com. It takes a selector and renders the elements described by buttons into it.

buttons is an array containing button descriptions, which can consist of

[displaytext, onclickfunc, color, buttonclasses]

In the special case that onclickfunc is the string "custom," displaytext should be a HTML string containing the control to render at the current position.

color is a value from 1 to 6 depicting the normal Paferalib button colors, which are red, orange, green, blue, purple, and brown. Leave this blank to automatically assign colors. Yellow is reserved for hover elements.

buttonclasses are any extra classes applied to this button. Each button will already have the class "Button{num}" where num is the index of the button within the bar starting from 0.

P.MakeFloatingButton(selector, content)

A floating button is a special button which is fixed within the viewport and can be dragged around to a user's preferred spot. The idea came from Android's round action button which normally reside to the lower right corner of an app and makes clear what the current default action is. On pafera.com, we use it to implement the quickfuncs button, which is basically a command-line for the website.

Depending on what your site is, you may find it useful, or you may disgard it entirely.

P.MakeRange(selector, classname, min, max, value, title, onchangefunc)
Creates a control for selecting a numerical value which displays the current value to the right. An optional title can be displayed to the left. I tend to use it for things like ratings and volume.
P.MakeSortableTable(selector, onchangefunc), P.SortableTDs()

Enables sorting and deleting functionality for the table found by selector. The helper function P.SortableTDs() returns HTML for three tds for moving the current row up, moving the current row down, and deleting the current row. These should be the last three columns in your table for all rows. onchangefunc, if provided, is called whenever a row has been changed.

This function is designed to easily shuffle rows on small screens such as phones. For computers with the greater control of a mouse, you should use drag and drop instead.

P.OnResize()
Handles automatically loading the small, medium, and large stylesheets in apps/h/css along with the current wallpaper and text theme. You should not use this directly to monitor resize events, but use P.AddHandler('resize') instead.
P.StoreInArray(selector)
Converts dotted name and value pairs such as animals.lions.number = 5 into animals['lions']['number'] = 5. Useful for form elements when you want to convert them to a JSON array.
P.FormToArray(selector)
Converts all of the controls in selector into a JavaScript array. This include such things as inputs, textareas, selects, toggle buttons, and other custom UI controls.
P.NumToPath(num, numdigits)
Converts num into a filename and directory path such as 123456789 becoming "123/456/789." Useful with SUIDs. numdigits is the number of digits to pad the final result to, and defaults to 9.
P.DeleteParent(parentselector, selector)
A convenience function for deleting a parent element based upon a selector. For example, this can delete a table from one of its tds by using P.DeleteParent('table', '.testtd').
P.MakeAccordion(selector)

Creates a series of accordions like Wikipedia mobile uses where a quick click can collapse or expand the contents of this element. To use this correctly, each section should have a <div> surrounding it such as:

<h1>Title</h1> <div> <p>Content</p> <p>Content</p> <p>Content</p> </div> <h1>Title</h1> <div> <p>Content</p> <p>Content</p> <p>Content</p> </div> <script> P.MakeAccordion('h1'); </script>
P.SortChildrenByText(selector, children1, children2)

Does the same thing as SortArray(), only with the DOM tree.

Let's say that you have the following:

<ul> <li>oranges</li> <li>apples</li> <li>grapes</li> </ul>

Calling P.SortChildrenByText('ul') will result in

<ul> <li>apples</li> <li>grapes</li> <li>oranges</li> </ul>

children1 and children2 are selectors which give you fine control over exactly which child elements are used for sorting in nested lists.

P.MakeToggleButtons(selector, tritoggle), P.ToggledButtons(selector)

Toggle buttons are the Paferalib equivalent of checkboxes. They're special buttons which have no rounded corners and change colors to indicate their condition. Gray means no action, green means on, and red means off. Normal toggle buttons only switch back and forth betwen gray and green, while tritoggle buttons add the red option. To set these, use the data-toggled attribute with "unset," "on," and "off" values.

P.ToggledButtons() will return an array of all toggled buttons within the selector. The key will be data-name and the value will be the toggled value.

Whenever a toggle button changes its value, it will check the data-ontoggle attribute. If it's set, it will call the attribute as a global function with the parameters (event, element, newstate). This can be convenient if you're using toggle buttons to redisplay the page.

P.MakeRadioButtons(selector, buttons, onchange)

Radio buttons are either a series of buttons placed horizontally or a select element if the buttons are too many to comfortably fit within a line. The default behavior is to switch to a select if there are more than eight buttons.

buttons is an array containing [displaytext, value, initialstate, buttonclasses]. If a select is used, then buttonclasses won't be applied to the option element.

P.Set(obj)

Sets variables in the current user's session. Note that due to security concerns, a user's session variables are not available via JavaScript. You must include a section in your page such as

// Sets variables P.Set({ 'maps.zoomlevel': 5, 'maps.usegps': 1 }); // In your PHP page <script> maps = {}; maps.zoomlevel = <?=$_SESSION['maps.zoomlevel']?>; maps.usegps = <?=$_SESSION['maps.usegps']?>; </script>
P.AJAX(url, data, options)
This is your typical wrapper for XMLHTTPRequest. options include method for get or post and timeout for the number of seconds to wait before firing a timeout error. Returns a promise which can be chained using .then(onsuccess, onerror).
P.API(command, data, onsuccess, options), P.LoadingAPI(resultsdiv...), P.DialogAPI()

All of these functions call Paferalib APIs by sending data to a script and reading the JSON returned. The difference is that P.API() does nothing special while P.LoadingAPI() will fill resultsdiv with a loading GIF until the call returns and P.DialogAPI() will popup a loading GIF in the middle of the page. If the API returns JSON data with a property named error, it will be shown in resultsdiv.

options is the same as for P.AJAX() since that is the underlying call.

onsuccess will be given the JSON data returned already parsed. P.LoadingAPI() will have an extra parameter named resultsdiv to make it easier to change the loading GIF into a success message.

P.LoadingDialog()
Shows an animated loading GIF in the middle of the screen for indicating that an operation is going on without have any excessive graphical action. Call P.CloseThisPopup('.LoadingResults') when your operation is finished to dismiss the GIF.
P.SetWallpaper(filename, saveinsession)
Changes the current pages' wallpaper to filename, which must be a filename in apps/h/images/wallpaper. Set saveinsession to save this choice in the user's session.
P.SetLanguage(langcode)
Sets the user's language preference to the IETF language code provided and reloads the current page.
P.LoadURL(url, contentonly, options)

Shows a loading GIF and loads the requested page.

If contentonly is set, then loads the contents into options.contentdiv, which defaults to #Content. This, in effect, is a JavaScript refresh of the page without touching the header, navbar, or footer. On the server side, h_page will notice the query string contentonly=1 and will skip rending the full page, saving a decent bit of processing.

options can include the following:

title
The new page title
content
Manually specify content without going through AJAX
contentfunc
Use the return value of this function as the new content
data
Any additional data to keep in the history with this page
contentdiv
The element to render the new content into. Defaults to #Content to change the page.
P.PageBar(count, start, limit, orderby, numonpage, listfunc)

This function creates a buttonbar showing how many pages are available to the user and controls for selecting which page to go to.

count, start, and limit should be obvious. numonpage is how many items are on the current page. listfunc is the name of the global function to call to change the page, and defaults to "ListObjects."

listfunc should accept two arguments: start, and limit.

P.Play(id, url, options)

Plays media. Currently, this only plays audio files using howler.js as the backend.

options has only the property "autoplay" at the moment. If it's not set, the sound is simply cached for instant play at a later time.

P.AddLayer(), P.RemoveLayer(), P.CloseTopLayer()
Adds support for using the escape key to close fullscreen layers and popups. You should not bother with these unless you plan on creating your own layers outside of the P.Popup() and P.EnterFullScreen() functions.
P.EnterFullScreen(num, classes, contentlist), P.ExitFullScreen(num)

These two functions animate a fullscreen window which slides in from the left. There are four layers by default: blank, 1, 2, and 3. This allows a fullscreen window to have yet another fullscreen window on top. The requested window can be set by the num parameter. classes are extra classes to add to the window, while contentlist is a list of strings for the window's content.

Fullscreen windows are handy for things like panels, playlists, and other such UI elements that take up a good portion of room but don't require a page reload.

P.ShowDrawer(side, size, classes, contentlist), P.HideDrawer(side)

These functions are similar to P.EnterFullScreen() and P.ExitFullScreen(), except that they show drawers instead. Drawers is a window which slides out from an edge much like the Windows taskbar or iOS title bars, and is handy for quickly accessing information regardless of where the page is scrolled.

side can be "Left," "Top," "Right," or "Bottom." If not set, it defaults to "Bottom."

size is how far the bar sticks out from the edge, and defaults to 2em.

classes are any additional CSS classes to apply to the drawer.

contentlist is a list of strings containing the HTML for the drawer content

P.NoPaste(selector)
Disables pasting inside this selector. Of course, if JavaScript is disabled, then this won't do anything, so don't count on this being 100% effective if other parts of your application can work without JavaScript.
P.FlexInput(selector)
Creates a textarea which automatically resizes itself to fit the content. Very useful for forms on mobiles.
P.MakeCalendar(selector, month, switchmonthfunc, dayclickfunc)

Creates a calendar month inside selector. month should be in the format "2016-05" or blank for the current month.

If switchmonthfunc is set, the user will be able to choose the month by clicking on the buttons next to the current month. The function will be called with the selector and the desired month as parameters.

If dayclickfunc is set, whenever the user clicks upon a day, this function will be called with the event, the day element, and the date of the day.

P.OnEnter(selector, func), P.OnAltEnter(selector, func)
A quick convenience function to call func whenever enter or alt+enter is pressed while selector has focus.
P.MakeQuickFuncsButton(selector, avatar), P.AddQuickFunc(command, options, description, func)

The quickfuncs button is a floating button that appears on every page and allows functionality to be quickly accessed via a command line. It's primarily for use on mobile, but desktop users who like to use the command line should also find it handy as well.

Paferalib will activate this button by default whenever a user has logged into the site. It only has four initial functions: change password, choose bot, logout, and exit. However, any page can add commands using the P.AddQuickFunc() function.

To use a quickfunc, simply click on the button, type in your command, and then press alt+enter. If you're used to typing cd and ls, you shouldn't have any problems using it.

P.AddQuickFunc() takes four arguments: the base command, the command with all available actions, the description of what the command does, and the JavaScript function to call. Upon the user typing a command in, Paferalib will search for the base command from longest string to shortest string, then send the text behind the command and the resultsdiv to the function. Your function should do its work and then place a success message inside resultsdiv to finish.