FreeBSD Programming Primer
FreeBSD Programming Primer
-2 60 0
High Performance, High Density Servers for Data Center, Virtualization, & HPC
MODEL: iXR-22X4IB
http://www.iXsystems.com/e5
KEY FEATURES
iXR-22X4IB
Dual Intel Xeon Processors E5-2600 Family per node Intel C600 series chipset Four server nodes in 2U of rack space Up to 256GB main memory per server node One Mellanox ConnectX QDR 40Gbp/s Infiniband w/QSFP Connector per node 12 SAS/SATA drive bays, 3 per node Hardware RAID via LSI2108 controller Shared 1620W redundant high-efficiency Platinum level (91%+) power supplies
of RAM in 1U
768GB
iXR-1204+10G
Dual Intel Xeon Processors E5-2600 Family Intel C600 series chipset Intel X540 Dual-Port 10 Gigabit Ethernet Controllers Up to 16 Cores and 32 process threads Up to 768GB main memory Four SAS/SATA drive bays Onboard SATA RAID 0, 1, 5, and 10 700W high-efficiency redundant power supply with FC and PMBus (80%+ Gold Certified)
Call iXsystems toll free or visit our website today! 1-855-GREP-4-IX | www.iXsystems.com
High-Density iXsystems Servers powered by the Intel Xeon Processor E5-2600 Family and Intel C600 series chipset can pack up to 768GB of RAM into 1U of rack space or up to 8 processors - with up to 128 threads - in 2U.
On-board 10 Gigabit Ethernet and Infiniband for Greater Throughput in less Rack Space.
Servers from iXsystems based on the Intel Xeon Processor E5-2600 Family feature high-throughput connections on the motherboard, saving critical expansion space. The Intel C600 Series chipset supports up to 384GB of RAM per processor, allowing performance in a single server to reach new heights. This ensures that youre not paying for more than you need to achieve the performance you want. The iXR-1204 +10G features dual onboard 10GigE + dual onboard 1GigE network controllers, up to 768GB of RAM and dual Intel Xeon Processors E5-2600 Family, freeing up critical expansion card space for application-specific hardware. The uncompromised performance and flexibility of the iXR-1204 +10G makes it suitable for clustering, high-traffic webservers, virtualization, and cloud computing applications - anywhere you need the most resources available. For even greater performance density, the iXR-22X4IB squeezes four server nodes into two units of rack space, each with dual Intel Xeon Processors E5-2600 Family, up to 256GB of RAM, and an on-board Mellanox ConnectX QDR 40Gbp/s Infiniband w/QSFP Connector. The iXR-22X4IB is perfect for high-powered computing, virtualization, or business intelligence applications that require the computing power of the Intel Xeon Processor E5-2600 Family and the high throughput of Infiniband.
IXR-22X4IB
Intel, the Intel logo, and Xeon Inside are trademarks or registered trademarks of Intel Corporation in the U.S. and other countries.
Call iXsystems toll free or visit our website today! 1-855-GREP-4-IX | www.iXsystems.com
12
Rob Somerville has been passionate about technology since his early teens. A keen advocate of open systems since the mid-eighties, he has worked in many corporate sectors including finance, automotive, airlines, government and media in a variety of roles from technical support, system administrator, developer, systems integrator and IT manager. He has moved on from CP/M and nixie tubes but keeps a soldering iron handy just in case.
Publisher: Hakin9 Media SK 02-676 Warsaw, Poland Postepu 17D Poland worldwide publishing [email protected] www.bsdmag.org Hakin9 Media SK is looking for partners from all over the world. If you are interested in cooperation with us, please contact us via e-mail: [email protected]. All trade marks presented in the magazine were used only for informative purposes. All rights to trade marks presented in the magazine are reserved by the companies which own them.
TBO 01/2014
Contents
ADMIN
ithin the I.T. environment there are many disciplines, and often these skill sets work in isolation. The sys-admin doesn't always understand the challenges faced by the programmer or developer, the support engineer doesn't understand the problems of the developer, and the project manager doesn't understand the problems of the technical staff. In this new series, we will examine from first principles how to develop a CMS that will run on any Apache / MySQL / PHP stack. This will involve writing HTML, CSS, PHP and SQL code.
Does what it says on the tin Is user friendly Is secure and reliable under stress Is fast and efficient (Don't Repeat Yourself) Is easily modified and extended Can be easily understood Has documentation
Code is Everywhere
To the uninitiated, writing computer code from scratch may seem a challenge. Certainly, some programming languages are more complex than others, but the fact remains you have already programmed some device at some stage without realizing it even if you have not been near the command line (for example a VHS recorder, central heating timer etc.). As a result you have instructed the device to do something (Record the Simpsons at 10:00PM on Friday evenings). Software is effectively just a collection of instructions, logic and actions like this that allow the computer to interact with another computer, an end user or just itself. The skill is in writing good code that meets the following guiding principles:
While some of these points are essential to any piece of software, some may be more important than others depending on the operating environment and specification. For instance, a piece of code that pulls pages from a website on a daily basis into a a new directory in the format day_month_year (like 01_01_2013, 02_01_2013 etc.) for later reading by a technician would not necessarily require anything other than a log file entry saying 404 Not Found if no content was available. However, if this was a critical program designed for an end user, it would be better practice to raise a friendly error message e.g. The page you requested was not found. Please try again later or contact the helpdesk on 123 456789. Software writing should be creative and enjoyable, and part of the challenge is to have a reasonable idea of what you want to achieve beforehand, who your audience is, what limitations you must consider, and the environment the software will run under. A good functional
TBO 01/2014
specification should cover these details, but it is important to realize that software is never really finished. More functionality may be required, the environment may change, or bugs and faults need to be rectified in the program. That is why code should be easily modified and understood as it is the programmers worst nightmare having to maintain a badly written, undocumented, broken program. Trying to get inside someone else's logic especially when under pressure to meet deadlines can be very stressful!
The old adage Garbage In = Garbage Out is most applicable in the area of programming. As CANVC, they can only literally interpret any instructions that they receive. For instance, you might think you have asked the program to print the date, but due to an error in your logic, it might return 01-01-1970, NULL, or UNDEF. It might not even return anything at all. Sometimes when writing code you will be convinced the computer is your enemy. This is where defensive programming and debugging come to the fore, by re-thinking the obvious (and not so obvious) assumptions such as All input data is valid. The defensive programmer would respond by saying All data is important and tainted unless proved otherwise. Expect the unexpected. Sometimes it is best to walk away, take a break and return to the problem later. Late night coding sessions can be frustrating, especially if the result is not what is expected. Trying to debug an issue without a decent IDE (Integrated Development Environment) is possible, but time consuming.
Not all programming languages are equal, and some are less equal than others. Different languages are geared towards different tasks. Shell programming languages (for example Bash, Sh etc.) are great for system administration tasks e.g. clearing out and archiving directories, running commands depending on the user response etc. However they are not fully fledged programming languages as such. BASIC and Pascal are great for learning how to code, but they have some limitations. While it would be possible to write a CMS in either of them, as they are not primarily geared towards the web the program would be complex and convoluted. The same argument applies to C. C is extremely powerful and flexible and PHP, Apache and MySQL are written using it. It would be complete overkill to write the CMS in scratch from C as we would effectively have to re-invent the wheel.
Java would make a great platform for a CMS due in part to its extensive library support and security, but as it is object orientated rather than procedural, the code and underlying principles would be more complex. Script based languages (for example Ruby, Perl, Python, PHP) are geared towards the Internet, and most ISP's will support them. As PHP has good support, is very portable, the documentation is excellent, and integrates well with both Apache (Our web server software) and MySQL (our database) it is a strong choice. While the the other script languages are just as suitable for our CMS, the author has more experience with PHP so that is the reason for the choice. SQL, HTML and CSS are different types of language. While not considered real programming languages as such (on their own you could not write a software application) they are essential to our CMS. SQL (Sequential Query Language) is the de facto standard language of databases. While most databases today use some form of SQL to extract, view and alter data, the dialect differs from database to database. We will use SQL to fetch our dynamic content from our database. HTML (Hyper Text Markup Language) is the language of the web page. Each document has separate elements e.g. a body, header, images etc. and the HTML standard defines what these elements are. HTML pages are served by Apache and interpreted by the client browser e.g. Firefox. CSS (Cascading Style sheets) are used in conjunction with HTML to change the style of the raw HTML pages. While it would be possible to write a CMS without it, it would probably not be very aesthetic. JavaScript is a lightweight programming language used for dynamic tasks in conjunction with HTML e.g. changing content on the fly. It is run seamlessly from the client browser. Generally, programming languages fall into 2 categories, complied and interpreted. For instance C, Basic and Pascal are compiled whereas most script languages are interpreted. The major difference between compiled and interpreted languages is how the program itself is accessed and run. In the compiled scenario, the initial source code is passed though a compiler which generates a stand-alone binary if the source code is valid. The operating system then handles the corresponding output. A binary compiled for one particular Operating System will not run on another in general the compiler has to match the O/S unless some form of emulation and library support is available. With interpreted languages each line of the source code is passed through the interpreter which handles the corresponding output. Both language types sup-
www.bsdmag.org
ADMIN
port additional libraries which extend the core functionality of the language (e.g. graph support) and these are used as required. See Figure 1 and Figure 2 Compiled and Interpreted languages. The bottom line is that you need to choose your language for the task you have in hand. Some all purpose languages are great but you need to remember the limitations. The author often uses PHP for add-hoc scripts, but Perl or Bash would be just as effective. Often it is a case of what you feel most comfortable with, but at the same time you don't want to fall into the trap When the only tool you have is a hammer every problem is a nail.
able to snapshot and document our changes as well as quickly isolate any problems. As part of the series we will look at version control and debugging.
The initial specification of our CMS is per Table 1. Further additions may be made over the series to demonstrate specific principles. The inspiration for parts of the specification came from the excellent CMS, Drupal by Dries Buytaert.
Testing
To err is Human
Writing code is paradoxically both infinitely creative and flexible yet structured and pedantic. One missed semicolon, a full stop in the wrong place, even word case can be the difference between a working code segment and an esoteric error message. Sometimes by fixing one problem other problems are introduced, sometimes the real problem was never addressed at all. It is important that we are
It is critical that any application is properly tested before release. While automated testing methods are available, for the purpose of this series will limit testing to some crude load and security testing and ensuring that the program just works as advertised.
In a commercial environment, the bare minimum would probably consist of a test (development) server, a live
Table 1. CMS draft specification
TBO 01/2014
(production) server, a Version Control Server (VCS), possibly a database server (MySQL) and the developers workstation with an Integrated Development Environment (IDE) for code development, syntax checking and debugging. Source code would be pulled from the VCS, edited and tested on either the workstation or the development server, committed to VCS and pushed to the production server for access by the users when stable and ready for release. This scenario is too complex for our series, but while it is possible to develop just from the command line, debugging (and certainly testing) will be close to impossible outside of a graphical environment. As a very bare minimum, you will need a headless FreeBSD box (without any GUI) and some sort of workstation with Firefox installed, but ideally your BSD development box should support Firefox, Netbeans, Apache. PHP, GIT and MySQL. Your favorite CLI editor can of course still be used for editing.
We will start programming in earnest and start serving our first CMS page.
ROb SOMerville
Rob Somerville has been passionate about technology since his early teens. A keen advocate of open systems since the mid eighties, he has worked in many corporate sectors including finance, automotive, airlines, government and media in a variety of roles from technical support, system administrator, developer, systems integrator and IT manager. He has moved on from CP/M and nixie tubes but keeps a soldering iron handy just in case.
www.bsdmag.org
ADMIN
B
Key
efore we get started, you need to have a FreeBSD test server available with the AMP (Apache / MySQL / PHP ) installed. We will also use a version control system (VCS) and a CLI based text editor. I am using FreeBSD 9.0 with VI, MC (for file management) and GIT running under Virtualbox. Start by installing FreeBSD from DVD and configure networking, user and root accounts, etc. as normal.
Ensure hosts has your machine name set in otherwise Apache will not start.
::1 localhost dev
/etc/hosts
Command line instructions Alterations to configuration files MySQL prompt / SQL HTML / XHTML / PHP code
127.0.0.1
localhost dev
Start Apache:
dev# /usr/local/etc/rc.d/apache22 start
Start MySQL:
dev# echo 'mysql_enable="YES"' >> /etc/rc.conf
10
TBO 01/2014
/usr/local/etc/php.ini
dev#
cp /usr/local/etc/php.ini-development
Now we need to setup a development area in our home directory. We will create an account with username dev:
dev# adduser
Follow the prompts (the defaults are fine), and give the new user a password. We want to edit / develop as dev, so move the apache data directory across to /home/dev and symlink back. That way, Apache can serve the files we create as a non-root user as we can run GIT as a normal user:
dev# mv /usr/local/www/apache22/data/ /home/dev/ dev# chown dev:dev datapwd dev# cd /home/dev/data dev# ln -s /home/dev/data/ /usr/local/www/apache22/data dev# chown dev:dev index.html
If you visit your dev box with a browser (http://youripadress) you should see the standard Apache It works! welcome page.
As a developer, a version control system is an important tool not only to track code changes, but to allow quick recovery from mistakes. Once a file is added and committed to the repository, any errors can be quickly rectified by rolling back to a previous version. Login with (or su to) the new DEV user account, change to the data directory, and create a new repository then
www.bsdmag.org
11
ADMIN
commit index.html to it after setting your details. When prompted in the editor, the commit message should be Initial Load.
dev# su dev
dev# cd /home/dev/data/
This will commit the original index.html to the new GIT repository. Edit index.html to reflect Code Listing 1 Hello World is always the first statement written in experimental code. Check with your browser that the page has changed (you may need to press Shift F5 to refresh the cache). Now commit it to the repository:
dev# git commit -am "First line of HTML"
Listing 2. index.xhtml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1- strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>My first XHTML page</title>
</head> <body>
</body> </html>
<p>Hello world</p>
Listing 3. phpinfo.php
<?php phpinfo();
12
TBO 01/2014
Further reading
To go back to the original Apache file (Where 0007073d is the first 8 digits of the file checksum) and overwrite your changes permanently:
dev# git checkout 0007073d
Now the log will only show the original file. Create two files index.xhtml and phpinfo.php with the code from code Listing 2 and 3 respectively and add and commit to the repository:
dev# git add * dev# git log
dev# git commit -am "XHTML and PHP test page "
You should see a log file similar to Figure 4. Listing 1 is a standard XHTML page, with the XML and document type defined. In the next article, we will look at adding CSS and Javascript to this skeleton, but the important point to note here is that all the tags are balanced every opening tag (e.g. <p>) has to have a matching closing tag. To view this page, visit http://youripaddress/index. xhtml in your browser. Listing 2 is a very simple PHP command phpinfo(); displays all the configuration values, modules loaded etc. available to the PHP interpreter. You should see a page similar to Figure 3 if you visit http://youripaddress/ phpinfo.php.
We will look at code structure, program flow and how to embed CSS and Javascript in out pages. We will also start using SQL to dynamically generate pages.
ROb SOMerville
Rob Somerville has been passionate about technology since his early teens. A keen advocate of open systems since the mid eighties, he has worked in many corporate sectors including finance, automotive, airlines, government and media in a variety of roles from technical support, system administrator, developer, systems integrator and IT manager. He has moved on from CP/M and nixie tubes but keeps a soldering iron handy just in case.
www.bsdmag.org
ADMIN
efore we start coding in earnest, we will look at the basic construction of our programming language (PHP), the directory and functional structure of our CMS and how this all fits together. Our CMS will be designed to be as extensible as possible, and will follow the design as detailed in Figure 1. Pages will be stored in the MySQL database, merged with the header, templates, CSS and Javascript and returned to the client browser. This will allow us to separate design from content efficiently.
The following code examples can be created in the examples directory using your favorite editor (in this case I am using VI). Login to the webserver using SSH or from the console, switch to the DEV account from root and create the files:
dev# su
dev# su dev
Any language both verbal and programming comprises of separate elements that fit together in a logical structure that communicates meaning to the recipient. For language to be effective, rules are strictly defined, not only to preserve efficiency but to prevent misunderstanding. For example, a written sentence will comprise of nouns, verbs and adjectives likewise computer code will consist of variables, expressions and control structures. The main functional difference between a human language such as English and a programming language is flexibility and interpretation as humans we are adaptable enough to interpret a missing full stop or misspelled word correctly, whereas a computer will fail miserably. Here will will look at some of the the basic building blocks of PHP.
14
TBO 01/2014
dev# cd examples
dev# vi example1.php
The example can then be run from: http://yourserveripaddress/examples/example1.php (see Figure 2). You do not need to run the examples to develop a working CMS, but they are useful to experiment with. Change values and experiment.
The circumference of a circle is calculated by multiplying the diameter by PI (3.14). As PI is a constant and will not change, we can define PI as a constant. See Listing 1.
Variables
PHP tags
All PHP code requires an opening <?php tag. The closing ?> tag is only necessary when combining PHP with Javascript, HTML etc. In PHP comments are denoted with //. A block of comments can also be with a block using /* . */.
A variable holds the result of an expression or statement. As the name implies, the value of the variable will change over the life of the program. The variable name is defined on the left hand side of the = sign and the value of the variable is defined on the right hand side see Listing 2.
Data types
Comments
Data types, as the name suggests, define what type of data we can hold in a variable or constant. The basic types are booleans, integers, floats, strings, and arrays.
Booleans
Expressions
To quote the PHP website Expressions are the most important building stones of PHP. In PHP, almost anything you write is an expression. The simplest yet most accurate way to define an expression is anything that has a value. For instance, if the server has 3 disk drives this could be written as:
<?php
A boolean is used where a dual state is useful. A boolean has one of two values, TRUE or FALSE. For instance, we can define the constant DEBUG and act accordingly depending on how DEBUG evaluated by the if control structure: see Listing 3.
Listing 1. example1.php
<?php /*
$disk_drives = 3;
Constants
* example1.php * Constants *
A constant is useful where the the value will not change across the execution of the script. Unlike variables, constants are accessible within function calls and throughout the script, whereas variables may be local only to that particular function. This aids program readability.
* Define PI as the constant 3.14 and output the result. */ define(PI, 3.14); echo PI;
Listing 2. example2.php
<?php /*
* example2.php * Variables * Define circumference as the variable $circumference * 12.775 and output the result * */ with the value
$circumference = 12.775;
echo $circumference;
www.bsdmag.org
15
ADMIN
An integer is a whole number of the set = {..., -2, -1, 0, 1, 2, }. As the maximum and minimum value is platform independent, we can access this value via the constant PHP_INT_MAX. If PHP encounters a number larger than PHP_INT_MAX, the number will be converted to a float. See Listing 4.
Integers
Floats
The maximum size of a floating point number, like Integers, is platform dependent. However, all sorts of rounding and logic errors can be introduced into code with floats as they behave differently from integers so particular care should be used. For instance, 1/3 is equal to 0.33333 with 3 recurring to infinity, but as we have limited space it is impossible to represent this fully. If accuracy is critical, various libraries are available to improve float functionality. See Listing 5.
endeavor to evaluate any variables contained within. A single quoted string is called a string literal as the string is interpreted literally rather than expanding any variables contained within it. Like the Vi versus Emacs discussion, the use of single or double quotes is very much a question of what you want to achieve. While single quotes may be considered quicker than double quotes, other coding factors have a greater impact on speed. See Listing 6.
Arrays
Strings
A PHP string can contain up to 2Gb of characters. A string is limited to 256 ASCII characters, and does not internally store strings in Unicode format unlike some other languages. A string can be surrounded either by single or double quotes. If surrounded by double quotes, PHP will
Listing 3. example3.php
<?php /*
An array is a list with a key and value pair. For instance, we can list the major BSD distributions as an array variable, rather than multiple separate variables. We can then perform operations on the list by looping through the key /value pairs. Arrays are useful where we have to keep records in sync. For instance if the records from a database table were dumped into an array it would be easy to manage using the record ID as key. If separate variables were used, it would be difficult to manage and the potential for errors would be great. Arrays do not need to have sequential keys, indeed PHP supports the mixing of numeric and string values as keys within arrays. You can even define another array as the value of an array
* Integers
* Check the maximum integer size available on your * For a 32 bit system this will be 2147483647. * */ echo PHP_INT_MAX; platform.
* Change TRUE to FALSE to change the output message. */ define(DEBUG, TRUE); if (DEBUG) { } else { } echo We are in Debug mode; echo We are not in Debug mode;
Listing 5. example5.php
<?php /*
* example5.php * Floats * Calculate PI as a float using the more accurate * This should return 3.1428571428571. * */ formula 22 / 7.
Listing 4. example4.php
<?php /*
* example4.php
16
TBO 01/2014
The first array example is the traditional PHP method for defining arrays, the second version onwards is available in > PHP 5.4. See Listing 7.
Functions
Operators
Operators are used to compare values (Comparison / Logical) or change values (Arithmetic / Assignment / Increment / Decrement / String). Care has to be taken with operator precedence, as certain operators will fire first. For example, the multiplication operator * takes precedence over the addition operator + unless the addition portion of the equation is wrapped in brackets: see Listing 8. See Table 1 for the full list of of the most common operators.
Listing 6. example6.php
<?php /*
Functions are small blocks of code that can act as effectively as a black box to the code that is calling it. As functions can be called repeatedly from anywhere within code and if written properly will provide a consistent result. Functions can act independently of the code that is calling them, or can return a result that can be manipulated by the main body of the program. An important point to realize is that variables defined inside a function are generally out of scope of the main body that is to say $a in the main body of a program cannot be accessed by the function unless it is either passed as a parameter or accessed via some other method (PHP has a rich library of internal functions, if
$array_1 = array(
);
* Note that the last line is functionally identical to * line and we are separating each line with a HTML <br />. * */ define(BR, <br />); $pi = 22 / 7; echo The value of PI is: $pi . BR; echo The value of PI is: $pi . BR;
$array_2 = [
];
Listing 7. example7.php
<?php /*
// Arrays can use mixed key values - they do not have to start at 0 $array_3[5] = FreeBSD; $array_3[7] = NetBSD; print_r($array_3); echo BR;
// Let PHP assign the key values $array_4[] = FreeBSD; $array_4[] = OpenBSD; $array_4[] = NetBSD; print_r($array_4); echo BR;
define(BR, <br />); // Define the array then print it out using the function // Use BR to separate each line print_r()
www.bsdmag.org
17
ADMIN
you do not recognize a function call in the later CMS sample code the script will be using a built in PHP function. The same apples to the javascript sample). See Listing 9.
Control structures
Control structures, along with Comparison / Logical operators provide the logic for our program. Example 3 is a good example of the if/else control structure. These are only a very small subset and the most common of the extensive features available with PHP. To see the full list, please visit the PHP language guide at http:// www.php.net/manual/en/langref.php.
Table 1. PHP Operators
See Table 2 CMS directory structure. Create the directories and the 14 files as per the instructions for the example code under Part 1 PHP fundamentals. Before we can start coding in earnest, we need to populate our MySQL database. Create the following files, and create the database, table and our first page stored in the database: see Listings 10-12. Note that the Ipsum Lorem test should be on one line with no carriage returns or line feeds. Your editor may wrap this very long line. Create the the database, table and page as follows in Listing 13. Operator
Arithmetic
Example
-$a $a + $b $a - $b $a * $b $a / $b $a % $b $a = 3 $a += 5 $a = Hello $a .= world $a == $b $a === $b $a != $b $a <> $b $a !== $b $a < $b $a > $b $a <= $b $a <= $b $a and $b $a or $b $a xor $b ! $a $a && $b $a || $b ++$a $a++ --$a $a--
Name
Negation Addition Subtraction Multiplication Division Modulus Assignment Assignment Assignment Assignment Equal Identical Not equal Not equal Not identical Less than Greater than Less than or equal to Greater than or equal to And Or Xor Not And Or Pre-increment Post-increment Pre-decrement Post-decrement
Result
Opposite of $a. Sum of $a and $b Difference of $a and $b Product of $a and $b Quotient of $a and $b Remainder of $a / $b Sets $a to 3 Sets $a to 8 Sets $a to Hello Sets $a to Hello world TRUE if $a is equal to $b after type juggling. TRUE if $a is equal to $b, and they are of the same type. TRUE if $a is not equal to $b after type juggling. TRUE if $a is not equal to $b after type juggling. TRUE if $a is not equal to $b, or they are not of the same type. TRUE if $a is strictly less than $b. TRUE if $a is strictly greater than $b. TRUE if $a is less than or equal to $b. TRUE if $a is greater than or equal to $b. TRUE if both $a and $b are TRUE. TRUE if either $a or $b is TRUE. TRUE if either $a or $b is TRUE, but not both TRUE if $a is not TRUE. TRUE if both $a and $b are TRUE. TRUE if both $a and $b are TRUE. Increments $a by one, then returns $a. Returns $a, then increments $a by one. Decrements $a by one, then returns $a. Returns $a, then decrements $a by one.
Assignment
Comparison
Logical
Inc / Dec
18
TBO 01/2014
Listing 8. example8.php
<?php /* }
* example8.php *
// As BR is a global constant and $pi has been passed to our // function we can access them directly. // Return our result to the main body of the program return $pi * $diameter . BR;
$b = (1 + 5) * 3; echo $a will evaluate to 16: . $a . BR; echo $b will evaluate to 18: . $b . BR;
Listing 9. example9.php
<?php /*
* example9.php *
* Demonstration of a function call */ // As BR is a constant, this is available to our function directly define(BR, <br />); // $pi is not available to our function, we will need to // other methods $pi = 22 / 7; echo Circumference with a diameter 5: . print_circ1(5); access it by
body TEXT
My first page, Page header, Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris
interdum auctor tellus sed dignissim. Phasellus non orci massa, nec feugiat sem. Vestibulum molestie interdum bibendum. Nunc quis elit nulla, sit amet rutrum lorem. odio est, sagittis nec accumsan ut, placerat sit fringilla at. Sed ornare aliquet lacus, quis
echo Circumference with a diameter 10: . print_circ2(10,$pi); function print_circ1($diameter) { // print_circ1() will display $pi * $diameter // Define $pi as a global variable global $pi; // As BR is a global constant we can access it directly // Return our result to the main body of the program
Quisque
amet lectus. Curabitur aliquam dignissim felis, a malesuada leo imperdiet augue mattis eu. Nulla porta odio ut erat molestie justo suscipit. Aenean convallis
consectetur at
Morbi in );
pellentesque nisl, vitae posuere mauris facilisis vitae. tellus nisl, vel facilisis diam.
www.bsdmag.org
19
ADMIN
The following PHP files contain the code for out website. Create each file as per Table 2. See Listing 14. global.css holds the style information for our site. Experiment with the font sizes, line spacing etc. to style the site to your liking. If you use Firefox, install the Firebug plugin to dynamically change the values, but if you want to make them pernanet you will need to edit this file. Also try
renaming global.css and refreshing your browservs cache to see the effect of the styling on the site. See Listing 19. The include files build our basic HTML header and footers, add the CSS add the CSS via global.css and load the Javascript. See Listing 20-22. The javascript files
Figure 3. Our first page index.php Listing 13. Creating the database, table and pages in MySQL
#dev mysql -u root password cms-password < createdb.sql
#dev mysql -u root password cms-password < createpagetbl.sql #dev mysql -u root password cms-password < createpage.sql
Directory / file
examples
Purpose
Example PHP code
Files
example1.php example2.php example3.php example4.php example5.php example6.php example7.php example8.php example9.php cms.inc core.inc html.inc mysql.inc index.php postload.js preload.js createdb.sql createpage.sql createpagetbl.sql global.css header.inc footer.inc template.inc
includes
PHP includes for CMS. Each file contains specific functionality as named
Start page for our CMS Javascript support for our website Contains the SQL loader scripts for our website.
stylesheets templates
Holds the CSS stylesheets for the website Holds the templates for the website
20
TBO 01/2014
* Contains default settings for our CMS * * NOTE: denotes a line wrapped all code should be * on one line
* index.php *
* Index page for FreeBSD CMS */ // Get required files // Our global settings - Note need full path require_once includes/cms.inc; // Core functions
define(COPYEMAIL, [email protected]); // Version define(VERSION, Version 1.0 not for production use); // Mode - If DEBUG is set to true, show errors and debug info
require_once INCLUDES.mysql.inc; // Turn full debug functionality on if enabled if(DEBUG){ // Turn on full PHP error reporting error_reporting(E_ALL); }else{ // Hide all error messages error_reporting(0); } // Build page - use first record in database $page[id] = 1; buildpage($page);
define(DEBUG, TRUE); // Where to find our files define(TEMPLATES, templates/); define(INCLUDES, includes/); define(SQL, sql/);
// HTML tags that are orphaned and not defined in out template files
define(BODY, <body>);
define(HEAD, </head>); // MySQL details define(DBSERVER, localhost); define(DBUSER, bsduser); define(DBPASSWORD, cmsdbpassword); define(CMSDB, freebsdcms);
www.bsdmag.org
21
ADMIN
* Contains core functions for our CMS */ function buildpage($page) { // Builds a standard page $id = $page[id]; // Build the SQL and get the result $sql = SELECT * FROM pages WHERE id=$id LIMIT 1; $result = mysql_select($sql); // Output our page header outfile(TEMPLATES . header.inc); // Create our body $markup = ; }
fclose($fh);
$markup .= wraptag(title, $result[4]); $markup .= HEAD; $markup .= BODY; // If we are in debug mode, show an alert if(DEBUG){ $debug = ¶ ; }else{ $debug = ;
* Contains core html functions for our CMS */ function wraptag($tag, $text) { // Wraps $text with compliant tags // wraptag(p,sometext) // <p>sometext</p>
// Add to markup $markup .= wraptag(h1,$debug . $result[3]); $markup .= wraptag(p,$result[5]); Copyright and $markup .= divclass(ahref(COPYRIGHT, LICENCE,
function divclass($divcontent, $class, $id = ) { // Generates a div tag $text with compliant tags // divclass(content,class)
22
TBO 01/2014
$db->connect_error . ]);
]); }else{ }
$db->error .
die();
function ahref($text, $url, $title = ) { // Generates an href tag $text with compliant tags // ahref(Click here,freebsd.org) here>Click here</a> // <a href=http://freebsd.org title=Click // ahref(Click here,freebsd.org,Link title) // <a href=http://freebsd.org title=Link
// Pass our results to an array to be returned $r = array(); $r[] = $result->num_rows; // No of rows returned
// No of rows affected
> .
$ahref = <a href= . $url . title= . $title . $text . </a>; return $ahref;
* Contains MySQL functions for our CMS */ function mysql_select($sql) { $db = new mysqli(DBSERVER, DBUSER, DBPASSWORD, CMSDB);
www.bsdmag.org
23
ADMIN
type=text/javascript></script>
padding: 21px; }
text-transform: uppercase;
p { }
float: left;
body {
text-align: justify; }
html {
background: none repeat scroll 0 0 #F9F8F2; border: 1px solid; color: teal;
function displaydate(){ // Displays the date and time in AM/PM format. var currentTime = new Date()
.jstime, .licence {
background: none repeat scroll 0 0 #EDEAC6; border: 1px solid #DADADA; color: slategrey; float: right;
var minutes = currentTime.getMinutes() var month = currentTime.getMonth() + 1 var day = currentTime.getDate() var year = currentTime.getFullYear() if (minutes < 10){ }
font-family: Verdana; font-size: x-small; margin-bottom: 5px; margin-right: 10px; margin-top: 10px; } padding: 3px 10px;
minutes = 0 + minutes
<html xmlns=http://www.w3.org/1999/xhtml xml:lang=en> <meta http-equiv=Content-type content=text/html; charset=iso- 8859-1 /> <link rel=stylesheet type=text/css href=/https/www.scribd.com/stylesheets/global.css /> <script src=/https/www.scribd.com/javascript/preload.js
24
TBO 01/2014
Useful links
W3C Validator (by file upload) http://validator.w3.org PHP documentation http://www.php.net/manual/en W3 Schools http://www.w3schools.com
are split into 2. Preload.js provides the date and time on each page, postload.js is just an empty file which provides hooks we will use later on in the series. See Listing 23-24.
The unformatted text is stored as plain text in our database table. Index.php forms the first page of our website, and loads our settings and functions from the include files. The first stage is to load our header from a plain text file, which is the HTML at the start of our page. The header file in turn loads the CSS and javascript, and returns control to index.php. We then query the database, wrap the text in the relevant HTML tags and output to our browser. We then close the HTML with our footer HTML. In the next part of our series we will develop the CMS further, passing parameters via our browser to load different pages, We will also start using our template file so that we can design our site the way we want it with separate blocks and regions.
Once you have entered the code as per table 2, point your browser at http://yourserveripaddress/index.php. You should see a page similar to Figure 3. Turn debug off and on in cms.inc, and the paragraph mark should disappear. If you copy the HTML source from the page (In you browser view source, select all, copy and paste into the W3C validator) the page should validate.
a d v e r t
ROb SOMerville
Rob Somerville has been passionate about technology since his early teens. A keen advocate of open systems since the mid eighties, he has worked in many corporate sectors including finance, automotive, airlines, government and media in a variety of roles from technical support, system administrator, developer, systems integrator and IT manager. He has moved on from CP/M and nixie tubes but keeps a soldering iron handy just in case. i s e m e n t
ADMIN
n the early days of the World Wide Web, HTML pages were literally handcrafted masterpieces of content. Before applications such as Dreamweaver arrived that allowed content providers to design attractive pages with the ease of a document produced in a word processor, it was a matter of writing copious amounts of HTML for each page, checking that the links and the HTML were correct, and repeating for each page. This model was highly inefficient, as not only was a lot of the HTML repeated across pages, the chances of errors coming in and either causing the page to render incorrectly or pointing to the wrong address became greater as the site grew. Managing a website with 100 pages is possible; a website with 10,000 pages a nightmare. The complex sites we see today on the Internet would be impossible without the Content Management System. Yet even now, large innovative sites are moving away from the CMS model toward frameworks that consider the locally provided content to be only a part of the website with 3rd party content supplying a significant proportion of the content. While the technology meets the ethos of the web in that data can be shared freely, it poses the web designer and brand manager with a huge challenge how can we take disparate pieces of content and serve these in a wrapper that to our website visitors appears as if it seamlessly represents our brand values? How can we
divorce the business process from the presentation? Is it possible for a website to develop a unique personality while at the same time remaining fresh, dynamic and easily changeable? These hurdles are being overcome with the use of CSS (Cascading Style Sheets) and templating technologies. While the CSS manages the color, fonts, size, etc. of the content, templates allow us to adjust the order and visibility of the content. For example, we want to generate widely different content (both from a stylized and literal
26
TBO 01/2014
ADMIN
content perspective) depending on website section, page number and content type. See Figure 1 Page generation process.
If you prefer, create a SQL file createpage2.sql in the SQL directory with the following content:
USE freebsdcms;
MySQL Interface
As it is important that we can quickly test our CMS, for those that would prefer the Cut, Paste and Click approach rather than managing long SQL statements via the command line, you can use a lightweight web-based database manager. The lightest of these (a single PHP page) is Adminer. An alternative is SQL buddy, and either of these can be quickly installed if desired by downloading the archive and extracting into a folder under the /usr/ home/dev/data. The web-based interface can then be accessed from: http://myserver/dirname. See Table 1 Useful links.
INSERT INTO `pages` (`title`, `h1`, `body`) VALUES (My second page, H1, 2);
Alternatively use the SQL command function in Adminer to execute the following SQL statement:
INSERT INTO `pages` (`title`, `h1`, `body`) VALUES (My second page, H1, 2);
At the moment, we only have one content type a page. This is stored in the pages table and holds the following content as shown in Table 1.
Table 1. Page content from MySQL pages table
We now have two pages in our database, but index.php still contains the following code:
// Build page - use first record in database $page[id] = 1; buildpage($page);
id title
1 My first page
h1
body
Page header Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris interdum auctor tellus sed dignissi...
This results in the following output as seen in Figure 2. Now let us create a second page in our database:
mysql> INSERT INTO `pages` (`title`, `h1`, `body`) -> VALUES (My second page, H1, 2);
This hard-wires index.php to only serve a page with an ID of 1. Depending on the URL passed to the webserver, we want to serve that type of content. For example http:// mysite/pages/1 will serve a page with an ID of 1, whereas http://mysite/faqs/1 will serve an FAQ with an ID of 1, etc. Visiting http://mysite will return the home page (Page 1). This leads us to the next problem where do we store the content types? We could include this in a separate MySQL table, but this would require an additional SQL query to be executed every time a page is loaded. As content types will not be changed very often, we can create another include file that defines our content
28
TBO 01/2014
Listing 1. content.inc
<?php /* *
* content.inc *
// Define the content type. This must match any tables // CMS defined in our
// Map each content type to a table. Each content type // to a corresponding table
from our DB
Listing 2. pages_template.inc
<?php /* *
* pages_template.inc *
* Template for our page content type * For content type foo the corresponding template would be: * foo_template.inc * }
* To display a field: *
* To change the rendering order, just re-order the fields * NOTE: Any content generated by javascript will not be * * */ managed here A closing ?> tag is mandatory
require INCLUDES.content.inc; // Routes our incoming request to the right content // the content from out DB. $content_type = $request[0]; $id = $request[1]; template.inc; $template_file = TEMPLATES . $content_type . _
www.bsdmag.org
29
ADMIN
// Check we have some content to display if($result[0] == 0){ echo No data; return; } // Check we have a template file if(!file_exists($template_file)){ echo No template; return; }
// Dump the title & id out to our theme template $theme[id] = $result[id]; $theme[title] = $result[title]; // As we dont know how many fields we will have in
our content
// type after our id, iterate through each in turn and wrap // the field with a div
foreach($result as $key => $value){ if($pos > $offset){ $theme[$key] = div($result[$key], $key.-.$id, $key); } $pos ++; } // Add our standard copyright notice $theme[licence] = div(ahref(COPYRIGHT, LICENCE, Copyright and licence details),,licence);
// Dont write any output to browser yet as we want // our content using a theme. If enabled use our // callback to remove white space etc. ob_start(optimize_callback); // Output our page header outfile(TEMPLATES . header.inc); // Create our body echo wraptag(title, $result[title]); echo HEAD; echo BODY; // Generate a unique ID based on content type
// Include our template file require_once($template_file); // Close our content type tag echo </div>; // Output our HTML page footer
// Map the requested content type from our real table name $ct = array_search($content_type, $content_tables); echo <div id=.$ct.>; // If we are in debug mode, show an alert if(DEBUG){
30
TBO 01/2014
table name
$array[0] = $content_tables[$ct];
// parse_request(/rubbish/123456)
if(is_numeric($id)){ // If ID is a number, third test passed $valid ++; } if($valid == 3){ // Valid parameters passed, return content type
// Content definitions
require_once INCLUDES.content.inc; $ct = NULL; $id = NULL; $valid = 0; // Fetch the parameters from the URL $array = explode(/,$URI); // We dont need the first / - delete the first // array item $a = array_shift($array); // Check we have 2 parameters $paramcount = count($array); if($paramcount == 2){ // First test passed - We have 2 parameters $valid ++; $ct = $array[0]; } $id = $array[1];
and page ID
return $array; }else{ // Test failed - return NULL return NULL; } } function optimize_callback($buffer){ // Replace all spaces and cruft between tags if(OPTIMIZE){ $b = preg_replace(~>\s+<~, ><, $buffer); $b = preg_replace(/\r\n|\r|\n/,,$b); $b = preg_replace(!\s+!, , $b); return $b;
empty
} }
passed
$valid ++;
www.bsdmag.org
31
ADMIN
Figure 3. Using Adminer to execute SQL statement Listing 6. mysql.inc replacement code
<?php /* * $r[] = $db->field_count; update / delete // No of columns in table
$r[] = $db->affected_rows;
* mysql.inc *
// Append the results to our result count if($result->num_rows != 0){ $r = array_merge($r, $result->fetch_array(MYSQLI_
function mysql_select($sql) { $db = new mysqli(DBSERVER, DBUSER, DBPASSWORD, CMSDB); if($db->connect_errno > 0){
ASSOC)); }
$db->connect_error . ]);
// Free the result $result->free(); // Close the connection $db->close(); return $r; }
// Pass our results to an array to be returned $r = array(); $r[] = $result->num_rows; // No of rows returned
32
TBO 01/2014
Your donations have helped make FreeBSD the best OS available! By investing in the services provided by The FreeBSD Foundation you have helped us fund projects to keep FreeBSD a high-performance, secure, and stable OS. What will the Foundation accomplish with your donation in 2013? Software development projects for FreeBSD: $600,000. Paid staff time supporting Release Engineering and Security teams. Grow staff: Five technical staff members by year-end. Provide support for BSD conferences around the globe, in Europe, Japan, Canada, and the USA. Hardware to maintain and improve FreeBSD project infrastructure: $130,000. FreeBSD community growth through marketing and outreach to users and businesses. Legal services and counsel protecting the FreeBSD trademarks.
FreeBSD is internationally recognized as an innovative leader in providing a high-performance, secure, and stable operating system. Our mission is to continue and increase our support and funding to keep FreeBSD at the forefront of operating system technology. But, we cant do this without your help!
Last year with your generosity, we raised over $770,000. This year we will invest $1,000,000 to support and promote FreeBSD. We have kicked off the new year with three newly funded projects, and are actively soliciting additional project proposals. Please support the Foundation during our Spring Fundraising Drive, and help us raise $100,000 from 1000 donors between April 15th and May 30th.
www.freebsdfoundation.org/donate
Then talk to your employer about matching your gift or making their own donation.
ADMIN
types. We can then automatically use a custom template depending on the content type to post process our specific content. First of all, we need to make some modifications to Apache so that it serves our index.php page as default. Edit the line in /usr/local/etc/apache22 /httpd.conf to match the following:
DirectoryIndex index.php
apache22/data> # #
/usr/local/www/
* html.inc *
* Contains core html functions for our CMS */ function wraptag($tag, $text) { // Wraps $text with compliant tags // wraptag(p,sometext) // <p>sometext</p>
function div($divcontent, $class, $id = ) { // Generates a div tag $text with compliant tags // div(content,class) // <div class=class>content</div> // div(content,class,id) // div(content,,id) // div(content,,) // <div>content</div> if ($id != ) { $id = id= . $id . ; // <div id=id class=class>content</div> // <div id=id>content</div>
return $ahref;
34
TBO 01/2014
Useful Links
This will force all traffic to be passed to our index.php for processing. As root, delete our unwanted files then re start Apache:
$ rm /home/dev/data/index.xhtml $ rm /home/dev/data/index.html $ apachectl restart
When you visit http://mysite or http://mysite/, page 1 should be displayed. Now for the modifications that will facilitate content type routing and theme control. Create a file in the includes directory called content.inc with the content from Listing 1. Create the following template file pages_template.inc in the templates directory shown in Listing 2. Remove the following section entirely from index.php:
// Build page - use first record in database $page[id] = 1;
That is a lot of code we have added, but we now have a major jump in functionality. We can create any number of content types now by creating a new table (e.g. faq, news, etc.) The only essential fields we must define are ID and TITLE. After these two fields you may define as many or as few as you require. You will need to create a matching template file with the fields you want to display or else the content will be unable to render. Once you have added new records to your content type (Adminer makes this quick and easy), the content can be accessed via your browser at: http://mysite/mycontenttype/mypageid. If you attempt to access invalid content, you will be presented with a rudimentary error message. In the next article in the series, we will look at theming in detail and how we can lay out the site using a combination of templates and CSS.
buildpage($page);
Replace with the one shown in Listing 3. Remove entirely the function call buildpage($page) from core.inc. Replace with the code shown in Listing 4. Add the function calls from Listing 5 to the end of core.inc. Replace html.inc with Listing 7. Append the following to cms.inc:
// Optimize output by removing white space between tags etc. define(OPTIMIZE, true);
Errata
In the previous article of this series the following syntax was incorrect:
#dev mysql -u root password cms-password < createdb.sql
#dev mysql -u root password cms-password < createpagetbl.sql #dev mysql -u root password cms-password < createpage.sql
ROb SOMerville
Rob Somerville has been passionate about technology since his early teens. A keen advocate of open systems since the mid-eighties, he has worked in many corporate sectors including finance, automotive, airlines, government and media in a variety of roles from technical support, system administrator, developer, systems integrator and IT manager. He has moved on from CP/M and nixie tubes but keeps a soldering iron handy just in case.
Our apologies.
www.bsdmag.org
35
ADMIN
eveloping a popular and effective web presence does not just rest on the back office technology per se, the website also requires character and often in corporate environments, strict style guidelines exist to ensure cohesive branding across media. Separating the design (in respect of the look) from the functional (what it does) remains a challenge for web developers. Quite often, the end result is a trade-off, especially when considering the number of web-enabled devices that are now available, the range of browser versions, font support and screen resolutions, etc. The bottom line is this no matter how conscientious the web designer is, there are certain circumstances where the design of the website will not render as the designer expected. The industry standard response to this is twofold. First, end users are encouraged to embrace newer browsers, thereby eliminating the more obvious compatibility issues, and secondly, designers look to format the site in such a way in that the visual output degrades graceListing 1. Content added to content field via Adminer
fully when approached by less compatible browsers. This scenario is further highlighted where Microsoft introduced compatibility mode in Internet Explorer 8 (available as an icon next to the refresh button) as it would not support the non-standard techniques used in previous versions of the browser.
In this article we will focus on the basic techniques used to integrate CSS with our CMS, and demonstrate how modern developers tools available for the browser can assist in design. You will need access to a PC with Mozilla Firefox installed to follow this tutorial. Some useful references can be found at: <ul>
36
TBO 01/2014
In this article, we will focus on the basic techniques used to integrate CSS with our CMS, and demonstrate how modern developers tools available for the browser can assist in design. You will need access to a PC with Mozilla Firefox installed to follow this tutorial. Lets get started: First of all, I have created a new news item (in this case, with an ID of 3) via the Adminer interface. I added the following content to the content field (the title and heading can be anything you want) See (Listing 1) and (Figure 1). If you point your browser at http://youripaddress/news/3 you should see a web page similar to (Figure 2).
Firebug
ity such as Dreamweaver), one of the biggest headaches for the designer is writing CSS. The cycle goes like this write CSS, refresh and preview in browser, correct mistakes, then repeat. With Firebug, we can view our CSS changes in real-time, adding selections and classes, etc. as required. The resulting amendments can be copied and pasted into our CSS file as required. Either visit the Mozilla Firebug website available from the link or install Firebug via the Tools / Addons menu item and restart your browser. You should see the Firebug icon at the top right hand side (Figure 3). Clicking on the Firebug icon should bring up the Firebug interface and change the color of the icon (Figure 4). If you click on the HTML, head or body tags in the left hand panel of Firebug, you will see the corresponding CSS as defined in our global.css file appear on the right. You can then disable or edit the values displayed as appropriate. Click on the HTML tag on the LHS and disable each CSS rule in turn by clicking next to it. To revert your changes, press F5. (Figure 5).
www.bsdmag.org
37
ADMIN
Click on the HTML tab. Expand the body tag by clicking on the + sign, and click on <div id=jstime>. The div will be highlighted and the relevant CSS displayed. The corresponding CSS can be edited in situ as desired. See (Figure 6). Firebug is not just an essential tool for modifying and testing CSS, you can also debug and step through Javascript, edit HTML on the fly, check how quickly the components of your web page download, etc. Click on the Script tab and reload the page by pressing F5. The Javascript code from preload.js will be shown in the window. Set a breakpoint by clicking to the left of line 15 (Var CurrentTime .) and reload the page again. You can then step through the javascript code by pressing F11. See (Figure 7).
Listing 2. news_template.inc
render($theme[heading]); render($theme[title]); render($theme[timestamp]); render($theme[content]); //render($theme[debug]); //render($theme[licence]);
CSS syntax is very straightforward. Every HTML tag is referenced via a CSS selector (e.g. HTML html, BODY body etc.) and in turn, this selector has properties and values. Where matters get complicated is the cascading nature of CSS; for instance if the HTML is defined as having a font size of 12px (12 pixels), unless this is overridden somewhere, the body (and our footer areas) will have a font size of 12px. Good CSS is a balancing act between optimized selectors and overrides. Define your HTML or BODY too tightly and your theme will require lots of overrides. Likewise, if you have too loose a definition for your HTML or BODY tags, there will be a lot of unnecessary code duplication. Cross browser compatibility also creates issues, developers often use a CSS reset to level the playing field to build on with their CSS. As with all aspects of programming, there is never a definitive right answer. In some circumstances, speed will be more important than compatibility. In others, maximum compatibility will be more important and will require a lot more CSS. Complex designs will add to this payload.
We now have the following design requirements for our news page: The debug status symbol should be disabled for this page It should show the FreeBSD logo The content should be on a light background centered against a dark background The timestamp should be in a small font and should be prefaced with Posted at The heading should be in a large font and appear before the title List items should be discs All hyperlinks should be highlighted light red and change color on hover The content should leave a space on the RHS for some menu items to be added later The time display should be in the footer as well as the license conditions First of all, download the FreeBSD logo from the FreeBSD site. This logo is a transparent PNG, which means whatever color background we display on the website will shine though the image. Download this into a new directory called images under /dev/data. We need to make some modifications to our news template and javascript files. Edit news_template.inc to follow
38
TBO 01/2014
Listing 3. header.inc
<link rel=stylesheet type=text/css href=/ stylesheets/reset.css />
Listing 4. footer_template.inc
<?php ?> render($theme[licence]);
Listing 5. cms.inc
// Definition of our footer template define(TEMPLATE,footer_template.inc);
Listing 6. core.inc
// Include our template require_once(TEMPLATES . FOOTERTEMPLATE); // Output our HTML footer
the code in (Listing 2). This will change the order of the displayed items and disable the debug symbol. Copy all the content from preload.js into postload.js, and delete everything apart from the opening comment from preload.js. Change the file title in the comments section of postload.js to postload.js. This will load the javascript clock at the end of the body section. See (Figure 9). We now need to decide how global our CSS should be. Do we want all the links on the site to be light red? Should all list items be discs? Should the logo be the same on every page? For the sake of consistency, we will do this but can override this later via the theme files (by adding new divs, classes and ids) and adding further CSS. Let us start off with a blank canvas. Delete the contents of global.css and download reset.css from meyerweb. com into the styelsheets directory. Add the following line immediately before we load global.css (Listing 3). This will give us a page that looks like (Figure 10).
www.bsdmag.org
39
ADMIN
Create the file footer_template.inc and add the following content (Listing 4). Add this to cms.inc (Listing 5). Add the footer template to core.inc and add these lines just before // Output our HTML footer (Listing 6).
Table 1. Global CSS
Create a new global.css with the following content (Listing 7). This should result in the pages as displayed in (Figure 11) and (Figure 12).
Selector
body
Comments
No-repeat stops the image being repeated across the body area Margin and Width setting force the body to the centre !important overrides reset.css The: hover modifies the default a css to fire when the link is hovered over text-transform: capitalize forces the first letter of each heading word to uppercase list-style:circle inside forces a disc to be used with indented list items
background-color: #FFFFFF;
margin-top: 3px;
background-image: url(/https/www.scribd.com/images/logo-full.png); background-repeat: no-repeat; border: 1px solid #000000; font-family: helvetica; font-size: 14px; margin: 20px auto 0;
#heading {
margin-bottom: 10px;
text-transform: capitalize;
html { }
background-color: olivedrab;
#content {
color: #727272;
a { }
font-size: 20px;
color: #FD5EA9;
line-height: 30px; }
text-align: justify;
a:hover {
background: none repeat scroll 0 0 #FFFFFF; font-size: 10px; margin-top: 1px; padding-right: 10px;
#news, #page {
border: 1px solid #DADADA; margin-top: 190px; padding: 20px; width: 65%; }
padding-top: 5px;
li { }
#timestamp:before { }
#timestamp {
color: #A2A2A2;
font-size: 10px;
40
TBO 01/2014
Mozilla firefox http://www.mozilla.org/en-US Firebug https://addons.mozilla.org/en-US/firefox/addon/ firebug W3 Schools CSS tutorial http://www.w3schools.com/css/ default.asp FreeBSD logo http://www.freebsd.org/logo/logo-full.png Eric Meyers CSS reset http://meyerweb.com/eric/tools/css/ reset/reset.css
Useful links
Apart from moving the javascript clock and licence outside of the main content area (which required the addition of a template and a small modification to core.inc and cms. inc), most of the heavy lifting has been performed by reset.css and global.css. Reset CSS sets the defaults for all major browsers etc, and this is reflected by the raw output on our non-news pages. We now have a choice, either embed our website standards in reset.css or depending on our database and template definitions, add some further CSS to global.css. With hindsight, choosing body for a field name in the pages table was not ideal, as it cannot be referenced in CSS without causing havoc. Renaming this to content, changing the name in pages_template. inc and commenting out the render($theme(licence)) will fix the pages content to match the news item. As for global.css, most of the selectors and values should be self-explanatory. The more subtle attributes are listed in (Box 1).
We will continue with some more advanced CSS, and begin to build our menu system.
ROb SOMerville
Rob Somerville has been passionate about technology since his early teens. A keen advocate of open systems since the mid-eighties, he has worked in many corporate sectors including finance, automotive, airlines, government and media in a variety of roles from technical support, system administrator, developer, systems integrator and IT manager. He has moved on from CP/M and nixie tubes but keeps a soldering iron handy just in case.
www.bsdmag.org
ADMIN
o far in this series, we have focused on adding and displaying standard HTML pages which have been pulled from our database. We are now going to shift directions and start to look at the user interface of the CMS itself. Traditionally, menu links were hard coded into pages, which not only made long-term maintenance time-consuming but also error-prone. By leveraging the power of a database back end, we can easily extract the title and section of pages we want to display and if desired, include or exclude that content from the menu. For flexibility, we will also include the facility to add disparate links to other sites, etc. Many sites now use multi-level menus which are driven by a combination of SQL, Javascript / Jquery and CSS. Later on in the series, we will look at using Jquery to add this functionality, but for now we will concentrate on a block navigation menu that is displayed alongside the main content.
down and filter by section, but we would just be postponing the inevitable. An additional improvement would be to use a combination of a content type filter and a pager with the MySQL LIMIT keyword, restricting the display to a certain number of items. This would help in the final design
Listing 1. Logging in to MySQL
#dev mysql -u bsduser -pcmsdbpassword
The SQL
To demonstrate, lets spin up a MySQL session and take a look at our content. At the shell prompt, login to MySQL and run some queries (Listing 1 2). By using the UNION keyword, we can combine the output of both SELECT statements into one result. This would be fine if we had a small site with not much content, but as the site grows, the menu would become unmanageable in size. We could build the interface with a drop-
42
TBO 01/2014
and theming of the site, as we will know exactly how much browser real estate would be occupied by the menu itself even if the content expanded rapidly. The remaining issues are how to add disparate links and whether we want to display the content in the menu at all. For example, we might have an error page that only is displayed when the content is not found. While it would be useful to store this in the database, displaying it in the menu would be rather pointless. The question is where to
Listing 3. Creating FAQs table and adding status flag
mysql> CREATE TABLE faqs LIKE news; content;
store this data? We could have a separate menu table, with the ID of each page and a numeric flag (0, 1) to represent do not display in the navigation menu or include in the menu. We would then have to maintain 2 tables when content is added and removed. This could be easily accomplished using MySQL triggers. Alternatively, we could store the page status in the relevant content tables (e.g. news, pages) with a flag (0,1,2) to represent do not publish, publish but do not show in menu, and publish and
| | | | |
5 | 6 | 7 | 8 | 9 |
| | | | | |
| 10 |
mysql> (SELECT news AS contenttype, id, status, title pages AS contenttype, id, status, title FROM pages)
contenttype, id, status, title FROM faqs); +-------------+----+--------+-----------------------+ | contenttype | id | status | title | news | news | news | pages | pages | faqs | faqs | faqs | faqs | faqs | faqs | faqs | faqs | faqs | faqs | | | | | | | | | | | | | | 1 | 2 | 3 | 1 | 2 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | +-------------+----+--------+-----------------------+ 1 | My first page 1 | My second page 2 | My first page 2 | FAQ 1 0 | FAQ 2 1 | FAQ 3 2 | FAQ 4 2 | FAQ 5 2 | FAQ 6 2 | FAQ 7 2 | FAQ 8 2 | FAQ 9 2 | FAQ 10 | | |
contenttype, id, status, title FROM faqs); +-------------+----+--------+-----------------------+ | contenttype | id | status | title | news | news | news | pages | pages | faqs | faqs | faqs | faqs | | | | | | | | | 1 | 2 | 3 | 1 | 2 | 1 | 2 | 3 | 4 | +-------------+----+--------+-----------------------+ 0 | My first page 0 | My second page 0 | My first page 2 | FAQ 1 0 | FAQ 2 1 | FAQ 3 2 | FAQ 4 | | | | | | | | |
| 10 |
www.bsdmag.org
43
ADMIN
show in menu. Both designs have their good and bad points from the implementation and data integrity viewpoint, but for the sake of simplicity, I will use the latter for our navigation menu. In the meantime, we have an FAQ definition in our file content.inc but we do not have any table data for it. We will now manually create the table and add 10 random FAQ entries (Listing 3-5). This will result in a new FAQ table with our status field. However, the ID field is not set to auto increment, so we need to change this (Listing 4). Now insert the data (10 entries) replacing the title, heading and status (0, 1 or 2) as appropriate. We need to repeat the structural amendments for our news and pages tables as well (Listing 6). Lets check what data we now have in the three tables (Listing 7). As we can see, the news and pages will not be published or displayed in the menu. Change this so the news items are not in the menu but published, but the pages are (Listing 8). Let us check in a browser if FAQ 1, 2 and 3 are displayed. Visit http://yourserverip/faq/1 and
Listing 9. FAQ template
<?php /* *
template.inc on line 23. If you want to try and diagnose the problem, have a look at core.inc and skip the next code listing. The problem lies in the following code snippet. To fix it, change as follows (Listing 10-11). If you visit http://yourserverip/faq/1, you will find the page is still not rendering correctly (Figure 2). The reason for this is that the the global CSS doesnt know about our FAQ content type yet, so we need to modify global.css as follows (Listing 12). You may have to refresh or clear your browser cache to pick this up. This should result in the
you should get an error message No template. To rectify this, create a faqs_template.inc file in /usr/home/dev/ data/templates with the following content (Listing 9). Bug alert! If you visit http://yourserverip/faq/1 you will find the page is not rendering correctly (Figure 1). You will receive an error message: Notice: Undefined
?>
* faqs_template.inc *
* Template for our faq content type * For content type foo the corresponding template would be: * foo_template.inc *
* To display a field: *
* To change the rendering order, just re-order the fields * NOTE: Any content generated by javascript will not * * be managed here A closing ?> tag is mandatory
render($theme[heading]); render($theme[content]);
*/
44
TBO 01/2014
Useful links
libraries. Download jquery-1.10.2.min.js and jquery.cookie.js from the Jquery website. Place these files in the Javascript folder, then modify our source code as follows (Listing 1518). When you visit http://youripaddress/faq/1, you should see a page similar to Figure 4. Clicking on the FAQ, News or Page button will raise a Javascript dialogue box.
correctly rendered content in (Figure 3). However, if we visit http://yourserverip/faq/2 , we will see an FAQ page even though the status is 0. Modify core.inc as follows to fix this (Listing 13). This should now give a No data message. If you are still experiencing problems, ensure that the content.inc file is as follows (Listing 14).
We will tie the onclick event to writing a local cookie, and extracting the links for the MySQL table. We will also look at using the Jquery library to build a multi-part menu.
ROb SOMerville
Rob Somerville has been passionate about technology since his early teens. A keen advocate of open systems since the mid-eighties, he has worked in many corporate sectors including finance, automotive, airlines, government and media in a variety of roles from technical support, system administrator, developer, systems integrator and IT manager. He has moved on from CP/M and nixie tubes but keeps a soldering iron handy just in case.
How can we remember the filter value selected for the content type? As HTTP is stateless, we could pass the parameter to each page. This would get complex very quickly with multiple menus. A better solution would be to write a cookie to the visitors browser when the content type is filtered. To do this we will use Javascript, and specifically a suite of Jquery
Listing 14. content.inc
<?php /* *
* content.inc *
* Defines content types for our CMS */ // Define the content type. This must match any tables // CMS defined in our
$content_types[] = page; $content_types[] = faq; $content_types[] = news; // Map each content type to a table. Each content type // to a corresponding table must be matched
www.bsdmag.org
45
ADMIN
$offset ++;
$menu = ; $menu .= <div class =menu- . $type . >; $menu .= <h2> . $type . <h2>; $menu .= <p> </p>; $menu .= $option; $menu .= </div>; return $menu;
background-color: #E5E6AD;
color: tomato;
font-weight: 600; }
in turn
$categories = count($content_tables); $option = ; foreach ($content_tables as $contenttype) { // Build the option for the content type $option .= <button onclick=window.alert(\.
$contenttype.\)>.$contenttype.</button> ;
46
TBO 01/2014
KNOW!
An ACROS Penetration Test is conducted exactly like a real attack by a skilled, motivated adversary only without the damage. We will find the weakest links in your security and use all our knowledge, skills and capabilities to try to achieve exactly what your security measures and policies are there to prevent. If it sounds difficult, we're interested. Experience the ultimate test of your security. (After all, the only alternative is to wait for an actual attack.)
ADMIN
o far, we have built navigation section buttons that represent the three content types that we have defined in content.inc: pages, news and FAQs. When
the button is pressed, a Javascript popup alerts the user as to what button was clicked via the onclick event (Figure 1). We now need to add additional functionality when the page is loaded, by default the pages links should be displayed, the menu option (or filter) needs to be displayed to the user, and when the button is clicked, the menu content needs to be rebuilt (Figure 2). Later we will build a more sophisticated menu using the Jquery library.
48
TBO 01/2014
Listing 1. postload.js
// Set navigation menu cookie function setnavitem(item){ $.cookie(navmenuitem, item); } } foreach ($content_tables as $contenttype) { // Build the option for the content type $option .= <button onclick=setnavitem(\. $menuvalue = pages;
Listing 2. menu.inc
<?php function menu($type) { require INCLUDES . content.inc; if ($type == navigation) { // Build select statement for each content type // Omit the UNION keyword on the last item $offset = 1; $sql = ;
$contenttype.\);
document.location.reload(true);>.$offset..
} $menu = ; $menu .= <div class =menu- . $type . >; $menuvalue.)</h2>; $menu .= <h2> . ucfirst($type) . ( . $menu .= <p> </p>; $menu .= $option; $menu .= </div>; return $menu;
in turn
$categories = count($content_tables); $option = ; // Get the value of the cookie if set if(isset($_COOKIE[navmenuitem])){ $menuvalue = $_COOKIE[navmenuitem]; }else{ }
www.bsdmag.org
49
ADMIN
Ensure you have downloaded the Jquery libraries as detailed in the previous article. If you view the page source
for any page, it should be similar to (Figure 3). Modify postload.js and menu.inc as follows (Listing 1 2). If you now navigate to http://yoursiteip/faq/1, you should now see a page similar to (Figure 4). If you click on the buttons, instead of a Javascript popup you should see the navigation menu title changing to reflect the new selection. Using Firebug and the Cookie console, you will see the content of the cookie changing when a new menu item is selected. Deleting the cookie and refreshing the
Listing 3. add to preload.js
// Init routines function preinit(){ document.body.style.display = none; }
50
TBO 01/2014
// Requires ID (page id), title and contenttype $links = <div class=menulinks>; $links .= <ul>;
if($mysqlfetchrows){ foreach ($mysqlfetchrows as $key => $value) { // Convert the content type to the relevant // See content.inc $path = array_search($value[2], $content_
CMSDB);
table name.
tables);
title=.$value[1].>.
} $links .= </ul>;
// Free the result $result->free(); // Close the connection $db->close(); if (isset($r)) { } else { } return $r;
return NULL;
www.bsdmag.org
51
ADMIN
$menu .= <div class =menu- . $type . >; $menuvalue.) - .$categories. categories</h2>; $menu .= <p> </p>; $menu .= $option; $menu .= $links; $menu .= <h2> . ucfirst($type) . ( .
$categories = count($content_tables); $option = ; // Get the value of the cookie if set if(isset($_COOKIE[navmenuitem])){ }else{ }
foreach ($content_tables as $contenttype) { // Build the option for the content type $option .= <button onclick=setnavitem(\. }
margin-left: 10px;
margin-bottom: 10px;
$contenttype.\);
#news, #page, #faq { margin-top: 190px; padding: 20px; overflow: auto; min-height: 640px; }
} // Build the SQL statement for the menu item selected $sql = SELECT id,title,.$menuvalue. AS
Listing 11. Add global menu support to News, FAQ and pages templates
Add at the beginning of each file (e.g. just before render($theme[heading]);)
WHERE status = 2 ORDER BY title;; // Get the result $result = mysql_fetchrows($sql); // Convert the array into HTML links $links = arraytolinks($result); $menu = ;
render(menu(global));
52
TBO 01/2014
<link rel=stylesheet type=text/css href=/https/www.scribd.com/stylesheets/jquery-ui.css /> <script src=/https/www.scribd.com/javascript/jquery.cookie.js type=text/javascript></script> <script src=/https/www.scribd.com/javascript/jquery-ui.min.js type=text/javascript></script> <script src=/https/www.scribd.com/javascript/preload.js type=text/javascript> </script>
page should load the default menu type of Pages (Figure 5). The titles have also been cleaned up using the PHP ucfirst() function call to uppercase the first character of the selection, and we have added a sequential option number to each menu item. One disadvantage of this method is the following piece of code as shown in (Figure 6). Each button has two pieces of Javascript attached, setnavitem() and document. location.reload(). The former sets the cookie via our function call in postload.js (and subsequently via the jquery.cookie.js script) and then refreshes the page. This causes the page to flicker every so often when the con-
tent is reloaded. A better way of implementing this would be to use Ajax, but for the time being, we will demonstrate a useful Jquery call Fade in. Add the following code to preload.js (Listing 3) and core. inc (Listing 4 and Listing 5). This will halt the display of the page, allow the menu to be built etc. and the page will then fade in. The time can
www.bsdmag.org
53
ADMIN
be adjusted by incrementing or decrementing the fadeIn() parameter. While this is not an ideal solution, it does demonstrate the ease of integrating Jquery with a web page.
Useful links
Now we need to plug in the SQL result to our menu module. Add the following code (Listing 6-8). We now need to make a few minor modifications at the theme and CSS levels, so change faq_template.inc to display the menu before the content (Listing 9).
Listing 14. Additions to menu.inc
Add elseif at the end of the navigation block $menu .= <div class =menu- . $type . >; $menu .= <h2> . ucfirst($type) . ( .
This will float the navigation menu on the FAQ page to the right and increase the height of our news, page, and FAQ content to accommodate the new menu. See (Figure 7-9) for the final result. I added an extra Ipsum Lorem to pad the content out in FAQ 3. Note how the menu responds to user input decoupled from the content that the user is currently visiting.
$menu .= </div>; return $menu; }elseif ($type == global) { ?> <ul id=menu> <ul>
Jquery provides an extensive library for the user interface. Rather than building the Javascript and CSS from scratch, we can install the CSS and JS libraries quickly into our CMS. Download Jquery-ui-1.10.3.zip and extract Jquery-ui. css into the stylesheets directory and Jquery-ui.min.js into the javascript directory. Use MC, or extract the file into a temporary area using unzip. Add the global menu to all of our content templates (news_ templates.inc, pages_template.inc and faqs_tempate.inc) and add the Javascript function to preload.js. Add the Javascript and CSS files to the header.inc file and add a new menu option to menu.inc and finally tweak our CSS file to reduce the width of the menu (Listing 11-15). Finally, visit the homepage of your site with your browser, refresh the page and voila, one multi-level menu (Figure 10).
We will continue refining the menu system and start building the user interface.
</ul> <?php }
</li>
</ul>
ROb SOMerville
Rob Somerville has been passionate about technology since his early teens. A keen advocate of open systems since the mid-eighties, he has worked in many corporate sectors including finance, automotive, airlines, government and media in a variety of roles from technical support, system administrator, developer, systems integrator and IT manager. He has moved on from CP/M and nixie tubes but keeps a soldering iron handy just in case.
54
TBO 01/2014
msab.com
ADMIN
n the previous article, we implemented a menu using the Jquery library. Looking at menu.inc, we see the menu is hard coded with a top level menu Home, and sub menus Pages, News and FAQs. To make our CMS user friendly, ideally we would store the menu values in a database table that we could access and amend from a web form (Listing 1 and Figure 1). Rather than building a custom page for each table, it would be good practice to design a set of global functions (e.g. sign on, retrieve fields, save fields etc.) and design a
template that we could change on a per table / form basis. We could then quickly build forms to modify each type of content. We also need to tweak the CSS for our dropdown menu. At the moment with the default CSS, the menu is floating to the left hand side. We will modify this to accommodate a wider menu with more options.
Step 1
For the initial testing, we will hand code a menu in menu. inc and add a few placeholders. Once we are happy with the CSS, we will then add this to a database table and add our forms. In the next article, we will write the code to extract the menu values and fire them into Jquery.
Listing 1. menu.inc
<ul id=menu> <li> <ul> <a href=/>Home</a> <li><a href=/https/www.scribd.com/page/1>Pages</a></li> <li><a href=/https/www.scribd.com/news/1>News</a></li> <li><a href=/https/www.scribd.com/faq/1>FAQs</a></li> </ul> </li> </ul>
56
TBO 01/2014
<li><a href=/>Home</a></li>
<li><a href=/https/www.scribd.com/login.php>Login</a></li>
</div>
Listing 3. preload.js
function globalmenu(){ $(function() {$( #top-menu-home ).menu();}); $(function() {$( #top-menu-news ).menu();}); $(function() {$( #top-menu-faq ).menu();}); ).menu();}); } $(function() {$( #top-menu-user
<li><a href=>Pages</a> <li><a href=/https/www.scribd.com/page/1>Page 1</a></li> <li><a href=/https/www.scribd.com/page/2>Page 2</a></li> </ul> <li><a href=/https/www.scribd.com/page/3>Page 3</a></li>
</ul>
</li>
<li><a href=>News</a> <li><a href=/https/www.scribd.com/news/1>News 1</a></li> <li><a href=/https/www.scribd.com/news/2>News 2</a></li> </ul> <li><a href=/https/www.scribd.com/news/3>News 3</a></li>
Listing 4. global.css
#jquerymenu { border: 1px solid #DADADA; margin-bottom: 10px; height: 48px; padding: 5px; }
</ul>
</li>
background-color: #e8e7cf;
<li><a href=>FAQs</a> <li><a href=/https/www.scribd.com/faq/1>FAQ 1</a></li> <li><a href=/https/www.scribd.com/faq/2>FAQ 2</a></li> </ul> <li><a href=/https/www.scribd.com/faq/3>FAQ 3</a></li>
.ui-menu{ }
float: left;
</ul>
</li>
www.bsdmag.org
57
ADMIN
WHERE table_schema = freebsdcms ORDER BY table_name, ordinal_position; // The tables we will allow the user to edit via this form
`submenutitleurl` varchar(50) DEFAULT NULL, `enabled` int(1) NOT NULL DEFAULT 1, TIMESTAMP,
$tables[] = menus; $tables[] = pages; // Fields that are automatically assigned via a default // definition value in MySQL table
$skiplist[] = id;
$skiplist[] = timestamp;
(2,jquerymenu,Pages,NULL,NULL,NULL,2,1,2013-09-02 (3,jquerymenu,Pages,NULL,Page 1,/ (4,jquerymenu,Pages,NULL,Page 2,/ (5,jquerymenu,Pages,NULL,Page 3,/ page/3,3,0,2013-09-02 17:58:08); page/2,2,1,2013-09-02 17:57:28), page/1,1,1,2013-09-02 17:56:33),
/////////////////////////////////////////////////// /////////////////////////////////////////////////// /////////////////////////////////////////////////// // Build the page up to the body tag outfile(TEMPLATES . header.inc); echo HEAD; echo BODY; // Page control logic if(isset($_POST[table])){ // User has not selected a table or we are testing
Listing 7. amendcontentpage.php
<?php require_once includes/cms.inc; require INCLUDES . core.inc; require INCLUDES . html.inc;
require INCLUDES . mysql.inc; // SQL statements $sql[0] = SELECT COUNT(DISTINCT TABLE_NAME) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = freebsdcms AND TABLE_NAME = ---P0---;
their result
$sql[1] = SELECT
NULLABLE,DATA_TYPE,
TABLE_NAME,COLUMN_NAME,COLUMN_DEFAULT,IS_
build_page_1($tables); }else{
CHARACTER_MAXIMUM_LENGTH
58
TBO 01/2014
// Check selected table is valid $s = $sql[0]; // Replace the marker in the SQL statement with $s = str_replace ( ---P0--- , $t , $s ); $result = mysql_select($s); TABLE_NAME)];
// HTML form definition echo <div id=content>; echo <div id=php>; echo <div id=h1>1: Select content</div>; echo <select name=table>; foreach ($tables as $t){ // $tables is an array - split each value out echo <option value=.$t.>.$t.</option>; } // Finish form and add footer echo </select>; edit>;
$valid_table_count = $result[COUNT(DISTINCT
edit data
}else{ // Send user to first page build_page_1($tables); } } }elseif(isset($_POST[update])){ // Save the input. As we have not validated this,
echo </div></div>;
echo <a href=licence.txt title=Copyright and licence details>Copyright © 2013 Rob Somerville me@ merville.co.uk</a>;
echo </div>; }
function build_page_2($t,$sql,$skiplist){ // HTML form echo <div id=content>; echo <div id=php>; echo <div id=h1>2: Edit <?php ?> content</div>; echo $t;
build_page_3($_POST); }else{ // Invalid value - return to start build_page_1($tables); } /////////////////////////////////////////////////// /////////////////////////////////////////////////// /////////////////////////////////////////////////// function build_page_1($tables){
echo <form action=amendcontent.php method=post>; // Get the schema for that particular table $s = $sql[1];
www.bsdmag.org
59
ADMIN
value=.$t.>;
foreach($result as $row){ // Loop through each field and build the form // type
echo <a href=licence.txt title=Copyright and licence details>Copyright © 2013 Rob Somerville me@ merville.co.uk</a>;
echo </div>; }
$fieldtype =
$row[4];
function build_page_3($post){ // HTML echo <div id=content>; echo <div id=php>; echo <ul>; echo <div id=h1>3: Save content</div>;
div><input class=varchar
type=text name= .$field. ><br />; }elseif($fieldtype == int){ echo $divstart . ucfirst($field).</
foreach($post as $key => $value){ // Just loop through and dump out values - we need
div><input class=int
type=text name= .$field. ><br />; }elseif($fieldtype == text){ echo $divstart . ucfirst($field).</ }
echo </div></div>;
$row[2].|. $row[3] .|. $row[5] .<br />; } } } // Finish form and add footer echo </select>;
$row[4].|.
echo <a href=licence.txt title=Copyright and licence details>Copyright © 2013 Rob Somerville me@ merville.co.uk</a>;
echo </div>; }
60
TBO 01/2014
Replace the code in (Listing 1) with the code in (Listing 2) and modify preload.js as well as global.css to match (Listing 3) and (Listing 4). This will provide the menu as shown in (Figure 2 & 3). Add jquery support for each menu: Listing 3. Add some additional CSS so that the individual menus line up: Listing 4.
In MySQL, create the menus table (Listing 5). Populate with some basic menus (Listing 6).
The amendcontent page is a PHP script that allows the user to add new content to the CMS. As we have not validatListing 8. additions to global.css
#php {
.varchar {
Useful links
.int {
Jquery UI source http://jqueryui.com/resources/download/ jquery-ui-1.10.3.zip Jquery menu reference http://jqueryui.com/menu PHP manual http://php.net/manual
.textarea {
.inputname {
font-size: 12px;
ed the user input yet, well just capture the input for now. Create a new PHP file called amendcontent.php in the root directory where index.php is already saved (Listing 7). We need to add a small modification to global.css to line up the fields (Listing 8). Now visit http://yoursite/amendcontent.php and you will have a dynamic form ready to save data to any table in the CMS. See (Figure 4-6).
font-weight: bold;
We will use the data from the menu tables to populate the Jquery menus and write some validation code for the user input prior to saving to the database.
ROb SOMerville
Rob Somerville has been passionate about technology since his early teens. A keen advocate of open systems since the mid-eighties, he has worked in many corporate sectors including finance, automotive, airlines, government and media in a variety of roles from technical support, system administrator, developer, systems integrator and IT manager. He has moved on from CP/M and nixie tubes but keeps a soldering iron handy just in case.
www.bsdmag.org
61
ADMIN
n part 8 of the series, we created the PHP script amendcontentpage.php which allowed the user to add data to any table in the CMS (Figure 1 & 2). We will refine this page, and add a login page and the corresponding database table. Create a login table in MySQL to hold the user credentials (Listing 1). We require a blob field as we will be storing binary rather than string data for the encrypted password. The auth field will be used to define the user rights later on, but for the moment setting a value of 1 via the form we will construct will be sufficient. Now add the following to our global.css file to format the output from our amendcontent.php page (Listing 2). Create a file in the includes directory called login.inc (Listing 3). This holds the name and secret key for the login cookie. Create a new file login.php in the root directory of the application (Listing 4). Finally, amend amendcontent.php as follows. As there are a lot of changes throughout the file, the script is detailed here in its entirety (Listing 5). Hopefully, most of the code should be self explanatory, but here is a breakdown of the major functionality of each page.
Login.php
As we are storing the hashed value of the password in the database, rather than a text string that we can com-
62
TBO 01/2014
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=latin1; require INCLUDES . content.inc; require INCLUDES . core.inc; require INCLUDES . html.inc;
require INCLUDES . mysql.inc; require INCLUDES . login.inc; // SQL statements $sql[0] = SELECT password,auth FROM login WHERE username = ---P0--- AND password = ---P1---;;
font-size: 14px;
.tablerow1 {
$sql[1] = INSERT INTO `login` (`username`, `password`, `auth`, `timestamp`) VALUES (---P0---, (---P1---), ---P2---,
now());;
line-height: 25px; width: 22%; /////////////////////////////////////////////////// /////////////////////////////////////////////////// /////////////////////////////////////////////////// // Delete these 3 lines after first user added if(!isset($_POST[action])){ createnewlogin(); }
.tablerow2 {
Listing 3. login.inc
<?php define(KEYNAME,gpl9867fghlls);
define(LOGINKEY,117hkJ23230rT);
Listing 4. login.php
<?php require_once includes/cms.inc;
www.bsdmag.org
63
ADMIN
POST[password])){
} /////////////////////////////////////////////////// /////////////////////////////////////////////////// /////////////////////////////////////////////////// function validatelogin($username, $password, $sql){ // As the password is hashed and hopefully cannot be decrypted, // We need to usend the encrypted password $hashed_password = hash(whirlpool, $password); // Fetch credentials from DB, if match create a login cookie
$username = $_POST[username]; $password = $_POST[password]; // We have valid credentials, validate validatelogin($username, } $password,$sql);
}elseif($action == createnewlogin){
if(!isset($_COOKIE[KEYNAME])){ // Create a new login to the system createnewlogin($username, $password,$sql); }else{ // User failed cookie test, request them to } login
$s = $sql[0];
requestlogindetails();
$auth = $row[1]; }
}elseif($action == appendnewlogin){
$password = $_POST[password];
if ($auth == 1) {
// Create auth cookie setcookie(KEYNAME, LOGINKEY, time()+3600, /); // Display options $title = Welcome . $username; buildheader($title);
appendnewlogin($username,$password,$auth,$sql); }else{
requestlogindetails();
// Try again
64
TBO 01/2014
ADMIN
requestlogindetails(); }
echo <form action=login.php method=post>; echo Username . div(<input type=text name=username>,$class); name=password>,$class);
echo Password . div(<input type=password echo <input type=submit value=Submit>; echo <input type=hidden name=action value=validatelogin>; echo </form>;
buildheader($title);
echo wraptag(h1,$title); echo <form action=login.php method=post>; echo Username . div(<input type=text name=username>,$class); name=password>,$class); name=auth>,$class);
echo Password . div(<input type=password echo Auth . div(<input type=text echo <input type=submit value=Submit>; echo <input type=hidden name=action value=appendnewlogin>; echo </form>;
buildfooter(); } function buildheader($title){ } function buildfooter(){ echo </div>; echo </div>; echo <div id=licence>; // As cookies need to be set before any output is sent to the browser // use a function call to build the page header // Build the page up to the body tag outfile(TEMPLATES . header.inc); echo wraptag(title, $title); echo HEAD; echo BODY;
buildfooter(); } function appendnewlogin($username,$password,$auth,$sql){ // Create a new entry in the login table $hashed_password = hash(whirlpool, $password); $s = $sql[1];
mysql_select($s);
echo <a href=licence.txt title=Copyright and licence details>Copyright © 2013 Rob Somerville me@ merville.co.uk</a>;
echo </div>;
66
TBO 01/2014
Listing 5. amendcontent.php
<?php require_once includes/cms.inc; require INCLUDES . core.inc; require INCLUDES . html.inc;
outfile(TEMPLATES . header.inc); echo wraptag(title, Content Input); echo HEAD; echo BODY; // Page control logic if(isset($_POST[table])){ // User has not selected a table or we are testing
require INCLUDES . mysql.inc; // SQL statements $sql[0] = SELECT COUNT(DISTINCT TABLE_NAME) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = freebsdcms AND TABLE_NAME = ---P0---;
their result
build_page_1($tables); }else{ // Check selected table is valid $s = $sql[0]; // Replace the marker in the SQL statement with $s = str_replace ( ---P0--- , $t , $s ); $result = mysql_select($s); TABLE_NAME)];
// The tables we will allow the user to edit via this form
$tables[] = menus; $tables[] = pages; // Fields that are automatically assigned via a default value in MySQL table definition
$valid_table_count = $result[COUNT(DISTINCT
if($valid_table_count == 1){ // Valid table selected - present form to edit data build_page_2($t,$sql,$skiplist);
$skiplist[] = id;
$skiplist[] = timestamp; /////////////////////////////////////////////////// /////////////////////////////////////////////////// /////////////////////////////////////////////////// // Build the page up to the body tag
www.bsdmag.org
67
ADMIN
$editcontrol .= <input type=radio name=inputmode $editcontrol .= <input type=radio name=inputmode value=update>Update current content; value=new checked=checked>Add new content;
echo Updateold record; }else{ // Invalid value - return to start build_page_1($tables); } /////////////////////////////////////////////////// /////////////////////////////////////////////////// /////////////////////////////////////////////////// function build_page_1($tables){ // HTML form definition echo <div id=content>; echo <div id=php>; echo <div id=h1>Select content</div>;
// Add the DIV to format the controls echo div($tablecontrol, formcontrol); echo div($editcontrol, formcontrol); echo div($submitcontrol, formcontrol); // Complete the form echo </form>;
echo </div></div>;
echo <a href=licence.txt title=Copyright and licence details>Copyright © 2013 Rob Somerville me@ merville.co.uk</a>;
echo </div>; }
function build_page_2($t,$sql,$skiplist){ // HTML form echo <div id=content>; echo <div id=php>;
$tablecontrol .= <select name=table>; foreach ($tables as $t){ // $tables is an array - split each value out $tablecontrol .= <option value=.$t.>.$t.</option>; }
// Check we have a valid inputmode if(!isset($_POST[inputmode])){ echo Error: Invalid inputmode; exit; }else{
68
TBO 01/2014
ADMIN
id,$populate); echo
$value = populatefields($sql[4],$field,$t,$row
// New content - populate with selected value if we // via an update content request. if(isset($_POST[rowid])){ $rowid = $_POST[rowid]; $populate = TRUE; }else{ $rowid = ;
$divstart . ucfirst($field).</div><input
wid,$populate); echo
$divstart . ucfirst($field).</div><input
$populate = FALSE; }
div>;
$rowid,$populate); echo
method=post>;
div><textarea rows=10 cols=30 class=textarea name= .$field. >.$value.</textarea><br />; }else{ // Shouldnt get here echo Error field(.$field.) .
$divstart . ucfirst($field).</
value=.$t.>;
} } } //echo </select>; }elseif($_POST[inputmode] == update){ echo <div id=h1>Select content .$t.</div>; method=post>; echo <form action=amendcontent.php
foreach($result as $row){ // Loop through each field and build the form fields
$fieldtype =
$row[4];
value=.$t.>;
70
TBO 01/2014
value=new>;
chosen value
}elseif($zebra == 1){ $class = tablerow2; $zebra = 0; } // Radio button control $editcontrol = <input type=radio name=rowid
$displaycols = array(2, 4, 5); }else{ // Everything else $displaycols = array(1, 2, 3); } // Get the field names for our table $titles = mysql_fetchrows($s); // Build the title row echo div(Select, tablehdr); foreach ($displaycols as $offset) { echo div($titles[$offset][0], tablehdr); } $s = $sql[2]; $zebra = 0; $action = Update; // Replace the marker in the SQL statement with the $s = str_replace ( ---P0--- , $t , $s ); $result = mysql_fetchrows($s); } }
value=.$row[0].>;
$editcontrol); } }else{
// Finish form and add footer echo <input type=submit value=.$action. .$t. echo </form>; item>;
echo </div></div>;
echo <a href=licence.txt title=Copyright and licence details>Copyright © 2013 Rob Somerville me@ merville.co.uk</a>;
chosen value
echo </div>; }
www.bsdmag.org
71
ADMIN
function build_page_3($post){ // HTML echo <div id=content>; echo <div id=php>; echo <ul>; echo <div id=h1>Save content</div>;
foreach ($displaycols as $offset) { // First check we have some content - use a NBSP
if NULL or blank
if($row[$offset] == ){ $row[$offset] = ; } // Ensure length < 25 chars, else add elipses if(strlen($row[$offset]) > 25){ $row[$offset] = substr($row[$offset],0,24) .
foreach($post as $key => $value){ // Just dump out values - we need to validate before
adding to DB
...; }
echo <a href=licence.txt title=Copyright and licence details>Copyright © 2013 Rob Somerville me@ merville.co.uk</a>;
echo </div>; }
function populatefields($sql,$field,$t,$rowid,$populate){ if($populate){ $s = str_replace ( ---P0--- , $field , $sql ); $s = str_replace ( ---P1--- , $t , $s ); $s = str_replace ( ---P2--- , $rowid , $s ); $v = mysql_fetchrows($s); $value = $v[0][0]; }else{ $value = ; } return $value; }
format. content
// Formats the rows from our select query in zebra // To prevent the CSS from breaking due to NULL // and displays the appropriate rows as the menu // different from everything else. // Display the radio button echo div($editcontrol, $class); // Format each row
schema is
72
TBO 01/2014
Figure 4. Logging in
www.bsdmag.org
73
ADMIN
pare, we need to initially seed the database with a valid username and password the first time login.php is run. Once the login table is updated, the call to createnewlogin() can be removed. The page control logic branches depending on the action we want to achieve, and the corresponding function calls either build an HTML form, query the database or add a user to the database.
Amendcontent.php
Most of the action takes place within build_page_2. In the previous version, we could not select any previously entered content so to add this functionality we have added an intermediate step which displays all the content avail-
able to be edited. Once the user selects the table record to be amended, this is fed back into our original form, which is essentially identical whether we are updating or adding content. Some bells and whistles are added in the form of zebra striping of the table rows, and the automatic generation of the titles. As the script is referring directly to the database, we need a conditional branch at line 266 as the menu table has a different schema from the pages, news and FAQ content.
Getting it to work
Visit the login item via the menu and create a new user and password with an auth level of 1. Check the user has been
74
TBO 01/2014
Useful links
added to the login table and remove the 3 lines from the start of login.php as commented. Revisit login.php, login and proceed to edit your content as desired (Figure 3-12).
Further tuning
The security is poor we can have multiple users with the same name and password. A better form of encryption other than hashing is desirable, and we are missing lots of backlinks etc. We should also refactor the code e.g. the license and footers.
[ GEEKED AT BIRTH ]
ROb SOMerville
Rob Somerville has been passionate about technology since his early teens. A keen advocate of open systems since the mid-eighties, he has worked in many corporate sectors including finance, automotive, airlines, government and media in a variety of roles from technical support, system administrator, developer, systems integrator and IT manager. He has moved on from CP/M and nixie tubes but keeps a soldering iron handy just in case.
You can talk the talk. Can you walk the walk?
ADMIN
n the previous article we put in place a very crude login system that allowed anyone to login to our CMS and add content. We assume that the user has been correctly authenticated by comparing their password against a hashed
Listing 1. Logout function
function logout(){ setcookie(KEYNAME, LOGINKEY, time()-3600, /); } echo You have been logged out;
password stored in the CMS database, then writing a cookie at the client side. It is then a simple matter of checking that authorization has been granted prior to carrying out sensitive actions (e.g. adding a user or amending content).
requestlogindetails();
Listing 3. logoutform
function logoutform(){ // Check if user is logged in, if so display the logout button.
$password = $_POST[password];
echo <form action=login.php method=post>; echo <input type=submit value=logout>; value=logout>; echo </form>; echo </div>; } echo <input type=hidden name=action
76
TBO 01/2014
Unfortunately, exposing any login system on the World Wide Web leaves us open to undesirable elements. Brute force attacks (repeatedly attempting a login using dictionary attacks) and spambots that want to add advertising or phishing spam are commonplace, and our basic login system needs to defend against this. We also need to add logout functionality to every page that requires it.
As the parameters passed to the cookie that is set when we are logged in, it makes sense to hold the logout func-
tion as part of the login.php page. We can then detect a logout post event to login.php and delete the cookie by setting the expire date to a time in the past. Add the following code at the end of login.php (Listing 1). Now we need to check for a post event that carries the value logout. Add the following elseif branch between append and the closing else (Listing 2). We now need a logoutform() function that will provide a logout button whenever a user is logged in to the system. If we check whether or not the user is logged in we can place this in the footer of all pages where login / logout
outfile(TEMPLATES . header.inc);
Listing 7. phpinfo.php
<?php // Check we are logged in require_once includes/cms.inc; require INCLUDES . core.inc; ifnotloggedin(); phpinfo(); logoutform();
logoutform();
Listing 9. global.css
#logout { float: right;
Listing 6. amendcontent.php
// Check we are logged in ifnotloggedin(); // Build the page up to the body tag
www.bsdmag.org
77
ADMIN
buildfooter();
echo wraptag(h1,$title);
78
TBO 01/2014
functionality is required, and the button will be displayed only if the user is logged in. Add this to the end of core. inc (Listing 3). We need to add a function call to check if the user is logged in or not, and redirect them to the login page if they are not. Add this at the end of core.inc (Listing 4). As we cannot guarantee that the user does not spoof HTTP headers for the redirect, define our CMS Domain in cms.inc. Replace 192.168.0.118 with either the IP address or domain name of your server (if accessible via DNS). (Listing 5) Add the ifnotloggedin() function call to the beginning of amendcontent.php and replace phpinfo.php with the content in Listing 7 (Listing 6 & 7).
Listing 13. Amended logoutform();
function logoutform($forcelogout = 0){ // Check if user is logged in, if so display the logout button.
`ipaddress` varchar(64) NOT NULL, `page` varchar(64) NOT NULL, `status` int(1) NOT NULL,
`timestamp` timestamp NOT NULL DEFAULT CURRENT_ PRIMARY KEY (`id`) TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
* sqlstatements.inc *
* Contains CMS SQL statements */ $sql[0] = INSERT INTO `access` (`ipaddress`, `page`, `status`, `timestamp`) now());; VALUES (---P0---, (---P1---), ---P2---,
Listing 15. Remove the comment out from createnewlogin, suffix with // to revert to normal login action
if(!isset($_POST[action])){ } createnewlogin();
Add the following line to cms.inc [Listing ] // Honeypot for bad traffic define(HONEYPOT, www.google.com);
Listing 16.
CREATE TABLE `access` (
www.bsdmag.org
79
ADMIN
CMSDB);
// Pass our results to an array to be returned if(isset($result->num_rows)){ $r = array(); $r[] = $result->num_rows; table $r[] = $db->field_count; // No of rows returned
// Ban em banip($ip, login.php); } }else{ // Check that they have not been flagged as
// No of columns in
$r[] = $db->affected_rows;
// No of rows affected
suspicious
$s = $sql[1];
array(MYSQLI_ASSOC)); }
}else{
$status = $row[0]; }
$r = NULL;
}else{
80
TBO 01/2014
$status = 0; }
mysql_select($s);
} }
session_start(); // As the password is hashed and hopefully cannot be decrypted, // We need to send the encrypted password
function banip($ip, $page){ require INCLUDES . sqlstatements.inc; } function logip($page){ require INCLUDES . sqlstatements.inc; // Just log a visit $ip = $_SERVER[REMOTE_ADDR]; // Add to our banlist $s = $sql[0];
$hashed_password = hash(whirlpool, $password); // Fetch credentials from DB, if match create a login cookie
$s = $sql[0];
$s = str_replace ( ---P0--- , $ip , $s ); $s = str_replace ( ---P2--- , 1 , $s ); mysql_select($s); // Redirect to our honeypot header( Location: http:// . HONEYPOT ) ;
$auth = $row[1]; }
}else{ $auth = 0;
$s = $sql[0];
if ($auth == 1) {
www.bsdmag.org
81
ADMIN
requestlogindetails(); } }
unset($_SESSION[loginattempts]); // Create auth cookie setcookie(KEYNAME, LOGINKEY, time()+3600, /); // Display options $title = Welcome . $username; buildheader($title,1);
buildfooter();
}else{
if(isset($_SESSION[loginattempts])){ $_SESSION[loginattempts] = $_
SESSION[loginattempts] + 1;
}else{ } // If they have exceeded our limit, ban em if($_SESSION[loginattempts] > 3){ $ip = $_SERVER[REMOTE_ADDR]; banip($ip, login.php); $_SESSION[loginattempts] = 1;
// Try again
82
TBO 01/2014
Add the logoutform(); after every occurrence after echo BODY; in login.php and amendcontent.php (Listing 8). Add the following to global.css to highlight and position the logout button (Listing 9). With Firebug enabled in Firefox, check that a cookie called gpl9867fghlls is created when a user is logged in. The logout button should appear on all pages except the second time login.php is called and we arrive at the welcome page (Figure 1 6). Now this is a problem, as we should be able to logout immediately after we login. Subsequent calls to login.php will show the logout button. So what is happening here? The problem lies in the validatelogin() function (Listing 10). We must set the cookie prior to creating the page header, but as the cookie data is generated at the client browser side when the page is loaded, as far as the PHP code running at the server side is concerned the cookie is not present yet. We can fool buildheader() by passing a parameter to force the display of the logout button (Listing 11 13). This will result in login.php working as desired (Figure 7). While we could use the very effective Apache MOD_SECURITY module to trap bad behaviour, this can be tricky to set up. What we will do here is monitor behaviour in two ways. First, we will create a hidden field that a normal user will not see under normal circumstances, which most spam-robots will fill in assuming it is a genuine field. On completing the field, our CMS will automatically ban all connections from that IP address to login.php permanently. We will also check that no more than 3 invalid attempts are made to the login.php page, and if that is exceeded, that IP address will be banned as well. First create another testuser by changing login.php as follows and visit login.php anew to create another user (e.g. Test, Test, Auth = 1). Dont worry about the error messages we will fix them later. Once you have created the new user, go back and comment out createnewlogin(); and check that you can login as the test user (Listing 14 and 15). If you visit login.php you should be able to login as Test (Ignore the Email field), then Logout. (Figure 8). Now create our access table in MySQL to hold our banlist (Listing 16). Now create the file sqlstatements.inc in our includes directory (Listing 17). Replace the mysql_select() function call in mysql.inc with the following code (Listing 18). This fixes a bug where a PHP error is raised when no results are returned. Add the following function calls to core. inc (Listing 19). Replace validatelogin() in login.php with the following code (Listing 20). Modify buildheader() in login.php to call loginsecurity() (Listing 21).
www.bsdmag.org
Useful links
Testing
It is recommended that you run Firebug to view the cookies and PHP sessions generated during this test. Clear all cookies etc. from you browser and visit login.php: Login as Test with the correct password. You should be able to login. Logout. Login as Test with the correct password and an email address. You should be redirected to google.com. Any visits to login.php will cause a redirect to google.com. Use Adminer to clear all the entries from the access table. Visit login.php and click on the submit button 3 times without making any input. You should be redirected on the 4th attempt. Use Adminer to clear all the entries from the access table. Visit login.php and login and logout as normal. Your access attempts should be logged correctly with IP address and date. Login with a mixture of bad username and good password, good username and bad password. You should be banned on your 4th login attempt.
CSS modification
Finally, add the following code to global.css and refresh your browser with Ctrl F5 a couple of times to clear the cache. The email field should now be invisible to human visitors, but available to robots etc. (Listing 22).
Next steps
It might be a good idea to add the banlist functionality to all pages on a failed login etc. and keep a tally of what pages are accessed etc. legitimately. We also need to add the facility to add a user rather than manually editing code each time. Our CMS is getting quite large, with over 2,100 lines of code (excluding the Jquery libraries) so we will look at refactoring some of this code in the next article.
ROb SOMerville
Rob Somerville has been passionate about technology since his early teens. A keen advocate of open systems since the mid-eighties, he has worked in many corporate sectors including finance, automotive, airlines, government and media in a variety of roles from technical support, system administrator, developer, systems integrator and IT manager. He has moved on from CP/M and nixie tubes but keeps a soldering iron handy just in case.
83
ADMIN
nfortunately, the Internet gremlins have got me at the moment so this how-to is going to be very short. My local telco is currently rolling out fibre in the area, and my ADSL internet connection is very unreliable, but hopefully I will be able to wrap up the programming primer in part 12 with a bumper edition. While debugging at the command line using echo statements or commenting out code is possible, a more frequent scenario is that our project will be residing on a remote server and we will need to see the actual processes in action. Often developers will have a local copy of the LAMP stack on their PC or laptop, so that they can debug locally. However, what happens when our development environment is on a laptop and the code is on a remote server? A frequent approach is to use an Integrated Development Environment (IDE) with a built in file transfer utility. Coupled with Xdebug, which supports PHP, we can download our remote code and debug (step through) each line, examine variables etc. To do this, we will need to install Xdebug on our server and install the IDE of our choice on an available local PC. This can be FreeBSD, Windows or Linux, but in my case I was using an Ubuntu desktop. The IDE installation will vary from environment to environment, full details can be found at https://netbeans. org. The IP address of of my desktop PC for this exercise was 192.168.0.123.
lems getting the standard packaged version of Xdebug working with certain distros, where as the latest Xdebug
Listing 1. Install Xdebug
tar -xvzf xdebug-2.2.3.tgz
cd xdebug-2.2.3 phpize ./configure enable-xdebug make cd modules cp xdebug.so /usr/local/lib/php/20100525/ touch /var/log/xdebug.log chmod 666 /var/log/xdebug.log touch /user/local/etc/php/xdebug.ini
Listing 2. /user/local/etc/php/xdebug.ini
zend_extension=/usr/local/lib/php/20100525/xdebug.so xdebug.remote_enable=1 xdebug.remote_host=192.168.0.123 xdebug.remote_port=9000 xdebug.remote_mode=req xdebug.remote_handler=dbgp xdebug.profiler_enable = 1
xdebug.remote_log=/var/log/xdebug.log
Installing Xdebug
Rather than using the FreeBSD provided software, I downloaded the latest version from http://xdebug.org. The reason for this is that in the past I have had prob-
84
TBO 01/2014
and latest Netbeans IDE always seem to work OK together. Once you have downloaded the latest version of the tarball (Currently xdebug-2.2.3.tgz) into your home directory, on the remote server (192.168.0.118) as root, perform the following (Listing 1). Add the following to /user/local/etc/php/xdebug.ini (Listing 2). Replace 192.168.0.123 with the IP address of your client machine. Restart Apache (Listing 3). If we now login as admin and visit our PHPinfo page at http:/192.168.0.118/phpinfo.php, we should see that Xde-
bug is installed and running (Figure 1). If you have not already done so, download and install Netbeans on a local PC of your choice. You will need a working Java installation and Firefox installed for this to work.
Figure 5. Create a new SFTP connection (SSH must be running on Port 22 of your server)
www.bsdmag.org
85
ADMIN
Useful links
Figure 10. Load Index.php click on line 52 and start the debug session by pressing Ctrl F5
Now follow the Figures (Figure 2 10). If all goes to plan, you should be able to step through your code by pressing F7, and interrogate variables by hovering over them e.g. $request. While debugging, the breakpoint line should change colour from pink to green. If it does not, there is some mis-communication between Netbeans and the server. See xdebug.log for further details.
ROb SOMerville
Rob Somerville has been passionate about technology since his early teens. A keen advocate of open systems since the mid-eighties, he has worked in many corporate sectors including finance, automotive, airlines, government and media in a variety of roles from technical support, system administrator, developer, systems integrator and IT manager. He has moved on from CP/M and nixie tubes but keeps a soldering iron handy just in case.
Figure 8. The final settings of the remote project. Replace with your server IP address as required
86
TBO 01/2014
ADMIN
ny programmer or developer will freely admit that his or her code is never finished. The best we can hope for is a piece of code that is bug free, reliable and extensible i.e. we can accommodate future changes easily. Sadly, this is the last part of the our programming primer series, and while we have a lot of code (over 3,200 lines excluding external libraries) a lot of further development is required to bring our fledgling CMS up to scratch. While I could carry on and take the project to the point where it is a fully functional CMS, I would not be able personally to support the code and testing cycle in the long term, so it it is now time for me to hand this embryonic project over to the community to add the final touches and squash any inevitable bugs and areas of inefficiency that I have inadvertently included. Rather than me catching the fish, it is now time for you to cast your rod into the deep pool where many fish (including sharks) dwell. In reality, the lesson of Part 12 of the series is probably the hardest in the series wrapping your head around
Listing 1. /user/local/etc/php/xdebug.ini
zend_extension=/usr/local/lib/php/20100525/xdebug.so xdebug.remote_enable=1 xdebug.remote_host=192.168.0.123 xdebug.remote_port=9000 xdebug.remote_mode=req xdebug.remote_handler=dbgp xdebug.profiler_enable = 1
xdebug.remote_log=/var/log/xdebug.log
88
TBO 01/2014
someone elses code and fixing or developing it. As a developer, this has always been the biggest struggle I have had when faced with maintaining legacy code. It is very easy to code from scratch but the demons always lie further down the road. Hopefully, though you will have a head start so that life is a bit easier.
We covered setting up both of these in Part 11. Ensure your xdebug.ini is configured correctly and restart Apache if required /user/local/etc/php/xdebug.ini (Listing 1).
To initiate a real time debugging session, open the FreeBSD CMS project and navigate to line 12 of index.php. Click on the LHS margin next to line 12 to set the breakpoint (which should turn a salmon pink color), and press Ctrl F5 to initiate a debug session. This should open your default browser (in my case Firefox) at the index.php file on the remote server with the parameter XDEBUG_SESSION_ START passed to xdebug. This will cause the browser to report Connecting ... but no HTML will be parsed as Netbeans is now in control of the program flow. Switch back to Netbeans, and line 12 should be highlighted green which means we are in debug mode and communicating with the remote server (Figure 1 & 2). Pressing F7 will walk you through each line of code, and Netbeans will automatically open the first file CMS.INC for you (Figure 3). Continue to press F7 until you come to line 41 of index.php.
Adding a watch
Figure 4. Setting a watch expression
A watch in debugger terms allows you to grab a variable and monitor its value in real time as you step through the code. Highlight $_SERVER[REQUEST_URI], right click and Add Watch. A dialogue box will appear, click OK and the value of this variable will be shown in the lower pane (Figure 4 & 5).
The Call stack shows us the code from each of the files that are open. For instance, the function buildpage() is contained in core.inc. When we reach that point of execution in the code, both index.php and core.inc will be shown in the call stack. Likewise, all breakpoints are shown in the lower pane (Figure 6 & 7).
If we hover over a variable on the left or right hand side of a statement, we can determine its value. If the value is not shown, ensure this is enabled in the PHP configuration settings (Figure 8 10).
www.bsdmag.org
89
ADMIN
90
TBO 01/2014
Figure 12. Note the Connecting delay as we step through the code
Figure 16. Content of the $array variable after first element is deleted in code
www.bsdmag.org
91
ADMIN
Program flow
Set a breakpoint at line 46 of index.php. Navigate to ../ page/1 in your browser and step through the code using F7. Note that your breakpoint will be ignored as we are not making a request to the home page of the server. Also, the value of $array will change as we step through the code (Figure 13 18). Pressing F5 in Netbeans will allow us to continue until the page is loaded. To prevent Netbeans from stopping at the first line of your code (even if a breakpoint is not set) disable this in the settings (Figure 21).
92
TBO 01/2014
Useful links
will have to modify cms.inc to meet your own environment. Please note that in its current form this project is not suitable for production use.
So what next?
There are many additional functions our CMS could use, help for user adding content, filtering to check that HTML entered is valid, a facility for uploading photos, additional modules for chat, XML feeds, you name it the sky is the limit. The next Howto series will cover image manipulation with the Gimp.
Edit line 60 of index.php and comment out the echo statement. When the file is saved, it should be uploaded to the remote server for you (Figure 19 20).
Solution
Line 365 of amendcontent.php only displays the changes, they are not committed to the database. For example of this, see the logip() function.
With the debugger running, log in to the CMS and add some content (Figure 22 23). Why is the content not being saved? The answer is at the bottom of the article.
ROb SOMerville
Rob Somerville has been passionate about technology since his early teens. A keen advocate of open systems since the mid-eighties, he has worked in many corporate sectors including finance, automotive, airlines, government and media in a variety of roles from technical support, system administrator, developer, systems integrator and IT manager. He has moved on from CP/M and nixie tubes but keeps a soldering iron handy just in case.
The code
The full code for this project is available at https://github. com/merville/FreeBSD_CMS complete with the database backup which is stored in DUMP.sql. Please note that you
a d v e r t i
U P D AT E NOW WITH
STIG
AUDITING
IN SOME CASES
REMOVED
the
HAS VIRTUALL Y
nipper studio
NEED FOR a
MANUAL AUDIT
CISCO SYSTEMS INC.
Titanias award winning Nipper Studio configuration auditing tool is helping security consultants and enduser organizations worldwide improve their network security. Its reports are more detailed than those typically produced by scanners, enabling you to maintain a higher level of vulnerability analysis in the intervals between penetration tests. Now used in over 45 countries, Nipper Studio provides a thorough, fast & cost effective way to securely audit over 100 different types of network device. The NSA, FBI, DoD & U.S. Treasury already use it, so why not try it for free at www.titania.com
www.titania.com