Switch to Reading Mode

Blackwolf PHP Framework

Posted: December 28, 2008 21:17:01 • By Natasha L. • 4798 words

Note: Some content on this site, including this article, is more than a decade old, and may not accurately reflect the author's current feelings or writing style. More information here.


This document details the usage, installation, and configuration of the PHP-based modular site framework designed by Natasha L., with gracious assistance and guidance from William Harris. This system was designed to be lightweight, highly portable between sites, easily expandable, and non-restrictive to designers implementing it while making the site as user-friendly as possible to visitors as well as site owners maintaining their website.

History

The Blackwolf PHP framework was a long-term project, initially developed for Lupinia.net in 2006. In 2008, I started turning it into a more robust platform to use in clients' websites, and development of new extensions and features continued until 2012.

Requirements

This framework requires PHP 5.1.x or greater to work properly. Converting it to run on PHP 5.0.x may be possible, but attempts to port it to PHP 4.x have been unsuccessful, due to the amount of re-coding required. It has not been tested on versions greater than 5.2.x, as of this writing. The database interface currently is written for MySQL, but can be easily converted to other engines.


Files And Directories

This system can function properly with a small fileset. The files required for operation are:

Filename Location Description
class_core.php /INC/ The central object, which handles all central functions, security, basic user management/authentication, and stores/creates configuration information.
class_main_old.php /INC/ Handles all templating and layout-related functions, extends the core class. Recommended for all pages with visible output, not needed for pages without visible output. Not necessarily required for pages to function, if the site is designed not to need it.
class_user.php /INC/ Object responsible for advanced user management, including registration, login, account editing, and administrative functions. Not required if using a version of this framework with security/user features removed.
login.php / Processes user logins.
register.php / Processes user registrations. Not required for single-user sites, but omission not recommended.
account.php / Allows users to edit their accounts, and administrators to edit information for other users. Can be modified for expanded field sets, if needed.
users.php / Administrative page for managing user accounts. Provides interface for deletion, activation, promotion/demotion, and viewing security information (last IP address, last login time).

Directory structure is fairly simple. This framework expects all classes to be in /INC/, with one file per class, and the names to be in the form "class_{CLASSNAME}.php". This is done so that PHP's auto-load function can initialize them properly. This can be modified if needed, as long as the changes occur across all necessary files. Also, if using the Flash auto-embed function in class_main_old.php, all Flash files must be stored in /SWF/. This can also be changed by modifying the code responsible for it. And, most of the functions/files dealing with output assume that CSS files will be in /CSS/, and images will be in /images/.


Configuration

To reduce page generation times, and improve security, this system lacks a separate configuration file. Instead, the configuration variables are stored in class_core.php, in a well-marked area of the file. The configuration area is denoted by these comment lines. The wording was chosen because a client may need to edit the config information themselves (server moves, title adjustments, etc), so this is worded in such a way to set the user-changeable portions apart from the rest of it (which is fair game for developers using this system).

//	@@@@	DO NOT EDIT ANYTHING ABOVE THIS LINE!	@@@@
...
//	@@@@	DO NOT EDIT ANYTHING BELOW THIS LINE!	@@@@

With the exception of database credentials, all configuration variables are stored in a public associative array called config, which can be expanded as-needed. To increase security, the database credentials are stored in a protected array called db, which cannot be accessed outside of the core/main classes.

Many of the elements of the config array are dynamically generated, but some are set manually in the configuration area of the core class. The following elements are present in the config array once the constructors for main and core have been executed.

Variable Set By Contents Description
base_path (manual) string Stores the root path for the site, relative to the system root. This may not be necessary in some deployments, but it's handy to have.
cookie_prefix (manual) string Stores a string appended to each cookie, to prevent site cookies from interfering with other sites using this system. Not needed outside this object unless the setcookie() function is called outside of it (not recommended, to maximise portability of code).
title (manual) string A string containing the long title for the site. If a page-specific title is set using $section (explained in Global Variables), this will be updated accordingly.
short_title (manual) string Appended to the global variable $section, if present. Also useful for pages like login and email forms, when the name of the site needs to be inserted.
sysemail (manual) string Default From: address for system-generated emails. Not recommended to be used for output, due to spam harvesting.
email_activation (manual) bool Allow users to self-activate via email. Set to TRUE to allow self-activation. Set to FALSE to disable it, requiring admins to manually approve all new accounts.
date_format (manual) string Default date/time format, in syntax used by the date() function. Affects the format of the 'gen_date' and 'lastmod' config values, and can be used in other areas around the site.
main_table_prefix (manual) string Appended to the beginning of database table names, to keep tables for different applications separated.

