The following is a detailed description of the necessary steps to add support for a module. We will add support for the PHP scripting language ( http://www.php.net).
We take a look at the configuration information for PHP: http://www.php.net/manual/configuration.php3
As it turns out PHP can be configured in different ways, either inside the Apache httpd.conf file or in a separate file. The syntax varies too from PHP3 to PHP4. Since this is just an example, we will configure PHP3 for httpd.conf syntax. The user interface will remain unchanged among all of them, so to support the other versions we just need to change the translating functions since as can reuse the directive and property pages definitions.
One thing to remember is that we do not need to support all directives at once. Directives not known to Comanche are safely ignored and restored when writing the output configuration file. We can provide support for the most commonly used directives and then go for the more obscure parameters.
Before doing any coding we need to plan what the user interface is going to look like. We plan on having the following property pages:
PHP3 (Enable/disable and tags) | \__ Resources (Limit use if resources CPU/Time) | \__ Error Handling (Error logs, display, etc.) | \__ Data Handling (POST variables, prepend, autopend...) | \__ Paths (auto include directories)
Next step is to actually define the directives. The XML language used here is described in the programmer guide. It is really easy so you probably can pick it up just by taking a look at how the other modules are described. Most directives have a one to one correspondence, others will need more elaborated XML directives.
In the docs we find
asp_tags boolean Enables the use of ASP-like <% %> tags in addition to the usual <?php ?> tags. This includes the variable-value printing shorthand of <%=$value %>. For more information, see Escaping from HTML.
It is clear that this maps directly into the XML boolean type so we write:
<boolean name="php3_asp_tags" label="php_asp_tags"> <default>0 </default> </boolean>
Name is php3_asp_tags because this is how it will appear on Apache config files. This way Apache already nows which httpd.conf directives match to which XML directives.
Instead of writting the description of the directive directly on the label we store the description in the messages/ subdirectory. The contents of the messages.en file:
php_asp_tags {Enable ASP (<% %>) style tags?}If we want an additional Spanish version, we write (lenguage can be later on selected by the user) messages.es:
php_asp_tags {Permitir tags como las de ASP (<% %>)?}
This one, which accepts a number as an argument gets turned into
<number name="php3_max_execution_time" label="php_max_execution_time"> <default>30 </default> </number>with messages.en
php_max_execution_time {Limit in execution time (seconds)}
This one sets the document root for safe mode. This is a string, more precisely a directory, so the corresponding entry:
<string name="php3_doc_root" label="php_doc_root" classes="directory" > <default> </default> </string>(note the classes attribute, it hints the gui to provide a directory selector button and to write back quoted directories with blank spaces)
with messages.en:
php_doc_root {PHP root directory (for safe mode)}
This one could have been modeled in a different way, but this ones works just fine at it is clear
<choice name="php3_gpc_order" label="php_gpc_order"> <syntax> <option name="GPC" value="GET/POST/COOKIE" /> <option name="GCP" value="GET/COOKIE/POST" /> <option name="PGC" value="POST/GET/COOKIE" /> <option name="PCG" value="POST/COOKIE/GET" /> <option name="CPG" value="COOKIE/POST/GET" /> <option name="CGP" value="COOKIE/GET/POST" /> <option name="GP" value="GET/POST" /> <option name="GC" value="GET/COOKIE" /> <option name="PG" value="POST/GET" /> <option name="PC" value="POST/COOKIE" /> <option name="CP" value="COOKIE/POST" /> <option name="CG" value="COOKIE/GET" /> <option name="G" value="GET" /> <option name="P" value="POST" /> <option name="C" value="COOKIE" /> </syntax> <default>GPC </default> </choice>messages.en:
php_gpc_order {Order of GET/POST/COOKIE variable parsing}So far the mapping between httpd.conf and XML directives has been pretty straightforward. For the previous entries the conversion is handled automatically by Comanche and we do not need to do anything else!
Now comes the interesting part. It requires a little bit more of work but it pays off in a more easy to use interface for the user. There are some directives that could be better expressed in some other way. Take for example the directive auto_append_file (File to always parse after processing the main file). It can have a special value of 'none' to disable this feature. Instead of mapping it to a string and have the user know that piece of information we make it a little bit easier and provide a pair of radiobuttons. First one is simply 'none' the other is the name of the file.
This, translated to XML gets us:
<alternate name="php3_auto_append_file" label="php_auto_append_file" style="normal"> <syntax> <label name="none" label="php_auto_append_file_none" /> <string name="file" label="php_auto_append_file_file" classes="file" /> </syntax> <default>none </default> </alternate>You get the idea. By default the 'none' element is selected.
We need a way to convert from httpd.conf to this XML format and the other way around. This involves a little bit of programming, which is shown below
# This procedure is called whenever the php3_auto_append_file is encountered # in httpd.conf proc ::apache1.3::parse_php3_auto_append_file {text parser dirDef xmlConf currentContainer} { # Create a new instance of this directive or overwrite the existing one set xuiObj [apacheparserutils::getOrCreateIfNotExists php3_auto_append_file \ $dirDef $xmlConf $currentContainer] # Depending on the value (none or a filename), select the appropriate component and store the value set value [lindex $text 1] switch [string tolower $value] { none { $xuiObj selectComponentByName none } default { $xuiObj selectComponentByName file $xuiObj.file setValue $value } } } # Procedure called to dump the contents to httpd.conf proc ::apache1.3::dump_php3_auto_append_file {xuiObj} { set value [[$xuiObj getSelectedComponent] getValue] return "php3_auto_append_file $value" }Similar goes for php3_auto_prepend directive or php3_error_log
This is another directive that would benefit of special treatment is php3_include_path (and php3_open_basedir)
From the PHP docs:
include_path string Specifies a list of directories where the require(), include() and fopen_with_path() functions look for files. The format is like the system's PATH environment variable: a list of directories separated with a colon in UNIX or semicolon in Windows. Example 3-1. UNIX include_path include_path=.:/home/httpd/php-lib Example 3-2. Windows include_path include_path=".;c:\www\phplib" The default value for this directive is . (only the current directory).Now we could map this directive to a string directive and hvae the user enter a string of directories separated by : or ; Instead we make it a little bit easier for them by abstracting it and presenting them with a list of directories.
The XML description:
<list name="php3_include_path" label="php_include_path"> <syntax> <string name="php3_include_path_file" label="php_include_path_file" classes="directory" /> </syntax> <default> <item>. </item> </default> </list>We need now a couple of functions for converting from : or ; format (httpd.conf) to XML and viceversa.
proc ::apache1.3::parse_php3_include_path {text parser dirDef xmlConf currentContainer} { set xuiObj [apacheparserutils::getOrCreateIfNotExists php3_include_path \ $dirDef $xmlConf $currentContainer] global tcl_platform switch $tcl_platform(platform) { windows { set separator {;} } default { set separator : } } foreach element [split [lrange $text 1 end] $separator] { set child [$xuiObj newChild] # Split fscks up quoted filenames, so this is required regsub {\\\"} $element {"} result $child setValue $result $xuiObj insertChild $child } } proc ::apache1.3::dump_php3_include_path {xuiObj} { global tcl_platform switch $tcl_platform(platform) { windows { set separator {;} } default { set separator : } } set values {} foreach child [$xuiObj getChildren] { lappend values [$child getValue] } set result [join $values $separator] if [llength $result] { return "php3_include_path $result" } return {} }
Once we have described all the directives (take a look at plugins/apache/modules/php3/directives.xml for a complete listing) We need to tell Comanche how do we want them organized in property pages (like in the tree described at the beginning)
In plugins/apache/modules/php3/propertyPages.xml
As an example, the main page:
<propertyPage label="php3_pp_phpEngine" icon="smallWheel" name="pp_phpEngine" align="vertical"> <directiveInclude name="php3_engine"/> <group name="php3_pp_phpEngine_tags"align="vertical"label="php3_pp_phpEngine_tags" style="normal"> <directiveInclude name="php3_asp_tags"/> <directiveInclude name="php3_short_open_tag"/> </group> </propertyPage> Finally, the moduleDescription.xml puts everything together: <apacheModuleDescription name="php3" directivesXMLDefinition="directives.xml" propertyPagesXMLDefinition="propertyPages.xml" description="PHP3 Apache module." > <nodesInterested> <node type="mainserver,virtualhost"> <propertyPage name="pp_phpEngine" /> [...] <propertyPage name="pp_phpPaths" hookUnder="pp_phpEngine" /> </node> </nodesInterested> <specialCases file="specialCases.tcl"> <specialCase confDir="php3_open_basedir" xmlDir="php3_open_basedir" parser="apache1.3::parse_php3_open_basedir" dumper="apache1.3::dump_php3_open_basedir" /> [...] </specialCases> </apacheModuleDescription>