PHP Base Library Documentation, Release phplib_7 Boris Erdmann, be@shonline.de, Kristian Köhntopp, kk@shonline.de and Sascha Schumann, sascha@schumann.cx $Id: documentation.sgml,v 1.6 1999/10/24 13:29:34 kk Exp $ ____________________________________________________________ Table of Contents 1. Quick Start 1.1 License 1.2 Target Group and Prerequisites 1.3 Quick Guide to Installation 1.4 Using core features of PHPLIB 1.5 Testing 2. Overview and Installation 2.1 Files, classes and functions 2.1.1 Customization 2.1.2 Core functionality 2.1.3 Extended functionality 2.1.4 HTML widgets 2.2 Downloading and unpacking the distribution 2.3 Requirements and things to check for 2.3.1 Interpreter requirements 2.3.2 Database requirements 2.3.3 Name space requirements 2.3.4 Year 2000 compliance statement 2.4 Installation procedure 2.5 Using 2.6 PHPLIB with mod_php (Apache module) 3. Core Functionality 3.1 DB_Sql 3.1.1 Instance variables 3.1.2 Instance methods 3.1.2.1 Accessible instance methods 3.1.2.2 Internal instance methods 3.1.3 Example 3.1.4 Additional information about database connections 3.1.5 Using 3.2 Page Management 3.2.1 Accessible Functions 3.2.2 Example 3.2.3 The "cart" feature is gone 3.3 CT_Sql 3.3.1 Instance variables 3.3.2 Example 3.4 CT_Split_Sql 3.4.1 Instance variables 3.4.2 Example 3.5 CT_Shm 3.5.1 Instance variables 3.5.2 Example 3.6 CT_Dbm 3.6.1 Instance variables 3.6.2 Example 3.7 CT_Ldap 3.7.1 Instance variables 3.7.2 Example 3.8 Session 3.8.1 Instance variables 3.8.2 Instance methods 3.8.2.1 Accessible instance methods 3.8.2.2 Internal instance methods 3.8.3 Example 3.8.4 Using "auto_init" 3.8.5 Unregistering variables and deleting sessions 3.8.6 Reading and understanding session data for debugging 3.8.7 How "serialize()" operates 3.9 Auth 3.9.1 Instance variables 3.9.2 Instance methods 3.9.2.1 Accessible instance methods 3.9.2.2 Internal instance methods 3.9.3 Example 3.9.4 Using default authentication 3.9.5 Using Challenge-Response Authentication 3.9.6 The complete guide to authentication and user variables 3.9.6.1 How is the 3.9.6.2 How does 3.9.6.3 How do $sess and $auth interact? 3.9.6.4 Where is the beef? 3.9.6.5 I still do not understand! What am I supposed to code? 3.9.6.6 Ok, I did that and it works. I even understood it. Now, what exactly is that uid used for? 3.9.6.7 But is the uid used internally by PHPLIB? 3.10 Perm 3.10.1 Instance variables 3.10.2 Instance methods 3.10.2.1 Accessible instance methods 3.10.2.2 Internal instance methods 3.10.3 Example 3.10.4 How permissions work 3.11 User 3.11.1 Instance variables 3.11.2 Instance methods 3.11.2.1 Accessible instance methods 3.11.2.2 Internal instance methods 3.11.3 Example 4. Extended functionality 4.1 Cart 4.1.1 Instance variables 4.1.2 Instance methods 4.1.2.1 Accessible instance methods 4.1.3 Example 4.1.4 On using Cart 4.2 Template 4.2.1 Instance variables 4.2.2 Instance methods 4.2.2.1 Accessible instance methods 4.2.2.2 Internal instance methods 4.2.3 Example 5. HTML Widgets Classes 5.1 Sql_Query 5.1.1 Instance variables 5.1.2 Instance methods 5.1.2.1 Accessible instance methods 5.1.2.2 Internal instance methods 5.1.3 Example 5.2 Table and CSV_Table 5.2.1 Instance variables 5.2.2 Instance methods 5.2.2.1 High-level instance methods 5.2.2.2 Mid-level instance methods 5.2.2.3 Low-level instance methods 5.2.3 Example 5.3 Form 5.3.1 Using OOH Forms 5.3.2 Customizing OOH Forms 5.4 tpl_form 5.4.1 Instance variables 5.4.2 Instance methods 5.4.2.1 Accessible instance methods 5.4.2.2 Internal instance methods 5.4.3 Example 5.5 Tree 5.5.1 Instance variables 5.5.2 Instance methods 5.5.2.1 Accessible instance methods 5.5.3 The Tree Array 5.5.4 Example 5.5.5 Known Bugs / Tips 5.6 STRINGS2 function set 6. Acknowledgments ______________________________________________________________________ 11.. QQuuiicckk SSttaarrtt The Quick Start chapter tries to give you a ten-minute introduction to PHPLIB installation, outlines a few simple testing procedures and closes with an overview of PHPLIB features. 11..11.. LLiicceennssee PHPLIB consists of the files in this directory and all its subdirectories. It is made available as free software under the LIBRARY GNU General Public license, as spelled out in the file COPYING in this directory. Also, it is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the license for more details. 11..22.. TTaarrggeett GGrroouupp aanndd PPrreerreeqquuiissiitteess PHPLIB targets the PHP application developer. You need to have good knowledge of the PHP language, at least basic SQL database knowhow and at least basic knowledge on how to operate your web server to be able to use the library. The library will help you to write medium to large sized data-driven web applications. "Medium to large sized applications" are applications that consist of multiple database queries, have to generate tables from database data, need a user interface that generates SQL queries or need a comfortable and user-friendly way to protect pages or functionality on pages. "Data-driven" applications are applications that make use of a supported SQL-database to create HTML content and that use HTML forms to drive database transactions. To make use of the library you obviously need access to a web server with a working installation of a current PHP interpreter (we recommend 3.0.12 or newer for this release of the library) and access to a supported SQL database (currently, PHPLIB supports MySQL, PostgreSQL, mSQL, Oracle 7 and Oracle 8, Sybase, Microsoft SQL Server and ODBC databases). You need to be able to create and drop database tables in that database and your web server must be able to execute SELECT, INSERT, UPDATE and DELETE statements on these tables. Throughout this manual, we assume that you are using the MySQL database server. PHPLIB will run with any supported SQL server, but we are using MySQL in the development of PHPLIB. PHPLIB can be used in conjunction with the CGI version of PHP and with mod_php, integrated into Apache. Usage of the CGI version has an impact on overall speed, because you cannot take advantage of persistent database connection. We recommend the Apache module over the CGI version, although we personally use the CGI version for various reasons (easier to update and can be run with Apache suexec). PHP 4 is still in beta. We do not support deployment of this library with beta software. 11..33.. QQuuiicckk GGuuiiddee ttoo IInnssttaallllaattiioonn These instructions apply to PHPLIB running with CGI PHP. Most of them are valid for mod_php as well, though. _V_E_R_Y _I_M_P_O_R_T_A_N_T _N_O_T_E_: This is a quick installation guide to get you started if you have an installation where you control the web server, PHP interpreter and database server completely. They are not suitable for a web hosting setup where you have only limited to no control over the installation. Refer to Chapter 2 of this documentation for the complete installation instructions and troubleshooting information. Before installing PHPLIB, get your web server up and running and have it executing files with the extension .php3. Check that with a simple script. Make sure the web server accepts index.php3 as well as index.html as a default file for URLs ending in "/" (Apache: DirectoryIndex index.html index.php3). Get your MySQL database server up an running. Create an empty database for your application and make sure the owner of your web server processes can access this database with SELECT, INSERT, UPDATE and DELETE access. Don't forget the mysqladmin reload after changing the user and db tables. SStteepp 11 Create an include directory named php parallel to your web servers document root directory. Do not put the include directory below your web servers document root. SStteepp 22 Unpack your PHPLIB distribution. Move the contents of the php distribution directory into the php directory you just created. SStteepp 33 Get to the php3.ini file for your web servers PHP interpreter and update the include_path statement so that it points to that php directory. Update the auto_prepend_file statement so that it points to the prepend.php3 file in that include directory. If you do not have control over your php3.ini file, you did not read the _V_E_R_Y _I_M_P_O_R_T_A_N_T _N_O_T_E above. SStteepp 44 Also check that track_vars are enabled and that you have enabled magic_quotes_gpc. While you are at it, you might want to check sendmail_path, if you plan to send mail from your application. It has to be set to /usr/lib/sendmail -t on most UNIX systems to work. If you do not have control over your php3.ini file, you did not read the _V_E_R_Y _I_M_P_O_R_T_A_N_T _N_O_T_E above. SStteepp 55 cd into the php include directory. Edit local.inc. In class DB_Example supply the appropriate parameters for your database connection. SStteepp 66 For this database, run create_database.mysql from the distribution to create active_sessions and auth_user. auth_user will be populated with a sample user named kris with a password test. SStteepp 77 Move the contents of the pages directory and all its subdirectories into your document root directory. SStteepp 88 Access the "/" URL of your web server with cookies enabled. If no index.html is present, index.php3 will be displayed. If you reload that page, the number shown must increment. Access your database with the mysql command client and select * from active_sessions. Check that there is a single session record for your browser and see how the text in val changes when you reload the page and select * from active_sessions again. If this works, the session class is functional with cookie mode. SStteepp 99 Now access showoff.php3. Try to login as kris, password test. Check active_sessions again. You now should have a Example_Session entry (see the name column) and a Example_User entry in your table. Both should increment on reload. SStteepp 1100 Try again with cookies disabled. You should get a new session (the cookie is lost) and you should be able to see your session id as the get parameter part of your URL. 11..44.. UUssiinngg ccoorree ffeeaattuurreess ooff PPHHPPLLIIBB Many applications don't use PHPLIB's advanced features, but see PHPLIB as a convenient way to protect pages or functionality with passwords. This section covers such core functionality usage of PHPLIB. CCuussttoommiizziinngg tthhee llooggiinn ssccrreeeenn Edit loginform.ihtml in the include directory to suit your needs. CCuussttoommiizziinngg tthhee ppeerrmmiissssiioonn lleevveellss Edit local.inc and change the class Example_Perm to enumerate your permissions. Your users in auth_user must have one or more comma separated permission names from that list. Edit perminvalid.ihtml for a suitable error message. CCrreeaattiinngg NNeeww UUsseerrss Use new_user.php3 from the pages/admin directory of the distribution. If you followed the installation instructions, it should be available under the /admin URL of your web server. To manually create a user, run print md5(uniqid("some magic string") to get a user id. insert into auth_user values ( "that userid", "username", "password", "permissions");. CCrreeaattiinngg aann uunnpprrootteecctteedd sseessssiioonn ppaaggee Begin that page with ___________________________________________________________________ "Example_Session")); ?> ___________________________________________________________________ End that page with ___________________________________________________________________ ___________________________________________________________________ CCrreeaattiinngg aa pprrootteecctteedd sseessssiioonn ppaaggee Begin that page with ___________________________________________________________________ "Example_Session", "auth" => "Example_Auth", "perm" => "Example_Perm")); $perm->check("desired protection"); ?> ___________________________________________________________________ and end that page with ___________________________________________________________________ ___________________________________________________________________ CCrreeaattiinngg pprrootteecctteedd ffuunnccttiioonnaalliittyy Begin that page with ___________________________________________________________________ "Example_Session", "auth" => "Example_Auth", "perm" => "Example_Perm")); ?> ___________________________________________________________________ and end that page with ___________________________________________________________________ ___________________________________________________________________ Enclose the protected functionality in ___________________________________________________________________ have_perm("desired protection")): ?> Put protected HTML or PHP here ___________________________________________________________________ _N_o_t_e_: desired protection is any combination of permissions from Example_Perm. Using the default values from Example_Perm, "user", "user,author" or "admin" are all valid sample values. A user can access a page, if that user has all permissions that are being requested in a $perm->check() or $perm->have_perm() call. _N_o_t_e_: Users can have multiple permission in their perms column of auth_user. A user with perms "user,author,editor" can access all pages requesting any combination of these permissions. _N_o_t_e_: Don't use spaces. "user,author,editor" works. "user, author, editor" does not. _N_o_t_e_: If $auth->auth["uid"] is set on a protected page _a_n_d if (time < auth->auth["exp"]), then and only then the authentication is valid. You may then use $auth->auth["uname"] as the user name, $auth->auth["uid"] as a unique user id and $auth->auth["perm"] for the current permissions of that user. Actually, you never want to touch $auth->auth["perm"] manually, but use $perm->have_perm("...") for that. GGeettttiinngg aa ggrriipp oonn PPHHPPLLIIBB Read on. Then read the source. Read it again - Session->serialize() and Auth->start() are ugly. Get a CVS account. Contribute. Become famous. Buy a ferrari. _N_o_t_e_: You want to understand what registered variables are. You want to understand in what order form variables and session variables are imported into your page. You want to understand how to copy values from form values into session values without killing yourself. You do not want to make form variables persistent, ever. Then you will live happily thereafter... 11..55.. TTeessttiinngg These instructions apply to PHPLIB running with CGI PHP. Most of them is valid for mod_php as well, though. This section offers an incremental approach to find installation problems, should the above installation process fail. We do have a support mailing list available under the address phplib@lists.netuse.de. To subscribe to the list, send the command subscribe to the address phplib-request@lists.netuse.de. CChheecckkiinngg tthhaatt tthhee wweebb sseerrvveerr iiss uupp aanndd rruunnnniinngg Make sure your web server is up and serving the virtual host you just set up. To do this, construct a small file test1.html in your DocumentRoot and access test1.html through your web server. CChheecckkiinngg tthhaatt tthhee wweebb sseerrvveerr iiss eexxeeccuuttiinngg CCGGII pprrooggrraammss Make sure your web server is up and does run CGI. Check the current directory, the UID/GID it is running programs under and have a look at the environment variables. Install the shell script ___________________________________________________________________ #! /bin/sh -- echo "Content-Type: text/plain" echo id echo pwd echo env | sort echo ___________________________________________________________________ in your cgi directory under the name of cgi-test and in your document root under the name of cgi-test.cgi. Make it executable. Try to access /cgi/cgi-test?par1=one&par2=two and /cgi- test.cgi?par1=one&par2=two and check the output. What UID/GID are you running under, what is the output of pwd and what environment variables are set? What does QUERY_STRING look like? What does the PATH variable look like, what does the LD_LIBRARY_PATH variable look like and are all libraries needed by PHP accessible to PHP running in the CGI environment (Check by running the Unix ldd command on PHP). In particular, if you built Oracle support into PHP and linked libclntsh dynamically: Can it be loaded from the CGI environment? If not, PHP will not come up later in the next step. CChheecckkiinngg tthhaatt tthhee PPHHPP iinntteerrpprreetteerr iiss rruunnnniinngg ((AAssssuummiinngg CCGGII PHP)" Copy your PHP binary into the cgi binary directory (which should NOT be below DocumentRoot!) and make it executable. Copy php3.ini into the same directory. In DocumentRoot, create a test2.php3 and put into it. Are you running Apache? Add ___________________________________________________________________ Action php3-script /cgi/php AddHandler php3-script .php3 DirectoryIndex index.php3 index.html index.htm FancyIndexing on ___________________________________________________________________ to your config. This will map all requests to files ending in .php3 to the php3-script handler and define /cgi/php as the URL handling php3-script requests internally. Request /test2.php3 and see that it is being executed. Make changes to your php3.ini (preferable some color definitions) and reload. Are they reflected in the output of phpinfo()? If not, your php3.ini is not being found and your are having a problem. Recompile with proper settings. Check the output of phpinfo() carefully! Is your PHP version current (We have tested and developed this release with PHP 3.0.12)? Are your database interfaces present in the output of phpinfo()? If not, recompile again. Can you access /test2.php3 under the URL /cgi/php/test2.php3 as well? If so, you did not compile your PHP interpreter with --enable-force-cgi-redirect. PHPLIB will not work with this interpreter. Recompile with the switch being set. PPHHPP iinntteerrpprreetteerr ((AAssssuummiinngg mmoodd__pphhpp)) Assuming your server is already correctly setup (don't forget to activate the PHP lines in srm.conf!), enter the following file and save it as test2.php3 under your DocumentRoot. ___________________________________________________________________ ___________________________________________________________________ If you access this using a web browser now, it should spit out much info about PHP, Apache and its environment. CChheecckkiinngg PPHHPPLLIIBB iinncclluussiioonn Does you PHP include PHPLIB properly? Check your php3.ini file. It must include the following settings: ___________________________________________________________________ include_path = pathname to directory with all the .inc files auto_prepend_file = path to prepend.php3 track_vars = On ___________________________________________________________________ It should contain the following settings, too: ___________________________________________________________________ magic_quotes_gpc = On ___________________________________________________________________ If PHPLIB is included properly by your setup, the following page will execute without errors: ___________________________________________________________________ \n"; ?> ___________________________________________________________________ CChheecckkiinngg ddaattaabbaassee ccoonnnneeccttiivviittyy PHPLIB installation requires that you adapt local.inc properly. Particularly, the provided class DB_Example must be customized for your database connection. Test that your web server can access the database with the following page: ___________________________________________________________________ query("select * from auth_user"); $t = new Table; $t->heading = "on"; $t->show_result($db); ?> ___________________________________________________________________ When executing properly, this page will show you the user entry for kris, password test, permissions admin from the auth_user table. If this does not happen, your DB_Example definition in local.inc is broken. CChheecckkiinngg tthhaatt sseessssiioonnss wwoorrkk Access the page /index.php3 that has been provided with the distribution. This page will try to set a cookie in your browser. Allow that cookie to be set. The page will display a headline with a counter. Reload that page. The counter must increment. If not, either your browser cannot deal properly with cookies or PHPLIB cannot properly read or write the table active_sessions in your database. Check that the cookie is being set by viewing the output of phpinfo() (the fourth table will report the cookie and other per-call data). Check your database permissions with your database command line interface. CChheecckkiinngg tthhaatt AAuutthheennttiiccaattiioonn wwoorrkkss Try loading /showoff.php3 that has been provided with the distribution. This page will require a login. Login as kris, using a password of test. If the login is successful, you will see the per-session counter and a per-user counter again. Reload that page: The counters must increment. If you can't login, you probably have a problem with cookies. Check again that your browser accepts and sends session cookies. Another problem may be access to the auth_user table. You must be able to SELECT on that table and there must be at an entry for the user you are trying to login. 22.. OOvveerrvviieeww aanndd IInnssttaallllaattiioonn The following sections discuss the installation, verification and layout of PHPLIB: How to install PHPLIB? Which functionality and class definitions are contained in which files? How do you layout a web server with PHPLIB installed? Which installation options are available and how do these affect performance? 22..11.. FFiilleess,, ccllaasssseess aanndd ffuunnccttiioonnss PHPLIB contains a set of core classes and functions that offer session tracking, per-session and per-user persistent variables, user authentication and permission checking. Building upon this core functionality, PHPLIB offers a set of commonly needed "background" classes and a set of "HTML widgets", classes that allow you to quickly generate HTML based user interfaces. All PHPLIB definitions are designed that you don't need to change any of these files. Your customization of PHPLIB can be contained in two or three files, depending on the setup: local.inc, setup.inc and, in some cases, prepend.php3. You _N_E_V_E_R need to change any other file with PHPLIB. Details are outlined below. 22..11..11.. CCuussttoommiizzaattiioonn The following three files are the only files from PHPLIB that require changes in normal PHPLIB applications. AApppplliiccaattiioonn ccoonnffiigguurraattiioonn iinn local.inc: Your application will almost certainly not work with the default values supplied by the above classes. You are supposed to extend the classes described below as you see fit. In your subclasses, you only have to specify what is different in your application. These are things like database host names, database names, table names and username/password combinations. You need to provide login screen definitions (HTML) and user validation functions (SQL) to make the example work. The distribution provides a local.inc to illustrate a typical setup. These definitions are also needed to get the administration and testing scripts provided with the distribution to run. The file is required and you must change it for your setup. AApppplliiccaattiioonn sseettuupp iinn setup.inc: The Session class provides the ability to execute initialization code at session setup. See the class description for instructions on how to set this up. Per convention, we store such code in setup.inc in the include directory. The code is being executed whenever a new user connection to out application and a new session is started. The file is optional. No default is provided. SSeelleeccttiioonn ooff aauuttoommaattiiccaallllyy llooaaddeedd ccllaasssseess iinn prepend.php3 The file prepend.php3 determines which code is being loaded for all PHP3 interpreted pages. Normally, we include the class definitions for all core classes in this file: db_mysql.inc, session.inc, auth.inc, perm.inc, user.inc, then your local customizations from local.inc and the page management functions from page.inc. You must change prepend.php3 to reflect the database interface that you are using: Change the require statement for db_mysql.inc appropriately. If you are not using some core features from PHPLIB in your application or if you want some other features to be present on all your pages, you can delete or add require statements for their respective include files here. The file is required. You must change it for your setup, unless you are using MySQL. 22..11..22.. CCoorree ffuunnccttiioonnaalliittyy The following files are included from prepend.php3 and provide definitions for the core classes of PHPLIB. We recommend that you always include all of them, as they are a tightly integrated set of classes with many dependencies among them. CCllaassss DB_Sql defined in exactly one of db_mysql.inc, db_msql.inc, db_pgsql.inc, db_odbc.inc, db_sybase.inc, db_mssql.inc, db_oracle.inc or db_oci8.inc:" A database access class for your database server. PHPLIB depends on the presence of a SQL database server. Depending on the type of your database server, you have to select the appropriate include file. The file contains the definition of a class DB_Sql suitable for your database server. The class manages a database connection (connection setup is implicit) and result memory is managed automatically. An independent class. CCllaassss Session defined in session.inc: Manages an arbitrary amount of arbitrarily named session variables of scalar, array and object types (Object support requires that you implement two instance variables in your classes). Tracks sessions via cookies or a get-variable appended to each URL. Depends on DB_Sql. CCllaassss Auth defined in auth.inc: Manages session authentication. Sessions are authenticated against usernames and passwords in a database. Authentication can be time limited. Depends on Session and DB_Sql. CCllaassss Perm defined in perm.inc: Manages permission checks on authenticated session pages. Protected pages are only accessible to users with the specified rights. Depends on Auth, Session and DB_Sql. CCllaassss User defined in user.inc: Manages user dependent variables. Unlike session variables these are bound to a user id, not to a session id. They are persistent over multiple sessions, but are only available after a user has been authenticated. Depends on Auth, Session and DB_Sql, extension of Session. ffuunnccttiioonnss page_open() and page_close() defined in page.inc:" Setup and Shutdown functions, must be present on any session page. Depend on Session. 22..11..33.. EExxtteennddeedd ffuunnccttiioonnaalliittyy The extended functionality classes offer GUI-less background features that are commonly needed in HTML-applications. They may make use of core functionality (indicated for each class below). Cart in cart.inc: Manages a simple shopping cart. Items can be put into the cart, taken out of the cart and the carts contents can be enumerated. Depends on Session to be useful. Requires that you add the statement require("cart.inc") to prepend.php3. Template in template.inc: Manages templates and variable replacement. Templates can be stored in files. They are loaded on demand and variables are replaced in these files. An independent class. Requires that you add the statement require("template.inc") to prepend.php3 or that you include it manually on each page where you want to use it. 22..11..44.. HHTTMMLL wwiiddggeettss HTML widgets are classes that generate some HTML-code (often forms or tables) to display GUI-elements. We try to provide functionality commonly used in applications, in a way that the actual look of the GUI-elements can be easily customized. CSV_Table in csv_table.inc: Creates a dump of a two dimensional array or a query result in CSV format, suitable for loading into a database or a spreadsheet program. Depends on Table, extension of Table. Sql_Query in sql_query.inc: Create a selection widget that allows a user to choose arbitrary conditions on one or more table columns. SQL is being created from these selections that can be used in the where-clause of a larger SQL select statement. Depends on Session and DB_Sql. Requires that you add the statement require("sqlquery.inc") to prepend.php3. Table in table.inc: Creates HTML tables from two dimensional arrays or from database query results. The class can either filter out the desired columns from an array or you can explicitly name which columns to show. A heading can be turned on if desired. All generated HTML elements are tagged with a classname you specify for stylesheet support, if needed. When used in a form tag, each table row can be prefixed with a checkbox input element to allow for row selection. An independent class. Form in oohforms.inc: Creates HTML forms from feature->value arrays. This provides a single syntax for creating all of the different types of form elements. The class provides easy access to Javascript and server side validation, and supports 'freezing' some or all of the form elements to display static data. In addition, the library relies on object oriented implementations for the various form elements and these can easily be extended and customized. An independent class. 22..22.. DDoowwnnllooaaddiinngg aanndd uunnppaacckkiinngg tthhee ddiissttrriibbuuttiioonn The base library is supplied at the PHP Base Library download location. Two different formats are provided: A tar.gz version and a shar version. If you are on a windows system, you can use phplib.tar.gz, if you have WinZIP installed. Current versions of WinZIP know how to handle compressed tar archives. The uncompressed files may be installed on your windows system or transferred to your Unix system. If you can't handle binary files, you may download phplib.shar. This is a pure ASCII file containing a self extracting shell script. Just save the file, make it executable and feed it to your Unix shell (for example, by typing sh phplib.shar). The PHPLIB support mailing list is available should you run into problems with the library. To subscribe send the command subscribe to the mailing list subscription address. 22..33.. RReeqquuiirreemmeennttss aanndd tthhiinnggss ttoo cchheecckk ffoorr 22..33..11.. IInntteerrpprreetteerr rreeqquuiirreemmeennttss The PHP base library requires a working web server with CGI capability and the CGI version of PHP 3.0.12 or higher installed. Alternatively mod_php can be used. Lower versions of PHP do not work at all: The session class uses the base64_encode() and base64_decode() functions which are known to be buggy in lower versions (up to 3.0.7) of the library. Also, the OOH Forms classes are using constructor syntax, which has been introduced into the PHP language in 3.0.5 and later versions. An issue with the $PHP_SELF variable and CGI PHP has been resolved with version 3.0.5 and later. Perl regular expression functions are being used in the Template class and these are not really avilable up to 3.0.12. _N_o_t_e_: If you are using CGI PHP, it _m_u_s_t have been compiled with the --enable-force-cgi-redirect switch for $PHP_SELF to have the correct value. Basically, if PHP_SELF is the exact local part of your $URL, all is well. If it instead contains the modified URL with /your cgi-bin/php prefixed, you have a buggy version of CGI PHP. Either upgrade your version of PHP or replace all occurrences of $PHP_SELF with $PATH_INFO in PHPLIB. _N_o_t_e_: PHPLIB requires that you have track_vars compiled in and enabled. _N_o_t_e_: PHPLIB does not require short_open_tag to be enabled. The library always uses .inc. The require() statement has to be adapted to reflect this). Assuming your database server is named database.netuse.de and your CGI user is webuser and you are accessing the database myapp, do ___________________________________________________________________ mysql -h database -u webuser myapp ___________________________________________________________________ If it does not work, connect your database as administrator and create the proper mysql access permissions. Adapt and run create_database.mysql from the stuff subdirectory of the distribution to create the databases active_sessions and auth_user as well as the sample user kris with password test. Try again to connect like shown above. Can you do select * from active_sessions? and insert into active_sessions values ("1", "2", "3", "") as well as delete from active_sessions? Can you select * from auth_user? _N_o_t_e_: Additional database creation scripts are provided for several different databases in the stuff directory of the distribution. MMeerrggiinngg tthhee lliibbrraarryy wwiitthh yyoouurr PPHHPP ffiilleess Decide if you want to use include or auto_prepend_file. We do use auto_prepend_file here and we add the statement auto_prepend_file = /home/www/servers/phplib.netuse.de/php/prepend.php3 to our php3.ini. Not all classes are included/required by prepend.php3, only core functionality files are: db_xxx.inc, ct_sql.inc, session.inc, auth.inc, perm.inc, user.inc, local.inc and page.inc. The library provides other, less essential classes that can be included manually on a page-by-page basis. Some classes make themselves persistent, if used. These classes require that you include their definitions in the prepend.php3 file where indicated to function correctly. See the usage instructions for these classes for details. Having done this, access /index.php3. The counter should increment when that page is being reloaded. Also, checking active_sessions in the database should reflect that session. SSuubbssccrriibbee ffoorr ssuuppppoorrtt Subscribe to the mailing list phplib@lists.netuse.de. Do so by sending a mail body of subscribe to phplib- request@lists.netuse.de and follow instructions. Share your experiences. 22..55.. UUssiinngg iinncclluuddee(()) iinnsstteeaadd ooff aauuttoo__pprreeppeenndd__ffiillee== If you do not want to use auto_prepend_file to load the PHPLIB core functionality, you can load the class definitions for the core manually on each page that requires them. You will have to define a valid include_path=-statement in your php3.ini file as outlined previously to reflect the location of the *.inc files. Then, all core functionality can be loaded with include("prepend.php3") as the first statement at the top of each page. To further optimize performance, you can minimize the contents of the prepend file, if you do not need all core functionality. You _m_a_y leave out auth.inc, perm.inc and user.inc, if you do not require these features (note that there are dependencies among these classes!). 22..66.. PPHHPPLLIIBB wwiitthh mmoodd__pphhpp ((AAppaacchhee mmoodduullee)) Installing PHPLIB onto a web server that has PHP3 as a module (actually Apache) mainly differs in where you can set up runtime settings for PHP3 itself. PHP3 can be compiled with a wealth of parameters (see the PHP section in phpinfo()), most of which can get overridden by the php3.ini file. The location of this file is shows as part of the output of phpinfo(). With PHP3 as a module you have a wider choice on placing these settings: they are overridden, in this order, by what is defined in httpd.conf and in your per-directory .htaccess file. Directives in these files are identical to their php3.ini brothers, but are prefixed with php_ to avoid clashes with Apache configuration keywords. Also, as they are Apache configuration keywords, they have no equals ("=") sign in them. If x=y is a configuration directive from php3.ini, you should be using php3_x y within the Apache configuration instead. That is, you should prepend php3_ to the keyword and omit the equals sign. If you misspell a configuration directive, you will get an error 500 from your webserver and find more details about the error in the logfile you configured with ErrorLog in your webserver setup. _E_x_a_m_p_l_e_: If below we talk about setting in your php3.ini the configuration ______________________________________________________________________ include_path = "/bla" ______________________________________________________________________ mod_php users may alternatively configure in their httpd.conf the following: ______________________________________________________________________ php3_include_path "/bla" ______________________________________________________________________ Of special interest to PHPLIB users are the following directives: ______________________________________________________________________ ;;;;;;;;;;;;;;;;; ; Data Handling ; ;;;;;;;;;;;;;;;;; magic_quotes_gpc = Off ; magic quotes for incoming ; GET/POST/Cookie data magic_quotes_runtime = Off; magic quotes for runtime-generated data, ; e.g. data from SQL, from exec(), etc. magic_quotes_sybase = Off ; Use Sybase-style magic quotes ; (escape ' with '' instead of \') track_vars = On ; enable $PHP_GET_VARS[], $PHP_POST_VARS[] ; and $PHP_COOKIE_VARS[] arrays ; automatically add files before or after any PHP 3.0 document auto_prepend_file = (add path to prepend.php3 here) auto_append_file = ;;;;;;;;;;;;;;;;;;;;;;;;; ; Paths and Directories ; ;;;;;;;;;;;;;;;;;;;;;;;;; include_path = (add path to the directory with all .inc files) ______________________________________________________________________ All of this comes very handy when you have multiple virtual hosts (e.g. you are an ISP). In this case you can comfortably place the php3 directives in the block or in an .htaccess file in the client directory. 33.. CCoorree FFuunnccttiioonnaalliittyy Each class contains instance variables and instance methods. Some of these variables and methods are available for customization, some are internal to the classes themselves. All are documented, but tampering with internal variables and methods is not supported. Internal interfaces are subject to change without notice from one version of the library to another. This section covers PHPLIB core functionality in reference form. Classes are presented in order of dependency, though, because the core structure is easier understood in this order. You will need to understand the complete core structure to successfully use all of PHPLIB's features. 33..11.. DDBB__SSqqll DB_Sql is used by CT_Sql and Auth to access a SQL database. You are encouraged to use it directly, too. 33..11..11.. IInnssttaannccee vvaarriiaabblleess Accessible instance variables. Internal instance variables. 33..11..22.. IInnssttaannccee mmeetthhooddss 33..11..22..11.. AAcccceessssiibbllee iinnssttaannccee mmeetthhooddss DDBB__SSqqll(($$qquueerryy == Constructor. When creating an instance, you may optionally supply a query string. ___________________________________________________________________ $db = new DB_Sql_Subclass("select * from mytable");) ___________________________________________________________________ qquueerryy(($$qquueerryy__ssttrriinngg)) query_string is a SQL statement that is sent to the database. After sending the statement, Error and Errno are updated. If the query is syntactically incorrect (no valid result id is being produced), halt() is called with a meaningful error message. If there is no active link to the database, a pconnect() is made using the information from the Host, Database, User and Password instance variables. Returns the result of the query() statement, which is guaranteed to be a valid result id (or false, if Halt_On_Error isn't "yes"). nneexxtt__rreeccoorrdd(()) next_record() advances the cursor through the current query result and updates the Record, Row, Errno and Error instance variables. Returns true, if there is a new result record. Returns false, if done with the current result set. If Auto_Free is true, free_result() is called automatically before false is returned. nnuumm__rroowwss(()),, nnff(()) Returns the number of rows returned by the current SELECT query. _N_o_t_e_: This information is not available in all database interfaces. Some of the more advanced databases begin to return query results asynchronously while the backend is still appending result rows. In such environments the complete size of the result set is never known. You should duplicate your WHERE clause of the query in such environments and ask for the COUNT(*). This will be less inefficient as it seems as the query path and query result have been cached by the database. aaffffeecctteedd__rroowwss(()) Returns the number of rows affected by the current INSERT, UPDATE or DELETE query. nnuumm__ffiieellddss(()) Returns the number of columns returned by the current query. nnpp(()) Prints the number of rows returned by the current query. ff(($$ffiieelldd)) Identical to accessing Record[$field]. pp(($$ffiieelldd)) Identical to printing Record[$field]. hhaallttmmssgg(($$mmssgg)) This function is called by halt() and will actually print the database error message. You may override this method in your subclass of DB_Sql and format the error message to be consistent with the layout of the rest of your application. You may also add additional error handling such as informing the application operator by mail that a database error has occured. sseeeekk(($$ppooss)) Positions the Row pointer within the result set. Useful for reading the same result set twice or otherwise jumping around within the result. $pos is not checked in any way for validity. _N_o_t_e_: If Auto_Free is true, seek() may not be useable, because the result set has already been free'ed when next_record() when behind the last record of the result set. _N_o_t_e_: Not all database interfaces provide a cursor that is capable of seeking. This function will be unavailable in such environments. lliinnkk__iidd(()) This function will return the current link ID, as returned by the pconnect() executed internally by the database class. You should not need this information. qquueerryy__iidd(()) This function will return the current result ID, as returned by the query() executed internally by the database class. You should not need this information. mmeettaaddaattaa(($$ttaabbllee == $table is a SQL table name in the current database. The function returns an array of hashes indexed on the (0 based) column number of $table. Each hash is indexed by table (table of which this column is part of), name (name of this column), type (column data type), len (column width) and flags (database specific column flags, if applicable) with one row per table column. Each row describes a column in your table. The data returned by metadata() is suitable for passing it to the Table class. If you specify the full parameter, an additional column meta is added, which is indexed by field name and returns the field number of that name. Also, a column num_fields is added, containing the width of the table. If $table is omitted, the function returns metadata on the result of the last executed query. _N_o_t_e_: This is currently implemented only for the MySQL interface. You are encouraged to implement this feature for other interfaces. _N_O_T_E_: At the moment, the PostgreSQL and ODBC interface only report the table, name and type data reliably. You are encouraged to fix this. ttaabbllee__nnaammeess(()) Returns an array with table name and tablespace name. ___________________________________________________________________ table name : $return[$i]["table_name"] tablespace_name : $return[$i]["tablespace_name"] ___________________________________________________________________ Tables are from $i=0 to last table; Implemented in db_oracle.inc,db_oci8.inc,db_mysql.inc,db_pgsql.inc nneexxttiidd(($$sseeqquueennccee__nnaammee)) This function will return a sequence number from the sequence named by $sequence_name. This number is guaranteed to be obtained in an atomic manner and can be used as a primary key. 33..11..22..22.. IInntteerrnnaall iinnssttaannccee mmeetthhooddss ccoonnnneecctt(()) Used internally to generate a Link_ID, if necessary. Link creation is implicit, there is no need to call connect() manually, ever. hhaalltt(($$mmssgg)) Used by query() if the initial database connection cannot be made or the target database does not exist. Depending on the setting of Halt_On_Error, this method will call haltmsg() to report the error. ffrreeee(()) Used internally by next_record() to free the result set, if so configured. 33..11..33.. EExxaammppllee Use a subclass to provide the appropriate parameters for a database connect. You may overwrite halt() to customize the error message, although a sensible default is provided. ______________________________________________________________________ class DB_Article extends DB_Sql { var $classname = "DB_Article"; var $Host = "sales.doma.in"; var $Database = "shop_project"; var $User = "webuser"; var $Password = ""; function haltmsg($msg) { printf("Database error: %s
\n", $msg); printf("MySQL Error: %s (%s)
\n", $this->Errno, $this->Error); printf("Please contact shopmaster@doma.in and report the "); printf("exact error message.
\n"); } } ______________________________________________________________________ Use an instance of the subclass to manage your queries: ______________________________________________________________________ $q = new DB_Article; $query = sprintf("select * from articles where article like '%%%s%%'", $searchword); $q->query($query); while($q->next_record()) { printf("%s%s\n", $q->f("art_id"), $q->f("article")); } ______________________________________________________________________ 33..11..44.. AAddddiittiioonnaall iinnffoorrmmaattiioonn aabboouutt ddaattaabbaassee ccoonnnneeccttiioonnss PHP reuses connections, if possible. When a connection is being made to the same Host with the same Username and Password as an existing connection, no second connection is being made by PHP. Instead the existing connection is returned to the caller. This is true for both, the *_connect() and *_pconnect() calls of all PHP database interfaces. This has implications for MySQL users: Never use the MySQL "use" command to change the current database. If you do, session management will fail to operate properly. Instead, create all PHPLIB tables as part of your application. Some databases (for example Oracle) have very expensive connect() operations. For these databases, performance is dramatically improved if you switch from CGI PHP to mod_php. This is, because PHPLIB uses the "*_pconnect()" method to connect to your database. In mod_php, the database connection is kept around by the web server process after the page has been processed and is reused if a further connect requires a connection with the same Host/Username/Password pattern. This means that there will be at most "number of web server processes" times "number of Host/Username/Password-combinations" many simultaneous connections to your database server. Keep that in mind when planning licenses and server load. Using CGI PHP will probably reduce the number of concurrent connects to your database server at the expense of connection setup time. For database servers where connection setup time is negligible (MySQL for example) this is a viable solution (don't try it with Oracle) though. 33..11..55.. UUssiinngg nneexxttiidd(()) The nextid() function can be used to obtain a sequence number which can be used as a primary key. The function manages an arbitrary number of named sequences, you have to provide the name of a sequence upon call. ______________________________________________________________________ $db = new DB_Article; $artnr = $db->nextid("article_sequence"); $query = sprintf("insert into articles ( artnr, ...) values ('%s', ...)", $artnr, ...); $db->query($query); reset($articles); while(list($itemnr, $itemdesc) = each($articles)) { $itemnr = $db->nextid("item_sequence"); $query = sprintf("insert into items (artnr, itemnr, ...) values ('%s', '%s', ...)", $artnr, $itemnr, ...); $db->query($query); } ______________________________________________________________________ 33..22.. PPaaggee MMaannaaggeemmeenntt 33..22..11.. AAcccceessssiibbllee FFuunnccttiioonnss Page Management currently consists a collection of functions: ppaaggee__ooppeenn((aarrrraayy(( This function is to be called with an array of page features/classname pairs. Valid features are at the moment: sseessss This page makes use of session variables. aauutthh This page uses session authentication. If you specify the auth feature, you MUST specify the sess feature, also. ppeerrmm This page is protected by permissions and only accessible to authenticated users with matching rights. If you specify the perm feature, you MUST specify the auth and sess features, also. uusseerr This page makes use of user variables. If you specify the user feature, you MUST specify the auth and sess features, also. Each feature specifies the name of the class that implements that feature, for example ________________________________________________________________ page_open(array("sess" => "Shop_Session")); ________________________________________________________________ The function creates an instance of Shop_Session as $sess and initializes it. It also checks feature dependencies. Note that you are expected to provide an implementation of the class Shop_Session. This is usually done in local.inc and usually you do so by extending the provided Session class. Examples on how to do this is given in the documentation below when the classes are introduced. ppaaggee__cclloossee(()) At the end of your page (after all results have been calculated) you have to call page_close(). This will save all page state, session and user variables into database. Changes to session or user variables after page_close() has been called are not recorded. Currently it is allowed to call page_close() multiple times on a single page (not guaranteed for future versions!). Each time session state will be saved. _N_o_t_e_: This is going to change. When we introduce record locking, it is important that you call page_close() only once per page, because that will implicitly unlock your session record. Also, it is important that you call page_close() as early as possible on a page so that the locking time is kept minimal. sseessss__llooaadd((aarrrraayy(( _A_d_v_a_n_c_e_d _f_e_a_t_u_r_e. Some applications have need to manually load data belonging to one or multiple session classes. @@TODO sseessss__ssaavvee((aarrrraayy(( _A_d_v_a_n_c_e_d _f_e_a_t_u_r_e. @@TODO 33..22..22.. EExxaammppllee ______________________________________________________________________ "Shop_Session")); $sess->register("s"); // See "Session" below for explanation. ?>