locale set_locale() string String identifying the region/group for the URL the user visited. This is often not needed for most sites, but for sites with many domains, it can be helpful for managing content specific to each domains. For example, if a site has European and American versions, with different domains (or subdomains), this can be used to tell them apart for content-display purposes (can correspond to a flag in database-stored content), without requiring a completely different site. By default, this contains either "DEFAULT" (known domains for the site) or "UNKNOWN" (the catch-all case for unknown domains), but can be configured arbitrarily by editing the set_locale() function of class_core.php.
allow_cookies set_locale() bool Set by the same function as 'locale', this determines whether or not to allow cookies to be set for a particular domain. Standard values are TRUE, when locale is "DEFAULT", or FALSE when locale is "UNKNOWN". Can be set on a per-domain basis as part of set_locale().
force_www set_locale() bool/NULL Allows us to force a domain to contain www., or force it to not have it. Also used on a per-domain basis. Value of TRUE to force a domain to contain www., FALSE to force it not to contain www., or NULL to ignore it.
google_code set_locale() string Used in a meta tag in index.php to authenticate a site for Google Webmaster Tools. Must be set per-domain, due to the way Google reads this. String value of "NULL" to ignore it, all other values will be inserted as output and cause the tag to appear on the index.php page.

gen_date auto_config() string Current date and time, using the date() function.
lastmod auto_config() string Last-modified date and time for the current page.

browser detect_browser() string Server-side browser detection based on identification headers. Values vary based on the needs of the site, and can be rewritten accordingly. Typical values include "IE", "Other", "IE6", "CHROME", etc. Can be used to control output, to make browser-specific code/hacks invisible to users who don't need them.
enable_flash detect_browser() bool Used by the flash auto-insert function, but can be used outside of it if desired. Internet Explorer allows the presence of Flash player to be detected server-side, without javascript. Browsers that aren't IE (determined with the 'browser' element of the config array) always receive a value of TRUE for this. But, when it's FALSE, it's a very safe bet that the user does not have Flash. Useful for speeding up output to IE users without Flash installed (for example, on a site that requires it so heavily that an alternate version can't be constructed from the same site elements, and a redirect must be performed).

cookie_flash get_cookies() int Provides a method for users to disable Flash content if desired, and a server-side method of providing alternate content to those users. Useful for low-bandwidth users to force things like non-Flash navigation, and disabling intro movies. Value of 2 to indicate that the user has disabled Flash, 1 to indicate that Flash was detected and should be used.
Note that this function may be expanded for sites that have more cookies, so this function may generate other values. Typically, other cookie-generated values in the config array are prefixed with "cookie_".

login set_login() bool Stores the logged-in status of the current visitor. Should always be checked with === or !==. Value of TRUE for a logged-in user, FALSE for a guest. Should never contain any other value, or NULL.
user set_login() array Associative array containing information about the current user. Only present if the user is logged in.
- ID set_login() int User ID for the current user
- username set_login() string Login username
- display set_login() string Display name
- email set_login() string Email address
- last_ip set_login() string Last IP address used
- last_time set_login() int Unix timestamp of last login
- isAdmin set_login() int Administrative status. Value of 1 (not TRUE) for an administrator. All other values, including boolean, should be ignored.

db_conn db_connect() int (resource) Resource ID for the DB connection. Necessary for mysql_real_escape_string, if not already connected. Irrelevant on sites containing versions of this framework with user systems, because the connection will be established before anything else could possibly need to know this.
gen_sql db_query() int Total number of queries performed during the page load. Useful for debug information, or "bragging rights" on informal/technical sites.

Global Variables

To streamline the configuration process and extend the usability of this system while minimizing redundant code, this framework uses global variables to set various pre-load options for the main object. These can be tailored to a site's specific needs, and must be set before class_main_old.php is included. These should only be used to handle exceptions and special cases that vary from page to page. For properties that should apply site-wide, changes should be made to the main code of the site.

