Blackwolf PHP Framework By Natasha L. - http://www.lupinia.net/ Posted: December 28, 2008 21:17:01 | 4798 words ======================== ### Table of Contents 1. Introduction - History - Requirements 2. Files and Directories 3. Configuration 4. Global Variables 5. Functions 6. Usage 7. User Management 8. Extensions 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](http://www.lupinia.net/framework/globals.php)), this will be updated accordingly. `short_title` | _(manual)_ | string | Appended to the [global variable](http://www.lupinia.net/framework/globals.php) $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: 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](config.php) 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: 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](http://gallery.menalto.com/) (image insertion in other pages, internal links, random display, album display) - Interface for [Wordpress](http://wordpress.org/) (post aggregation without RSS, for display elsewhere in the site) - Interface for [Simple Machines Forum](http://www.simplemachines.org/) (in progress) - Timeclock application (in progress) - FAQ Manager ======================== (c) 2008 Natasha L. Original version and further downloads available at https://seneca.lupinia.net/code/projects/php/bw-framework.htm