______________________________________________________________________ 33..22..33.. TThhee ""ccaarrtt"" ffeeaattuurree iiss ggoonnee There used to be a feature "cart" for page_open() in versions of PHPLIB up to release-5. The cart has been removed from the core functionality of PHPLIB to keep the library small, maintainable and structured. Consequently the "cart" feature is gone. The Cart class is still present and exists as an extended feature. You have to include and instantiate your cart manually on that pages that use it, though. See the Cart class for more information. 33..33.. CCTT__SSqqll The Session class used to contain a bit of SQL to read and write session data from and to a database. To make sessions database independent, this SQL has been isolated and put in a separate class, CT_Sql. Session now makes all storage accesses through a container class, which may or may not be an SQL container. 33..33..11.. IInnssttaannccee vvaarriiaabblleess Accessible instance variables. 33..33..22.. EExxaammppllee Use a subclass to provide the appropriate parameters to your container. Usually your subclass looks like this: ______________________________________________________________________ class My_Sql extends CT_Sql { var $classname = "My_Sql"; var $database_table = "active_sessions"; var $database_class = "DB_Session"; } ______________________________________________________________________ You can then use My_Sql in class Session. Reference it by putting "My_Sql" in the "that_class" variable. 33..44.. CCTT__SSpplliitt__SSqqll The Session class used to contain a bit of SQL to read and write session data from and to a database. To make sessions database independent, Session now makes all storage accesses through a container class. The CT_split_sql container is very similar to CT_Sql container, with the difference that if serialized data exceeds a specified amount of bytes, multiple rows will be used to memorized the entire field. This class is NOT compatible with CT_Sql class, since table layout is different and column names are different in order to avoid reserved words in various database implementation. This uses a DB_Sql like class so you can access all supported databases with this container. 33..44..11.. IInnssttaannccee vvaarriiaabblleess Accessible instance variables. 33..44..22.. EExxaammppllee Use a subclass to provide the appropriate parameters to your container. Usually your subclass looks like this: ______________________________________________________________________ class My_Sql extends CT_Split_Sql { var $classname = "My_Sql"; var $database_table = "active_sessions_split"; var $database_class = "DB_Session"; var $split_length = 4096; } ______________________________________________________________________ You can then use My_Sql in class Session. Reference it by putting "My_Sql" in the "that_class" variable. 33..55.. CCTT__SShhmm The Session class used to contain a bit of SQL to read and write session data from and to a database. To make sessions database independent, Session now makes all storage accesses through a container class. To let Session use shared memory as container, you use CT_Shm. 33..55..11.. IInnssttaannccee vvaarriiaabblleess Accessible instance variables. 33..55..22.. EExxaammppllee Use a subclass to provide the appropriate parameters to your container. Usually your subclass looks like this: ______________________________________________________________________ class My_Shm extends CT_Shm { var $classname = "My_Shm"; var $max_sessions = 500; var $shm_key = 0x1234232; var $shm_size = 64000; } ______________________________________________________________________ You can then use My_Shm in class Session. Reference it by putting "My_Shm" in the "that_class" variable. 33..66.. CCTT__DDbbmm The Session class used to contain a bit of SQL to read and write session data from and to a database. To make sessions database independent, Session now makes all storage accesses through a container class. To let Session use a DBM database file as a container, you use CT_Dbm. 33..66..11.. IInnssttaannccee vvaarriiaabblleess Accessible instance variables. 33..66..22.. EExxaammppllee Use a subclass to provide the appropriate parameters to your container. Usually your subclass looks like this: ______________________________________________________________________ class My_Dbm extends CT_Dbm { var $dbm_file = "data/session.dbm"; } ______________________________________________________________________ You can then use My_Dbm in class Session. Reference it by putting "My_Dbm" in the "that_class" variable. 33..77.. CCTT__LLddaapp The Session class used to contain a bit of SQL to read and write session data from and to a database. To make sessions database independent, Session now makes all storage accesses through a container class. To let Session use a LDAP database as a container, you use CT_Ldap. 33..77..11.. IInnssttaannccee vvaarriiaabblleess Accessible instance variables. 33..77..22.. EExxaammppllee Use a subclass to provide the appropriate parameters to your container. Usually your subclass looks like this: ______________________________________________________________________ class My_Ldap extends CT_Ldap { var $classname = "My_Ldap"; var $ldap_host = "localhost"; var $ldap_port = 389; var $basedn = "dc=your-domain, dc=com"; var $rootdn = "cn=root, dc=your-domain, dc=com"; var $rootpw = "secret"; var $objclass = "phplibdata"; } ______________________________________________________________________ You can then use My_Ldap in class Session. Reference it by putting "My_Ldap" in the "that_class" variable. 33..88.. SSeessssiioonn The session class keeps a list of global variable names and provides a set of functions to load and save these variables from and to a data storage container (we will call it container for shortness). The named variables may be scalar variables (strings, integers and floats) or arrays. Objects are handled as well, provided they implement two instance variables naming their class and enumerating their (persistent) slots. 33..88..11.. IInnssttaannccee vvaarriiaabblleess Accessible instance variables. Internal instance variables. 33..88..22.. IInnssttaannccee mmeetthhooddss 33..88..22..11.. AAcccceessssiibbllee iinnssttaannccee mmeetthhooddss rreeggiisstteerr(($$vvaarrnnaammee)) Registers a global variable name as a session variable. The name may identify a scalar variable, an array or an object. If an object is to be made persistent, it must have two instance variables: ccllaassssnnaammee A string with the name of the objects class. ppeerrssiisstteenntt__sslloottss An array with the names of all object slots to save. uunnrreeggiisstteerr(($$vvaarrnnaammee)) Unregisters a global variable name as a session variable. The variable is not deleted, but its value will be lost at the end of a page. It is no longer saved to the database. iiss__rreeggiisstteerreedd(($$vvaarrnnaammee)) Returns true if the variable named $varname is registered with the session, false otherwise. ddeelleettee(()) Destroy the current session and put_id() the current session id. After delete() has been executed, all session data has been removed from the database. Also, the session object is unusable on this page. Consequently, page_close() may not be called for this session. Session variables are still available on this page, even after the delete(), but will be lost on the following pages. In cookie mode, it is possible to page_open() a new session after delete() has been called, if no HTML has been output so far so that the new cookie can be set. If you do this, you can also re-register some of the previous session variables and can call page_close() for the new session. This allows you to change the session on the fly and selectively carry over session data from the previous session. uurrll(($$uurrll)) Return an URL referencing the current session. If in get mode, the current session id is attached to this URL, else the URL is returned unmodified. ppuurrll(($$uurrll)) A shorthand for print $this->url($url); sseellff__uurrll(()) Return an URL referencing the current page, including PHP_SELF and QUERY_STRING information. If in get mode, the session id is included. ppsseellff__uurrll(()) A shorthand for print $this->self_url(). hhiiddddeenn__sseessssiioonn(()) Adds a hidden form element containing the session name and id. aadddd__qquueerryy(($$qqaarrrraayy)) Return string to be appended to the current URL for parameters in GET query format. Intended usage is like this: ___________________________________________________________________ "yes")) ?>"> Reload and log in? ___________________________________________________________________ ppaadddd__qquueerryy(($$qqaarrrraayy)) A shorthand for print $this-> add_query($qarray). rreeiimmppoorrtt__ggeett__vvaarrss(()) When a FORM variable is made persistent, that form variable is imported into PHP, then page_open() is being called and the new variable value is overwritten from the database. The FORM value is lost. If you had enabled track_vars and were accessing HTTP_GET_VARS directly, which is recommended, this were not a problem. Some legacy scripts rely on persistent FORM input variables, though. These scripts may call the appropriate reimport_x_vars() functions. These functions will re-read the tracked variable arrays and reinitialize the appropriate global variables after session variables have been restored. Use of this function is discouraged. rreeiimmppoorrtt__ppoosstt__vvaarrss(()) See reimport_get_vars(). rreeiimmppoorrtt__ccooookkiiee__vvaarrss(()) See reimport_get_vars(). sseett__ccoonnttaaiinneerr(()) You shall not call this function directly. It is called back by the start() function of Session() during initializiation. It is documented so that you can override its implementation in your subclass of Session if you know what you are doing. This function creates and starts the container class used by this instance of session. sseett__ttookkeennnnaammee(()) You shall not call this function directly. It is called back by the start() function of Session() during initializiation. It is documented so that you can override its implementation in your subclass of Session if you know what you are doing. This function determines and sets the internal session name. rreelleeaassee__ttookkeenn(()) You shall not call this function directly. It is called back by the start() function of Session() during initializiation. It is documented so that you can override its implementation in your subclass of Session if you know what you are doing. This function determines the current method of session propagation and determines if a new session token has to be generated. ppuutt__hheeaaddeerrss(()) You shall not call this function directly. It is called back by the start() function of Session() during initializiation. It is documented so that you can override its implementation in your subclass of Session if you know what you are doing. This function determines which header lines are to be generated by the session, including cache control headers. 33..88..22..22.. IInntteerrnnaall iinnssttaannccee mmeetthhooddss ggeett__iidd(()) See get_id(). ggeett__iidd(($$iidd__ttoo__uussee)) get_id() is used internally to determine a session identifier. Currently, a session identifier is a hex number of 32 characters (128 bits) and it is generated by md5(uniqid($this->magic)) to make it hard to guess. get_id() may be called with an optional session id to use as a parameter. This is useful if you want to change a session id without breaking the session (taking over an old, left over session). get_id() can be overwritten by a subclass, if you want a different system to create session ids. For example, some applications want to use a constant session id that is not propagated to the client to use a shared pool of persistent variables (a guestbook for example). These applications need locking (to be implemented soon). ppuutt__iidd(()) put_id() is used internally to "unuse" a session it. At the moment it deletes the client side cookie and deletes $HTTP_COOKIE_VAR[$this->name] for that cookie. The variable ${$this->name} is _n_o_t deleted. sseerriiaalliizzee(($$pprreeffiixx,, &&$$ssttrr)) serialize() is used internally to append to str all PHP code needed to reconstruct the variable named in prefix. ffrreeeezzee(()) freeze() serializes all register()ed variables and writes the resulting code into the database, tagged with the current session id and the current session name. tthhaaww(()) thaw() loads a set of freeze()ed variables for the current session id and session name out of the database and recreates them. ggcc(()) The active_sessions table contains one row for each session. That row is uniquely identified by the sid and name values (name is the name of the session class that has written the row). Each time that row is written, the column changed is updated with the current time. The gc() function deletes all rows that are older than gc_time minutes and have a matching name field. For speed reasons, gc() is not not called every time an update to active_sessions is being made. Instead it is called randomly with a probability of gc_probability. rreeiimmppoorrtt__aannyy__vvaarrss(($$aarrrraayynnaammee)) Used to implement the three official reimport functions. ssttaarrtt(()) Initialization function, to be called after object instantiation. Calls get_id() to get the current session id, creates a database connection, then calls thaw() to load all session variables. Randomly activates gc(). Checks allowcache to send proper headers to control browser caching. 33..88..33.. EExxaammppllee Use a subclass to provide the appropriate parameters to your session. Usually your subclass looks like this: ______________________________________________________________________ class My_Session extends Session { var $classname = "My_Session"; ## Persistence support var $mode = "cookie"; var $lifetime = 0; ## use session cookies ## which container to use var $that_class = "Session_sql"; } ______________________________________________________________________ Remember that you have to provide a DB_Sql subclass with the parameters needed to access your database. Use the page management functions (see above) to use your session subclass. The feature name for session management is sess; provide the name of your session subclass as a parameter to the sess feature: ______________________________________________________________________ page_open(array("sess" => "My_Session")); ______________________________________________________________________ Use the register() instance method to register variables as persistent. If $sess is your session object, use ______________________________________________________________________ $sess->register("s"); ______________________________________________________________________ to make the global variable $s persistent. $s may be a scalar value, an array or an object with persistence support slots. Do not use the instance methods freeze() and thaw() directly, but use the page management functions instead. To have some pages cached and others not cached, use multiple instances of the session object. For example, for those pages that should be cached, use a session object instance like ______________________________________________________________________ class My_Cached_Session extends My_Session { ## pages that use this session instance are cached. var $allowcache = "private"; } ______________________________________________________________________ Be careful when using the public cache option. Publically cached pages may be accessible to unauthenticated users. The private cache option prevents unauthenticated access, but is only functional in HTTP/1.1 browsers. 33..88..44.. UUssiinngg ""aauuttoo__iinniitt"" You may define $sess->auto_init to the name of an include file in your extension of session. Per convention, the name setup.inc is being used. ______________________________________________________________________ class My_Session extends Session { var $classname = "My_Session"; var $magic = "Calvin+Hobbes"; var $mode = "cookie"; var $gc_probability = 5; var $auto_init = "setup.inc"; // name of auto_init file. } ______________________________________________________________________ Whenever a new session is established, that is, a user without a session id connects to your application, the auto_init file is included and executed exactly once. The file is executed from within the context of the page_open() function, that is, _n_o_t within a global context. To define or access global variables from the auto_init file, you have to global them. When auto_init is being executed, all features of your page already exist and are available globally. That is, you can safely rely on the existence of the $sess, $auth, $perm and $user variables, if your application specifies them. _N_o_t_e that you cannot in general know which particular page triggered the execution of auto_init, though. If you have some pages that request authentication and others that don't, you cannot rely on the presence of the $auth object in general, but have to test for it with is_object($auth) before accessing it. The auto_init file is the appropriate place to initialize and register all your session variables. A sample setup.inc may look like this: ______________________________________________________________________ register("lang"); global $cur; // application currency $cur = "EUR"; // Euro by default $sess->register("cur"); global $cart; $cart = new Shop_Cart; // Create a shopping cart object as defined in local.inc $sess->register("cart"); // register it. ?> ______________________________________________________________________ _N_o_t_e_: If you don't use a fallback_mode and you get users that turn off cookies, these users will force a new session each time they hit any page of your application. Of course this will force inclusion and execution of setup.inc for each page they visit, too. Nothing can be done about this. 33..88..55.. UUnnrreeggiisstteerriinngg vvaarriiaabblleess aanndd ddeelleettiinngg sseessssiioonnss To get rid of a persistent variable, call $sess->unregister() with the name of that variable. The value of the formerly registered variable is still available after the call to unregister, but the variable is no longer persistent and will be lost at the end of the current page. To get rid of all session related data including the session record in the database, the current session id and the session cookie in the users browser, call $sess->delete(). In shopping applications this is commonly done when the user commits his order to get rid of the current shopping cart and everything else. You may want to remember selected information about that user, though, as shown below. ______________________________________________________________________ "Shop_Session")); // send order as mail mail_order($shopowner, $user, $cart); // delete the current session $sess->delete(); // now get a new session id, but retain the users // address and name: page_open(array("sess" => "Shop_Session")); // will force auto_init again! $sess->register("user"); // could be done in auto_init as well ?> ______________________________________________________________________ 33..88..66.. RReeaaddiinngg aanndd uunnddeerrssttaannddiinngg sseessssiioonn ddaattaa ffoorr ddeebbuuggggiinngg When debugging PHPLIB applications, it is often useful to be able to read and understand the contents of the active_sessions table. Each session is represented by a single line in this table. The primary key to this table is the pair name and sid. name is the content of $this->name and is usually the classname of your session class. sid is the content of $this->id and is usually the MD5 hash of a uniqid and some magic string. By choosing a pair, it is possible for PHPLIB to have more than one session type (for example, session and user data, see the User class below) per application and store all this data in a single table. If you are debugging a session class, for example Example_Session, only records where name = "Example_Session" are of interest to you. Determine the current session id of your Example_Session by printing $sess->id and select the record with that name and sid from the database. The changed field indicates when this record has been updated the last time. It is a 14 character (Y2K compliant) string of the format YYYYMMDDhhmmss. Ordering by changed desc will show you the most current session records first (the MySQL "limit" clause may come in handy here). The val column of a session record contains a PHP program that can be safely fed to stripslashes() first and eval() after that. The PHP program consists entirely of assignments and contains all instructions necessary to recreate the persistent variables. The structure and order of instructions within this program is always the same. First item is always an assignment to $this->in. If set to 1, auto_init has been executed by this session. If _n_o_t set to 1, auto_init has not been executed, yet. This may be because no auto_init file is defined for that session. After that comes code like this: $this->pt = array(); followed by a bunch of assignments like $this->pt["somestring"] = 1;. Each somestring is the name of a registered variable. Variable registrations are persistent themselves and are saved with the $this->pt array. Even if the variable in question is not set, it may be registered and stays so until it is unregistered or the session is deleted. Check the contents of the pt array is you want to see which variables are currently registered with your session. Finally, the actual contents of your variables are saved. This is always done by accessing the $GLOBALS array and always by enumerating the scalar values that make up the persistent variable. For a scalar, you will see code like $GLOBALS[somevar] = "value";. For an array, first $GLOBALS[someary] = array(); is generated. Then the scalars that make up the array, if any, are written out, generating code that looks like $GLOBALS[someary][index] = "value". And for objects, code to create an object instance is saved: $GLOBALS[someobj] = new Classname;. "Classname" is taken from the objects $classname slot, which _m_u_s_t be present and accurate. Then the scalars that are to be saved are written out, according to the contents of the objects persistent_slots array: $GLOBALS[someobj]->slot = "value"; is written. If you want to see what values have been saved to the database, you just have to look at the $GLOBALS assignments for that session. 33..88..77.. HHooww ""sseerriiaalliizzee(())"" ooppeerraatteess The following information is applicable only to library developers, that is, programmers that want to change the internal workings of PHPLIB. You may safely skip this section; some information here requires advanced understanding of the PHP language. The heart of the session class is the serialize() internal function. This function takes an expression called prefix and generates PHP code that will assign the value of that expression to the expression when executed. For example, if the expression is $GLOBALS["a"] and the global variable $a has the value 17, then serialize will create the PHP program $GLOBALS["a"] = "17";. To save memory, serialize() operates on a reference parameter $str, where is will append the code generated. First thing serialize() does is to determine the type of the current expression using the PHP gettype() function. The current type is stored in $t. The type of the expression may indicate either a scalar value (integer number, float number or string), an array or an object. Scalar values are the easiest to handle: serialize() just evaluates the current expression and remembers the result value in $l. An assignment is generated that will assign the current value to the current expression. Since the current value may be a string and that string may contain bad characters (any of backslash, double quotes or dollar sign), these characters are backslashed. We are done, serialize() ends here for scalars. In the case of $t indicating an array, code is generated to create an empty array (expression = array();). Then the keys of current expression are enumerated and for each key serialize() is called recursively with the current key appended to the expression. That will append code for each array slot. Should $t indicate an object, code is generated to create that object (expression = new Classname;). Since one cannot find out the name of the class of an object for arbitrary objects in PHP, objects handled by serialize() must have a slot named classname. The object handler will then enumerate the contents of the objects slot persistent_slots and call serialize() recursively for each of these slots with the appropriate prefix. Since many of the expressions used in serialize() require variable variable names or even variable code, eval() is used liberally. Unfortunately, this makes the code hard to read. 33..99.. AAuutthh Authentication management can be used to authenticate a session, that is, to identify the user at the client side of the session. Authentication is done inline, with HTML forms, _n_o_t with HTTP authentication (that's the browser popup you get when you hit a page protected with htaccess). Inline authentication has several advantages over HTTP authentication: · It can be undone: A session can be un-authenticated, the user can "log out". · It can expire: A session can automatically be un-authenticated after a given idle time. · It can be customized: You are not limited to user/password pairs. Instead you could use a customer number, operator id and a password to log in. Also, you have full control over the login screen, which is a normal HTML page with logos, help and forms as you see fit. · It is database based. Authentication is being done against a database of your design, not a htpasswd text file. · It is per page. You decide on a per-page basis which pages are authenticated and which aren't. · It can be user authenticating and optionally self registering. In _r_e_g_i_s_t_r_a_t_i_o_n mode, a user without a valid login is encouraged to register and an account is created for this user. · It works with CGI PHP. HTTP authentication is available only in mod_php. · It is integrated with a permission checking scheme. 33..99..11.. IInnssttaannccee vvaarriiaabblleess Accessible instance variables. Internal instance variables. 33..99..22.. IInnssttaannccee mmeetthhooddss 33..99..22..11.. AAcccceessssiibbllee iinnssttaannccee mmeetthhooddss uurrll(()) A function that can be used in auth_loginform()a and auth_registerform. It returns the appropriate "action=" attribute to the form tag. ppuurrll(()) A function that can be used in auth_loginform()a and auth_registerform. It prints the appropriate "action=" attribute to the form tag. llooggiinn__iiff(($$tt)) A function that can be used to change the current user identity. See the section and example on using default authentication below. uunnaauutthh(($$nnoobbooddyy == ffaallssee)) This function destroys the authentication information in $this->auth, forcing the user to relogin the next time a protected page is being loaded. $this->auth["uname"] is being kept, so that the correct username is available as a default. Since V6: To give the user the credentials of `nobody', pass true as the first parameter to unauth. This will also change $this->auth["uname"]. Since V7.2: Passing $nobody to this method is deprecated. llooggoouutt(($$nnoobbooddyy == $$tthhiiss-->>nnoobbooddyy)) This function destroy all authentication information in $this->auth, forcing the user to relogin the next time a protected page is being loaded. Most applications want to use $this->unauth() instead. Since V6: To give the user the credentials of `nobody', pass true as the first parameter to logout. This defaults to the value you set in the class definition ($nobody). logout() will call unauth() (passing $nobody), so the behaviour is identical (except logout() will always clear $this->auth["uname"] and unregister the auth class). Since V7.2: Passing $nobody to this method is deprecated. iiss__aauutthheennttiiccaatteedd(()) Will return false, if the current authentication is invalid or expired. Will return the authenticated uid otherwise. aauutthh__pprreeaauutthh(()) This function can be overridden in a subclass to Auth. It is being called as the very first step in the authentication process and has the opportunity to authenticate the user without a loginform being displayed (by deriving all necessary information telepathically, or by using cookies, or divining the user identities from the incestines of a dead squirrel). If it returns a UID value, the user is authenticated and neither auth_loginform() nor auth_validatelogin() are called. If it returns false, all goes on as usual. aauutthh__llooggiinnffoorrmm(()) This function must be overridden by a subclass to Auth. It should output HTML that creates a login screen for the user. We recommend that you use an include() statement to include your HTML file. aauutthh__vvaalliiddaatteellooggiinn(()) This function is called when the user submits the login form created by auth_loginform(). It must validate the user input. If the user authenticated successfully, it must set up several fields within the $auth[] instance variable: must contain the user id associated with that user. must contain the user name as entered by the user. must not be tampered with (field is maintained by start(), contains the time when the login expires). if you want to use the permission feature, you must store the permissions of the validated user here. (Hint: due to a name conflict with sybase, "perm" is called "perms" in all the databases tables. Look for this small difference!) See the example below for more information. aauutthh__rreeffrreesshhllooggiinn(()) This function is called every refresh minutes. It must refresh the authentication informations stored in auth array by auth_validatelogin() method. It is not called if the user is logged in as nobody. It must return true on success, false otherwise (i.e.: the userid is no longer valid). aauutthh__rreeggiisstteerrffoorrmm(()) See auth_doregister(). aauutthh__ddoorreeggiisstteerr(()) These functions mirror auth_loginform() and auth_validatelogin() in registration mode. 33..99..22..22.. IInntteerrnnaall iinnssttaannccee mmeetthhooddss ssttaarrtt(()) Initialization function, does the authentication. If we are in log (login) mode, auth_loginform() is called to draw a login screen. When the login screen is submitted back, auth_validatelogin() is called to validate the login. If the validation was successful, the actual page content is shown, otherwise we're back at auth_loginform(). In reg mode, auth_registerform() is called to draw a registration form. When the registration form is submitted back, auth_doregister() is called to register the user and to validate the session. If registration was successful, the actual page content is shown, otherwise we're back at auth_registerform(). 33..99..33.. EExxaammppllee Use a subclass of Auth to provide parameters for your authentication class and to implement your own auth_* functions. ______________________________________________________________________ class My_Auth extends Auth { var $classname = "My_Auth"; # Object serialization support var $lifetime = 15; ## DB_Sql subclass and database table to use var $database_class = "DB_Session"; var $database_table = "auth_user"; ## Some magic value to make our uids harder to guess. var $magic = "Abracadabra"; ## Use an own login form function auth_loginform() { global $sess; include("loginform.ihtml"); } function auth_validatelogin() { global $username, $password; ## form variables from loginform.ihtml ## If authentication fails, loginform.html will ## find $this->auth["uname"] set and use it. $this->auth["uname"]=$username; ## Value to return in case auth fails. $uid = false; ## Check the database for this user and password pair. $query = sprintf( "select * from %s where username = '%s' and password = '%s'", $this->database_table, addslashes($username), addslashes($password) ); $this->db->query($query); ## If we found a matching user, grab the uid and permissions... while($this->db->next_record()) { ## Required. $uid = $this->db->f("uid"); ## Optional, for the perm feature. $this->auth["perm"] = $this->db->f("perms"); ## if you use perm feature be aware, that the db-field in our ## example table is called "perms" due to a name conflict with sybase } return $uid; } } ______________________________________________________________________ Your loginform.ihtml contains HTML and PHP code to draw a login form. $this->auth["uname"] will be empty on the first login attempt and set on all further login attempts. You can use this to detect repeated login attempts and display an appropriate error message. You must print the result of $this->url() to create your forms action attribute. See the provided loginform.ihtml for an example. Use the page management functions (see above) to use your authentication subclass. The feature name for authentication management is auth; provide the name of your Auth subclass as a parameter to the auth feature. The auth feature requires the sess feature: ______________________________________________________________________ page_open(array("sess" => "My_Session", "auth" => "My_Auth")); ______________________________________________________________________ 33..99..44.. UUssiinngg ddeeffaauulltt aauutthheennttiiccaattiioonn Many applications want to use $auth and $perm objects to protect functionality on a page, but do want to make the unprotected part of this page available to users with no account. This presents a kind of dilemma, because you need $auth and $perm objects to protect functionality on a page, but you don't want a login screen to appear by default. Default authentication solves this dilemma by providing a special uid and uname "nobody", which is guaranteed to fail every permission check. If you set the nobody flag, $auth will not create a login screen to force a user to authenticate, but will authenticate the user silently as nobody. The application must offer a login button or other facility for users with accounts to change from that id to their real user id. To use default authentication, create a subclass of My_Auth as shown above with the nobody flag set (_N_o_t_e_: No need to extend in two steps. The only important thing here is that the nobody flag is set.) ______________________________________________________________________ class My_Default_Auth extends My_Auth { var $classname = "My_Default_Auth"; var $nobody = true; } ______________________________________________________________________ To create a page that uses default authentication, use the page management functions. Check for relogin requests with the login_if() function. Create a relogin link on your page. ______________________________________________________________________ "My_Session", "auth" => "My_Default_Auth")); $auth->login_if($again); if ($auth->auth["uid"] == "nobody"): ?> ">Relogin to this page. ______________________________________________________________________ 33..99..55.. UUssiinngg CChhaalllleennggee--RReessppoonnssee AAuutthheennttiiccaattiioonn As distributed, local.inc contains an example class named Example_Challenge_Auth, which uses a Challenge-Response authentication scheme. If the client browser supports Javascript, this login screen does not transmit passwords in clear over the network. If the client does not support Javascript, login is still possible, but passwords are transmitted in clear, as regular Example_Auth always does. Example_Challenge_Auth is there to demonstrate advanced usage of PHP and Javascript and to show off the flexibility of the library base classes: The Challenge-Response authentication scheme has been implemented completely and naturally in local.inc by subclassing Auth with no alteration of library code. Example_Challenge_Auth includes crloginform.ihtml. It also requires that the file md5.js is present in the document root directory of your web server. That file contains an implementation of the MD5 message digest algorithm done by Henri Torgemane. The basic idea behind this authentication scheme is simple: $auth->auth_loginform() creates a challenge value which is incorporated into this form. When the user tries to submit the form, MD5("username:password:challenge") is calculated and filled into the reply field. The password field is erased. The server can calculate the expected reply from the username received, the password in the database and the challenge, which it knows. It can compare the expected reply to the actual reply value. If they match, the user is authenticated. If the reply field is empty and password is set, the server knows that the client cannot do Javascript. The user can still be authenticated, but the password is visible on the network. The class is a dropin-replacement for Example_Auth. 33..99..66.. TThhee ccoommpplleettee gguuiiddee ttoo aauutthheennttiiccaattiioonn aanndd uusseerr vvaarriiaabblleess This feature has originally been written for the PHPLIB mailing list by Kristian Köhntopp and was included into the documentation later. 33..99..66..11.. HHooww iiss tthhee AAuutthh ccllaassss uusseedd uussuuaallllyy?? Usually, you write code like this into the top of the page you want to protect: ______________________________________________________________________ "My_Session", "auth" => "My_Auth")); ?> ______________________________________________________________________ 33..99..66..22.. HHooww ddooeess $$aauutthh wwoorrkk iinntteerrnnaallllyy?? When you access this page, the call to page_open() is being made as the first thing on that page. page_open() creates an instance of My_Auth named $auth and starts it. $auth then detects that you are not authenticated (how it does, I will explain below) and displays loginform.ihtml. $auth then exits the interpreter, so that is never being executed or displayed. The user now sits in front of a loginform.ihtml screen, which is shown under the URL of the page the user originally tried to access. The loginform has an action URL, which just points back to itself. When the user filled out the loginform and submits it, the very same URL is requested and the above page_open() is reexecuted, but this time a username and a password are submitted. When the $auth object is created and started, it detects these parameters and validates them, resulting in either a NULL value or a valid user id. If the validation failed, creating an empty user id, the loginform is displayed again and the interpreter exits. Again is not executed. If a UID is returned, that UID and a timestamp are being made persistent in that session and $auth returns control to page_open(). When page_open() finishes, which it may or may not do, depending on the presence and result of an optional $perm check, is being executed or shown. Later calls to other pages or the same page check for the presence of the UID and the timestamp in the sessions data. If the UID is present and the timestamp is still valid, the UID is retained and the timestamp is refreshed. On page_close() both are written back to the user database (Note: Authenticated pages REQUIRE that you page_close() them, even when you access them read-only or the timestamp will not be refreshed). If the UID is not present ($auth->logout() or $auth->unauth() have been called, for example) or the timestamp has expired, $auth will again intercept page display and draw the loginform. The only way to get into a page with an $auth object on it is to have a UID and a valid timestamp in your session data (Note: This is true even for default authentication. These create a dummy UID and timestamp in your session data). 33..99..66..33.. HHooww ddoo $$sseessss aanndd $$aauutthh iinntteerraacctt?? Your browser has a session cookie, named after your session class. This is the only thing that is ever shipped between your browser and PHPLIB, as far as core functionality is concerned. The session cookie value is used as a reference into active_sessions, to retrieve PHPLIB generated PHP code, which is then eval()ed and recreates your session variables within page_open(). Part of the $auth object now makes itself persistent and is retrieved when the $sess part of page_open() is being executed. This is just before the $auth part of page_open() gets its turn, so that $auth can rely on its persistent data being present when it is being called. From the PHPLIB source you all know that $auth has only one persistent slot, called $auth->auth[], of type hash. This hash contains the slots uid, exp and uname. $auth->auth["uid"] is the currently authenticated user id, $auth->auth["exp"] is the currently active expiration timestamp (Unix time_t format) for that uid. $auth->auth["uname"] is completely irrelevant as far as the regular PHPLIB Auth class is concerned. It is relevant in the context of the supplied default Auth subclass Example_Auth, though. So a session is authenticated, if it contains $auth->auth["uid"] != false and time() < $auth->auth["exp"]. 33..99..66..44.. WWhheerree iiss tthhee bbeeeeff?? The original Auth class as included in PHPLIB makes no assumptions at all on how a loginform looks or how and where uids come from. There is no code at all in Auth that ever checks anything but the above two conditions. It is your responsibility to modifiy a subclass of Auth in a way that these conditions can ever be met. Auth helps you in doing this by calling its own function $auth->auth_loginform() when it wants to draw a loginform. Unfortunately this function is empty in Auth itself, so you have to provide an implementation for that. The suggested standard implementation in local.incs Auth subclass Example_Auth is ______________________________________________________________________ function auth_loginform() { include("loginform.ihtml"); } ______________________________________________________________________ and you put your code into that file. We also provide sample code for that file, but you are not limited to that code and may write a loginform.ihtml as it meets your needs. When the loginform has been filled in and submitted back by the user, Auth calls $auth->auth_validatelogin(). Again, this function is empty in Auth itself and so Auth by itself will never function correctly. You have to subclass Auth and provide your own implementation of $auth->auth_validatelogin() in local.inc to make it work. What you actually do in that function is completely irrelevant to Auth itself. It only exspects that you either return false, if the user- supplied authentication data was invalid, or a user id, if the user could be validated. Auth then takes care to create the appropriate entries ($auth->auth["uid"] and $auth->auth["exp"]) in the session record. 33..99..66..55.. II ssttiillll ddoo nnoott uunnddeerrssttaanndd!! WWhhaatt aamm II ssuuppppoosseedd ttoo ccooddee?? You write your code into local.inc, after you have removed the classes Example_Auth, Example_Default_Auth and Example_Challenge_Auth from that file (keep a copy around, just for reference). You code a class called My_Auth and you use that name later in your calls to page_open as an argument to the auth feature, as show at the start of this message. Follow the standard rules for deriving persistent classes in PHPLIB when you create your code, that is, do it like this: ______________________________________________________________________ class My_Auth extends Auth { var $classname = "My_Auth"; // we inherit $persistent_slots and do not need to modify it. // later code is inserted here } ______________________________________________________________________ Now configure the lifetime of the authentication, that is, how many minutes in the future shall the current value of $auth->auth["exp"] be? Also, name a database connector class and name the table that you will be using to check usernames and passwords. ______________________________________________________________________ // insert this code as indicated above. var $lifetime = 15; var $database_class = "DB_Example"; var $database_table = "my_special_user_table"; // later code is inserted here ______________________________________________________________________ Okay, now we have a basic implementation of My_Auth that is only lacking the required functions auth_loginform() and auth_validatelogin(). Our implementation of auth_loginform() will have access to all $sess variables by globaling $sess into our context (because these can come in handy) and to all $auth variables (via $this). ______________________________________________________________________ function auth_loginform() { global $sess; include("loginform.ihtml"); } ______________________________________________________________________ The loginform is free to do whatever it damn well pleases to create a form for the user to supply the needed values for authentication. It has access to anything $sess and anything $this related. The loginform will display some input fields for the user, for example a given name, a surname and a password. When the form is submitted back, auth_validatelogin() is being called. The form values are global variables (or $HTTP_x_VARS[]) and must be imported into $auth->auth_validatelogin(). Then, $auth->auth_validatelogin() is free to do whatever it must do to produce a unique identifier for that user (or return false). Suppose you created input fields named given_name, surname and password. So go ahead, global $given_name, $surname and $password and set $uid to false. Then create the SQL needed to access you user table and retrieve the user record from your database as indicated by $given_name and $surname and $password. The query may succeed, if a record with matching $given_name, $surname and $password is present. In that case return the uid, which uniquely identifies exactly that (given_name, surname) pair. Else return false. In code: ______________________________________________________________________ function auth_validatelogin() { // import authentication data global $given_name, $surname, $password; $uid = false; $query = sprintf("select uid from %s where given_name = '%s' and surname = '%s' and password = '%s'", $this->database_table, $given_name, $surname, $password); // we really should use addslashes() here, // or have magic_quotes active. // $auth->db is our DB_Example database connection $this->db->query($query); // now check for any results while($this->db->next_record()) { $uid = $this->db->f("uid"); } // either $uid is false now (no results) // or set to the last retrieved value from the uid // column. // Anyway we are set now and can return control return $uid; } ______________________________________________________________________ Okay, that's all and useable now. There is room for some improvements, though: First we did not retrieve permission data, so this will not work, if we want to use the perm feature as well. This is easily changed: Modify the query to select uid, perms instead of select uid alone. Of course, you may call your perm column whatever you like, just adapt the SQL accordingly. Also, add a line after the $uid assignment so that the code looks like this: ______________________________________________________________________ $uid = $this->db->f("uid"); $this->auth["perm"] = $this->db->f("perms"); ______________________________________________________________________ This will store the retrived perms value under the key perm within the $auth->auth[] array. It will be kept around in that place in case $perm is called and starts looking for the current permissions of that user. Another possible improvement becomes apparent when you try to login and fail to do so correctly: auth_validatelogin() returns false and you hit the loginform again. Empty loginform that is, because we did not remember what you typed into the given_name and surname fields before. If we remembered what you typed, we could easily supply these values back to you so that you can correct them. We would also be able to detect if this is a second, third, ... attempt to login and display an appropriate error message somewhere in that loginform to inform the user of his or her typo. A convenient place to store these values is the $auth->auth array, which is persistent anyway. Standard Example_Auth uses the field $auth->auth["uname"] to store that value, but you may use any field and as many fields as you like as long as you make sure not to clash with any of the three officially used fields, uid, exp, and perm. Do not try to turn the global variables $given_name and $surname into persistent variables by calling $sess->register("given_name") and $sess->register("surname")! Remember: These are form variables! Never ever make form variables persistent and never ever trust unvalidated user supplied from the Internet! So add the folling code just below the "global" line: ______________________________________________________________________ $this->auth["gname"] = $given_name; $this->auth["sname"] = $surname; ______________________________________________________________________ and check for these two variables in loginform.ihtml at the appropriate places. 33..99..66..66.. OOkk,, II ddiidd tthhaatt aanndd iitt wwoorrkkss.. II eevveenn uunnddeerrssttoooodd iitt.. NNooww,, wwhhaatt eexxaaccttllyy iiss tthhaatt uuiidd uusseedd ffoorr?? It is simply a token to indicate that the user is authenticated. We use a different token for each user, so that we can decide which user we are currently dealing with. You can think of the uid as a primary key for your auth_user table (or whatever it is being called in your current application). The ( given_name, surname ) tuple would also be a possible primary key, albeit a compound one. It is the external, human-readable (and probably sometimes very long) representation of the internal uid. The password field is functionally dependent on either of both key candidates. The internal user id should never be presented to the user; the ( given_name, surname ) pair is much more natural to handle for the user and easier to remember (A user who does not remember his or her name would probably not be in a state of mind to operate the rest of the application anyway :-). The internal user id should always be used to identify a user internally within an application, though. That is, because the uid is of a fixed length and has a known form and structure, so you can make assumptions. A given_name or surname may be of any length and may contain about any character, so you probably do not want to use this as a user-reference internally. 33..99..66..77.. BBuutt iiss tthhee uuiidd uusseedd iinntteerrnnaallllyy bbyy PPHHPPLLIIBB?? Yes, if you make use of the user feature of page_open(), that is, if you create user variables. The User class is actually a subclass of Session. That is, user variables are just like session variables. They are even stored in active_sessions. The only difference is that the session has a different name (it is called Example_User instead of Example_Session, if you use the classes and names supplied in local.inc). And in Example_User, the user id of the authenticated user becomes the session id in the active_sessions table. That is the reason why we recommend md5(uniqid("abracadabra")) style uids. 33..1100.. PPeerrmm Permission management relies on an authenticated session. It associates a set of required permissions with a page. The actual page content is only visible to users with ALL matching permissions; all other users are shown a screen of your design. 33..1100..11.. IInnssttaannccee vvaarriiaabblleess Accessible instance variables. 33..1100..22.. IInnssttaannccee mmeetthhooddss 33..1100..22..11.. AAcccceessssiibbllee iinnssttaannccee mmeetthhooddss cchheecckk(($$rreeqquuiirreedd)) Checks that the currently authenticated user has all the rights that are specified in required. If not, perm_invalid() is called. If one or more of the required rights or user rights are invalid (not to be found in the permissions hash), perm_invalid() is called as well. hhaavvee__ppeerrmm(($$rreeqquuiirreedd)) Similar to check() in usage, only that it doesn't halt the session if the user doesn't have the appropriate rights: This function returns true, if the user has the required rights, false otherwise. ppeerrmm__sseell(($$nnaammee,, $$ccuurrrreenntt == This function returns a SELECT-tag with the given name. Within this tag, all available permission values from $perm->permissions are contained as OPTION tags. If you supply a value for current, the permission value that matches current is SELECTED. If you supply a value for class, the tags are marked with that CSS stylesheet class. 33..1100..22..22.. IInntteerrnnaall iinnssttaannccee mmeetthhooddss ppeerrmmssuumm(($$rriigghhttss)) Logically or's all the rights and returns a pair (valid, or_result). If valid is true, an or_result is provided. If valid is false, the or_result is undefined and one or more of the rights do not exist at all. This is a severe error and the application should be halted at once. ppeerrmm__iinnvvaalliidd(($$ddooeess__hhaavvee,, $$mmuusstt__hhaavvee)) Called in case of an access violation. does_have is a string listing the rights the user actually has. must_have are the rights the page requires. 33..1100..33.. EExxaammppllee Use a subclass of Perm to provide parameters for your permission class and to implement your own perm_invalid function. ______________________________________________________________________ class My_Perm extends Perm { var $classname = "My_Perm"; var $permissions = array ( "user" => 1, "author" => 2, "editor" => 4, "moderator" => 8, "admin" => 16 ); function perm_invalid($does_have, $must_have) { global $perm, $auth, $sess; include("perminvalid.ihtml"); } } ______________________________________________________________________ Use the page management functions (see above) to use your permission subclass. The feature name for permission management is perm; provide the name of your Perm subclass as a parameter to the perm feature. The perm feature requires the sess feature and the auth feature: ______________________________________________________________________ page_open(array("sess" => "My_Session", "auth" => "My_Auth", "perm" => "My_Perm")); ______________________________________________________________________ Use the check() instance method to protect your page: ______________________________________________________________________ $perm->check("admin"); ## This page is for users with admin rights only. ______________________________________________________________________ Use have_perm() to create protected functionality on a page: ______________________________________________________________________ have_perm("admin")): ?>

Admin only functionality

______________________________________________________________________ 33..1100..44.. HHooww ppeerrmmiissssiioonnss wwoorrkk Your subclass of Perm defines an array $permissions, which translates permission names into bit patterns. For example, the definition of Example_Perm in the distributed local.inc defines the names user, author, editor, supervisor and admin, all of which translate into a bit pattern with a single bit set. A user may be assigned any number of permissions as a comma separated list of permission names (no spaces!) in the perms column of the auth_user table. The effective permissions of the user are determined by logically OR'ing the bit patterns of these permissions. A page may require any permissions as a comma separated list of permission names (again no spaces!) with the $perm->check() function. The required permissions are again determined by logically OR'ing the bit patterns of these permissions. Similarly, a page function may be protected by requiring permissions with $perm->check(). Access is granted to a protected page or a protected page function, if the effective permissions of the authenticated user have all the required bits set, that is: If the effective permissions of the user logically AND'ed with the required permissions are equal to the required permissions. With the permission names as defined in Example_Perm from the distribution, a user kris may be defined with admin permission in the auth_user table. A page that requires admin,user permission with $perm->check("user,admin") is inaccessible to this user. This is how it is calculated: ______________________________________________________________________ Effective Permissions of User: admin translates into: 16 Required Permissions of Page : user,admin translates into: 1 OR 16 == 17 Permission Check: Effective Permissions 16 AND Required Permissions 17 ARE 16 & 17 = 16 MUST BE Required Permissions 17 -> access denied ______________________________________________________________________ The example permissions as defined in Example_Perm from the distribution are called _a_t_o_m_i_c permissions, because each of them has only a single bit set. Atomic permissions are the simplest of all schemes, because they allow for easy permission checks: To access a page protected with user,admin, you need to have at least user,admin rights in your auth_user table. Another common scheme used in permission definitions are inclusive permissions. In this scheme, each permission definition has all bits of its predecessor set plus one addition bit. For example ______________________________________________________________________ class Inclusive_Perm extends Perm { var $classname = "Inclusive_Perm"; var $permissions = array( "user" => 1, "author" => 3, "editor" => 7, "supervisor" => 15, "admin" => 31 ); } ______________________________________________________________________ defines a set of inclusive permissions. In this example, a user kris with admin permissions can easily access a page protected with editor permissions. This is how it is calculated: ______________________________________________________________________ Effective Permissions of User: admin translates into: 31 Required Permissions of Page : editor translates into: 7 Permission Check: Effective Permissions 31 AND Required Permissions 7 ARE 31 & 7 = 7 MUST BE Required Permissions 7 -> access granted ______________________________________________________________________ Inclusive Permissions are easy to deal with, too, because a user with a _h_i_g_h_e_r access level may access all pages or page functions with a _l_o_w_e_r access level. Due to limitations of your machines integer size you can only define up to 31 permission levels. 33..1111.. UUsseerr The user class is an extension (a subclass) of the Session class. It keeps a list of global variable names and provides a set of functions to load and save these variables from and to a database. The same restrictions as for session variables apply to user variables. Unlike session variables, user variables are not lost when the user stops and restarts the browser or moves to a different workplace (the session id is then lost and consequently all session variables are lost, since they are bound to the session id). User variables require that the user logs in, because they depend on the availability of a User id to bind variables to this id. Thus, User is dependent on Auth. The User class is an extension of the Session class. It has all instance variables and instance methods of Session, only that some are implemented different. This documentation only describes these differences. Note that Session and User can successfully share a single active_sessions table in a database due to the different values in the name column. 33..1111..11.. IInnssttaannccee vvaarriiaabblleess Accessible instance variables. Internal instance variables. 33..1111..22.. IInnssttaannccee mmeetthhooddss 33..1111..22..11.. AAcccceessssiibbllee iinnssttaannccee mmeetthhooddss rreeggiisstteerr(($$vvaarrnnaammee)) Works as expected. uunnrreeggiisstteerr(($$vvaarrnnaammee)) Works as expected. ddeelleettee(()) Works as expected. uurrll(($$uurrll)) Not useful with User. ppuurrll(($$uurrll)) Not useful with User. sseellff__uurrll(()) Not useful with User. ppsseellff__uurrll(()) Not useful with User. rreeiimmppoorrtt__ggeett__vvaarrss(()) Works as expected. rreeiimmppoorrtt__ppoosstt__vvaarrss(()) Works as expected. rreeiimmppoorrtt__ccooookkiiee__vvaarrss(()) Works as expected. 33..1111..22..22.. IInntteerrnnaall iinnssttaannccee mmeetthhooddss ggeett__iidd(()) This is only a stub implementation that depends on the user id provided by the page management functions. The page management functions will use $auth->auth["uid"], which is set up by Auth. ppuutt__iidd(()) Empty. Not useful with User. sseerriiaalliizzee(($$pprreeffiixx,, &&$$ssttrr)) Works as expected. ffrreeeezzee(()) Works as expected. tthhaaww(()) Works as expected. ggcc(()) Works as expected. You do not want to use it, though. rreeiimmppoorrtt__aannyy__vvaarrss(($$aarrrraayynnaammee)) Works as expected. ssttaarrtt(()) Initialization function, to be called after object instantiation. Calls get_id() to get the current session id, creates a database connection, then calls thaw() to load all session variables. _N_o_t_e_: gc() activation is commented out! Remove the comments if you really want gc with User variables. 33..1111..33.. EExxaammppllee Use a subclass to provide the appropriate parameters to your user variables. Usually your subclass looks like this: ______________________________________________________________________ class My_User extends User { var $classname = "My_User"; ## Persistence support var $that_class = "CT_Sql"; } ______________________________________________________________________ Remember that you have to provide a DB_Sql subclass with the parameters needed to access your database. Use the page management functions (see above) to use your User subclass. The feature name for user variables is user; provide the name of your User subclass as a parameter to the user feature: ______________________________________________________________________ page_open(array("sess" => "My_Session", "auth" => "My_Auth", "user" => "My_User")); ______________________________________________________________________ Use the register() instance method to register variables as persistent. If $user is your user object, use ______________________________________________________________________ $user->register("u"); ______________________________________________________________________ to make the global variable $u persistent. $u may be a scalar value, an array or an object with persistence support slots. Do not use the instance methods freeze() and thaw() directly, but use the page management functions instead. _N_o_t_e_: Using default authentication and user variables is going to be a problem, because currently User does not do any locking. This is, because the DB_Sql has currently no portable locking mechanism. 44.. EExxtteennddeedd ffuunnccttiioonnaalliittyy The section on extended functionality covers non-GUI classes that provide often needed application functions without a user interface. Some extended classes depend on core functionality, some contain independent classes. Extended classes are treated differently from core classes in that their code is not automatically included by prepend.php3. You have to include the class definition manually where needed or you modify prepend.php3. 44..11.. CCaarrtt The Cart class is programmatically independent, but makes sense only if its instances are made persistent in some way. The Cart class automatically registers itself as a session variable in its start() function. Cart implements a shopping cart. At the moment, items within the shopping cart are independent of each other; the cart can only hold simple things. Support for compound articles that require other articles to function and provide a base for dependent articles is to be added at a future time. An example of a simple article is any article with no options, for example an apple or a book. Common examples for compound articles are a pizza (which requires a foundation in either American or Italian style, a selection of toppings, and cheese, to function correctly) and a computer system (which requires a housing, a motherboard, RAM, a video card, etc to function correctly). _N_o_t_e_: Cart was a core class up to _r_e_l_e_a_s_e_-_5. If your applications uses the Cart class, you _m_u_s_t manually add the statement include("cart.inc") to your prepend.php3 file where indicated in that file. _N_o_t_e_: The page management functions do no longer support the feature cart to set up and start the cart class. It is recommended that you use Session's auto_init feature instead to start your cart automatically or that you manually set up your cart. 44..11..11.. IInnssttaannccee vvaarriiaabblleess Accessible instance variables. 44..11..22.. IInnssttaannccee mmeetthhooddss 44..11..22..11.. AAcccceessssiibbllee iinnssttaannccee mmeetthhooddss cchheecckk(($$aarrtt)) Checks that an item with the given article number $art is in the cart. Returns an array of a boolean value and an integer number. If the boolean is true, there are number many articles of that article number in the cart. rreesseett(()) Deletes all items in current cart, resetting $this->currentItem to 1. Always returns true. nnuumm__iitteemmss(()) Returns the number of articles in the current shopping cart, or false if cart is empty. For compatibility reasons, this function is available as tot_arts as well (but will print a warning if used by this name). aadddd__iitteemm(($$aarrtt,, $$nnuumm)) Add $num many articles of article number $art to the current shopping cart. Returns the position number of $art in the shopping cart. rreemmoovvee__iitteemm Remove $num many articles of article number $art from the shopping cart, if there are at least that many articles in the cart. Returns the position number of $art in the shopping cart or false, if there weren't enough $art to remove them from the cart. If the function does return false, the cart has not been modified. sseett__iitteemm Set the quantity of article number $art in the shopping cart to exactly $num. If $num is set to zero, article is removed from cart. Returns the position number of $art in the shopping cart. sshhooww__aallll(()) If the shopping cart is empty, it will call show_empty_cart() once and then return. Calls show_item_open() once at the beginning of a shopping cart listing. Then calls show_item() once for each item in the shopping cart. Calls show_item_close() once at the end of a shopping cart listing. sshhooww__iitteemm(($$aarrtt,, $$nnuumm)) This function should be provided by the user. It renders the HTML to display a single item from the cart. $art is the article number of the item and there are $num of these in the cart. sshhooww__ccaarrtt__ooppeenn(()) This function should be provided by the user. It renders the prologue HTML to display a shopping cart listing. sshhooww__ccaarrtt__cclloossee(()) This function should be provided by the user. It renders the epilogue HTML to display a shopping cart listing. sshhooww__eemmppttyy__ccaarrtt This function should be provided by the user. It should render an appropriate message to symolize an empty cart. 44..11..33.. EExxaammppllee Use a subclass of Cart to provide an implementation of show_item(). ______________________________________________________________________ class My_Cart extends Cart { var $classname = "My_Cart"; // Look up article numbers... var $database_class = "DB_Article"; var $database_table = "articles"; var $db; var $sum = 0; function show_cart_open() { printf("\n"); $this->sum = 0; } function show_cart_close() { printf("
\n"); printf("That's a total of %s.\n", $this->sum); } function show_item($art, $num) { if (!is_object($this->db)) { $class = $this->database_class; $this->db = new $class; } $query = sprintf("select * from %s where artid = '%s'", $this->database_table, $art); $this->db->query($query); while($this->db->next_record()) { printf(" \n %s\n", $this->db->Record["name"]); printf(" %s\n", $this->db->Record["price"]); printf(" %s\n", $num); $rowsum = $num * $this->db->Record["price"]; $this->sum += $rowsum; printf(" %s\n", $rowsum); printf(" \n"); } } } ______________________________________________________________________ To use a cart, create an instance of your Cart subclass and call start(). This will automatically register cart. It is recommended that you set in your Session subclass in local.inc the slot $auto_init to the value setup.inc and create an include file of that name which contains the following code: ______________________________________________________________________ global $cart; ## $cart is a global variable. $cart = new My_Cart; ## Make a My_Cart instance named $cart $cart->start(); ## and have it register itself. ______________________________________________________________________ Use add_item() and remove_item to work with your Cart: ______________________________________________________________________ $cart->add_item("101", 2); ## Add two pieces of "101" $cart->remove_item("101", 1); ## Drop one piece of "101" ______________________________________________________________________ Use show_all() to display the contents of your cart. ______________________________________________________________________ $cart->show_all(); ## What's in a cart, anyway? ______________________________________________________________________ 44..11..44.. OOnn uussiinngg CCaarrtt To make use of the Cart class, you need to define a new table in your database that lists all articles you shop should sell. With PHPLIB and MySQL we recommend that you create a new instance of PHPLIB for each virtual web server and a new database for each customer. This database should hold the active_sessions and auth_user tables as well as all application specific tables like for example the article list. In other words, with MySQL we strongly discourage that you use PHPLIB and the MySQL directive use _d_a_t_a_b_a_s_e___n_a_m_e together. There is no support if you do (there is no support if you do as we say, too, because PHPLIB is an open source product you are using on your own risk, but ...). So let us assume you define a very simple new table articles with a structure like this: ______________________________________________________________________ # # Table structure for table 'articles' # CREATE TABLE articles ( name text, price float(8,2), artid int(11) DEFAULT '0' NOT NULL auto_increment, PRIMARY KEY (artid) ); ______________________________________________________________________ This table has an article number called artid, and for each artid there is an article description name and a price. You may extend this minimal definition for your purposes by adding article groups, BLOBs with article images and more, but this will suffice for our example purposes. Populate this table with some products that suit your taste. The next step is to teach PHPLIB about the cart class. Three steps are necessary to do so: · the Cart class has to be included on every page. Even on that pages that do not make use of the Cart class. On that pages that use Cart, a cart subclass is instantiated and saved. On all subsequent pages, that Cart object is recreated and to be able to recreate the Cart object, PHP must know what a Cart object is. Since you cannot know which pages a user loads after he has put the first item into the Cart, we need to have a definition for Cart on _a_l_l pages. The proper place to include the Cart definition from cart.inc is consequently prepend.php3. Edit prepend.php3 and require("cart.inc") as indicated by the comments in that file. · a subclass of Cart has to be created to suit your tastes. Your subclass of Cart will be called Example_Cart in this example. You may actually name it as you like, but you have to be consistent. The definition of Example_Cart goes into local.inc anywhere below your definition for Example_Session. It looks like this ______________________________________________________________________ class Example_Cart extends Cart { var $classname = "Example_Cart"; } ______________________________________________________________________ and we will add additional code later in this example. That additional code will teach your shopping cart about the database table that holds your articles and so on. · finally, you need to create an instance of your shopping cart class so that you have an object that actually holds the articles selected by the user. We will use a very nifty feature of PHPLIB to create that object instance: If you set up PHPLIB properly, it is able to load and execute an include file every time a session is being created. We call this feature auto_init, after the instance variable of Session that controls it. Go into local.inc and edit your subclass of Session. You will have some code like ______________________________________________________________________ class Example_Session extends Session { var $classname = "Example_Session"; ... } ______________________________________________________________________ in your local.inc. Add a line like ______________________________________________________________________ var $auto_init = "setup.inc", ______________________________________________________________________ to your definition of Example_Session and create a file setup.inc in the same directory that holds your local.inc. Whatever code is in this file will be executed every time we create a new session. The code is being executed after your $sess, $auth and $perm objects are loaded and initialized, but does run from within a function context. You have to global everything you define to export it from that func­ tion context. In setup.inc, create a global instance of Example_Cart named $cart and register that variable with PHPLIB: ______________________________________________________________________ register("cart"); ?> ______________________________________________________________________ Now you have a $cart object available by default on every page that uses PHPLIB. That object is created automatically at session startup, is carried from page to page by PHPLIBs session management and is destroyed by the garbage collection that reaps session records. You do not have to worry anymore about that cart, but simply use it anytime between page_open() and page_close(). PHPLIB does the rest for you. The Cart class is actually dead stupid. It maintains an array $cart->item[] that holds records about what the user bought. Each $cart->item[$x] consists of a $cart->item[$x]["art"], which is the article number of an item the user wants to buy and of a $cart->item[$x]["num"], which is the # of items with that article number that are wanted. $cart->currentItem is the next $x to use for articles added to $cart->item[]. You add articles to the shopping cart with ______________________________________________________________________ $x = $cart->add_item($art, $num) ______________________________________________________________________ This will add $num items with the article number $art to your cart contents. If you already have an item with that article number in your cart, the count for that article is increased by $num. Otherwise a new article entry is being created and set to $num. The function does return the $x index into the $cart->item[] array for that article. To remove an item from the shopping cart, code ______________________________________________________________________ $x = $cart->remove_item($art, $num) ______________________________________________________________________ This will remove $num items with the article number $art from your cart, if there are that many items in your shopping cart. If you do not have the $art in your cart or there are not $num many $art in your cart, the function will return false and not remove anything from the cart. Otherwise, $num articles with article number $art are taken out of the cart and if the count for that article drops to zero while doing this, we even unset the array element. You may check how many articles with a given article number are in the cart: ______________________________________________________________________ list($have, $num) = $cart->check($art) ______________________________________________________________________ The check function does return a two-element array. The first element $have is true, if we have the wanted article in the cart. If $have is true, $num holds the number of articles with that number in the cart, otherwise $num is undefined (actually, it is 0, but you must not rely on that). Finally, we have a function ______________________________________________________________________ $cart->show_all() ______________________________________________________________________ which you may call to walk your shopping cart and have Example_Cart to generate a list of articles in your cart. That function will first call $cart->show_cart_open(), for which you may provide code in your subclass. It will then call $cart->show_item($art, $num) for each item in the cart. We have a stupid default implementation for that function in Cart, but you may provide more sophisticated code in Example_Cart for that, too. Finally, at the end of your cart listing, $cart->show_cart_close() is being called, which again may be code of yours. The example in the previous section shows a more sophisticated implementation of a Cart subclass. That implementation uses show_cart_open() to create an opening table tag (formatted with a CSS class) and sets a counter $cart->sum to zero. In show_cart_close(), the table is being closed and the $cart->sum counter is printed. As you might have guessed, show_item($art, $num) queries the database for each article number, retrieves the article description and prices and finally sums up all prices, taking the number of articles per article into consideration. It also generates table rows, printing a nice receipt for the customer. 44..22.. TTeemmppllaattee _N_o_t_e_: If you think that this is like FastTemplates, read carefully. It isn't. The template class allows you to keep your HTML code in some external files which are completely free of PHP code, but contain replacement fields. The class provides you with functions which can fill in the replacement fields with arbitrary strings. These strings can become very large, e.g. entire tables. 44..22..11.. IInnssttaannccee vvaarriiaabblleess Accessible instance variables. Internal instance variables. 44..22..22.. IInnssttaannccee mmeetthhooddss 44..22..22..11.. AAcccceessssiibbllee iinnssttaannccee mmeetthhooddss TTeemmppllaattee(($$rroooott == Constructor. May be called with two optional parameters. The first parameter sets the template directory (see set_root(), the second parameter sets the policy regarding handling of unknown variables. sseett__rroooott(($$rroooott)) The function checks that $root is a valid directory and sets this directory as the base directory where templates are being stored. sseett__uunnkknnoowwnnss(($$uunnkknnoowwnnss == The function sets the policy for dealing with unresolved variable names. Must be either "remove", "comment" or "keep". If set to "keep", those are left untouched. If set to "comment", unresolved variable names are transformed into HTML comments reporting the error. If set to "remove", unresolved variable names are silently removed (the default). sseett__ffiillee(($$hhaannddllee,, $$ffiilleennaammee == The function defines a filename for the initial value of a variable. It may be called with either a $handle/$filename pair or with a hash of $handle/$filename pairs. The files are not referenced yet, but only when needed. sseett__bblloocckk(($$ppaarreenntt,, $$hhaannddllee,, $$nnaammee == A variable $parent may contain a variable block named by $handle. The function removes that block from $parent and replaces it with a variable reference named $name. If $name is omitted, it is assumed to be the same as $handle. sseett__vvaarr(($$vvaarrnnaammee,, $$vvaalluuee == The functions sets the inital value of a variable. It may be called with either a $varname/$value pair or with a hash of $varname/$value pairs. ssuubbsstt(($$hhaannddllee)) The function returns the value of the variable named $handle, with all defined variable values filled in. The resulting string is not "finished", that is, the unresolved variable name policy has not been applied yet. ppssuubbsstt(($$hhaannddllee)) This is a shorthand for print $this->subst($handle). ppaarrssee(($$ttaarrggeett,, $$hhaannddllee,, $$aappppeenndd == ffaallssee)) The function substitutes the values of all defined variables in the variable named $handle and stores or appends the result in the variable named $target. If $handle is an array of variable names, $append is ignored. The variables named by $handle are being sequentially substituted and the result of each substitution step is stored in $target. The resulting substitution is available in the variable named by $target, as is each intermediate step for the next $handle in sequence. ppppaarrssee(($$ttaarrggeett,, $$hhaannddllee,, $$aappppeenndd == ffaallssee)) A shorthand for print $this->parse(...). ggeett__vvaarrss(()) Returns a hash of all defined values, keyed by their names. ggeett__vvaarr(($$vvaarrnnaammee)) Returns the value of the variable named by $varname. If $varname references a file and that file has not been loaded, yet, the variable will be reported as empty. When called with an array of variable names, an hash of values, keyed by their names, will be returned. ggeett__uunnddeeffiinneedd(($$hhaannddllee)) The function will return a hash of unresolved variable names in $handle, keyed by their names (that is, the hash has the form $a[$name] = $name). ffiinniisshh(($$ssttrr)) The function will returned the finished version of $str, that is, the policy regarding unresolved variable names will be applied to $str. pp(($$vvaarrnnaammee)) The function will print the finished version of the value of the variable named by $varname. ggeett(($$vvaarrnnaammee)) The function will return the finished version of the value of the variable named by $varname. hhaallttmmssgg(($$mmssgg)) This function can be overridden by your subclass of Template. It will be called with an error message to print. 44..22..22..22.. IInntteerrnnaall iinnssttaannccee mmeetthhooddss ffiilleennaammee(($$ffiilleennaammee)) When called with a relative pathname, this function will return the pathname with $this->root prepended. Absolute pathnames are taken unchanged. The resulting filename must exist, or an error is generated. vvaarrnnaammee(($$vvaarrnnaammee)) The function will construct a variable name regexp for a given variable name. llooaaddffiillee(($$hhaannddllee)) If a variable is undefined or empty and is backed by a filename, the backing file will be loaded and the files contents will be assigned as the variables value. hhaalltt(($$mmssgg)) This function is called whenever an error occurs and will handle the error according to the policy defined in $this->halt_on_error. 44..22..33.. EExxaammppllee The class manages a set of variables which are text strings. These strings may contain references to other variables in the form of "{variable}". When parsed or substituted, a variable reference is being replaced by the value of that variable. A variable value may be defined manually by calling set_var("name", "value"); or it may be defined from a file by calling set_file("name", "filename.ihtml");. In the latter case, the contents of the file are being loaded when needed (as late as possible) and set as the value of that variable. A third way to define a variable value is to call set_block("parent", "block", "name");. In this case, the variable named parent is being searched for a block that starts with and ends with . This string is removed from the variable parent and assigned to the variable named block. In parent, a variable reference to name is placed instead. If the optional parameter "name" is left out, "block" is being used instead. Use Template direcly or define a subclass of Template as needed. Define a template file named page.ihtml as follows: ______________________________________________________________________ {PAGETITLE}

{PAGETITLE}

{OUT} Content
______________________________________________________________________ This file contains a reference to the variable pagetitle and a reference to the variable named out. Another template file, named box.ihtml, contains a block named row with three variable references {TITLE}, {NUM} and {BIGNUM}: ______________________________________________________________________
{TITLE}
{NUM} {BIGNUM}
______________________________________________________________________ The following php3 file demonstrates how to use these templates: ______________________________________________________________________ set_file(array( "page" => "page.ihtml", "box" => "box.ihtml")); # extract the block named "row" from "box", creating a # reference to {rows} in "box". $t->set_block("box", "row", "rows"); # define the variables TITLE and PAGETITLE $t->set_var(array("TITLE" => "Testpage", "PAGETITLE" => "hugo")); # define NUM and BIGNUM, then append "row" to "rows"... for ($i=1; $i<=3; $i++) { $n = $i; $nn = $i*10; $t->set_var(array("NUM" => $n, "BIGNUM" => $nn)); $t->parse("rows", "row", true); } # build out from box, then build out from page... $t->parse("out", array("box", "page")); # finish out and print it. $t->p("out"); ?>
get_undefined("rows")); ?> ______________________________________________________________________ 55.. HHTTMMLL WWiiddggeettss CCllaasssseess 55..11.. SSqqll__QQuueerryy Sql_Query will generate a query form for simple table queries: A list of field names, comparision operators and input fields is presented. The user may search for any values in any of the presented columns using SQL standard operators. Multiple query conditions are possible and these conditions can be joined using AND and OR operations. The number of query conditions can be made variable. If so, the user may shrink and grow the query widget using the appropriate buttons. All button labels and other messages of the interface are variable and held in language dictionaries. Currently, _d_e and _e_n dictionaries are provided. 55..11..11.. IInnssttaannccee vvaarriiaabblleess Accessible instance variables. Internal instance variables. 55..11..22.. IInnssttaannccee mmeetthhooddss 55..11..22..11.. AAcccceessssiibbllee iinnssttaannccee mmeetthhooddss ssttaarrtt(()) Initialization function. Currently empty. ffoorrmm(($$bbaassee,, $$ooppttiioonn,, $$ccllaassss,, $$ttaarrggeett)) The function will generate and return HTML for the SQL Query selection form. All variables in the form will start with the prefix $base and have numeric indices appended after an underline character. It is possible to have multiple Sql_Query instances on a single page, if they use different base characters. The function must know the field names of the SQL table that is to be queried. $option can be either a simple array of these field names ($translate set empty) or a hash field name to long name ($translate set to on). All tags in the generated form are tagged with a CSS stylesheet class, if $class is set to a CSS classname. $class is optional and if it is left empty, no class attributes are generated. $target is the URL of the SQL Query form target. It is optional and if it is left empty, a self referencing form is generated (recommended). The function returns a string containing the HTML to render the SQL Query selection form. wwhheerree(($$bbaassee,, $$iinnccrr)) When the form() generated page is submitted, a lot of parameters have to be evaluated and transformed into a SQL _w_h_e_r_e condition matching the user selections. The where() function takes care of all this; it just needs to be told which $base prefix has been used in the form() call. The $incr parameter is optional and determines how many query condition rows are added or subtracted when the "More" and "Fewer" buttons are used. The default value is 1. The function returns a string which can be successfully used behind a "where" keyword in a SQL query. 55..11..22..22.. IInntteerrnnaall iinnssttaannccee mmeetthhooddss ppllaaiinn__wwhheerree(($$bbaassee)) This function does all the work for where(), but does not resize the query condition window. 55..11..33.. EExxaammppllee The Sql_Query class can be used directly. It is more useful when made persistent, so it is recommended that you add the line require("sqlquery.inc") to your prepend.php3 file where indicated in that file. See the Table class in this section for a nice method to display and format query results. See the DB_Sql class (a core class) for a nice method to connect to databases. The following code fragment is quite large, but contains a complete and working example using the Sql_Query, DB_Sql and Table classes to query a database table. ______________________________________________________________________ "Example_Session")); $db = new DB_Example; // That's a DB_Sql subclass. $t = new Table; // For formatting results $t->heading = "on"; // We want table headings.. ?> Testseite