Variable Type Description
section string This allows a custom per-page title to be set, while still keeping a unified page template. If not present, or empty, the site will display the default title. If set, this string will be appended to the site's short title. For example, if this is set to "Page 1", then the displayed title will be something like "Our Site - Page 1".
no_header bool For pages that require processing between the initialization of the main object, and the beginning of page output. Set to TRUE to allow the page_header() function to be called manually later in the page. Only use this on pages that have output. For efficiency, pages that have no output at all may call and initialize the core class directly, without the main class.
protected string Use this to secure a page to a specific security level. If a visitor does not meet the required security level, they will be automatically redirected to the login page (for guests), or to the index page (for logged-in users). These can be adjusted for custom sites, but standard values are "user" (no guests) and "admin" (administrators only). Also, any non-empty value will prevent IP-banned visitors from visiting a page, so use any other value (like "banned", or "bloody_chavs", or "dsf97ygs97dfgsodif") to prevent banned guests from viewing a page, without obstructing anyone else. This should be modified to be a config value if the desired protection level applies site-wide.

Functions

The files used by this system have a number of public functions available, to simplify various tasks used within a site, and create a unified environment for other applications/scripts. The following public functions are built into the system. Please note that parameters surrounded by brackets are optional.

Function Returns Location Description
db_query($query) array/int class_core.php Stores the root path for the site, relative to the system root. This may not be necessary in some deployments, but it's handy to have.
db_connect([$errors]) bool class_core.php Initializes a database connection, or checks to see if one already exists. Implicitly called by db_query. Returns TRUE if successful, FALSE on error. Creates config variable 'db_conn' containing the resource ID of the connection. Semi-deprecated as a public function, since sites with users to check will initiate this function before anything else that needs it could occur.
get_user_info($id) array class_core.php Retrieves data for user accounts other than the one logged in. Useful for profiles and email forms. Returns an array containing all profile fields that the current user has permission to access (there are more for admins), or FALSE if there's no data.
is_banned() bool class_core.php Boolean function, determines whether the current visitor is banned. Returns TRUE if they're banned, FALSE if they're not.
log_action($event,[$userid]) null class_core.php Enters data in the security log, and creates automatic brute-force bans. No return. The values for $event are arbitrary, and can be added to for any site configuration. The second parameter is optional, and logs the current user ID if logged in, or the null user (0) if a guest.
set_cookie($setting,$value) null class_core.php Sets a cookie using the prefix stored in the config file for 30 days (duration can be changed by modifying the function, and exceptions can be added). The first parameter determines which cookie will be set, and can easily be modified for other cookies. The second parameter is the value that will be stored in the cookie. Using FALSE (boolean) for the value will cause the function to delete the cookie.
set_title([$section]) string class_core.php Creates a value for the 'title' config variable. Semi-deprecated, but may be relevant in sites using certain types of content management schemes.

page_header() null class_main_old.php Begins output, and creates all template data needed before content. Implicitly called by initializing the main class, unless the no_header global variable is set to TRUE. May implicitly call other functions, depending on the site.
page_footer() null class_main_old.php Finalizes all output. Mostly deprecated as a public function, since it's now implicitly called from inside the main class by the object's destructor.
load_flash($flash_src, 
           $flash_height, 
           $flash_width,
           [$flash_loop],
           [$flash_title])
string class_main_old.php Generates output code needed to embed Flash files. Semi-deprecated, but useful in some sites.

logout() null class_user.php Logs out the current user. Should only be called from login.php, or the single file responsible for logging out a user.
process_login() null class_user.php Processes the login form, and logs in the current user. No parameters, but requires the request method to be POST.
process_reg() null class_user.php Processes the registration form. No parameters, but requires the request method to be POST.
edit_user($id) bool class_user.php Updates account information for the user specified by $id. Typically used to process form data.
delete_user($id) bool class_user.php Deletes the user account specified by $id. Can be used administratively, or by users, but a non-administrator may only delete their own account.
activate_user($id,[$code]) bool class_user.php Activates a user account. If used by a regular user or guest, requires the second parameter, a hash generated by user information. If used administratively, the second parameter is not required.
grant_admin($id) bool class_user.php Promotes a user account to administrative status. Can only be used by administrators.
revoke_admin($id) bool class_user.php Revokes a user's administrative privileges. Can only be used by administrators, and requires the method to be POST, as well as requiring the explanation form to be filled out. Can be used by a user to self-demote.
user_table() null class_user.php Generates a table displaying all user accounts. If called by an admin account, displays a wide array of profile information. If called by a user account, displays only a small field set. No return, but it does echo its output, so only call it in the spot where its output should go.

Usage

This system is meant to be easy to deploy, and easy to work with once it's deployed. The original application for it was a site with individual pages for content, and was designed to simplify the process of adding new pages to the site. It was later used to rapidly deploy client websites, and build them in ways that someone with even the most rudimentary knowledge of HTML could perform most of their own maintenance. So, the starting point for a new page using this system should look something like this:

<?php
{GLOBAL VARIABLES}
require './INC/class_main.php';
?>

Page content goes here.

No additional functions are required after the content of the page; all final output by page_footer is handled by the main object's destructor function. However, if the page uses the no_header global variable, be sure to add "${MAIN}->page_header();" before any output, where {MAIN} is the variable name of the main object (set at the very bottom of class_main_old.php, to simplify the per-page PHP coding required for simple pages).


User Management Systems

One of the integral features of this framework is its user management system. Designed to be both simple and powerful, it provides the basis necessary for dynamic applications, as well as providing site administrators with the tools necessary to manage the site and its users.

The user systems are meant to handle multiple levels of security, and/or multiple types of accounts. The most notable implementation of this is the default designation of admin accounts vs. user accounts, using the 'isAdmin' database value. This allows any user to be granted administrative privileges without sharing credentials, a rather dangerous security practice. This also allows for administrative controls to be added throughout the site, as well as having them in a protected control panel.

Login/Logout
These functions are, by default, both handled by login.php, though an AJAX-based interface, or a small login form, can be added to any page with ease. The login form features a "Remember" checkbox, so that users don't have to login next time they visit the site. When checked, this stores a cookie on the visitor's computer containing hashed data uniquely identifying them for their next login for 30 days, and is renewed every time they visit the site. In order to make this a secure system, the hash is created from the user's IP address, last login time, and a number of browser-specific headers, as well as some user-specific data, and the user ID stored in plain-text (which can't be used in any form of login attempt). When visiting, if this cookie is present, the site forms a new hash based on the current visitor's information (browser, IP, etc), and a third hash based on information stored in the database from the user's last successful login. If all three hashes match, the user is logged in as normal. If not, the cookie is deleted, and the user's stored login information is invalidated, preventing any further attempts. This is, admittedly, an "overkill" level of security for most sites, but it was originally conceived as part of a rather secure site, and it has proven to be highly effective.

If the Remember checkbox is not checked, the user's login is session-based as normal. The ability to remember logins cannot easily be removed/disabled at this time, but for a site requiring it to be omitted, the code can be edited to reflect this change.

Registration and Activation
User registration is handled by register.php, a file that is recommended to be present even for sites that don't need it, due to the complexity involved in creating a valid password hash. The registration form uses live Javascript-based validation (currently driven by Adobe's Spry framework, but can easily be replaced with a lighter version of our own design), but to maintain functionality when Javascript is disabled, the form and processing script will still function perfectly without it. Thus, it can be omitted altogether, if desired. The form currently uses a simple math question to serve as a bot-filter, but experience has shown this to be only moderately effective. A more typical CAPTCHA script can be added in the future.

Upon successful registration, the site's primary administrator receives an email informing them that a new user has joined. This can be adjusted in the code to email all admins, or none, depending on the site. By default, admin activation is used, requiring administrators to manually approve new accounts. For more community-oriented sites, this can be switched by a config variable to allow self-activation by users based on an activation code received via email, but the ability of admin accounts to manually activate accounts is still retained. In either case, the user will receive an email upon joining, containing either instructions on account activation, or informing them that an administrator must approve their account before they can login. Another email is sent when an account is administratively approved, informing the user that their account is now active. This second email is not necessary when a user self-activates.