Testpage

"Login Name", "password" => "Password", "perms" => "Permissions" ); // When we hit this page the first time, // there is no $q. if (!isset($q)) { $q = new Sql_Query; // We make one $q->conditions = 1; // ... with a single condition (at first) $q->translate = "on"; // ... column names are to be translated $q->container = "on"; // ... with a nice container table $q->variable = "on"; // ... # of conditions is variable $q->lang = "en"; // ... in English, please $sess->register("q"); // and don't forget this! } // When we hit that page a second time, the array named // by $base will be set and we must generate the $query. // Ah, and don't set $base to "q" when $q is your Sql_Query // object... :-) if (isset($x)) { $query = $q->where("x", 1); } // In any case we must display that form now. Note that the // "x" here and in the call to $q->where must match. // Tag everything as a CSS "query" class. printf($q->form("x", $field, "query)); printf("
"); // Do we have a valid query string? if ($query) { // Show that condition printf("Query Condition = %s
\n", $query); // Do that query $db->query("select * from auth_user where ". $query); // Dump the results (tagged as CSS class test) printf("Query Results = %s
\n", $db->num_rows()); $t->show_result($db, "test"); } page_close(); ?> ______________________________________________________________________ 55..22.. TTaabbllee aanndd CCSSVV__TTaabbllee The Table class is a neat way to format two-dimensional associative arrays of data or the results of a database query into a table. Table and its subclasses allow you to simply pass them either an array or a query result and they spit out the proper HTML for a table containing all the values. Table has some primitive filtering capabilities making it useful even without subclassing, but for the full power of Table you have to write your own subclass. When used with the check option, it is assumed that the table is part of a HTML FORM element. Code is generated to create an INPUT TYPE=CHECKBOX element before each table row. The checkboxes will form an array indexed by row number. The name of the array will whatever you set the check instance variable to. Exactly one of two types of possible column filtering take place when each table row is generated. If the fields instance variable is set, only the columns keyed by the named fields in that array are shown in that order. That is, if you fill in the fields instance variable with array("a", "c", "e"), only the columns a, c and e become part of the generated table. If fields has not been set, all data columns are traversed with each() and all columns whose names match the regexp in filter are shown in the table. By default, this regular expression lets through all column names that start with an alphabetic character and continue with either alphanumeric characters or "_" (underscore). This default has been chosen, because the DB_Sql database class uses mysql_fetch_array() internally to get data from the database and this function returns all columns twice with both a numeric index and proper column names. The default filter will make all data show up only once and with proper column names. Additionally, the map_cols instance variable provides column name remapping. If map_cols is set, it will remap the name of the found column with a new name. For instance, a table with the following columns, fname, lname, and mydate can be remapped to First Name, Last Name, and Date using the following code (where $t is your instantiated Table class object): ______________________________________________________________________ $t->map_cols = array("fname" => "First Name", "lname" => "Last Name", "mydate" => "Date"); ______________________________________________________________________ The map_cols instance variable also allows you to map column names to different languages using this same technique. For derived classes, the instance variable add_extra has been added. If this variable is set, then the functions table_heading_row_add_extra() and table_row_add_extra() are called. In the Table class, these functions do nothing, but in derived classes override these functions to provide additional functionality that may be needed. For instance, hyperlinks to provide edit, delete, or view capabilities for that row could be easily added into these functions (in your derived Table class) allowing greater customization. A subclass of Table, CSV_Table, is being provided to allow to create CSV representations of your data with minimal effort. CSV (comma separated values) can be imported by MySQL's LOAD DATA INFILE statement and many spreadsheet import functions. The Table class now provides both high-level, mid-level and low-level functions through modularization. This allows programmers to use either the simplified high-level functionality or, depending on the degree of complexity needed, the power of the mid- or low-level functions. Every effort to maintain backwards compatibility has been applied. However, it would be a good idea to become familiar with the new functions if you use the Table class extensively. Typically, the high- and mid-level support functions begin with show_ while the low- level functions do not. 55..22..11.. IInnssttaannccee vvaarriiaabblleess Accessible instance variables. 55..22..22.. IInnssttaannccee mmeetthhooddss 55..22..22..11.. HHiigghh--lleevveell iinnssttaannccee mmeetthhooddss sshhooww(($$aarryy,, $$ccllaassss == Will format and print the two dimensional array (or hash) $ary as a table according to the filtering rules explained above. If $class is set, each HTML element will be tagged as belonging to the named class; this is useful with cascading style sheets. sshhooww__ppaaggee(($$aarryy,, $$ssttaarrtt,, $$nnuumm,, $$ccllaassss == Just as show(), but will show only num elements starting at start. sshhooww__rreessuulltt(($$ddbb,, $$ccllaassss == Will format and print the result set of $db. $db is exspected to be a subclass of DB_Sql that has just been sent a query. Table will grab all available results from the result set of that query by calling $db->next_record() repeatedly and format them into a table. sshhooww__rreessuulltt__ppaaggee(($$ddbb,, $$ssttaarrtt,, $$nnuumm,, $$ccllaassss == Just as show_result(), but will show only num elements starting at start. 55..22..22..22.. MMiidd--lleevveell iinnssttaannccee mmeetthhooddss sshhooww__ttaabbllee__rroowwss(($$aarryy,, $$ccllaassss== Walks the passed array displaying each row of data as an HTML table row. sshhooww__ttaabbllee__rroowwss__rreessuulltt(($$ddbb,, $$ccllaassss== Walks the passed database object displaying each record as an HTML table row. sshhooww__ttaabbllee__ppaaggee__rroowwss(($$aarryy,, $$ssttaarrtt,, $$nnuumm,, $$ccllaassss== Walks the passed array displaying each row of data as an HTML table row. However, data does not start displaying until $start element and end after $num rows. sshhooww__ttaabbllee__ppaaggee__rroowwss__rreessuulltt(($$ddbb,, $$ssttaarrtt,, $$nnuumm,, $$ccllaassss== Walks the passed database object displaying each record as an HTML table row. However, data does not start displaying until $start record and ends after $num records have been displayed. sshhooww__ttaabbllee__hheeaaddiinngg__rrooww(($$aarryy,, $$ccllaassss== Uses the passed array to create an HTML header row. sshhooww__ttaabbllee__hheeaaddiinngg__rrooww__rreessuulltt(($$ddbb,, $$ccllaassss== Uses the passed database object to create an HTML header row. sshhooww__ttaabbllee__hheeaaddiinngg__cceellllss(($$ddaattaa,, $$ccllaassss== Walks the passed array and displays each item in an HTML table header cell. sshhooww__ttaabbllee__cceellllss(($$rrooww,, $$rrooww__kkeeyy,, $$ddaattaa,, $$ccllaassss== Walks the passed array and displays each item in an HTML table cell. 55..22..22..33.. LLooww--lleevveell iinnssttaannccee mmeetthhooddss ttaabbllee__ooppeenn(($$ccllaassss == This function can be overridden by a subclass of Table. It is called as the very first step in table creation and should output HTML that opens a table (for example printf("\n", $class?" class=$class":"");). ttaabbllee__cclloossee(()) This function can be overridden by a subclass of Table. It is called as the very last step in table creation and should output HTML that closes a table (for example printf("\n");/). sseelleecctt__ccoollnnaammeess(($$ddaattaa)) Internal function to generate a list of column names. ttaabbllee__hheeaaddiinngg__rrooww(($$ddaattaa,, $$ccllaassss == Internal driver function to generate a table heading row. ttaabbllee__hheeaaddiinngg__cceellll(($$ccooll,, $$vvaall,, $$ccllaassss)) This function can be overridden by a subclass of Table. It is called each time a table heading cell is to be generated. $col is the current column number, $val is the name of the column. $class is the HTML CSS class of the element that is to be generated. ttaabbllee__hheeaaddiinngg__cceellll__ooppeenn(($$ccllaassss== Starts a header cell. ttaabbllee__hheeaaddiinngg__cceellll__cclloossee(($$ccllaassss== Ends a header cell. ttaabbllee__hheeaaddiinngg__rrooww__aadddd__eexxttrraa(($$ddaattaa,, $$ccllaassss== Virtual function for derived classes. This function is called after all header cells have been created. It allows the programmer to add additional HTML code to the header row before it is closed. ttaabbllee__rrooww(($$ddaattaa,, $$ccllaassss == Internal driver function to generate a table row. ttaabbllee__rrooww__ooppeenn(($$rrooww,, $$ddaattaa,, $$ccllaassss == This function can be overridden by a subclass of Table. It is called as the very first step in row creation and should output HTML that opens a table row. $row is the current row number. $data is a hash of column name/value pairs for that row and $class is an optional HTML CSS class name for all generated elements. ttaabbllee__rrooww__cclloossee(()) This function can be overridden by a subclass of Table. It is called as the very last step in row creation and should output HTML that closes a table row. ttaabbllee__cceellll(($$rrooww,, $$cceellll,, $$kkeeyy,, $$vvaall,, $$ccllaassss)) This function can be overridden by a subclass of Table. It is called each time a table cell is to be generated. $row is the current row number, $cell is the current cell number. $key is the current column name, $val is the value of the cell. $class is the HTML CSS class of the element that is to be generated. ttaabbllee__cceellll__ooppeenn(($$ccllaassss== Starts a cell. ttaabbllee__cceellll__cclloossee(($$ccllaassss== Ends a cell. sseett__cchheecckkbbooxx__hheeaaddiinngg(($$ccllaassss== This function creates an empty header cell to coincide with the checkbox option for that column. ttaabbllee__cchheecckkbbooxx__cceellll(($$rrooww,, $$rrooww__kkeeyy,, $$ddaattaa,, $$ccllaassss== Outputs HTML code to display a checkbox. This function runs if the member variable $check has been set. $check should be set to some key within the $data array (ex: if $data["myKey"], then set $check="myKey"). sseett__cchheecckkbbooxx(($$rrooww,, $$rrooww__kkeeyy,, $$ddaattaa,, $$ccllaassss== Creates an HTML checkbox based on the passed data, only if the instance variable $check is set. 55..22..33.. EExxaammppllee Table is not automatically included or prepended into each page. Include the table class into the pages that are to use Table. Then create an instance of Table: ______________________________________________________________________ heading = "on"; ______________________________________________________________________ Now create a two dimensional array or prepare a database query and have table print it. ______________________________________________________________________ // Create a database object $db = new DB_Session; // create a twodim array called $tab $tab = $db->metadata("active_sessions"); // print that array $t->show($tab, "metadata"); // prepare a database query $db->query("select * from active_sessions"); // print that result $t->show_result($db, "data"); ______________________________________________________________________ 55..33.. FFoorrmm The form class (sometimes called OOH Forms) is a convenience library for dealing with html forms. It provides Javascript and server-side form validation, and is customizable and extensible. 55..33..11.. UUssiinngg OOOOHH FFoorrmmss The OOH Forms library consists of five files: oohforms.inc of_checkbox.inc of_radio.inc of_select.inc of_text.inc of_textarea.inc. oohforms.inc automatically includes the others. You may wish to modify this so you can manually include the files for just the form elements you use. Or you may wish to cut and paste the contents of the element files into oohforms.inc to save the overhead of multiple includes. Determining the appropriate configuration of the files for your site is left an exercise for the reader; for most purposes require("oohforms.inc") will suffice. In general, the structure of a page that uses oohforms is as follows: ______________________________________________________________________ require("oohforms.inc"); // include the library $f = new form; // create a form object $f->add_element(...); // set up form elements $f->add_element(...); $f->add_element(...); if ($submitname) // Is there data to process? if ($err = $f->validate()) { // Is the data valid? echo $err; // No; Display error $f->load_defaults(); // Load form with submitted data else { /* Process data */ // Data ok; Do something with it } $f->start(...); // Start displaying form $f->show_element(...); // Show elements $f->show_element(...); $f->show_element(...); $->finish(); // Finish form ______________________________________________________________________ There are obviously many variations on this theme, but that covers the basics. Specific methods are documented below. ssttaarrtt(($$jjvvssnnaammee,,$$mmeetthhoodd,,$$aaccttiioonn,, $$ttaarrggeett)) Outputs the initial tag and sets up some initial state needed by the class. All of the arguments are optional, though you'll usually want to use at least one in order to get Javascript validation. $jvsname is an arbitrary string used to link the Javascript to the form; if it is empty (the default), no Javascript validation is provided. $method is the HTTP method used to submit the form; default is "POST". $action is the URL that receives the form submission; default is $PHP_SELF. $target is the frame target for the form results; default is _self. ffiinniisshh(($$aafftteerr,,$$bbeeffoorree)) Outputs the any hidden fields that were added to the form, the final tag, then the Javascript validation function (if necessary). $after and $before are both optional; if either is a nonempty string it is output as additional Javascript to be run on submission of the form, either before or after the validation code. Though the order of the arguments may seem counterintuitive, it makes the common case easier to type; in general you'll want to wait until after the validation has taken place to do anything fancy with Javascript. Note that unlike with validation, OOH Forms has no way of giving you server side functionality equivalent to the Javascript you use here. aadddd__eelleemmeenntt(($$eelleemmeenntt)) add_element is used to define the attributes of a particular form element so that the other class methods can use and manipulate it properly. add_element takes exactly one argument: an associate array whose key value pairs are used to define the form element type and it's various attributes. Some of these attributes correspond to html attributes, while others are needed for the value added features of oohforms. The attributes and the syntax and semantics of the values they take are documented below; note that not all element types use all of the attributes ttyyppee The type of element this is; can be "submit", "hidden", "text", "textarea", "select", "radio", "checkbox", or "file". nnaammee A string naming this element. This name will be used as an argument to other methods and will be the name="" value in the generated html (and hence the variable name in PHP). DDoo nnoott append [] to the name if you want an array valued element; set the multiple attribute instead. vvaalluuee The default value of this form element. If the form element has the multiple attribute set, value can be an array. If the this is a select element, value can refer to either the textual name (label in the options array) or the submission value (value in options). mmuullttiippllee A flag to tell oohforms to assume this element is array valued. The use of this flag is most straightforward with select elements, but it can be use with text and checkbox elements as well. See the show_element documentation for more information about how oohforms deals with such elements. eexxttrraahhttmmll Extra html code that is inserted verbatim into the opening form tag. For select elements, the extra html goes into the select tag, not the option tags. ssiizzee For text elements, used to set the html size attribute that gives the width in characters of the text entry box. For select elements, the size (number of options visible at once) of the selection box. Validation is only performed on select elements if size is set to 1, since select validation doesn't make much sense if you can see multiple options at once. For file elements, the maximum size file upload to accept. ppaassss If set for a text element, renders the html as a password element, i.e. entry displays as asterisks. ssrrcc If set for a submit element, convert to an image element and use the value of src as the source URL for the image. mmaaxxlleennggtthh Used verbatim as the maxlength html attribute in text elements. mmiinnlleennggtthh If length_e is set, this is the minimum length text element entry accepted by the validator. lleennggtthh__ee If set, validate the text element to assure it has at least minlength characters. The value of length_e is the error string to be used in the event of failed validation. vvaalliidd__ee If set, perform validation on a text, radio, or select element. For a text element, validation assures a match with valid_ regex. radio element validation assures that one of the options in the group has been chosen. select validation only works for select elements with multiple unset and size equal to 1; the validator will not accept the first option of accept menu, assuming that it is some sort of prompt (e.g. "Please select an item"). In all cases, the value of valid_e is the error string used for failed validations. vvaalliidd__rreeggeexx Regular expression used to validate entry into a test field if valid_e is set. Note that if you must use ^...$ if you want the regex to match the whole entry. iiccaassee If set, regex matching is case insensitive. cchheecckkeedd Only used for a checkbox element that does not have multiple set. If checked is set, the element will display as checked. rroowwss Used verbatim as the rows= element in a textarea element. ccoollss Used verbatim as the cols= element in a textarea element. wwrraapp Used verbatim as the wrap= element in a textarea element. ooppttiioonnss Array of options to be displayed in a select element. If the elements of the array are simple values (strings or numbers), they are simply displayed verbatim and used as the value for that particular option. The elements may themselves be associate arrays with keys "label" and "value". In that case, the value of "label" is displayed and the value of "value" is used on submission. Examples: ___________________________________________________________________ $f->add_element(array("type"=>"text", "name"=>"foo", "valid_regex"=>"^[a-z]*$", "valid_e"=>"Letters only", "icase"=>1, "value"=>"bar")); $f->add_element(array("type"=>"checkbox", "name"=>"compress", "multiple"=>1)); $f->add_element(array("type"=>"textarea", "name"=>"comment", "rows"=>6, "cols"=>40, "value"=>"")); $o = array(array("label"=>"Please Select","value"=>0), array("label"=>"Apple","value"=>1), array("label"=>"Orange","value"=>2), array("label"=>"Pear","value"=>3), array("label"=>"Grape","value"=>4)); $f->add_element(array("type"=>"select", "name"=>"menu", "options"=>$o, "size"=>1, "valid_e"=>"Please select a fruit", "value"=>"apple")); ___________________________________________________________________ sshhooww__eelleemmeenntt(($$nnaammee,,$$vvaalluuee)) Outputs the form element named $name. Usually, the second argument is not used. It is always necessary for radio elements and checkbox elements with the multiple attribute set, since many of these may have the same name. It also must be used for submit elements to label the submission button; the value attribute is not used for submit elements. For other elements that may be array valued (notably text elements) multiple calls to show_element will show successive values. llooaadd__ddeeffaauullttss(($$eelleemmeenntt__lliisstt)) Sets the default value of form elements to the value of the PHP variables with the same name. This is generally used to redisplay a form with the same values which were submitted. The argument is optional; if given it is an array of element names; only these elements ares affected. vvaalliiddaattee(($$rreessuulltt,,$$eelleemmeenntt__lliisstt)) Validates a form submission. If all of the elements are valid, return $result, otherwise return the relevant error message (the valid_e or length_e attribute of some form element). $result defaults to false. The second argument is also optional; it is an array of names of elements to validate. ffrreeeezzee(($$eelleemmeenntt__lliisstt)) Freezes the form elements whose names are given in the array passed as the argument. If no argument is given, freeze all of the elements. Frozen elements are rendered as plain, static html rather than form widgets. This static rendering is accompanied by appropriate hidden elements to simulate the affect of using the unfrozen version of the element. 55..33..22.. CCuussttoommiizziinngg OOOOHH FFoorrmmss Since OOH Forms is object oriented, it can be easily customized by extending the classes that define the element types. In general, you must make sure your derived class has a constructor and you may override any of the self_* functions of of_element. The source for the existing elements is the best documentation for how to do this properly, but a few general notes follow. sseellff__sshhooww(($$vvaall,,$$wwhhiicchh)) Display an instance of this element unfrozen. $val is the $value argument of show_element if there was one; $which can be used as an index for array valued elements; it is equal to the number of times show_element has been called for this element previously. This function must return the number of hidden tags output. sseellff__sshhooww__ffrroozzeenn(($$vvaall,,$$wwhhiicchh)) Display an instance of this element frozen. In addition to the html to show the frozen element, you must output tags for hidden fields to duplicate the effect of submitting an unfrozen element of this type. The function must return the number of hidden tags output; sseellff__vvaalliiddaattee(($$vvaall)) Validate $val for this element. If it is valid, return false, otherwise return the relevant error string. sseellff__pprriinntt__jjss(($$nnddxx__aarrrraayy)) Print out Javascript code to validate this element. $ndx_array is an array of the indices of the elements to be validated as used in the Javascript form.element[] array. This is needed since the extra [] in array valued element names confuses Javascript. sseellff__llooaadd__ddeeffaauullttss(($$vvaall)) Set the default value for this element to $val. Usually the default definition of this function, which just copies $val to $this->value is all that is needed, but there may be special cases that need to do something else. See the implementation of the checkbox element for an example. 55..44.. ttppll__ffoorrmm The tpl_form class is intended to provide a general framework for HTML form deployment. It heavily depends on OOH Forms library, so it is required that you read and understand the relative documentation. The main idea is that you create a black box by sub-classing tpl_form, provide some HTML code mixed with OOH Forms calls to actually render the form itself. Your application will then use that black box to obtain some input from the user. Your application doesn't have to know how to handle user input, nor how to validate input data, since internal methods will take care of that. This approach is very similar (I think) to OOH Forms one, only at a higher level. OOH Forms elements have no way to communicate with each other, and are only able to perform "simple" checks on data integrity, while tpl_form adds a consistent interface for complex data evaluation and processing. Furthermore, the get_default_values and set_default_values methods can be used to maintain user input between sessions, without worrying about serialization of form variables (a BAD THING(tm)), using an hash array containing field names and values. You'll note that an array is used to share data with the application. You may object this is kinda futile, since all user input data may be found in global vars and HTTP_POST or HTTP_GET global hashes. This is true, and in the general case you'll pass back and forth an empty array. The values variable is intended for use in very complex data- entry setup, where a form behaviour may depend on previous data entered by the user. In this case, if all forms cooperate reading and writing to values hash array, final result may be constructed step by step across multiple HTML pages. 55..44..11.. IInnssttaannccee vvaarriiaabblleess Internal instance variables. 55..44..22.. IInnssttaannccee mmeetthhooddss 55..44..22..11.. AAcccceessssiibbllee iinnssttaannccee mmeetthhooddss iinniitt(($$vvaalluueess)) This is a sort of a constructor for the class. $values is an hash array intended to store form values to be passed back to the application via get_values method. ggeett__ddeeffaauulltt__vvaalluueess(()) Returns an array containing all data submitted by the user for the form. This array is intended to be passed to set_defaults_values some time later. sseett__ddeeffaauulltt__vvaalluueess(($$ffvv)) Restore form defaults from an array as returned by get_default_values. ddiissppllaayy(()) Actually display form fields. This method should not be overridden in descendants. User should instead provide a file named as the derived class and with ".ihtml" extension which will be automatically included. ggeett__vvaalluueess(()) This method should not be overridden. It is intended as the main interface between the application and the form. Once the form has been properly derived to suit designer's needs, application calls get_values and receives back the array passed to init, eventually modified by process_input method, or false if user input is invalid. In that latter case, the application should call display to (re)present the form to the user, eventually filled with proper default values. cclleeaarr(()) Sort of a "destructor". There should no real need to call it, except maybe freeing some memory. May be called from the application, otherwise is not executed. Returns true. 55..44..22..22.. IInntteerrnnaall iinnssttaannccee mmeetthhooddss sseettuupp(()) Init the Form object, which will contain all fields info. The hidden field form_name, automatically added by this routine, is used by other methods to determine if form has already been submitted by the user. You shouldn't override this in descendants, use setup_fields instead. Returns true. sseettuupp__ffiieellddss(()) Override this method in order to provide form fields definition that suit your needs. vvaalliiddaattee(()) Validates user input. This method should not be overridden in descendants. See validate_input instead. Returns false on error and sets error variable accordingly. vvaalliiddaattee__iinnppuutt(()) This method should be overridden in descendants, in order to provided complex validation methods (i.e. field2 should not be empty IF field1 == "other"). Should return false on error and set error variable with a sensible error message. pprroocceessss(()) Process user data. This method should not be overridden by descendants. See process_input and process_default instead. Returns true on success, false otherwise. pprroocceessss__iinnppuutt(()) This method should be overridden in descendants. It is executed after validation has taken place. The data passed to the form could be used to fill values array. pprroocceessss__ddeeffaauulltt(()) This method should be overridden in descendants. It is executed when form validation fails or before presenting the form for the first time. Should be used to bypass form displaying if data can be extracted from previous actions, divination, penguin fly watching or whatever. 55..44..33.. EExxaammppllee Suppose you have a form that the user should fill with her (eheh) name and e-mail. You want to check wether this e-mail is valid, or your blind date setup is lost. A... er... simple regular expression for validating syntactically the e-mail is presented in the example code below. ______________________________________________________________________ $this->form_data->add_element(array( "type"=>"text", "name"=>"email", "valid_e"=>"Syntax error in E-Mail address.", "valid_regex"=>"^([-a-zA-Z0-9.]+@[-a-zA-Z0-9]+(\.[-a-zA-Z0-9]+)+)*$" )); ______________________________________________________________________ Now, this piece of code should do the job, but since you're feeling very paranoid today, you'd also like to validate the host name part of the address with DNS. So, you put together some code which takes an hostname in input and reports true on valid hostname, false otherwise (HINT: on PHP Code Exchange you should find a procedure for "active" email validation). Now that you have your shining new code, you can check the address. The user fills out the form, you parse user input, no syntax errors, time to call your mycheckhost from the application. If the function is ok update your database, else load defaults into the form, display again, close the page, goodbye. I've done something similar for MANY forms, some of them with very complex validation procedures, and I found that was too easy producing very bad and unreadable code (well, I actually realized that the first time I had to change some logic in data validation...). tpl_form should provide a solid framework to build your forms with, and all the code will be self-contained and separated from main application logic. Hope you'll like it. Time to see some code. First of all, class declaration, sub-classing tpl_form: ______________________________________________________________________ class myform extends tpl_form { var $classname = "myform"; function setup_fields() { $this->form_data->add_element(array( "name"=>"email", ..., // See previous code snippet )); $this->form_data->add_element(array( "name"=>"submit", "type"=>"submit", "value"=>"submit" )); } function validate_input() { global $email; list($uname, $hostname) = split("@", $email); if (! mycheckhost($hostname)) { $this->error = sprintf("Sorry, unknown host %s, try again.", $hostname); return false; } // Additional checks here... return true; } } ______________________________________________________________________ You shuld provide a file myform.ihtml with HTML and php code to render the form. Minimalistic example: ______________________________________________________________________ form_data->start_form($this->classname, "POST", $sess->self_url(), ""); printf("%s
\n", $this->error); $this->form_data->show_element("email"); printf("
\n"); $this->form_data->show_element("submit"); $this->form_data->finish(); ?> ______________________________________________________________________ Your tpl_form class is complete, and will only need a little work on the artistic side... 8-) To use your brand new class, include class definition code into your application, then... ______________________________________________________________________ $mf = new myform; $mf->init(array()); // This is not strictly required, AT PRESENT, // but I recommend it if ($rv = $mf->getdata()) { $mf->clear(); // This is not strictly required, anyway it should free // some memory... global $email; // process your data at will } else { $mf->display(); } ______________________________________________________________________ Hope this very little example does help in understanding the real power of tpl_form class, at least in terms of rapid designing and code partitioning. 55..55.. TTrreeee The Tree class can render tree structures such as directory hierarchies and menu structures as HTML. The structure must be given to Tree as an nested array of arrays of arbitrary depth. The idea of Tree is, that there are several mathematical models a tree could be viewed: One model is a data structure like nested arrays or a pointer structure from where you can print multidimensional graphics and can do other neat things like deleting one part of the tree or inserting a whole subtree. But you can also imagine a tree as a one dimensional string or as a sequence of function calls (which is nearly the same in the mathematical sense). To generate HTML-code from a tree-structure it is like this: You need at the end a one-dimensional string, which tells the browser what to do. The Tree class assists you in generating this string in this way, that it will go through the whole tree and call several functions on every stage trough the way. It will be your task to change the functions, so that a nice layout will be generated. 55..55..11.. IInnssttaannccee vvaarriiaabblleess Accessible instance variables. 55..55..22.. IInnssttaannccee mmeetthhooddss 55..55..22..11.. AAcccceessssiibbllee iinnssttaannccee mmeetthhooddss bbuuiilldd__ttrreeee(()) This function is completely user driven! You have to create an array with the structure described below. See the example for details. Don't be shy to create some own functions which are called by build_tree() - e.g. for recursive calls. ggoo__ttrroouugghh__ttrreeee(($$kkeeyy== This is the most important function of this class. It will call the output functions in the right order with the correct parameters. All variables are optional. The parameters are perhaps useful, if you want to display only partial trees, but this is not supported now. ppaatthh__ttoo__iinnddeexx ((&&$$ppaatthh,,$$kkeeyy== This function is mostly used internally, but could be useful for you to generate $this->tree. This function generates a PHP3 associate array-index string from a path, which is also a string but truncated by $this->delimiter. If $key is given, it will be added to $path (minds empty path and so on). Example: ___________________________________________________________________ $t->delimiter="/"; $path= "usr/local/lib"; ## $path must be given as a var, because it is called by reference! $bla = $t->path_to_index($path,"etc"); ## $path is now "usr/local/lib/etc" ## $bla is now ["usr"]["local"]["lib"]["etc"] ___________________________________________________________________ ppaatthh__ttoo__ppaarreenntt ((&&$$ppaatthh)) This function isn't used internally, but could be useful for you during generating the output tree. It will remove one from the depth of the path. Example: ___________________________________________________________________ $t->delimiter="/"; $path= "usr/local/lib"; $bla = $t->path_to_parent($path); ## $path is now "usr/local" ## $bla is now ["usr"]["local"] ___________________________________________________________________ ppaatthh__aadddd (($$ppaatthh,,$$kkeeyy)) This function is the 'non-call-by-reference-version' of path_to_index. It will add the $key to the path and return it. ppaatthh__ssuubb (($$ppaatthh)) This function is the 'non-call-by-reference-version' of path_to_parent. It will find the parent of path and return it. ppaatthh__iinnddeexx (($$ppaatthh)) This function is the 'non-call-by-reference-version' of path_to_index(). It will return the associate key to the tree described by path. ssttaarrttttrreeee (()) This function is called by go_trough_tree() at the beginning of the output of a tree. All *tree-functions are called by go_trough_tree(), but it's your turn, to give them a nice layout. I think it is possible to generate nearly every kind of tree-layout with this. Have a look at the variables: E.g. $depth makes it possible to handle every "level" in another manner. ggrroowwttrreeee (($$kkeeyy,,$$vvaalluuee,,$$ppaatthh,,$$ddeepptthh,,$$ccoouunntt,,$$ppccoouunntt)) This function is called by go_trough_tree() at the beginning of the output of a tree. It is called every time, when go_trough_tree() will call itself recursively. You could also say it is called, when the current item has a successor. lleeaaffttrreeee (($$kkeeyy,,$$vvaalluuee,,$$ppaatthh,,$$ddeepptthh,,$$ccoouunntt,,$$ppccoouunntt)) This function is called, when the current item has _n_o successor. sshhrriinnkkttrreeee (($$kkeeyy,,$$ddeepptthh)) This function is the "opposite" of growtree(). It is called every time, when the current item was the last item in this sub- list. eennddttrreeee(()) Called when leaving tree. 55..55..33.. TThhee TTrreeee AArrrraayy As said above, before you call go_trough_tree(), first $tree must be generated. $tree consists of nested arrays of arbitrary depth. An example: ______________________________________________________________________ $t= new Tree; $t->tree = array( "usr" => array( 0 => "allowed", "lib" => "forbidden", "local" => "allowed", "bin" => "forbidden", "etc" => array( 0 => "allowed", "hosts" => "forbidden", "mailcap"=> "allowed" ), "var" => "allowed", "tmp" => "allowed" ), "root" =>"forbidden" ); $t->go_through_tree(); print $t->outp; ______________________________________________________________________ This is a completely recursive structure and I think, it is clear, how to create it with a recursive call of a function. If not, see the example below. One little quirk has to be explained, because it is a little bit confusing: the array name 0 (zero) is used for the value of the parent element. As shown in the example, an element with children (for example "etc") cannot have attributes (such as "allowed"). Instead the value of this element is stored in a pseudo-child named 0. If this element is not present, it will have the value "Array" (perhaps something that should be changed). The output of this example if you don't change the output-functions will look like this: ______________________________________________________________________ / ^---- usr->'allowed' : 'usr' (1) [1/2] | ^---- lib->'forbidden' : 'usr^lib' (2) [2/7] | O---- local->'allowed' : 'usr^local' (2) [3/7] | O---- bin->'forbidden' : 'usr^bin' (2) [4/7] | O---- etc->'allowed' : 'usr^etc' (2) [5/7] | | ^---- hosts->'forbidden' : 'usr^etc^hosts' (3) [2/3] | | \--- mailcap->'allowed' : 'usr^etc^mailcap' (3) [3/3] | O---- var->'allowed' : 'usr^var' (2) [6/7] | \--- tmp->'allowed' : 'usr^tmp' (2) [7/7] \--- root->'forbidden' : 'root' (1) [2/2] ______________________________________________________________________ Looks a bit confusing. From left to right the fields are · The _i_n_d_e_x_-_n_a_m_e of the current field · The _v_a_l_u_e of this field · The _f_u_l_l _p_a_t_h to this field (see path_to_*-functions) · The current _d_e_p_t_h or _l_e_v_e_l · The current _e_l_e_m_e_n_t _n_u_m_b_e_r. See below to understand, why it will begin sometimes with "2" in this example! · The _n_u_m_b_e_r _o_f _e_l_e_m_e_n_t_s in the subtree at this depth 55..55..44.. EExxaammppllee My example is just going trough the directory structure of your hard disk. The following code could read it: ______________________________________________________________________ class dir_Tree extends Tree { var $classname = "dir_Tree"; var $delimiter="/"; var $tdat; function build_tree ($path=".") { $this->tree=$this->recurs_dir($path,0); } ## This example code can read and output 1000 directory entries with ## many subdirs in about 20 seconds on my system (P200, 64 MB); ## 220 dir entries with a maximum depth of 4 are read in 2 seconds. ## This is ok. :) function recurs_dir ($path,$depth) { GLOBAL $flap_out; $d=opendir($path); while ( $name=readdir($d) ) { $pathname=$path . $this->delimiter . $name; if (is_dir($pathname) && !ereg("\.\.?",$pathname)) { if (isset($flap_out[$pathname])) { $array[$name]=$this->recurs_dir($pathname,$depth+1); } # ATTENTION: It is IMPORTANT fill the [0] array # *after* filling the rest of the array! $array[$name][0]=$pathname; } else { $array[$name]=$pathname; } } closedir($d); return($array); } ################################################# ## FLAPPING IN and OUT ## This is used to create an array which includes ## all sub-paths which should be showed ## function flapping ($path) { GLOBAL $flap_out; if ($path) { if (is_dir($path)) { if (isset($flap_out[$path])) { unset($flap_out[$path]); } else { $flap_out[$path]=$name; } } } } } $t= new dir_Tree; $t->flapping($val); ## $val is given by GET-method, see *tree()-functions $t->build_tree(); $t->go_through_tree(); print $t->outp; ______________________________________________________________________ With this code it is very easy to flap in and out whole parts of the tree. Send the path via GET-method and put this path in flapping(). The whole $flap_out-array must be persistent (e.g. via _s_e_s_s_i_o_n). Perhaps you can program a garbage collection, which will look into $flap_out and check for paths that already exist? 55..55..55.. KKnnoowwnn BBuuggss // TTiippss There is one known bug: If a name of a subpath contains the $delimiter-string. This cannot be solved correctly and you have to look for it when you create the tree. The same thing is with the value [0] (zero) of a sub-array. This element is always used as the attribute of the parent element. A good tip: when you build your tree recursively then the [0]-index must be filled _a_f_t_e_r the subtree is returned from recursive call. See in the example above what I mean. I think it's a PHP3 specialty. Also it is possible that not every name could be inserted into the associate index-field (Control-chars etc.), but this is untested. 55..66.. SSTTRRIINNGGSS22 ffuunnccttiioonn sseett This is a set of functions, which are used very often by me. They are so easy, that I now stop describing and simply insert the code. Perhaps the next revision of this set I will replace it with a better description: ______________________________________________________________________ num_rows(), ## "Found only one matching record", ## "Found %s records"); ## Will output if num_rows() is 1: "Found only one matching record" ## 200: "Found 200 records" ## ## if $val is empty() or "" a blank string will be returned! ## function o_1or2 ($val,$str1,$str2) { if (isset($val) && $val) { if (1==$val) { return(sprintf($str1,$val)); } else { return(sprintf($str2,$val)); } } else { return(false); } } function p_1or2 ($val,$str1,$str2) { print o_1or2 ($val,$str1,$str2); } ## ## This is for the case, that you want to output something ## if $val is false e.g. ## ## p_0or1($faxnumber,"THERE IS NO FAXNUMBER","Faxnumber: %s"); ## function o_0or1 ($val,$str1,$str2) { if (empty($val) || !$val) { if (isset($val)) { return(sprintf($str1,$val)); } else { return($str1); } } else { return(sprintf($str2,$val)); } } function p_0or1 ($val,$str1,$str2) { print o_0or1 ($val,$str1,$str2); } ## ## Replaces all blank-chars with ## This function is used, when you are not willing to let the browser ## break your lines an can be used instead of -Tag ## as very compatible replacement ## ## can also be replaced by a true whitespace which has in ## ISO-latin-1 the code 160 ## function o_nonbsp ($val) { return(ereg_replace("[[:blank:]\n\r]"," ",$val)); } function p_nonbsp ($val) { print o_nonbsp($val); } ?> ______________________________________________________________________ 66.. AAcckknnoowwlleeddggmmeennttss The initial idea on how to do serialization was contributed by KH Wild to the php3 mailing list. It was limited to serializing arrays of at most three dimensions, though. We worked on his solution, improving it to arrays of arbitrary depth and later rewrote the function from scratch, turning it upside down. Our new serialization code can now handle any first order data type available to PHP and is easily extensible. It is also encapsulated in a class, keeping the name space clean. While we were at it, we made session cookies more secure by not using uniquid() directly, but a md5() hash of uniqid(). Cameron Taggart and Guarneri Carmelo contributed ODBC support. Szandor van Verseveld contributed PostgreSQL support. Scott McMullan contributed some nice ideas for cleanup and is working on Sybase support. Sascha Schumann contributed much time developing and extending PHPLIB, including but not limited to mSQL/Sybase support, general storage container support, shared memory and LDAP support. Alexander Aulbach submitted his Tree class. Jay Bloodworth contributed his excellent OOH Forms library for form generation and input validation. A lot of people provided helpful hints and occasionally patches. Please see the file CREDITS for a complete list of contributors, testers and inspirations.