Account/Profile Editing
Users can edit their account information using account.php, which contains a form very similar to the registration form. This page can also be edited to show additional profile fields, if desired. The form is automatically populated with the user's account information on each load, to make sure the information is always accurate. It also provides an interface to change the user's password, which doesn't have to be entered in the form is the password is not being changed. The login username, however, cannot be changed once the account is created. Since it's used to generate the password hash, changing it via a form without breaking the user's ability to login is difficult to accomplish, and has been omitted for the time being. The login username is only used by the user to login to the system, so the ability to change it is irrelevant; the user can change their display name with ease if they wish to adjust their identity as perceived by other users. Since the login username cannot be seen by non-admins, and the display name cannot be used to retrieve any account information or login, setting a display name different from one's username is highly recommended for security reasons on community-based sites.

Any user viewing account.php will be presented with the Edit Account form for their own account. Additionally, an administrator viewing the form can edit any other user's account by supplying the GET variable "id" (linked from the user table). Non-administrators will not see this behaviour even if they supply this variable.

Advanced User Management
The users.php file is a multi-purpose page providing an interface for all other user management functions. It displays a table of users, containing a variety of information, including the user's display and usernames, last IP address and login time, email address, and a number of links to control the user's account. Administrators are displayed in bold, inactive accounts are displayed in italics, and if a user has opted to hide their email address, it will be displayed in italics instead of normal print. The Actions column contains, by default, icons to edit and delete each account, as well as text links based on the user's current status. For admins, a Revoke Admin link is displayed, a process that requires the revoking user to supply a reason for revokation, and sends an email to the former admin containing the reason, as well as the IP address and display name of the user performing the revokation. For inactive accounts, an Activate link is displayed, the process for which is explained above, under "Registration and Activation". And, for regular users, a Grant Admin link is displayed, a link that promotes the user to administrator status.

By default, users.php can only be visited by admins. However, in community-oriented sites, it can be changed to user-level (or even guest-level) security, and serve as a user directory. For non-admins, the table is generated with a different, smaller fieldset, containing only the display name, last login day (the time is omitted), and their email address (if not hidden). With the addition of the email form I've written to work with this system, the email address can be completely hidden to all non-admins; users can email each other using the form, but cannot access any other user's email address.

Due to its structure, users.php can also be tasked to display individual profiles simply by adding a function call to generate them. This versatility makes it a very powerful file for building robust community-oriented sites, and its security measures ensure that there can be no unauthorized use of administrative controls by non-admins. However, if preferred, the modular nature of the user management functions allows them to be split up and called by a number of different files, if desired.


Extensions

For a basic website, this framework will handle most scripted tasks without needing extra files. However, for a more complex site, extensions may be needed/desired, to create a more dynamic environment. Fortunately, this system was designed to be easy to expand, while keeping the special coding required for outside scripts/applications to a minimum.

Ideally, extensions should be classes, to maximise the effectiveness of the object-oriented nature of this site structure. To write a class that works as an extension to this framework, it only needs to copy (pass by reference) the current instance of the main/core objects into its own variable. For example, the starting code for an extension may look like this:

<?php
class MyExtension
{
	protected $main;
	
	function __construct()
	{
		$this->main = &$GLOBALS['MAIN'];	//	Replace MAIN with the variable name of the main class in the current site
	}
}
?>

Writing classes this way allows them to interact directly with the current instance of the main class, without redundant code, and without re-initializing it, maximizing security and efficiency. In this example, MyExtension can now use the security information, config variables, and database interface from the rest of the site without rewriting it. This also dramatically increases code portability, because the only thing that needs to be changed to move this to a new site is the name of the global variable containing the main object.

Theoretically, the core class can be re-initialized in other objects without affecting the rest of the site, but this approach is not recommended. Not only does it unnecessarily increase database and processing overhead, but the debug data (processing time, query counter) becomes useless.

New extensions can readily be added to any site containing this system. The following extensions have already been created, as of this writing:

  • Multi-category links manager
  • XML-based content management system (metadata/index generation only)
  • MySQL-based content management system (content pages/index generation only, in progress)
  • Simple image gallery
  • Interface for Gallery2 (image insertion in other pages, internal links, random display, album display)
  • Interface for Wordpress (post aggregation without RSS, for display elsewhere in the site)
  • Interface for Simple Machines Forum (in progress)
  • Timeclock application (in progress)
  • FAQ Manager