Sei sulla pagina 1di 74

ocPortal Code Book:   for ocPortal version 4.

The Code Book contains documentation to teach experienced developers how to program for ocPortal.

This guide is not a substitute for learning PHP, HTML, CSS, or other languages. Some introductory
material for learning web development is available in our main set of tutorials
(http://ocportal.com/docs/). There is an excellent PHP book available for free online. In addition to
this Code Book, we also have some tutorials with worked examples of making addons:
 http://ocportal.com/docs/pg/tut_hardcore_1
 http://ocportal.com/docs/pg/tut_hardcore_2
 http://ocportal.com/docs/pg/tut_hardcore_3
and an introduction to the ocPortal framework:
 http://ocportal.com/docs/pg/tut_framework

To learn ocPortal's API, see the phpdoc comments in the source files. The source files are logically
named, so it should be relatively easy to get a handle on it all. Alternatively, view the autogenerated
documentation on http://ocportal.com/docs/api/.

Table of Contents

ocPortal terms .......................................................................................................................6

Bootstrapping ........................................................................................................................9

Addons .................................................................................................................................9

Modules ..............................................................................................................................10

What is a module? ............................................................................................................10

How do you create a new full-module? ................................................................................11

The 'info' function .............................................................................................................11

The 'run' function .............................................................................................................12

The 'get_entry_points' function ..........................................................................................13

The 'get_page_links' function..............................................................................................13

The 'extract_page_link_permissions' function.......................................................................13

The 'install' and 'uninstall' functions.....................................................................................14

The 'install' function..........................................................................................................14

Upgrading via the 'install' function ......................................................................................16

Copyright ocProducts Ltd. Page 1 of 74
The 'uninstall' function ......................................................................................................16

Screen conventions ..........................................................................................................17

Templates and Tempcode ..................................................................................................17

Making a simple two-screen full-module ..............................................................................19

Setting up our basic screens ..............................................................................................19

Creating our templates .....................................................................................................20

Hooks..................................................................................................................................21

Another example: member profiles......................................................................................22

Another example: symbols.................................................................................................23

Installation code...................................................................................................................23

Programming/progmattic-interface standards ..........................................................................23

Javadoc-like commenting ..................................................................................................31

Error handling ..................................................................................................................32

Escaping .........................................................................................................................33

Security ..........................................................................................................................34

Type strictness..................................................................................................................37

Example strict-typing mistakes...........................................................................................38

Do-next-manager interfaces ..............................................................................................40

The translation table .........................................................................................................41

GUIDs..............................................................................................................................41

The persistent cache .........................................................................................................41

Feature architecting standards, and implementation notes ........................................................43

Design/copy standards .........................................................................................................44

Web development standards ..................................................................................................46

Templates and themes ......................................................................................................48

Tempcode syntax .............................................................................................................48

WCAG notes ....................................................................................................................49

PNG images .....................................................................................................................50

Copyright ocProducts Ltd. Page 2 of 74
Javascript ............................................................................................................................51

Javascript libraries ............................................................................................................51

Javascript event handlers ..................................................................................................52

Inline Javascript ...............................................................................................................52

Example: mixing event handlers, inline Javascript, and a library ............................................52

Simpler example ..............................................................................................................53

Diff tools..............................................................................................................................53

Debugging, and stack traces...................................................................................................54

How Tempcode works............................................................................................................54

Development mode (aka debug mode)....................................................................................56

Custom version of PHP...........................................................................................................57

Engineering standards and trade-off .......................................................................................57

Importers ............................................................................................................................58

Import or forum driver?.....................................................................................................58

Writing a forum driver.......................................................................................................59

Writing an importer...........................................................................................................59

eCommerce..........................................................................................................................62

Permissions .........................................................................................................................62

Referencing existing permissions ........................................................................................63

Adding a new specific permission .......................................................................................63

SEO meta-data ....................................................................................................................64

Feedback mechanisms ..........................................................................................................65

Language lookups, Comcode, and attachments ........................................................................67

Hidden features inside ocPortal ..............................................................................................68

Empty files ......................................................................................................................68

Hidden 'values' ................................................................................................................68

Hidden 'keep' parameters ..................................................................................................69

Hints for making websites for other people...............................................................................71

Copyright ocProducts Ltd. Page 3 of 74
Conclusion...........................................................................................................................72

Copyright ocProducts Ltd. Page 4 of 74
ocPortal structure

ocPortal's file system is basically split up into:


 Zones (the root directory itself, the 'site' directory, the 'adminzone' directory, the 'cms'
directory, the 'forum' directory, the 'personalzone' directory, and in a way the 'data' directory).
Zones contain entry scripts (such as index.php) and page sets. Zones are basically just
subdirectories that group pages, making them convenient for creating different ‘sections’ of the
website. Pages may be:
 Modules (.php files matching a certain class interface). Modules provide a set of related
screens; e.g. all the screens for the user portion of the 'downloads' addon are in the
'downloads' module.
 Comcode (.txt files)
 HTML (.htm files)

 'sources' which contain code to be included


 'sources/hooks' which contain hooks that tie addons together so they can interoperate
 'sources/blocks' and 'sources/miniblocks' which contain block files. Blocks are plugable units
that are can be included within Comcode pages and templates. They are usually rendered as a
box, e.g. a menu, or a list of recent forum discussions.
 'themes' which contain images, CSS and templates.
 'lang' which contains language strings.

When you make modifications to any of ocPortal’s default files, There is no need to overwrite the
existing files, because ocPortal comes with a file-based override/inheritance system. This allows files
to be overridden with replacements whilst leaving the old files intact for reference. This is done by
saving your modified files in the *_custom directories. i.e. if we need to make modifications in
'site/pages/modules/iotds.php', then we need to save our modified version as
'site/pages/modules_custom/iotds.php') .

ocPortal is written to the 'front controller' and MVC 'design patterns' (for more information about MVC
visit http://en.wikipedia.org/wiki/Model-view-controller):
 Model/API: Most of the scripts from the 'sources' directory represent the model/API.
 View: The templates (see 'themes/default/templates' directory) represent the 'view'. Comcode
Pages could also be considered part of the view (though they are also content).
 Controller: The entry scripts (e.g. the 'index.php' files, or files like 'site/dload.php') represent
the 'front controllers'. The modules (i.e. scripts from the '*/pages/modules' directories) and
blocks (i.e. scripts from the 'sources/[mini]blocks' directory) represent the controllers.
'sources/site.php' and the initialisation functions in 'sources/global.php' and
'sources/global2.php' bind the entry scripts to the modules and control final output.

Dynamic pages (modules) are written in PHP and can output a set of different 'screens'. Screens are
constructed out of templates (nested in a tree structure), with the PHP code specifying a certain kind
of template structure, calling up templates with appropriate parameters to match the data being
displayed.

Static pages are simpler. Static pages may be easily developed and edited by regular ocPortal

Copyright ocProducts Ltd. Page 5 of 74
webmasters. To create a static page, you can just add a Comcode page (via 'adminzone' and 'Add New
Page Wizard' functioanlity) and either write it in Comcode (for more info see
http://ocportal.com/site/pg/userguide_comcode ), or just put your HTML between Comcode '[html]'
tags.

Page output is displayed by wrapping the primary page output within the GLOBAL template, prefixed
with the HEADER template, and followed by the FOOTER template. The GLOBAL template references
panels, and if those panels exist, they will also be included. Typically zones will include a 'panel_left'
and a 'panel_right', so any pages within said zone will be surrounded by these panels.

ocPortal terms

Here is a glossary of some of the terms that are used to describe ocPortal concepts:
 Addon, A defined set of ocPortal files that work together towards a common purpose. Many
addons come with ocPortal, and even the 'core' of the system is considered to consist of non-
optional addons.
 AED module, (Add/Edit/Delete module) a module that inherits from the AED base class, in order
to abstract away standard content management interfaces, speeding implementation and
improving consistency. Most content management modules are AED module. The AED module
base class is very rich and provides a lot of optional functionality that AED modules can choose
to inherit.
 Block, A self-contained section of output that can be used repeatedly by a Comcode or module
page. e.g. IOTD (Image Of The Day), or RSS/Atom Feed. . By convention, a block could be either
a "main block" or a "side block" or a "bottom block", but this doesn’t cause ocPortal to use it
differently (it is to help webmasters identify where blocks were designed to be placed).
 Catalogue, A high-level abstraction of a collection of categorised entries. In many respects it is
similar to a database with separate “tables”. Catalogues allow you to determine the field names
and field types that the entries in the collection (the 'catalogue') will hold. There is no limit for
the number of catalogues or catalogue entries which a site may have. The catalogues entries can
be organised either in a tree or in a flat-category structure. Often developers have to make a
choice of whether to configure and customise a catalogue, or whether to create a new module. It
comes down really to whether new interactions, functionality, and/or complex layouts are
required - if they are, a new module should probably be written.
 Comcode, A simple mark-up language, for use within ocPortal. Similar in use and format to
'BBCode'. This basic form of Comcode is formally known as 'Comcode-Text'. Since version 3 of
ocPortal there is also Comcode-XML.
 Content, Custom data within the system; the term is used in most contexts as data specifically
designed for viewing (as opposed to a member profile, for example, which is automatically
updated and is more of a gateway to other information).
 Custom Profile Field (CPF's), A custom field for members. Such fields are supported by some
forum/member software including our own OCF. In OCF CPF's can be assigned to individual
usergroups. CPF's are an important tool on most websites, as it is very common to need to

Copyright ocProducts Ltd. Page 6 of 74
assign extra data to members that is specific to an individual website. CPF data is always entered
manually, either by the member themselves, or by staff.
 Do-next manager, An full-screen icon-based menu interface that lays out the
available/suggested routes a user may take.
 Emoticons, Little images of faces to represent emotions.
 Entry points, See 'Page links'.
 Entry scripts, Scripts such as 'site/dload.php' or 'index.php' that are launched directly by URL.
These might be called 'front controllers' by some programmers.
 Feedback, A system for collecting and returning feedback from any ocPortal resource (e.g. rating
and posting comments on a download).
 Forum, A place where forum topics sit (and posts site within the topics). Forums may be
organised in a tree structure. We do not use the term 'board' that some software uses.
 Forum Driver, A piece of code that allows ocPortal to integrate with third-party forum software.
 Fractional Edit, The process of editing a piece of data (usually a title), by interactively modifying
it at its point of display.
 Hooks, Elements (special objects) that plug into a module/block to provide different
implementations of functionality for that module/block. For example, the stats block calls upon
other module’s hooks (such as ‘downloads’) in order to generate each kind of stat; and the
search blocks calls on other hooks to search each kind of content. By using hooks, modularity
can be maintained and functionality extended by addition of hooks, not modification of existing
code.
 Join, the process of becoming a member.
 Member, a user with an account.Note that in occasional contexts we may refer to members,
when actually the member is using the "guest account" - the key point here is that a member is
identifiable in at least some way, even if it's just identified to the guest account
 OCF, the ocPortal forum/member system (optional).
 Pages, These are just pages a user might wish to view; they can be:
 'comcode pages' – written in Comcode
 'html pages' – written in HTML
 modules pages – pages, that belong to a modules.
 Panels, These are pages (usually Comcode pages) that are named with the prefix 'panel_'. They
may be referenced in the templates using the 'LOAD_PANEL' symbol. The default ocPortal
templates automatically place panels named 'panel_left' or 'panel_right'.
 Page link, These are paths formatted like "zone:page:screentype:id:param=value(:…)" that
ocPortal uses for detailing links. They are like URLs, but local to an ocPortal website. Page links
are converted to URLs and this conversion sometimes adds extra URL parameters (for instance,
any parameters prefixed 'keep_' are propagated across screens). Entry points are a special case
of page link - entry points are the page links to screens that modules provide regardless of the

Copyright ocProducts Ltd. Page 7 of 74
current state of the modules content (e.g. the catalogue index will always be there - it is a stable
entry point into the system).
 Personal post, a post either within a personal topic or within a normal topic, that can only be
seen by the sender, the recipient, and administration staff
 Personal topic, a topic only seen by a select group of members (currently, the initiator and target
members)
 Points, Members of a site can earn ‘points’ by participating in site activities. The site
administrator can adjust the value of different activities. Members can then gift these points to
each other or redeem them at the point-store.
 Point-store, The place where members can spend hard-earned points on gifts such as e-mail
addresses or flagrant text banners
 Standard box, The type of box that wraps most ocPortal content, like side-blocks (e.g. 'Site
statistics', 'Users online', 'Shoutbox', etc.). There are different styles of standard box.
 Splurge, A system for compressing tree structures, so that large tree structures may be
transmitted efficiently to visitor's web browsers. These compressed structures can be
decompressed by JavaScript (Javascript functions in the 'JAVASCRIPT_SPLURGH' template
interpret the compressed format, expanding it into a full uncompressed page.)
 System, A large aspect of ocPortal that has an influence spanning multiple files (e.g. IOTD
system, Addon system, Award system). This is an informal term - addon is used more formerly
and refers to a defined set of files.
 Tempcode, An intermediary format between Comcode and XHTML, used for transmission and
storage of screens in ocPortal. Also, the written programming language used in templates. The
two things are different representations of the same thing.
 Theme, A set of templates, images and CSS style-sheets that can be used on a website to
completely change the layout, look and feel of the website. Different themes can be used
concurrently by many members, with no impact on the system
 Tracking, The process of tracking topics for reply via e-mail notification. In other words: you can
mark topics for notification and when a new reply is posted, you will be notified by e-mail of its
presence.
 User, Someone visiting an ocPortal site, that may or may not be a member.
 Whisper, the process of creating a personal post embedded within a public topic (this is used to
carry on entirely private conversations, and allow members to whisper secret messages to each
other inside a public topic)
 Zones, Different areas of a website, each with its own security levels, layout and feel. e.g. The
Admin Zone is a different zone to the Site Zone. They are separated into a subdirectories of the
main site (like subdirectory "site" for the 'Site' zone and the subdirectory “adminzone” for the
“Admin Zone” zone). For the special “root” zone, called the "Welcome Zone", the files are stored
in the main directory.

The following ocPortal concepts are used to describe content:

Copyright ocProducts Ltd. Page 8 of 74
 Category. Categories hold entries, or sometimes, other categories, or both; they themselves
tend to be designed for organisation or navigation rather than direct viewing. Galleries, forums
and forum groups, are all kinds of categories; even a topic is a category in some contexts, as it
holds posts.
 Entry. An entry is a piece of content.
 Resource. A resource is a very general term that may mean anything from entries to categories
to files.

At the time of writing, 'resource' is often used loosely, and 'category' and 'entry' are used differently
depending on the context. This is not a design flaw, but merely indicative of the diversity of context in
ocPortal and the difficulty of being consistent across different contexts (e.g. a topic is a category for
posts, but really we would usually consider the categories of the forum system to be the actual
forums).

There are many other terms in ocPortal that belong to certain addons (such as 'Download' or 'CEDI'),
but these won't be explained here.

Bootstrapping

ocPortal is written to the 'front controller' design pattern. An 'entry point' script (a front controller)
runs a standard bit of PHP code that works out details of the PHP environment, and then initiates
ocPortal's boot sequence. The boot sequence involves:
1. loading the 'global' source file (which provides the key 'require_code' functionality that powers
the source file overriding system),
2. which in turn loads the 'global2' sources file,
3. which runs some startup code of its own and then in turn loads many other sources files,
4. each of which initiate a key aspect of ocPortal (such as the database connection) or provide
key APIs.
After the boot sequence is finished, the script will include its controller sources file and launch a
function from it to handle the request. In the case of 'index.php' (which loads up a page from a zone),
this is the 'site' sources file, and the 'do_site' function.

Addons

ocPortal is split into addons. Some addons are core, some are optional. Optional addons may be
removed, and the removal results in file-deletion. ocPortal has inbuilt addon import and export
support, that allows a user to use and distribute packaged addons for ocPortal.

Addons contain module-pages, and it is these modules that define things like database structure.
Modules are independently versioned, but the versions of individual modules are hidden from user –
the module versions exist so that modules have a way of tracking when they need to upgrade
themselves (a mismatch between the version on-disk, and the installed version in the database will
prompt a module to upgrade itself).

Copyright ocProducts Ltd. Page 9 of 74
ocPortal knows an addon is installed if there is an 'addon_registry' hook present. File lists for addons
are defined in these hooks. Templates may work against the 'ADDON_INSTALLED' symbol, to guard
dependencies; likewise, code may use the 'addon_installed' function. In some cases, modularisation is
enforced using hooks rather than extra logic; this is done in the case where there is a clear concept
that can be created, which might be useful for other people to add into in the future (e.g. the
'side_stats' blocks allows different addons to plug in hooks so their stats can be displayed). Third-
party addons do not have so much luxury over adding extra code into the main ocPortal code, so must
rely on either existing hooks, or code overriding.

Addons may define dependencies.

In the case where we have an addons [addon A] hook for another addon [addon B] (e.g. the galleries
hook for the newsletter addon), we place the hook in addon A. This is because the hook would only
run if addon B is there, so placing it in addon A is safe - but if it was placed in addon B and addon A
wasn't installed, it would fail unless we wrote extra code. ocPortal will auto create directories that do
not exist – directories aren't a part of addons, only individual files.

Config options that have dependencies between two modules (e.g. the points_ADD_DOWNLOAD option –
which needs points and downloads) should have a default value evaluation of NULL if the foreign
dependency is not met. So for this example, the option is part of the downloads addon, and the
default is NULL if the points addon is not installed.

Modules

What is a module?

In ocPortal terminology 'modules' are dynamic 'pages' .

Example: index.php?page=<module_name>

Each module has a number of 'screens', identified by a URL parameter named 'type'.

Example: index.php?page=moduleName&type=<screen_name>

Generally features relating to a single system (e.g. 'download' system, 'galleries' system, 'iotds'
system, ...) in ocPortal are contained in a single module, with the exception that content-management
and administrative features are each put into separate modules (e.g. 'cms_downloads' and
'admin_downloads') . Content-management (CMS) and Adminisative (admin) modules are made
separately so they may be placed in separate zones. Typically a website might be configured to allow
certain members to manage content via the CMS zone, but only staff may access the admin modules
(via the Admin Zone).

Module files are either full-modules or mini-modules. All standard ocPortal modules are full-modules.

Copyright ocProducts Ltd. Page 10 of 74
Mini-modules are designed to simplify development or integration on individual websites, where full
ocPortal coding quality is not important.
Henceforth any use of the word 'module' in this tutorial will be talking about a 'full-module'.

Like any ocPortal page, an ocPortal module resides in a zone. Most modules reside in the 'site' zone.
Content-management modules are all prefixed 'cms_' and reside in the 'cms' zone. Administrative
modules are all prefixed 'admin_' and reside in the 'adminzone' zone. The other zones are only used in
very special cases.

Recall that ocPortal zones are sub-directories of your site, which operate with different settings. By
default, ocPortal contains a number of zones:

 Welcome (/)
 Admin Zone (/adminzone) – Where ocPortal is configured
 Collaboration (/collaboration) – Where privileged members may access collaboration tools
(available to enterprise version users only)
 Site (/site) – Where the majority of the ocPortal modules are, by default
 Docs (/docs) – Documentation
 CMS (/cms) – Where the content is managed
 Personal Zone (/personalzone) – Only for installations using OCF
 Forum (/forum) – Only for installations using OCF

How do you create a new full-module?

A module is a PHP file that defines one class. For a page in the 'site' zone named 'example', the file
would be site/pages/modules/example.php (or site/pages/modules_custom/example.php if this
module is non-official, or a modified version of an official module). Our module 'example' would
contain a class named 'Module_example'. Usually this class does not inherit, although some modules
do inherit from a class named 'standard_aed_module', which is used for acceleration/standardising
development of add/edit/delete user interfaces.

The 'info' function

The module class must include a function named 'info', which returns a map of fields that describe the
module:
 author – this is a string indicating who authored the module. The author does not need to be
defined anywhere else – this field is only intended for human-reading.
 organisation – like author, but this is the organisation that the author is working on behalf of.
For all official modules, it is 'ocProducts'.
 hacked_by – this should be left to NULL, unless the module is an edited version of someone
else's module, in which case it would be the name of the person altering the module (the new

Copyright ocProducts Ltd. Page 11 of 74
author)
 hack_version – this should be left to NULL, unless the module is an edited version of someone
else's module, in which case it should start as the integer '1' and then be incremented for each
new version of the module (see explanation of 'version')
 version – this is the version number for the module. The version can start at any integer, but
'1' is usual. This number should be incremented whenever a compatibility-breaking change is
implemented.
 update_require_upgrade – this should be either 0 or 1 (it defaults to 0 if it is left out). If it is 1
then the 'install' function will be called when ocPortal detects that the module's version has
increased since it was originally installed. It should only be set to 1 if the 'install' function is
written to be able to perform upgrades (this requires extra effort).
 locked – this prevents administrators from uninstalling this module. This should be set to false
unless the module is a core module that should never be uninstalled.

The 'run' function

The module class must include a function named 'run'. This function is called when the module is
loaded up as a page. The function should never use ‘echo’ or ‘print’ commands, but rather, it returns
page output in Tempcode format using the ‘do_template’ function. Tempcode is essentially a tree
structure of composed template output, and will be properly explained later in the tutorial.

The 'run' function typically is written to do 4 things:


1. Loads-up/records dependencies that all screens in the module use, using the
require_code/require_lang/require_css/require_javascript API calls.
2. Gets the URL parameter named 'type' into the variable $type, usually giving a default 'type' of
'misc' for when no 'type' parameter was specified. Remember that 'type' indicates which
'screen' the module will be outputting.
3. Delegates to another function depending on $type.
4. Outputs the result of the function that was delegates to, or “new ocp_tempcode()” if no
delegate was found for the given 'type'.

For example,

function run()
{
// Load-up/records dependencies that all screens in the module use
require_code('downloads');
require_code('feedback');
require_lang('downloads');
require_css('downloads');

// Get the URL parameter named 'type' into the variable $type
$type=get_param('type','misc');

// Decide what to do (delegate)


if ($type=='tree_view') return $this->tree_view_screen();

Copyright ocProducts Ltd. Page 12 of 74
if ($type=='entry') return $this->dloadinfo_screen();
if ($type=='misc') return $this->category_screen();

// If we get to this point no delegate was found


return new ocp_tempcode();
}

The 'get_entry_points' function

This function returns a mapping that identifies all the supported 'types' (screens) that may be
launched directly via URL (i.e. all the ones that don't rely on form submissions to have been sent to
them). The mapping maps from the 'type' to a language string codename that provides a human-
readable label to describe the screen.
It is used by the menu editor to help ocPortal website administrators find links to add to their menus.

For example,

function get_entry_points()
{
return array('misc'=>'ROOT','tree_view'=>'TREE');
}

returns a mapping for two entry-points: the 'misc' screen (which almost any module will have), and
the 'tree_view' screen. 'ROOT' and 'TREE' are language strings.

The 'get_page_links' function


This function finds some of the page links that a module can provide. This usually involves looking at
the categories added to the module and providing page links to each of those categories.

This is advanced functionality, so it's best to skip over this function until you are more advanced.

The 'extract_page_link_permissions' function

This function converts a page links scoped toward our module into permission-module/permission-ID
pairs. It is used by the permission tree editor for getting/setting permissions corresponding to site-
tree nodes.

This is advanced functionality, so it's best to skip over this function until you are more advanced.

Copyright ocProducts Ltd. Page 13 of 74
The 'install' and 'uninstall' functions

Modules are responsible for the installation, upgrading, and uninstallation of the database (and some
filesystem) parts of the system that they provide screens for.
For example, the 'downloads' module sets up:
1. database tables that relate to downloads
2. database indexes that relate to downloads
3. configuration options that relate to downloads
and removes:
1. database tables that relate to downloads (and associated indexes)
2. configuration options that relate to downloads
3. entries in shared database tables that relate to downloads (access permissions and trackbacks)
4. uploaded files that relate to downloads
5. stored values that relate to downloads

The 'install' function

All database access is ocPortal is performed through the database objects. For almost all situations,
the $GLOBALS['SITE_DB'] object will be the one to be use.

Database tables are created using the 'create_table' function of a database object. This function
defines the schema of the table. This function takes two parameters:
1. The name of the table
2. A map between the field names, and ocPortal field-type-identifiers

ocPortal defines the following field types:


 AUTO, an auto incrementing unique key. This allows you to have a key field for the table and
not have to worry about generating the keys yourself - when you insert your data
('query_insert' function) for a new row, just leave out the key, and the key will automatically
be created for you, and returned by 'query_insert'.
 AUTO_LINK, a foreign key link referencing an AUTO field. It does not specify what table this
field is in, but the code itself will know how to use it. It's usually obvious from the name chosen
for the field (e.g. a field named 'category_id' in the 'news' table obviously is referencing the
key of the 'news_categories' table).
 INTEGER, an integer
 SHORT_INTEGER, a very short (don't assume a wider range than 0-127) integer
 REAL, a float
 BINARY, 0 or 1 (i.e. a representation for a Boolean)
 MEMBER, a link to a member
 GROUP, a link to group
 TIME, a date and time (output of the PHP time() function)
 LONG_TRANS, a long piece of text stored in the language/comcode translation table
 SHORT_TRANS, a short piece of text stored in the language/comcode translation table (255
length maximum)

Copyright ocProducts Ltd. Page 14 of 74
 SHORT_TEXT, a short non-translatable piece of text (255 length maximum)
 LONG_TEXT, a long non-translatable piece of text
 ID_TEXT, a very short piece of text (about 50 length maximum)
 IP, an IP address in string form
 LANGUAGE_NAME, a language identifier (e.g. EN)
 EMAIL, an e-mail address
 URLPATH, a URL or file path
 MD5, a MD5 hash, stored in base64encoded form (the output of the md5() function is as such)
If a field type has "*" in front of the name, then it will form the primary key. All tables must have a
primary key on at least one field.
The field types (apart from the AUTO and various string types) may have a "?" put in front of them
(E.g. "?INTEGER"). This indicates that the value may be NULL.

ocPortal minimises it's use of database features, in order to increase portability. We intentionally do
not use the following database features:
 stored procedures
 transactions
 default field values
 functions
 foreign key constraints, and automatic tidy up
On a practical level, these things aren't really necessary so long as the PHP code takes up the
responsibility for providing the same kind of abilities instead. E.g. the model code for an addon would
define default field values in how it pre-populates form fields for a new entry.

When tables are created, a special meta table is updated. This meta table stores the database schema
in a database independent manner that allows the backup/restore system to work. Because of this,
you should never manually change the database structure outside the context of the ocPortal
database functions.
You might wonder why we don't just read the database schemas directly via abstraction code in the
database drivers, and avoid the need for the special meta table. It would certainly make it easier to
manage the database if this were the case. The reason is that our typing system (with the field types
explained above) encodes semantic information beyond what simple SQL data types can encode, and
this is of wider use within ocPortal. For example, the broken-URL scanner looks at all field values of
type 'URLPATH'.

Database indexes are created using the 'create_index' function of a database object. This function
takes three parameters:
1. The name of the table to create the index on
2. A unique name for the index
3. A list of fields to include in the index
(Indexes provide important speed improvements if you routinely look-up data from tables using fields
other than the primary key)

Configuration options are created using the 'add_config_option' API function. This function takes 6
parameters:
1. A language string codename that identifies a human-readable name for the configuration
option.
2. The codename for the configuration option.

Copyright ocProducts Ltd. Page 15 of 74
3. A data type for the configuration option (this defines how the option is edited- all options are
stored and returned as strings). Valid data types are: integer, tick, line, float, transline,
transtext.
4. Some PHP code that returns (in string format) what the default/initial value for the
configuration option should be. Often it is just a static value, but sometimes it is calculated in
an intelligent way.
5. A language string codename that identifies a human-readable name for the configuration set
which the option belongs to. This is usually 'FEATURE', which is the conventional set for all
configuration options relating to optional ocPortal systems. Configuration sets do not need to
be individually defined- all sets that are referenced will be automatically listed and indexed.
6. A language string codename that identifies a human-readable name for the configuration
subset which the option belongs to. This usually indicates very specifically the name of system
that the module is providing screens for.

One important thing to understand at this point is that ocPortal supports multi-language content at it's
core. This means that any resources that are defined need to define their human-readable strings via
the 'translate' table rather than directly. This is the purpose behind the 'transtext' and 'transline'
configuration option, as well as the 'SHORT_TRANS' and 'LONG_TRANS' field-type-identifiers.
If you store data in tables using 'SHORT/LONG_TRANS' you retrieve that data via calling the
'text_lookup_original' function upon the database field value.
There is one other reason the 'translate' table is used, and that is so that content may be stored
efficiently in 'Comcode'. Comcode is our dynamic markup language, a superset of XHTML which
defines additional dynamic features. The 'translate' table allows Comcode to be stored along with the
parsed version of it, which is in Tempcode format. To get back back the Tempcode, which can then be
mixed in with templates, use the 'text_lookup_comcode' function.

When developing a module it is usually easiest to set up the database tables by hand, and then code
in the installer afterwards. Otherwise it can be time-consuming going backwards and forwards
reinstalling modules every time a small change is required.

Upgrading via the 'install' function

The code in the 'install' function needs to analyse the parameters passed to see if an upgrade is being
performed (and from what version) or if a new install is being performed. The code is then structured
to act accordingly.
For a new module there is no need to consider how upgrades would happen.

The 'uninstall' function

The 'uninstall' function is usually very simple, with calls to the 'drop_if_exists' function of a database
object, and calls to the 'delete_config_option' API function. Uninstall function takes no parameters and
doesn't return a value.

Copyright ocProducts Ltd. Page 16 of 74
Screen conventions

Modules functionality is subdivided by 'screen'. 'Screens' are segregated by their unique expected
output - individual error messages don't run from separate screen functions, because error messages
aren't expected in advance, they're incidental.

As an example, if a module was designed to add a resource, it would need at least two screens:
1. The interface to allow the user to add the resource (a form screen). By convention this would
be identified by 'type'='ad'.
2. The actualiser to actually add the resource, and the screen to say that the resource had been
added. By convention this would be identified by 'type'='_ad'.
The function names of screens do not need to mirror the names of the 'type' parameter, but usually it
is clearer this way.
All modules must also define the default screen, which is almost always identified by 'type'='misc' or
by there being no 'type' parameter given. The role of the default screen depends on the module, but
usually it either acts to display a category browser or a menu. Usually all other screens in a module
will be reachable by navigating starting from the default screen of that module.

Templates and Tempcode

ocPortal is basically a programming language within a programming language. The outside


programming language is PHP, and the inside programming language is Tempcode. Tempcode is two
things:
1. ocPortal's templating language/syntax
2. The name for instances of compiled ocPortal templates. These instances are instances of the
'ocp_tempcode' object.

Tempcode (the language) is very rich and dynamic. It is designed so that templates can be infinitely
complex but so that they can be cached so that the data that goes into them does not need to be
recalculated each time. This cacheing usually only applies to blocks- modules generally do not employ
Tempcode cacheing. For example, the 'main_recent_downloads' block might have been templated to
look different for people in a certain usergroup, yet we also don't want to have to regenerate this
block each time it is viewed. Tempcode is powerful enough to be able to represent arbitrary
differences like this in a way that survives cacheing.

Due to this requirement, ocPortal must not deal in string output, it has to deal in Tempcode output. All
through the system Tempcode is composed together, rather than strings.

A typical scenario for a module screen is as follows:

function category_screen()
{
// Get the ID number of the category being viewed
$id=get_param_integer('id');

Copyright ocProducts Ltd. Page 17 of 74
// Find all subcategories to the category being viewed, in id order
$rows=$GLOBALS['SITE_DB']->query_select('categories', // Use this table
array('id','name'), // Get these fields
array('parent'=>$id), // WHERE this
'ORDER BY id'); // Order By

// If there are no subcategories, exit with an error saying this


// NO_SUBCATEGORIES is a lang string which must be defined
if (count($rows)==0)
user_clean_exit(do_lang_tempcode('NO_SUBCATEGORIES'));

// Create a new Tempcode object to hold our subcategory composition


$subcategories=new ocp_tempcode();

// Loop through all our subcategories


foreach ($rows as $row)
{
// Compose the next subcategory onto the previous
// Note how $row['id'] is passed through 'strval'...
// ocPortal is coded to be type-strict,
// and we have to decide for any integer whether to convert it
// to a string using
// 'strval' (code-ready) or 'number_format' (pretty)
$subcategories->attach(do_template(
'SUBCATEGORY', // Template name
array( // Parameters to the template
'NAME'=>$row['name'],
'ID'=>strval($row['id']))));
}

// Wrap up our subcategory composition in a template


// that represents the whole screen
return do_template('CATEGORY_PAGE',array('SUBCATEGORIES'=>$subcategories));
}

themes/default/templates/SUBCATEGORY.tpl:

<li>
<!-- Don't worry about this PAGE_LINK syntax yet, that will be explained later -->
<a href=”{$PAGE_LINK*,_SEARCH:categories:misc:{ID}}”>{NAME*}</a>
</li>

themes/default/templates/CATEGORY_PAGE.tpl:

<ul>
{SUBCATEGORIES}
</ul>

Copyright ocProducts Ltd. Page 18 of 74
Making a simple two-screen full-module

We've discussed how to make a module with very basic screens and templates. Following is a more
detailed example on how to:
1. create templates

2. link screens using page-links (build_url).

3. use Tempcode

4. read variables from GET/POST

Setting up our basic screens

To keep this code short I have intentionally missed out some functions and PHP-doc comments that
would usually be defined, and not bothered using language strings. These should be included/used for
real code, but aren't needed for the code to run.

<?php

class Module_example
{
function info()
{
// Bare essentials of this function only here because I'm keeping this
// example as short as possible. We would normally expect this function
// to be properly defined.
return array('version'=>1,'hack_version'=>1,'hacked_by'=>1);
}

// This is the function called when the module is loaded up to produce


// output.
function run()
{
// Decide which screen to show
$type=get_param('type','screen_a');
switch ($type)
{
case 'screen_a':
return $this->screen_a();
case 'screen_b':
return $this->screen_b();
}

return new ocp_tempcode(); // An invalid screen was requested from this module
}

function screen_a()
{
// We normally would use a lang string but this is a short and simple example.
$title=get_page_title('Example Title',false); // false='not a lang string'

Copyright ocProducts Ltd. Page 19 of 74
// Produce a URL to screen_b of this module ("this module"=same page, same zone)
$screen_b_url=build_url(array('page'=>'_SELF','type'=>'screen_b'),'_SELF');

// Return out screen's output, which comes from a template


return do_template('EXAMPLE_SCREEN_A',array(
'TITLE'=>$title,
'SCREEN_B_URL'=>$screen_b_url
));
}

function screen_b()
{
// We normally would use a lang string but this is a short and simple example.
$title=get_page_title('Example Title 2',false); // false='not a lang string'

// Read in our 'my_checkbox' POST value. Because it's a checkbox, it will only
// be present if it was actually ticked. So we need to supply a default of 0
// in case it was not. The checkbox value was defined as "1" in the HTML so
// we expect it as an integer.
$_ticked=post_param_integer('my_checkbox',0);

$ticked=$_ticked==1; // Convert our 0/1 into a proper boolean

// Return out screen's output, which comes from a template


return do_template('EXAMPLE_SCREEN_B',array(
'TITLE'=>$title,
'TICKED'=>$ticked
));
}
}

?>

Creating our templates

EXAMPLE_SCREEN_A.tpl...

<!--
Almost all screen templates start '{TITLE}' as all screens have a title. Titles in
ocPortal are a bit more sophisticated than just the <h1> tag
so we pass in the HTML for the title using a parameter.
-->
{TITLE}

<!--
HTML form linking to URL as defined by the 'SCREEN_B_URL' parameter.
This parameter gets escaped via '*' because it comes in plain-text
(contains '&' instead of '&amp').
-->
<form action="{SCREEN_B_URL*}" method="post">
<!--
Put in our checkbox using standard HTML.
Because ocPortal is WCAG (accessibility) compliant,
we need to put in the label.
-->
<p>

Copyright ocProducts Ltd. Page 20 of 74
<label for="my_checkbox">Example checkbox</label>
<input type="checkbox" value="1" name="my_checkbox" id="my_checkbox" />
</p>

<!--
Our submission button. We use the existing 'proceed_button' CSS class
as we try to standardise styles in ocPortal, for consistency.
-->
<p class="proceed_button">
<input type="submit" value="Submit form" />
</p>
</form>

EXAMPLE_SCREEN_B.tpl...

{TITLE}

<p>
<!--
This uses a simple shorthand syntax equivalent to the
following PHP code...

echo 'The checkbox was ';


if (ticked) echo 'ticked'; else echo 'not ticked';
-->
The checkbox was {$?,{TICKED},ticked,not ticked}.
</p>

<p>
<!--
This does the same as above, using the more normal
'IF directive' syntax. This syntax is more general
than the above meaning it gets used in more places.
-->
Repeat, the checkbox was
{+START,IF,{TICKED}}ticked{+END}
{+START,IF,{$NOT,{TICKED}}}not ticked{+END}.
</p>

<p>
<!--
If we output directly we'll get '_true' or '_false'.
-->
It was ticked: {TICKED}.
</p>

Hooks

This section is an explanation of hooks in ocPortal, using search as an example of how they are used.

The search module is a general purpose search tool. It provides a UI and it shows results. It needs to
be able to find different kinds of results.

Copyright ocProducts Ltd. Page 21 of 74
When something in ocPortal needs to something but in lots of different kinds of way, or a place needs
to be defined for different things of the same kind to happen, hooks are used.

Hooks are modular. They are plugins. They prevent us having to hard-code too many different things
in an ugly bloated way that would be impossible to extend or strip down.

Hooks that go together do the same kind of thing. In the case of search, each search hook searches a
different content type - but they all search.

All hooks that go together (e.g. all search hooks) have the same class interface and are stored in the
same directory (*1). ocPortal instantiates them and loads method(s) with parameter(s). The
method(s) and parameter(s) used is essentially a design contract between hook implementations and
the code written to call the hooks. They all fit a pattern. What pattern to use is defined by whatever
calls the hooks - in our case, the search module (*2); the actual hooks have to fit that pattern. Most
hooks have an 'info' method and a 'run' method, but that is just a convention.

(*1) Not quite. Hook files of the same kind might be in


sources/hooks/<something>/<somethingelse> and
also sources_custom/hooks/<something>/<somethingelse>.
This is because you place custom files in 'sources_custom' so that they are easily identified as such. It
is very common in the case of hooks, if you're adding a new hook to the software.

(*2) The OCF members module does member searching, but does not use the search hooks. This
search is separate and hard-coded. There is still a search hook for member searching for the search
module to use though.

You can generally tell what the hook is used by by it's file path. For example hooks in
sources/hooks/modules/search are for the search module.

Another example: member profiles

ocPortal needs to be able to provide a spot for addons to put links on somebody's profile page (e.g.
maybe an ecommerce addon needs to put a link to their transaction history onto their profiles). So
there is a kind of hook in ocPortal to achieve this. The contract the hook meets is to take a member
ID and return a list of extra links to put in their profile.

The code that uses these hooks is as follows:


$hooks=find_all_hooks('modules','members');
foreach (array_keys($hooks) as $hook)
{
require_code('hooks/modules/members/'.filter_naughty_harsh($hook));
$object=object_factory('Hook_members_'.filter_naughty_harsh($hook));
$hook_result=$object->run($member_id);
$modules=array_merge($modules,$hook_result);
}

In this case you can see the code assumes the hooks have a '->run' method that takes a member ID
as a parameter, and returns an array. Again this is the 'contract' these hooks have to meet.

Copyright ocProducts Ltd. Page 22 of 74
Another example: symbols

ocPortal supports 'symbols' in templates, that do special things. An example of a symbol is our
'MEMBER_PROFILE_LINK' symbol, which makes it easy to link to a member's profile page from a
template (often templates are passed member id's as parameters, so it allows them to work with their
parameters in an effective way). But ocPortal needs to be extendable. Addons must be able to add
their own symbols (e.g. maybe an ecommerce addon would need to define a 'BALANCE' symbol to
show a member's balance in any arbitrary template), so there are symbol hooks.

For performance ocPortal doesn't use any symbol hooks for any default functionality, but they are
available for new addons.

Installation code
In ocPortal we need to program modules so that they auto-create database structure and database
contents. Even if working direct for a single client, it is best that modules be constructed like this as it
eases development (it's easy to set up different development sites for example).
If creating, for example, some kind of property website, it would be best to pre-populate location
tables in the installation code of the new key module that uses them. The default catalogues module
does a similar thing, with it's creation of some default catalogues.

It is natural that you won't get your database code right first time. When developing you probably do
not want to have to manually alter the database and code separately each time a non-minor change is
made, although of course you can do. Instead you can reinstall the module from the Admin Zone. Go
to the Setup section of the Admin Zone, then Addons, then you'll find a link for module management
under there. If you find you can't uninstall/reinstall/install your module, you coded it as 'locked' (in
the info method) and you probably shouldn't have.

If you find stack traces are generated by module management then some of your code is probably
faulty. It might not be the same code as you expect (module management probes various different
files), but look closely and you will probably find the error.

When developing you'll probably want to start your development site afresh a few times to test your
code in different configurations, or to wipe test data. If you develop on a server that is not reachable
from the public web, you can leave the installer in place so long as you have a blank file named
install_ok in the base directory. This is better than having to move or rename the install.php file
because if you do that you risk messing up it's versioning, especially if you work out of a source code
control system.

Programming/progmattic-interface standards

(Items in red cannot be picked up automatically by our code checking tools)

Code distributed with ocPortal should meet coding standards. This is not usually a requirement for

Copyright ocProducts Ltd. Page 23 of 74
addons, as not meeting these standards won't necessarily cause direct problems - but all our
standards exist for a good reason, so it is usually best to follow them regardless.

ocProducts uses an internally developed code quality checker tool (a lint, static code analysis) to scan
code for violations of many coding standards, and find bugs. We also use other tools internally, and
our custom version of PHP designed to be very strict.
These tools may now be downloaded:
● http://ocportal.com/docs/codechecker.zip
(Code Quality Checker which requires a recent Java, and the command-line PHP interpreter)
● http://ocportal.com/docs/php-5.2.4-ocproducts.zip
(The source code tree for our special version of PHP - confirmed to work on Mac and Linux)
● http://ocportal.com/docs/php-windows-ocproducts.zip
(The Windows build of our special version of PHP - to transplant into a vanilla PHP 5.2.4 install)

The following coding standards should be followed in your PHP code:


 Use 'hard tabs' for indentation, and set to '3 space' indents in your editor. If you forget to use
hard tabs it's not the end of the world, but at least use 3 spaces and don't use an editor that
shows hard tabs as fake tabs (by 'fake tabs' we mean tabs rendered as spaces instead of fixed
indentation points).
 All modules should be coded to use 'type' as the internal specifier of what 'screen' to show. The
'run' function should decide what function to call based on this parameter. The default entry
point for a module must either not exist, or be named 'misc'. Do not hardcode defaults other
than 'misc' as it will interfere with the pagelink permission security and the short-URL support.
 Write as if PHP was 'strict typed'. This is hard for many PHP programmers to grasp so is
explained in a separate section.
 Do not use $_GET or $_POST - instead use one of the set of get/post_param_* functions. Don't
use $_SERVER or $_ENV either, as IIS and Apache disagree on what goes what - use our
'ocp_srv' function with the same environment parameter names you'd otherwise extract as
indices from the arrays. E.g. ocp_srv('HTTP_HOST') instead of $_SERVER['HTTP_HOST']
 Use the correct database abstraction API. Do not assume the code will only run on mySQL. This
means:
 wherever possible just use our APIs, don't write SQL by hand. Use 'query', 'query_insert',
'query_update' and 'query_delete'. The only real case where you need to write SQL by
hand is if you need to do 'OR' queries, or do wildcard searches.
 no use of 'mysql_escape_string' or 'addslashes' - use 'db_escape_string'
 no assumption about how escaping is done (you might not know this but the mySQL
escaping method is non-standard and the Microsoft one is actually 'correct'!)
 you cannot use SQL syntax that is not widely supported such as 'UNION', sub-queries or
database functions such as the mySQL substring functions
 do not assume use of mySQL field name markers (`) - they are mySQL-only
 use typing correctly. For example, do not do "WHERE validated='1'" as "validated" is an
integer field and thus must be referred to as such. Put your copy of mySQL into
strict-typing mode to pick up on these problems. Most database are type-strict,
mySQL is an exception so it's a big mistake to get things wrong here.
 do not build string equalities into WHERE clauses directly (e.g. "WHERE foo='bar'"), use
the 'db_string_equal_to' function (e.g. "WHERE '.db_string_equa_to('foo','bar')"). This is
required as Oracle does not (shockingly) support string equalities.

Copyright ocProducts Ltd. Page 24 of 74
 generally keep your queries as simple as possible
 do not assume the first record in a table has an auto-increment value of '1'. This is the
case for mySQL but does not necessarily hold true. Use db_get_first_id().
 Do not assume any database table prefix (so don't assume 'ocp4_' is at the start of the table
names - use 'db_get_table_prefix' to find out what to use)
 Do not assume you are developing for OCF (unless you are coding OCF directly), or other
forum driver. In other words, use the forum driver API properly and do not assume anything
about the forum and member software beyond that.
 Do not hard-code URLs- use build_url() correctly. This function will convert to whatever URL
scheme is in place, as well as propagate 'keep_' fields. Never use relative URLs.
 Don't link to screens using non-canonical URLs. For example, the forumview module doesn't
pass through a 'type' if it is 'misc'. Most other modules do. Any 'misc' screen doesn't use an ID
if the ID would be db_get_first_id(). Stick to the convention, otherwise it will harm SEO by
having two URLs for the same content.
 Use software engineering best practices, whilst also keeping things inline with how ocPortal is
coded.
 Don't copy&paste big blocks of code. Don't copy&paste the same snippet of code time-
and-time-again. If you spot a reusable chunk of code, consider making both usages work
via a new shared function. The exception is if you are working on some code that is only
ever going to be used on one site.
 Understand all the code you write. Don't just copy some existing pattern into a new
context.
 Divide into functions into appropriate units (sensible modules interacting over reasonable
interfaces).
 Use sensible function/class/variable/file/identifier/token names.
 Write comments. Don't comment obvious things ("this is a loop", "this runs if $foo equals
$bar"). Write comments that explain your algorithms, and comments that give a high-
level overview of what chunks of code do. Very roughly one of two comments per 7 lines,
but don't feel bound to match that.
 Use the correct indentation methods, as used by other ocPortal code.
 Use white-space to space out chunks of complementary code, to make code easy to
read.
 If you do copy&paste a chunk of code, e.g. if implementing a new importer and using a
pre-existing one as a base - then make sure you at least adjust your copied code to
make sense. Don't leave comments in there that refer to the old context (e.g. refer to
the wrong importer in the pasted code).
 underline_between_words_for_variable_names in PHP variables, PHP function names, PHP
arrays and database tables.
 UPPER_CASE should be used for globals and constants.
 Functions in shared code modules that are considered 'private' should be prefixed with an
underscore.
 Don't do silly things that affect performance like running queries inside a loop/recursion.
 Don't put '?>' at the end of any PHP file - leave it out. It's not needed, and if whitespace ends
up on the end of the file (e.g. when a user edits their file in a buggy text editor), it can cause
problems.
 When programming the Comcode system, never allow Comcode to become context-sensitive
(e.g. behaving differently when run from different URL paths).

Copyright ocProducts Ltd. Page 25 of 74
 HTTP GET requests should not change database state in any major way (it can cause problems
with web spiders, problems with web accelerator systems, and XSS vulnerabilities). POST
requests should not be used for screens that might be reasonably bookmarked. ocPortal has
specific features in it's API to allow you to choose whether forms use GET or POST requests-
choose carefully.
 Show the appropriate maximum-upload size on all screens that support file uploads, unless the
size is very unlikely to be reached by the user (e.g. just uploading an avatar).
 ocPortal enforces a strict separation between content, presentation and code. This is referred
to as the MVC (model, view, controller) design pattern. No HTML is allowed in the PHP code
except for very select exceptions, such as <option> (for efficiency) and hardcoded emergency
error pages.
 Code must run on all PHP configurations, with PHP version 4.1 and higher. For example, never
start a file '<?' because it assumes PHP short-tags are enabled, and they often are not.
 Cookies must not be required for functionality to work, unless said functionality is not needed
to operate the system.
 Don't copy and paste GUIDs in template calls. If you copy and paste a line with a GUID
('_GUID'=>'blahblahblah',), remove it completely from your new line. Our automated
scripts can put one back in later.
 Almost all directories should contain an empty 'index.html' (to stop directory browsing on web
servers that have it enabled). Most directories should also contain the small '.htaccess' file
(copy it from another deep directory) that we use to block direct file access.
 Never reference files directly out of a cache (e.g. a CSS file out of the 'templates_cached'
directory) - use the correct API functions.
 Use object-orientated principles properly where ocPortal does already. For example, forum
drivers all have a database object as a property, rather than referencing the global FORUM_DB
object. This is because they provide a core service and cannot make assumptions about the
environment (e.g. it must be possible to have multiple simultaneous forum drivers running).
 Every file intended to be distributed with ocPortal must be registered in one addon. To do this
list it in the obvious position within one of the sources/hooks/systems/addon_registry files.
 Don't create interdependencies between addons unless they are handled properly. They may be
handled via hooks, dependency setting, or via code that uses the 'addon_installed' function to
guard off the dependency.
 Use the '*_custom' filing system properly - never put custom/user-files/logs in a non-custom
directory, and never put pure code files in a custom directory. Generally don't put files in places
where they don't belong.
 Use the 'custom_base_url', 'custom_file_base', 'base_url' and 'custom_base_url' distinctions
properly- use a custom_* function if referencing custom/user-data, otherwise don't. This is
important so that shared installs work.
 Don't assume any current directory (either URL or filesystem): use full paths (using
'get_file_base' or 'get_custom_file_base' to start off the full path).
 Remember that a site can have multiple themes and languages - be careful that you do not
cache things with implicit assumptions that should not have been made (e.g. instead of
cacheing something in English, you should cache stuff per-language).
 Remember that OCF may be run over an M.S.N. (i.e. different URL, not just from the 'forum'
zone) and also from a separate database - don't make false assumptions about databases,
tables (e.g. using SITE_DB instead of FORUM_DB and hence wrongly assuming the forum and
site always share a database) and file-storage/URLs (e.g. using 'get_base_url' instead of

Copyright ocProducts Ltd. Page 26 of 74
'get_forum_base_url'). Use the correct APIs for referencing OCF data and files. As a general
rule, any table starting 'f_' is an OCF table and therefore should be accessed through
FORUM_DB. If you are running development mode you will get an error ('Using OCF queries on
the wrong driver') if you use the wrong database object.
 Use the 'FIND_SCRIPT' symbol or 'find_script' function to find zone-based scripts (because the
user is allowed to move it). Put non zone-based scripts in 'data' (or 'data_custom' if for an
unofficial addon).
 Use the 'fix_permissions' function after making a new file (makes the file deletable via FTP).
 Run the 'sync_file' function after making any file changes (allows a site to run on a server
farm).
 Don't add more than a few lines of code to an 'entry script' - put it in a sources file and require
it in.
 Don't assume where a module is located, except in the case of admin modules. Use the
'get_module_zone' function or '_SEARCH' token to break down such assumptions.
 No two classes may have the same name, regardless of where they are defined or used. It
should be possible for the whole set of ocPortal PHP files to be simultaneously loaded.
 Make sure that modules upgrade properly between major versions of ocPortal, and preferably
between all versions. Do not assume a module will never be upgraded- if your module has
undergone database structure revisions then you must make sure there is upgrade code to
apply these revisions.
 Generally don't do things out-of-step with ocPortal as a project. For example, I found that
XMLHttpRequest on Internet Explorer had a caching bug… so instead of just throwing in a
solution to where it affected me, I considered the problem architecturally, making sure all
AJAX-facing PHP scripts have a common approach to solving the problem.
 All code is a liability, because it costs time/money to maintain across architectural upgrades,
and as dependencies evolve. Therefore do not leave in old or extraneous code that serves no
concrete need- it's better it gets removed and archived off.
 Language strings must be inserted into the database (the 'insert_lang_comcode' function)
before wider data records and before things are logged or notifications sent). This ordering is
because errors parsing Comcode in those language strings (which are picked up as a side-
effect of insertion) should be caught before any permanent action is initiated.
 Use the text/binary file distinctions properly on the 'fopen' and 'file_get_contents' functions.
Note that:
 PHP code is considered 'binary' unless it is code-generated (e.g. info.php) in which case
it is considered 'text;
 Serialized files are considered text
 Files containing simple textual represented numbers or CSS are considered binary (just
to make them quicker for PHP to read).
 Always respect/handle character sets properly. Don't assume the character set is Unicode, or
Western European for input data or for output data. Use appropriate conversions when
interfacing with things that come-from/go-to a potentially alien character set.
 Never rely on PHP functionality that is not in our minimum requirements. It might seem like
you are re-inventing the wheel if you can't assume, for example, CURL is available, but we
simply cannot assume it will be – because often it will not! Most users do not have rich PHP
environments, and do not have the ability to have the environments changed.
 Make sure the 'lang_code_to_default_content' function is used where appropriate in installation
code. This function puts in language strings to the database such that they get stored

Copyright ocProducts Ltd. Page 27 of 74
automatically for all translations available on the installation.
 Typically you should order data records by date, not by ID. You should never assume that data
comes out of the database in the same order as it goes in.
 Don't put loose code (code outside functions) in any PHP files. The only loose code allowed is
our standard boot code in the entry point files.
 Everything should be configurable from within ocPortal- no FTP/file-manager should ever be
necessary to operate, configure, or extend an installed ocPortal website.
 Multi-line textual input (from the web browser, or from the file-system) should go through the
'unixify_line_format' function, to make sure line-terminations are converted to the right format
that the server operating-system needs.
 Use the ocPortal database API functions to their full power rather than building up SQL queries
manually. This reduces the chance of security vulnerabilities.
 All public shared code functions should have proper API documentation/constriction. Use the
'phpdoc'-style syntax correctly, and never adlib on it's syntax (it gets parsed). Do not copy and
paste comments from elsewhere without changing it to be fully correct for where you've added
it.
 Before writing any code design/document the tests you are going to run to make sure your
written code works. Think of how it might break under situations you might have otherwise
forgotten about (e.g. what if a guest user is running your module) and add in tests to test that
it doesn't break. Use your judgement.
 Define all block parameters in the block code itself (in the info function for the block) then
document all block parameters via inserting the language strings in language files for the
addon the block is a part of (see how it's done for existing blocks for examples)
 Templates should nest, not be split up into START/END pairs. The only exception is HEADER
and FOOTER, but there is a technical reason for this exception.
 Template names should use a prefixing-based naming convention to indicate the 'template set'
they are in, and screens should end in '_SCREEN'. Templates only used for blocks should be
named 'BLOCK_<block-name>[_SUFFIX]'. Template names should be in upper case.
 Any template that is for Comcode must contain the substring 'MAIL_' or '_MAIL' or
'_FCOMCODE' or be called 'NEWSLETTER_DEFAULT'. Any template named like this must be in
Comcode format, except for 'MAIL_SUBJECT'.
 Any template that is for Javascript must be prefixed 'JAVASCRIPT_'. Any template prefixed
'JAVASCRIPT_' must be in Javascript format, except for 'JAVASCRIPT_NEED' and
'JAVASCRIPT_NEED_INLINE'.
 The only templates that are neither XHTML or Javascript or CSS or Comcode are
'MAIL_SUBJECT' or a template starting with 'PLAIN_'. All templates starting 'PLAIN_' must be
so.
 Any template that is for CSS must be defined in the 'css'/'css_custom' directories, rather than
the 'templates'/'templates_custom' directories.
 Pass in any additional unused parameters to the template where they might be useful; raw
timestamps, IDs, etc.

The following coding standards should be used to help you determine your interfaces:
 Write content/data management modules as an ocPortal AED module (add/edit/delete) .
 For viewing content: if someone has permission to edit something, then for the template that
shows the content, pass in a parameter called EDIT_URL, containing the URL to edit the
content. If the user doesn’t have edit permission, then pass in EDIT_URL empty. Use the

Copyright ocProducts Ltd. Page 28 of 74
standard STAFF_ACTIONS include method for displaying any actions within the template (see
the 'DOWNLOAD_SCREEN' template for an example).
 CMS/Admin 'completion' screens should either be redirects to an appropriate location (e.g. the
'view' screen for what was just added/edited), do-next managers, or very rarely, message
screens. Follow the conventions other modules use. If you do do a redirect, use the proper API
(the 'redirect_screen' function) so that the 'success' messages show up on the destination
screen for the redirect.
 A CMS/Admin module's functions should generally be divided by a welcoming do-next manager
(i.e. a set of icons). Exceptions include: when adding only involves specifying a single field (in
which case you can put add and edit on the same screen), and also when adding is for power-
users only (in which case the add form would be hidden behind a button). Follow the
conventions other modules use.
 Use ocPortal conventions for input forms, employing our standard methods of handling
alternate input fields (i.e. the case where users may enter something in a field, or something in
a different field, but never in both fields).
 Don't duplicate interface elements in ocPortal, unless the duplicate is clearly marked as some
kind of shortcut/dupe/jump. For example, if there is a shortcut link between two modules (a
duplication in navigation), use the standard ocPortal convention for informing the user they are
jumping between modules (for an example, see how the link from the 'Manage themes' screen
to the 'Zones' screen works).
 Define breadcrumbs correctly. Try to do it at the start of screen code, so it'll be set if an error
happens during screen generation.
 All screens should ultimately be wrapped with some kind of template suffixed '_SCREEN' that
includes a '{TITLE}'. This might be a standard screen template that already exists, such as
'INDEX_SCREEN' or it might be custom one such as 'DOWNLOAD_SCREEN'.
 Use inform/error messaging APIs/conventions correctly. There are 5 top-level kinds of
messaging output:
 Recoverable error: use these when an error occurs that is non-fatal (e.g. failure for an
email to be sent always causes a recoverable error in ocPortal)
 Fatal/Warn/Info additional message at top/bottom: use the appropriate type of this for
any kind of response based messaging, unless you feel compelled to give a full screen
message
 Fatal/Warn/Info exit/message: use the appropriate type of message for when you feel
compelled to do a full screen message, or if the message is truly terminal (in the 'fatal'
case here)
 'WARN_TABLE': place an on-screen warning about the action or content that a screen is
providing
 (In screen: do not put out messaging output in-screen using non-standard methods
unless you have an exceptional reason to)
 Generally try and replicate the interface conventions of existing modules, unless there is a
strong argument that you are dealing with a special case. For example, don't design new ways
of previewing things, use the standard previewing mechanism. Another example, use the
'results_browser' function instead of implementing new previous/next navigation code. There
are almost always API functions or templates for you to use, so it's actually easier to use
conventions than re-implementing from scratch.
 Re-use templates where possible. The following are some key reusable templates:
 BASIC_HTML_WRAP / STYLED_HTML_WRAP, For screens outside of the core screens

Copyright ocProducts Ltd. Page 29 of 74
system that need to generate full XHTML
 FORM_SCREEN, A screen that is for data entry
 FORM, A form, but only one that performs part of a screen (often used when a screen
contains multiple forms)
 VIEW_SPACE, A part of a screen that is for viewing title/value data in a table
 INDEX_SCREEN/INDEX_SCREEN_ENTRY / INDEX_SCREEN_ENTRY_DUMMY, A screen
having a shortish paragraphed sequence of things to choose
 INDEX_SCREEN_FANCY_SCREEN / INDEX_SCREEN_FANCY_ENTRY /
INDEX_SCREEN_FANCY_ENTRY_DUMMY, A screen having a shortish HTML list of things
to choose
 INDEX_SCREEN_FANCIER_SCREEN / INDEX_SCREEN_FANCIER_ENTRY /
INDEX_SCREEN_FANCIER_ENTRY_DUMMY, A screen having a shortish tabled/descripted
list of things to choose
 WARNING_TABLE, A table on a screen that is a warning about the nature of that screen
 CATEGORY_LIST/CATEGORY_ENTRY, A list of things to choose from inside a category
(some kind of category / screen)
 TABLE_TABLE / TABLE_TABLE_HEADER_ROW / TABLE_TABLE_HEADER_ROW_CELL /
TABLE_TABLE_ROW/TABLE_TABLE_ROW_CELL, Templates for rendering a plain table of
something
 TABLE_TABLE_ACTION_DELETE_CATEGORY / TABLE_TABLE_ACTION_DELETE_ENTRY/…,
Buttons for performing actions on a tables rows
 CONFIRM_SCREEN/FORM_CONFIRM_SCREEN, Show a preview and confirm it's okay
 YESNO_SCREEN, Confirm the action is as wanted
 FORM_GROUPED, FORM_GROUP, A form where the fields are grouped
 NEXT_BROWSER_*, Very simple browsing through multiple screens
 SCREEN_BUTTON/SCREEN_ITEM_BUTTON, Show a standard shaped button on the
screen
 CATEGORY_TREE_SPLIT/CATEGORY_TREE_SPLIT_ESCAPED, Very simple template for
separating elements of tree-elements in a line in a drop-down list

If you are writing regular expressions to extract data from XHTML, you should anticipate:
 there may be extra whitespace (there may be extra spaces inside tag definitions)
 whitespace may come in other forms than the " " symbol (there may be tabs)
 HTML used instead of XHTML. (there may be "<br>" instead of "<br />")
 that tags may be written in upper-case
 attribute quotes can be given as single-quotes as we well double quotestag contents may span
multiple lines
 there may be whitespace around tag-contents
 tag contents may not be properly entity-encoded
 quoted data may contain quotes of the other-type (there may be <a title="He's saying
hello">)
But may assume:
 A tag opener/closer stays on one line
 Attribute values are quoted
 The text is in either well-formed SGML (HTML) or well-formed XML (XHTML)

Copyright ocProducts Ltd. Page 30 of 74
Tip: We have found the best editors for ocPortal have been 'ConTEXT' (Windows), 'TextMate' (Mac),
and 'Kate' (Linux). We also love NetBeans. Use whatever you like, but that's our personal advice.

Javadoc-like commenting

All functions should be documented in the Javadoc-esque syntax used for other functions. Javadoc is a
simple way of describing a function via a specially formatted comment placed just before the function
starts. The syntax is parsed by ocPortal's own special functions so it isn't quite phpdoc/javadoc syntax
but it is very similar.

With phpdoc and the function header-line itself, every function has the following:

 A description
 A list of all parameters
 The code-name of the parameter
 The type of the parameter (including whether false [~type] or NULL [?type] values are
accepted)
 A description of the parameter
 Whether the parameter is optional
 The return type (if any), and a description of it

The syntax is as follows:

/**
* Enter description here...
*
* @param type Description
* @range from to
* @set a b c
* @return type Description
*/

As well as providing documentation, the API comments define function type signatures. This is used
by our Code Quality Checker to help determine bugs. The types are based off the types used to define
ocPortal database schema, and can be any of the following:
 AUTO_LINK (a link to a auto-increment table row)
 SHORT_INTEGER (an integer, 0-127)
 REAL (a float)
 BINARY (0 or 1)
 MEMBER (a member id)

Copyright ocProducts Ltd. Page 31 of 74
 GROUP (a user group id)
 TIME (an integer timestamp)
 LONG_TEXT (a large block of db-targetted text)
 SHORT_TEXT (a block of text, up to 255 characters)
 ID_TEXT (text for a string identifier, up to 50 characters)
 IP (an IP address in string form)
 LANGUAGE_NAME (a two character language code)
 URLPATH (a URL)
 PATH (an absolute file path)
 MD5 (a string representation of an MD5 hash)
 EMAIL (an e-mail address)
 string
 integer
 array
 boolean
 float
 tempcode
 object
 resource
 mixed (this should rarely be used; but when is, means multiple types may come out of the
parameter/function)

Question marks ("?") may appear before types, meaning the value may be NULL. NULL must always
have a meaning add must be documented in a note in the parameter description… "(null:
<explanation>)". Similarly tilde marks ("~") may appear before types, meaning the value may be
false, and this must also be commented like "(false: <explanation>)". Sometimes it is also useful to
specify the meaning of the empty string like "(blank: <explanation>)".

Error handling

Error handling in PHP is very inconsistent.

There are four main ways to get an error message:


 use a '*_last_error()' function
 check return values
 catch the output of a command using output buffering
 suppress the error with '@', then listen to the $php_errormsg variable

ocPortal will react fatally on errors unless they are suppressed. This includes PHP_NOTICE's. To
suppress errors from a PHP command, put an '@' symbol before the function call. If the PHP
command causing the error is buried inside an ocPortal API function and you want errors to be shown
as 'deferred' rather than fatal, you can set the global SUPRESS_ERROR_DEATH variable to 1
temporarily.

Copyright ocProducts Ltd. Page 32 of 74
There's no need in ocPortal code to listen for every possible error that PHP might give back. You can
use some common sense, only detecting errors that have a cause, rather than trying to detect those
that can't theoretically happen. Errors that can't theoretically happen, but happen anyway, will trigger
ocPortal's automatic error mechanism and we can then deal with them as they come up.
For example:
 File permissions may always be wrong, so we should check return values / suppress and
handle write-errors.
 We can assume things like critical ocPortal directories are present, such as 'sources'
 We can't assume non-standard directories like 'lang/FR' are present, so we must either add
some conditional logic, or cleanly handle the error event in an appropriate way (in this case
perhaps explaining to the user that 'The French language pack is not installed').

Escaping

A source of great confusion in programming is all the different kinds of 'escaping' that occur. Because
the web is a great fusion of technologies, all kinds of things operate together with their own
encodings, and when lumped together, it can be a mess unless you truly understand it all. And
understanding escaping is the key to secure and robust coding.

Therefore I will summarise the kinds of escaping here…

Javascript/PHP escaping:

If you want to output a string in Javascript/PHP that contains quotes, you need to prefix the quote
with a '\' symbol. The same applies for '\' symbols themselves. The reason for this is because strings
themselves are bounded by these quotes and hence to actually use these quotes within these quotes
they need to be distinguished. This convention was taken from C and is used in many languages.
For example,

echo 'Hello \'reader\'';

would output, Hello 'reader'.

SQL string escaping:

SQL encloses string values for fields inside quotes, which presents the same issue PHP had. With
ocPortal coding the 'db_escape_string' function abstracts the process of SQL escaping. Any manually
assembled queries should be assembled very carefully using db_escape_string (to escape strings) and
intval (to enforce integers not to contain string data).
SQL escaping is key to PHP security, as it prevents data leaking out into the query itself, which could
potentially allow users to rewrite the query to a malicious form.

HTML escaping:

HTML uses the characters < > " ' and & with special meaning: they help define the HTML tags
themselves. Thus if these symbols need to be referred to directly, things called 'entities' are employed

Copyright ocProducts Ltd. Page 33 of 74
instead. Entities are a special HTML syntax that encode characters which the standard character set,
or the natural of HTML, would normally preclude from use.
Important entities are:
 &nbsp, to get hard-spaces (prevents wrapping or trimming)
 &gt;, replaces > ('greater than')
 &lt;, replaces < ('less than')
 &quot;, replaces "
 &#<ascii-code>;, an ASCII-code-defined character
 &copy;, an example of a symbol that wouldn't ordinarily be usable as it isn't in the character
set (this is a copyright symbol)

ocPortal has the means to perform escaping of template parameters, so if you pass escape data to an
ocPortal template, and program the template to escape it itself, you will see the entity codes instead
of the entity replacements.
Escaping all data embedded in HTML is a key part of security.

URL parameter escaping:

Parameters in URL's are separated by '&'. Keys and values and separated by '='. Also spaces are not
allowed, amongst many other characters. If you pass any complex data that will go into a URL, use
the 'urlencode' function ('window.encodeURIComponent' in Javascript) which will sort all this for you.
The ocPortal 'build_url' function handles it all in a much better way though, so you should actually
almost always use this.

Security

Security is treated very seriously by ocProducts. We have implemented a number of abstractions and
policies to keep ocPortal as secure as possible and it is crucial that developers understand security
issues and our policies.
Even admin-only sections should not have unintended exploitable behaviour. If a user is hosted in a
restricted ocPortal-only environment, their software should not allow circumvention of current
environment. Also, if a user exploits their way into somewhere they shouldn't be, it is best that that
somewhere shouldn't allow them to exploit themselves into somewhere even worse… for example, if
ocPortal's Admin Zone gets hacked, a user should not be able to get full PHP code execution access on
a hosting account.

There are a number of notorious ways PHP scripts such as ocPortal can be attacked, including:
 SQL injection
 Logic errors / assumptions
 XSS attack
 Header poisoning
 Code uploading
 File accessing
 Code inclusion
 Jumping-in

Copyright ocProducts Ltd. Page 34 of 74
 Cross-Site-Request-Forgery
 Eval-poisoning
 preg_replace code insertion
We will briefly discuss many of these below:

SQL injection is prevented by the database abstraction layer. It is impossible to inject SQL through
this layer as the SQL calls are made up by a formal conversion from PHP parameter syntax to SQL
syntax by code that fully considers escaping strings and handling the-given data types accordingly.
Any manually assembled queries should be assembled very carefully using the db_escape_string
function as appropriate (to escape strings).

One common logic error that deserves to be mentioned is the false assumption that you can check
for resource ownership by equating the logged in user against the member-id of the resource. The
reason for this is it doesn't cover the case of the guest user - the guest user ID (‘1’) is shared
between all guests. Therefore handle guests as a special case and never give them any access over
guest-submitted resources.

Any special and obvious hack-attack situation that you can figure out (whether real or not) should be
logged using the log_hack_attack_and_die function, so that site staff may detect when people are
trying to hack or probe the site. Whilst hackers with the source code may avoid the attack scenario
you've predicted and logged, it is likely they will make mistakes and get logged anyway, which can be
a huge benefit to site admins.

XSS is short-hand for "cross-site-scripting", and essentially poisons a user’s HTML output. There are
two common ways this can occur:
 through URL's
 through submitted data
The general premise is that through some kind of invalid input, JavaScript gets inserted into the HTML
output. This kind of hack is indirect - invalid output is generated via the vulnerability, then someone
with wide website access is tricked, or naturally directed, to view that output.
The JavaScript in the output usually does something particularly malicious, like redirecting someone's
password cookie to a hackers website - allowing the hacker to steal their credentials.
ocPortal protects against this kind of attack in three major ways
 easy escaping syntax in templates
 parameter constraints (taking in integers instead of stirngs, for example)
 HTML filtering / Comcode
 our own custom version of PHP that we develop on, that can automatically detect these
vulnerabilities in any code whenever that code is run (yes we can do his amazingly enough - it
is a very cool technology!)
Every value in a template that is not meant to contain HTML is marked as an escaped value
({VALUE*}). This means that 'html entities' are put in replacement of HTML control characters,
making it impossible for such data to contain HTML tags.

Header poisoning is a technique whereby malicious header information is inserted to totally bypass
HTML restrictions. Because the attack operates at the header level, it is important that headers with
new-line symbols do not ever get inserted . An example attack would be to inject a complex header
that causes (a) HTML output to begin early, and then (b) some special Javascript to be injected (e.g.
\r\n\r\n<script>alert('hack');</script>).

Copyright ocProducts Ltd. Page 35 of 74
Code uploading is when malicious programs are placed on the server using ocPortal’s attachment
system. Such attacks are prevented by file type filtering, inside the upload abstraction code - so
always use the get_url function to handle uploads.

Also, never allow file paths to be given for editing or viewing in a URL without inspecting them for
validity. For example, the download system doesn't allow you to specify the local URL of a file that is
not publicly downloadable.

Code inclusion is usually performed as part of a register_globals hack. Whenever you include code
from a path given in a variable, you run the risk that that variable is poisoned. Remember that the
code included by a poisoned variable might not even be on your own server!

Jumping-in is a term I just coined for this guide to refer to when a .php file is accessed by URL that
was only designed to be included. Never put code outside functions in ocPortal, unless that script is
intended to be called by URL. ocPortal has an init__<file> function naming convention to handle the
situation where included files need to run some startup code - the require_code function runs these
functions automatically, simulating the effect of loose code.

CSRF (Cross-Site-Request-Forgery). It is easy to forget we encode actions in forms... potentially


destructive actions which are called by the automated click of a button. The person clicking a button
on a form has no idea where that form leads unless there is some text to say, and that text may not
be accurate. A hacker could create a form on his/her own website that is encoded to submit malicious
requests to a victim webmaster's own website. The hacker would then just need to trick the
webmaster to fill that form in via some form of deception ("fill in this form on my website to win a free
iPod"). Because the webmaster has access to his/her own site, any form they fill in also does by
extension and thus the submission of the malicious form could cause real destruction before the
webmaster realised what had happened.
The hacker in this situation has just performed a destructive action by proxy. Sometimes these hacks
are elaborate in that XSS was used to make a user not even know they were clicking a link in the first
place (and thus the hacker staying potentially anonymous).
We can't completely prevent this type of attack... a user might be tricked into filling in a form on
someone's website that directs to a manipulative URL on their own. It can only be advised that users
be careful what they click... i.e. they don't go to dubious web sites or click links given in dubious
circumstances, or click links with apparent malicious or strange intent.
It might be suggested that the REFERER be checked to make sure the REFERER for an admin action is
always inside the users site, but the REFERER field is often changed by privacy aware users to be non-
representative, and thus cannot be relied upon. Nevertheless, ocPortal does do this if the referer field
is provided.
One action ocPortal does take, is to refuse to allow a user to click-through to an internal Admin Zone
URL directly from an external site if they are not already logged in... it requires an explicit login for
every new browser window. It might seem that this is an unnecessary hassle, but it truly is the only
standard/environment-compliant way to prevent automated Admin Zone-actions. Users may disable
this feature by turning it off when they edit a zone, and likewise turn it on for other zones.

Some nosy or malicious users may choose to try and poke around in upload, or page folders, by
manually specifying URL's. They may attempt directory browsing, or to guess the URL to a file.
ocPortal prevents directory browsing by placing index.html files in folders that shouldn't be

Copyright ocProducts Ltd. Page 36 of 74
browseable, thus nullifying the browse-ability with a blank output (one cannot assume the web server
is configured to have directory browsing to disabled). In addition, .htaccess files are placed in folders
where files should not be accessed by URL at all, but this only works on Apache servers. The upload
system supports obfuscation of upload names in order to make URL guessing unrealistic.

It goes without saying, but I'll say it anyway, that any code that finds its way into an eval call should
only come directly from hard-coded ocPortal code itself. Never do something stupid like:

eval('add_image('.get_param('image_id').');');

This code would allow any user to inject malicious PHP code into the ‘image_id’ variable. Never use
variables inside eval() unless you’re positive that no user could have contaminated it’s value.

On a similar note, when using user-input with the PHP preg_replace function, always make sure that it
is properly escaped, as the preg_replace function can actually do arbitrary code execution if input is
not properly escaped.

Type strictness
Type strictness comes naturally in most traditional programming languages, but languages like PHP
and Javascript are 'weak typed' and automatically perform conversions on the fly. For simple systems
this is good as it makes things quicker and easier, but for complex systems like ocPortal it poses a
problem.

As a strict rule we always use strict typing. The custom version of PHP ocProducts uses enforces this.

There are a number of advantages to strict typing:


 Special tools can be written to scan code for errors, and these work much better if they are
allowed to infer relationships via correct type information. Our own Code Quality Checker does
this. For example, if we get parameter order wrong in a function call (e.g. to 'strpos') the
checker can identify that as the type signature of the function call does not match the type
signature of the 'strpos' function.

 Similar to the above, our own type-strict version of PHP will catch more mistakes with strict-
typing. Sometimes the mistakes will be a result of strict-typing not being followed, but usually
it will be something like a parameter being missed-out, or the wrong variable being used.

 It allows us better security. For example, if a string is being put into an expression that forms
the first parameter of the 'preg_replace' function where we think we are passing a number, it
could be a huge security hole (as it could be used to do arbitrary code execution). The
programmer might have thought they were using an integer but because he/she used
'get_param' instead of 'get_param_integer' to read in a value he/she in fact did not. Because
ocPortal enforces strict-typing these kind of mistakes get picked up on much more often.

 It discourages sloppy thinking.

 We have very close attention to detail. We don't like to throw numbers into templates and just
have them output directly - we want them to have commas for thousands, for example

Copyright ocProducts Ltd. Page 37 of 74
(depending on locale). Forcing explicit type conversions makes us consider these kinds of
issues where otherwise they would easily be forgotten.

 Many databases are type-strict, but mySQL is not. Thus for developers programming on
mySQL it is easy to just completely forget about these issues because code will always work on
their development environment.

 It makes it easier to port ocPortal, should we ever need to (who knows what the future has in
store, PHP might not survive for ever, or it might be surpassed by another language).

So, how does strict-typing work? In PHP each variable does have a type, it's just PHP does not
normally stop you from, for example, using a string in place of an integer, or vice-versa (it converts a
string holding an integer to a proper integer and vice-versa). With strict-typing we force ourselves to
change variables to the right type using PHP's functions like 'strval'.

Consider the following:

$values=array(NULL,0,'',false);
foreach ($values as $x)
{
if (!$x)
{

echo "Value is false\n";


}
}

This is a perfect example of how nasty PHP can be in the wrong hands, as "Value is false" is written
out four times (each time).

For ocPortal we would expect the following:

 Do not treat values as a boolean unless they are a boolean. Usually any single variable is going
to have a certain type (in this example this is not the case, but usually it is and should be). To
check if something is null use is_null($something). To check something is blank use
$something==''. To check if something is false use !$something. To check if something is zero
use $something==0.

 Don't do $something==NULL. As mentioned above, use is_null($something).

 Don't use 'isset' unless you really have to. Almost always use is_null($something) or
array_key_exists($element,$array) so our tools can discern properly what your code is
doing, and so the code stays portable.

 Similar to the above, don't use 'empty'.

 If you have a variable that really is mixed type (like $x in this example) and you need to do a
boolean check just for the case it is a boolean variable, do either is_bool($x) && (!$x) or
$x===false.

Copyright ocProducts Ltd. Page 38 of 74
Example strict-typing mistakes

If you have a list (an array with numeric keys, such as one returned from the 'query' function), don't
access the keys as if they are strings. E.g. $rows['1'] should be $rows[1].

Avoid this kind of thing, where one variable might be of more than one type...
if ($foo)
{
$number=1;
} else
{
$number='';
}

If a numeric variable has no value, set it to NULL, like...


if ($foo)
{
$number=1;
} else
{
$number=NULL;
}

and then use the 'is_null'/'is_integer' functions as required. We do not consider 'null' a type like the
others, due to how databases interpret it. We consider it more like a special marker meaning 'no
value'.

If a variable really must have multiple types, do...


$variable=mixed();
if ($foo)
{
$variable=1;
} else
{
$variable='';
}

as this will flag the situation to our tools.

Another example:
$secondary_groups=explode(',',$row['additionalGroups']);
foreach ($secondary_groups as $group)
{
$GLOBAL['FORUM_DB']->query_insert('f_groups',array(
'm_group_id'=>$group,'m_member'=>get_member()));

Copyright ocProducts Ltd. Page 39 of 74
}

This is a good example of why type strictness is important to us. This will create an array of strings,
and the DB will be modified using strings - however the database uses integers. So this code won't
work on mySQL strict mode, or other database software. This is better...
$secondary_groups=array_map('intval',explode(',',$row['additionalGroups']));

Do-next-manager interfaces

A 'do-next-manager' is a special navigation interface in ocPortal that is used in two roles:


 What the user wants to do next. These are often shown after an administrative/content-
management action is performed.
 Where to go. These are used for browsing through the Admin and CMS zone interfaces.
The do-next-manager provides a full-screen choice of options, laying out those options with icons.

The do-next-browser is called up by passing in an array of lines. Here is an example of a simplified


call to one (the do_lang lookups have been removed - this is hard-coded as English for simplicity):

return do_next_manager(
get_page_title('Page Title'),
make_string_tempcode('Text to show on the page'),
array(
...
array(
'blog_icon',
array('cms_news',array('type'=>'ad'),'cms'),
'Add a blog entry',
'Blah blah this text is hovered'
),
...
)
);

Going from top to bottom:


 the icon theme image name
 the page name (cms_news)
 the list of URL parameters (in this case, one: 'type' as 'ad')
 the zone (cms)
 the link caption
 the hover text

To place a new item into the Admin Zone or CMS "Where to go" menus, create a new hook file in
sources/hooks/systems/do_next_menus. The syntax is essentially as above, and there are plenty of

Copyright ocProducts Ltd. Page 40 of 74
existing files to use as examples.

The translation table

All text strings in ocPortal databases (including forum posts, page comments, and generally any user-
submitable text) are stored in the 'translate' table. There are two reasons for this:
 It can be used to store parsed Comcode as Tempcode format, effectively a cache
 ocPortal's API supports multi-language websites in the truest sense. Meaning that potentially,
translated versions of virtually any content can be supplied for visitors. Please be aware that
this functionality is not supported in the interface currently, but does exist in the API.

All translatable attributes in ocPortal are given priorities. The scheme for these priorities at the time of
writing are as follows…
1. absolutely crucial and permanent/featured
2. pretty noticeable: titles, descriptions of very-important
3. full body descriptions
4. for individual members, or very low level
These priorities are intended to be reflected in the content translation interface, with "yet
untranslated" text strings being ordered by priority.

GUIDs

There is a design issue that comes to us when we design template structure… do we re-use templates
so that editing is easier, load is decreased, and consistency is raised; or do we allow maximum
customisation of different aspects of the system by not re-using templates?

We stick to a middle ground between these extremes, and re-use templates when the usage pattern is
almost identical. For example, almost all of ocPortal uses the same form field templates, but CEDI
posts use different templates to forum posts. However, there are still places where we re-use
templates in situations that sites may wish to develop in separate directions.

Each 'do_template' call in ocPortal, which loads up a template, passes in a parameter, '_GUID'. The
GUIDs may then be used by themers with tempcode directives to control what the template outputs.

Don't bother manually putting in GUIDs. We have a script that auto-generates them and inserts them
across the code base. We run this script before major releases.

The persistent cache

Only read this section if you feel you need an explanation for this.

ocPortal supports a "persistent cache" that can store any arbitrary PHP data (e.g. query results)
between page views. This is faster than the database: although one would think databases are made

Copyright ocProducts Ltd. Page 41 of 74
very fast, they do not know how to directly access the actual data structures used, so will never be as
fast as a persistent cache.
The persistent cache is implemented in 3 ways, and one of them is used, or none if it is disabled:
 via eAccelerator/mmcache (requires PHP extension)
 via memcached (requires PHP extension)
 via file writing

The usage of the cache is very simple. You can:


 read a named (nameable by any data structure, like an array) entry
 write one
 delete one
 delete them all

There is a 'server' level cache, and a 'site' level cache. The programmer only needs to distinguish
these on writes. The server level allows storage for access by all ocPortal installations of the same
version (more efficient due to memory sharing). It is very rare that a server level cache may actually
be done, due to the high configurability of individual sites.

Some tips/guidelines for usage:


 Delete the entry on writes that affect it, and attempt to load the entry on reads, and put it
when it was not in the cache on a read and we had to do a full calculation; do not bother
caching when new data is written, as it's code that will give a tiny performance increase due to
the relatively rare situation
 remember to cache against theme where appropriate
 use the server server cache when appropriate (when things are known to be relevant to all
installs and when there is no site-specific aspects to the data) and never when not
 where possible with templates put user-specific stuff as symbols rather than parameters, so
that they may be cached so as not to depend on users (symbols 'survive' the cache and get
reinterpreted after cache extraction)
 where appropriate store 'false' rather than 'NULL', to distinguish between 'non presence in
cache' and 'not setness': often a little extra logic is needed, as ocPortal uses NULL for most
"not setness" API results
 emptying the cache on admin actions is fine; it is preferable to be able to delete specific
objects, but that's not always possible (e.g. with Comcode pages, they are cached against
theme, and having a load of loop code over themes would be excessive)
 make use of cache layers: e.g. I didn't optimise some functions because higher level block
caching would often make my optimisation almost pointless
 cache what is regularly used: we want to support lots of users, admin actions are almost
irrelevant
 don't cache non-featured stuff that could stay in the cache forever but relatively rarely get
used (even if a gallery is viewed on every page view, this is not justification to cache all 100 of
them: but we could cache the current iotd block): memory is finite, do not waste it
 remember that it is not just one site per server, usually - we want to increase the number of
users the server can take on average, not individual sites: memory is finite
 you must assume the cache might never remember a thing even if it is on - things get chucked
out to make space, don't assume anything
 don't assume the persistent cache exists (due to it's non reliability for long term storage): if

Copyright ocProducts Ltd. Page 42 of 74
something is really important to be cached, ocPortal should have a dependable cache to do it
 remember that it is okay if things keep getting lost from the cache - if something is re-made
every 5 minutes (the default ttl), then that means it persists for potentially
hundreds/thousands of requests: a regeneration rate of 0.3% is hardly bad is it? Of course, if
we are on a server with many sites, so things get re-generated after hardly being used, this is
non-optimal: but we would not have had the memory to store it all anyway!

Feature architecting standards, and implementation notes

ocPortal contains many "cross-cutting" features that are often present in different areas of the
system. These need to be individually implemented/referenced all throughout the code. These
features are:
 AED/CMS functionality
(this will be provided to you when you write your CMS module to inherit from the standard
AED module code. Most ocPortal CMS modules are AED module)
 SEO meta-data keywords/description
(instructions for implementation provided in this book)
 Feedback: rating, commenting, trackbacks
(instructions for implementation provided in this book)
 Permissions
(instructions for implementation provided in this book)
 Virtual roots
(see how breadcrumb code for the news addon handles virtual roots - it provides the capability
to define them, and also applies them in the way it decides the root of a breadcrumb trail)
 Edit lists (filtered according to what the logged-in user can actually see)
(just define a nice_get_whatever function like other similar functions)
 Do-next interface
(instructions for implementation provided in this book)
 Blocking guests trying to edit something owned by Guest (a bug if it isn't, but it would be good
to try and abstract this)
 Ditto blocking editing interface for something you don't have category access to
 Attachments, Comcode, Multi-language support
(instructions for implementation provided in this book)
 Splurgh views
(see the Module_downloads::tree_view_screen function for an example)
 Validation, validation-required e-mails, Unvalidated warnings, and permissions to determine
who may view unvalidated content, and a hook to identify all unvalidated resources
(see how it's done in existing modules, it's quite straight-forward- just identify the lines of
code that involve validation and basically copy&paste them to the right spots in your own
code)
 Control actions links
(see the '{+START,INCLUDE,STAFF_ACTIONS}' code in 'DOWNLOAD_SCREEN.tpl')
 Logging
(use the log_it function in your model code, for adding, editing, and deleting)
 Mass action, and policy for deletion of categories (implementation varies, but consider what

Copyright ocProducts Ltd. Page 43 of 74
happens to a categories entry when you delete that category)
 View counts and edit dates (implementation varies, but you should track them in your tables
and pass them into templates)
 Newsletter "added since" hooks
(just implement a hook)
 Importing
(define an import-type in the admin_import module code, and implement code for this import-
type in all the import hooks hook)
 Search
(just implement a hook)
 Preview support
(just implement a hook)
 AJAX fractional-edit support where appropriate
(see how CMS AED module's use INTEGER_MAGIC_NULL and STRING_MAGIC_NULL, and how
their view templates use the FRACTIONAL_EDITABLE directive)
 Rep-images (implementation varies, but basically these are icons for a module's categories)
 RSS
(just implement a hook, and set $GLOBALS['FEED_URL'] in the code when viewing what the
RSS feed subscribes to)
 awards
(just implement a hook)
 Possible others I've forgotten to list

Design/copy standards

(Items in red cannot be picked up automatically by our code checking tools)

The following standards should be followed:


 ocPortal 'brands' or unique terminology ('Zone', 'Comcode', …) should not be seen on the main
website
 Use correct terminology like 'Codename', 'Option', 'Title', 'Description'
 Use excellent grammar. For example, do not Capitalise Every Word In A Sentence unless there
is a good grammatical reason to. Use apostrophes correctly (believe me, I know it's hard to get
right sometimes [English is a mess of a language], but it's important).
 You may assume language strings can contain XHTML. There are only a few exceptions where
this is not true. Theoretically the burden usually lies on code removing XHTML formatting that
is there and can't be used, rather than the other-way-around. Practically, there are some cases
where XHTML entities are allowed but not XHTML tags (e.g. if a language string is used for a
'title' attribute), and you will just need to use your judgement and test if you change an
existing language string to include XHTML tags. The flip-side to XHTML being allowed is that
you must encode things like the ampersand symbol as entities.
 When needed, add comments to the lang files to define context. For an example of how to do
this, see global.ini.
 Use HTML entities where possible, in particular 'ndash', 'mdash', 'hellip', 'ldquo', 'rdquo', 'lsquo'
and 'lrquo'. These make the output that much nicer, and grammatically more correct (doing " -

Copyright ocProducts Ltd. Page 44 of 74
" instead of " &ndash; " is technically wrong, as a hyphen is not a dash).
 Try to reuse strings as much as possible. Don't add new strings unless it is an improvement, as
they need to be translated into potentially dozens of languages. To facilitate this, phrases
should be made as general as reasonable… i.e. avoid using context where possible (e.g. use
"Added entry {1}" rather than "Added download {1}"). Don't do this if it creates a usability
problem though.
 Apply consistency to your terminology. Sometimes multi-word proper nouns are given hyphens
(e.g. "multi-word"), sometimes concatenation is used (e.g. "multiword"), sometimes
abbreviation is used by just using the first letters placed together as capitals (e.g. "MW"), and
sometimes each word is given in capitals (e.g. "Multi Word"). All this inconsistency is ingrained
in English, but for any single term, reference it in a uniform way. We do not usually use the
antiquated form of abbreviations using dots (e.g. "M.W.").
 Don't use the name 'ocPortal', use the phrase 'the software'
 If phrases are not general to the rest of ocPortal, then put them in a language file that can be
individually included by a module
 Use smart quotes (quotes that curl toward the text), and <tt> (for something typed, or code),
to make things look nice
 End form field descriptions with a full stop
 Full stops and brackets mix so that the full stop follows the closing of the bracket. For example:
example (example).
 The word 'tick' in British English is 'check' in American English (referring to the square
‘tickbox’/’checkbox’ on HTML forms). Due to this disparity, write both like "tick (check)".

The follow standards should be followed for naming of “submit” buttons at the end of forms:
 For an add button, use the title text ('Add xxx')
 For an edit button, use the language string 'SAVE' ('Save')
 … only use the language string 'EDIT' ('Edit') when you are distinguishing it against the
language string 'DELETE' ('Delete')
 For a delete button (usually these are actually ticks for an edit, but sometimes we do have
them), use the language string 'DELETE' ('Delete')
 … or if it's more of a removal than a deletion, the language string 'REMOVE' ('Remove)
 For an intermediary button, use the language string 'PROCEED' ('Proceed')
 For sorting, use the language string 'SORT' ('Sort')
 For changing the number of items shown, use the language string 'CHANGE' ('Change'), or use
the language string 'PER_PAGE' ('Per page') if you are doing it as a sentence
 For jumping between paginated screens, use the language string 'JUMP' ('Jump')

Here are some guidelines for using ocPortal 'standard boxes':


 use the 'curved' boxes just for the squareish front page boxes;
 use the 'panel' boxes just on panels;
 use the 'classic' standard box for primary/block content (although usually primary content
would not be in any box), or for search results;
 use the 'med' standard box for a reasonably emphasised box on a screen, that isn't key and
isn't trivial and isn't a block;
 use the 'light' standard box for non-key, non-block, content.
 The above rules should be followed where reasonable, but if something looks wrong, then
usually there is a scientific reasoning behind that, based on visual hierarchy; if you think about

Copyright ocProducts Ltd. Page 45 of 74
it, you might actually realise the change you want does end up following the above rules, or
that something else is wrong with your design.
 Try to use the standard boxes where possible, but when you can't, you can use their CSS
classes. In particular though, try and use them consistently.

Web development standards

(Items in red cannot be picked up automatically by our code checking tools)

The following standards should be followed in your markup/CSS/Tempcode:


 If colours are added to the CSS then a proper themegen colour equation should also be added
(see how it works for other colours for examples). The best thing to do is usually to re-use an
existing colour definition - not only does that make the colour palette consistent, it stops you
needing to calculate a new equation.
We made our calculations using Photoshop, by adding white/black layers above the ‘seed’ color
and adjusting its opacity.
 Javascript should be written in DOM style (so no 'document.all', or 'window.images'), should
work with true XHTML turned on (so no 'document.write', or 'innerHTML'), not use any
deprecated methods (so no 'window.escape'), and work in all supported browsers (so no
'window.addFavorite').
 If you are using Firefox, turn on Javascript strict warnings in the “about:config” settings, and
make sure you don't produce any warnings (check the error console to see if you are producing
them).
 Don't use deprecated XHTML attributes or tags (e.g. the 'align' attribute or the 'b' tag). Use a
combination of CSS and semantic tags instead (e.g. 'text-align' and the 'strong' tag).
 Use the Tempcode escaping syntax correctly, to avoid security holes. For example, if you are
outputting a textual parameter, make sure to reference it as '{PARAM*}' rather than just
'{PARAM}'. Give big thought to this if you are outputting the variable into Javascript code-
usually you'll want to use the '%' escaper which locks down data very tightly.
 Write to XHTML-strict (even though ocPortal uses an XHTML-transitional doctype). We follow all
the rules of strict (including not using deprecated attributes) except for two things - we use the
'target' attribute and we use the 'iframe' tag.
 Meet WCAG (Web Content Accessibility Guidelines) 1 level 3, and WCAG 2. Write semantic
markup. Javascript must not be required for functionality, unless there are
alternative/degraded interfaces (however, you can assume that if Javascript is supported then
AJAX is also supported).
Always use the <p> element to mark true paragraphs if they are not alone in a table cell, and if
they are always going to be just one single paragraph. Images should not be wrapped in
paragraphs. Short lines may be wrapped in paragraphs as/if you wish.
 Never use native language in templates or modules/blocks. Colons and so forth may be used in
templates in a visual way (even though strictly it is an English'ism). However, you can't make
assumptions about word order by saying things like {!GO} {!HERE}.
 If a section of HTML needs a standard-box around it (to make it stand out as a lump on its
own), then use the easy 'BOX' directive rather than manually putting all the XHTML/CSS for a

Copyright ocProducts Ltd. Page 46 of 74
box into the template.
 If you want to have a tooltip for an image (perhaps the image meaning is not obvious), use the
title tag. The alt tag is intended for alternate text when the image itself is not viewable (it is
only Internet Explorer that allows 'alt' as a tooltip). This means, if you want a tooltip, you must
have both alt and title attributes ('alt' is always required for accessibility).
 Break CSS and Javascript up modularly, so to avoid shoving things for specific modules into
global files. At the same time, promote re-use by putting sensible stuff into global files.
 Don't use the XHTML style attribute except in certain conditions that could be considered
structural (e.g. float can be considered structural because tag order matters for floats)
 Using the <script> tag inline is absolutely fine, so long as it's not a primary screen that could
instead use the require_js function so as to conserve bandwidth. Inline <script> should use the
proper CDATA technique to protect it's contents rather than old-style HTML comments. We
don't insist on unobtrusive Javascript in ocPortal (it's not something that works clean with a
modular templating system). Instead we try and create an effective balance between clean
code (which might mean inline or might not - depending on context) and bandwidth
optimisation.
 Only use layout tables where CSS cannot replace them (there are a few major cases but they
are exceptions in the grander scheme of things); when using them, mark with a blank
summary. However, do not use crazy hacks just to avoid layout tables. Cases for tables:
 When grid layouts are needed but we can't assume widths.
[REASON: uses auto-sizing features of tables. IE doesn't support the CSS table model
yet.]
 To force a minimum height/width.
[REASON: min-height/min-width not supported by IE6]
 To shrink-wrap whilst also allowing word-wrapping.
[REASON: inline-block cannot do this, and IE doesn't support the CSS table model yet.]
 Test on IE6 (anything older is ancient), latest Firefox, latest Opera, latest Safari (the
assumption is that all but IE users regularly upgrade browsers).
 You may assume that if there is Javascript support, there is AJAX support.
 Name CSS classes according to what they are for and not what they do (e.g.
big_and_red_page_icon is a terrible name, but warning_icon is a good one).
 Don't use any extended-ASCII characters in templates, as you cannot assume what character
set will be in use.
 Only forcibly open up links in new windows in these conditions:
 The action is strictly auxiliary to an ongoing action (e.g. opening up documentation to
read whilst performing the action, or opening a small pop-up window)
 An ongoing action is being performed that will spawn many followup actions that are not
going to flow in sequence
 It is a link to an external website on the main website placed in a prominent position
(e.g. link to RSS news story)
 Do not forcibly open up links in these conditions, or any other not mentioned above:
 When it's a link to an external site not in a prominent position on the main website
 The action is arguably auxiliary but often many not be (e.g. Editing a zone by
alt+shift+control clicking a zone name)
 When moving between non-strongly related resources (e.g. clicking on a username in
the forumview)
 Do not provide a choice of 'open in new-window' and 'open in same-window' links to the user -

Copyright ocProducts Ltd. Page 47 of 74
their web browser already gives them this choice
 When links are sent to new windows, remember to use {!LINK_NEW_WINDOW} in the link title
attribute.

Here are some guidelines for styling tables:


 'dottedborder' is formalised as our style type used for input form tables (a type of 'Map table'
as we're calling them: a map between input field name and input area).
 'solidborder' is formalised for other kinds of 'Map table' or 'Columned table', or just about any
other kind of table.
 All our tables have border-collapse (defined in the dottedborder/solidborder definitions).
ocPortal does not use old HTML-style tables with regions between the cells. Thus we never use
the cellspacing, cellpadding, or border HTML properties.
 Any Map or Columned table must have the appropriate summary attribute indicating this.
 Any layout table we use (one not defined as 'Map' or 'Columned') should have a blank
summary, and should have an HTML or Tempcode comment that indicates why you had to use
that layout table.
 Use the colgroup/col tags to define column width suggestions. If you don't know a column
width and want to let the browser work it out, put an empty col tag in – unless it's the last
column, in which case just omit it.
 Use th properly to define headers. If you have a case where there are top-level and secondary-
level headers, put the top-level header in thead and also make use of tbody.

Templates and themes

Template files are stored in a raw format, and are defined with the standard 'override system' of
ocPortal; as well as this, though, a theme only has to define those templates and css files which have
been changed from the 'default' theme. Search, parsing, and applying linguistic translation, to
templates like this would not be efficient, so ocPortal compiles the templates upon first-use (unless
this option is disabled).
The ultimate compilation target for templates is 'serialised tempcode'; if the template is cached as
this, it will be loaded direct into memory, and it will be bound to the parameters of the template (for
example, a download box template might take the download name and URL as parameters).

Tempcode is ocPortal's template programming language. On the simplest level, it provides a


substitution mechanism so that parameters and global symbols (like the current user's username, or
the time) can be inserted into a template. It also serves as a complete programming language with a
powerful set of control mechanisms.

Tempcode syntax

The syntax for Tempcode (the ocPortal language used for controlling template output) has the

Copyright ocProducts Ltd. Page 48 of 74
following basic syntax:
 {X} means "Insert parameter X" (parameters are essentially variables which are passed into
the template by the PHP code).
 {!X} means "Insert language string X".
 {!X,BLAH} means "Insert language element X with parameter BLAH". BLAH will usually be a
parameter itself, so it might look like: {!X,BLAH,{SOME_PARAM}}. (often language strings will
have a place for such a parameter, for instance: A_SITE_ABOUT=A site about {1})
 {$X} means "Insert symbol X". The symbols are not PHP variables (although the syntax is
similar, because it is a similar concept). An example of a symbol is 'BASE_URL' (so to use this
write {$BASE_URL}). Symbols can also be used to perform functions, such as equality checks.
 {+START,BLAH}...{+END} means "Wrap (...) with a directive named BLAH". A directive can be
anything that does something to the code it wraps. There are a number of directives that you
may use, including output filters such as 'IF', loops, and boxes.
This syntax is explained in more detail in the main ocPortal documentation.

Any Tempcode construct may be escaped (made to fit in an encoding scheme, such as HTML or URLs,
such that special text characters do not interact with that encoding scheme in an insecure/corrupting
way) by escaping filters with ease, just by placing the symbol associated with the mode of escaping
before the closing '}'. For example, {BLAH*} will add the parameter BLAH, escaped for HTML output.
The following escaping filters are provided:
 (*) HTML (e.g. Hello & Goodbye –> Hello &amp; Goodbye)
 (;) Between single quotes (e.g. Who's here –> Who\'s here)
 (#) Between double quotes (e.g. She said, "Hello" –> She said, \"Hello\")
 (~) Where new lines are not allowed (text is drawn up to reside on a single line)
 (@) Comcode (e.g. Use the [url] tag –> Use the \[url] tag)
 (/) Special Javascript SGML-issue (e.g. print('</p>'); –> print('<\/p'))
 (^) Where new lines become "\n" (multiple lines drawn together with \n as a separator)
 (|) Javascript Ids (e.g. This is a -terrible- ID –> This__is__a____terrible____ID)

It is absolutely crucial that Tempcode programmers use the proper escaping. Without it, all kinds of
insecurities and unreliabilities can develop. About 50% of parameters in the default ocPortal templates
actually use HTML escaping so that plain text placed inside a template does not interfere with the
HTML structure itself and displays literally.

WCAG notes

Adherence to the following guidelines can't be automatically detected, so need to be checked


manually in your XHTML:
 <noscript> is given whenever appropriate and possible
 When plugins are used, info about it must be displayed
 When an appropriate markup language exists, use markup rather than images to convey
information.
 Mark up lists and list items properly.
 Ensure that all information conveyed with color is also available without color, for example from

Copyright ocProducts Ltd. Page 49 of 74
context or markup.
 <blockquote> may not used for non-quoting

The following guidelines must be adhered to by webmasters themselves:


 Until user agents allow users to freeze moving content, avoid movement in pages and Until
user agents allow users to control flickering, avoid causing the screen to flicker. By default,
nothing flickers, but Comcode allows it. It's a question of whether a site is designed to be
accessible for all, or 'fancy' for the majority
 Alternatives given to multimedia content
 Use the clearest and simplest language appropriate for a site's content.
 Divide large blocks of information into more manageable groups where natural and
appropriate.
 Specify the expansion of each abbreviation or acronym in a document where it first occurs.
 Place distinguishing information at the beginning of headings, paragraphs, lists, etc.

PNG images

As a standard ocPortal uses 32-bit PNG files over other image types, unless:
 animation is required (.gif used instead)
 background image transparency is required (8-bit .png used instead)
 where the image needs to be given a size different to it's natural dimensions (.gif used instead)
PNG usage is troubled by a lack of good support in Microsoft Internet Explorer 6, but ocPortal works
around this in it's Javascript.

We use PNG files as they have the added advantage that they can be 'alpha-blended' for smooth,
blended, visuals.
GIF files are:
 limited to 256 colours
 only have "binary transparency". so for smooth blending the image itself needs to be "pre-
compiled" against the background colour of your website (which may be edited in the CSS, or
be different in different areas of the site, so you really don't want pre-compilation
assumptions).
 can not be created by most versions of PHP, which rules out the ocPortal Theme Wizard from
being able to modify them
JPEG files do not support transparency, and as we prefer to not assume any particular image won't be
edited to have transparency, we opt to not use them. Also JPEG has serious problems when images
are re-saved, which is a standard event when editing default images. Otherwise we acknowledge JPEG
provides superior compression in most normal circumstances.

Besides the lack of animation, and problems on IE6, there is one further problem with PNG images-
various web browsers, including Internet Explorer and Safari, are incompatible with the settings used
by Photoshop to save them. The colours are always slightly wrong - noticeable if the images need to
line up with background colours defined in the CSS. The problem is caused by the browsers not
supporting PNG gamma settings properly. Fortunately this problem is solved by passing the images

Copyright ocProducts Ltd. Page 50 of 74
through a free compression-optimisation program called PNGGauntlet (for Windows), which is worth
doing anyway as it provides significant loss-less file-size savings over what Photoshop can do.
Instructions for using PNG Gauntlet:
 Backup your images, just in case something goes wrong.
 Download PNGGauntlet and run it. Select RGB+Alpha as the output type, and minimise depth
reduction. Do not preserve gamma information.
 Tick the 'Overwrite Original Files' checkbox.
 In the file selection dialogue, choose to only show PNG files. We should ONLY be optimising
these, otherwise PNGGauntlet will convert any GIFs and JPEGs to PNGs.
 Start optimi[sz]ing. Choose your files. You must do the optimisation one directory at a time
because PNGGauntlet will erase the task list each time you click 'Choose files to optimize'.
 Once done, you may need to tell Windows to reset the permissions in the images folder from
that of the parent folder (because PNGGauntlet will copy the compressed image in from a temp
dir, with the permissions of that temp dir). If you don't do this, you may find you get a
permission denied when viewing the images and lots of red crosses.

Javascript

Javascript is separate from Java, and built into web browsers directly. It is used to provide an
interactive element to the user's experience in their web browser. ocPortal has a loose framework for
producing templates that use Javascript.

Javascript libraries

ocPortal has an inbuilt set of Javascript libraries, which are split across a number of Javascript
template files that each begin with the name 'JAVASCRIPT'. The core library that is loaded onto every
screen is named just 'JAVASCRIPT.tpl'. This core library is very general and doesn't know about
ocPortal's individual modules- it is a general-purpose library to provide functionality usable anywhere.
Most of the other Javascript libraries are specific to individual modules (e.g. the chat module uses
'JAVASCRIPT_CHAT.tpl') or to specific aspects of ocPortal (e.g. the Comcode editing interface uses
'JAVASCRIPT_EDITING.tpl'). There are a few additional general purpose Javascript libraries:
1. JAVASCRIPT_AJAX – AJAX call functionality, which should be used for all AJAX code.
2. JAVASCRIPT_DRAGDROP – A drag and drop library (do not use this unless drag & drop really is
the best way to build an interface, which is very rare)
3. JAVASCRIPT_MORE – A supplementary library of useful functions that are not important
enough to be loaded up for every screen. Consider making use of this if you are making a
Javascript-intensive screen.
4. JAVASCRIPT_TRANSITIONS – Provide various transition/animation effects.

To flag a javascript file to be loaded up, use the 'require_javascript' API command in the PHP code. For
example, if you wanted to load up the Javascript defined in the 'JAVASCRIPT_MORE' template, use:

require_javascript('javascript_more');

Copyright ocProducts Ltd. Page 51 of 74
This command is usually best placed:
1. in screen functions if it is not used by all screens in your module
2. or, in the 'run' function if it is used by all/most screens in your module

You may add new Javascript templates as needed. Just make sure to follow the naming convention.
When writing new Javascript code try to make good use of the existing functions that are defined in
whatever Javascript you already have included. For example, use the standard cookie manipulation
functions and standard AJAX functions, rather than making new ones. If existing functions are not
sufficient consider improving those functions.

Javascript event handlers

Add in Javascript event handlers to your HTML normally as required. For example, to make a popup
window link:

<a href=”{BASE_URL*}/popup.htm”
onclick=”Javascript: window.open(this.getAttribute('href'));”>Click me</a>

It's acceptable to write inline event handler code like this as long as it doesn't get too long.

You should always remember to include 'Javascript:' at the start of an event handler. It isn't strictly
needed, but it makes things tidy and consistent.

Inline Javascript

It's acceptable to use inline Javascript (the 'script' tag) if it's only a short amount of code. This is a
useful technique:
1. if you want to pass template parameters directly into Javascript variables
2. or, if you need the code to run as soon as the screen loads

Example: mixing event handlers, inline Javascript, and a library

This is a little example that does something completely pointless. This complex combination of event
handlers, inline Javascript, and a Javascript library, should only be used if there is a reason for it. If
there is no reason, simpler code is better. The complex example is shown here to show how different
Javascript methods can fit together if they need to.

In the template:

<script type="text/javascript">// <![CDATA[


/* Store a template parameter into a global variable.
Placing on the window object is the same as setting a global variable.

Copyright ocProducts Ltd. Page 52 of 74
Writing it like this just makes it more clear it's a global variable which reduces
the risk of obscure bugs.
Note the escaping here: It adds escaping for both the string quotes (;) as well as
the CDATA section (/). This makes sure the parameter can not be used to create an XSS
injection vulnerability. */
window.my_template_parameter='{MY_TEMPLATE_PARAMETER;/}';
//]]></script>

<a href=”#” onclick=”output_my_parameter();”>Click me</a>

In the Javascript library (which must have been loaded using require_javascript):

function output_my_parameter()
{
// Output the global variable we made.
window.alert(window.my_template_parameter);
}

Simpler example

The above example used global variables which is bad programming practice. I did it because it was a
good example, but the following example is much better quality and simpler...

In the template:

<a
href=”#”
onclick=”output_my_parameter('{MY_TEMPLATE_PARAMETER;*}');”
>Click me</a>

In the Javascript library:

function output_my_parameter(message)
{
window.alert(message);
}

Core APIs

All the ocPortal APIs are documented online and visible via the PHP-doc comments. If you need to
look up the purpose/usage of a function the easiest way is to do a file search from your code editor
for "function <function-name>", so you can jump to the PHP-doc comment for it.

Note that as a general rule, you shouldn't be using any function/method that begins '_'. This indicates
a private/helper function.

Copyright ocProducts Ltd. Page 53 of 74
The following are the core ocPortal APIs (green is used to indicate the most important APIs)...

API File(s) Typical usage


GLOBAL sources/global.php Pre-loaded and available in global scope
sources/global2.php
sources/support.php
Web page display sources/site.php Pre-loaded and available in global scope
Forum, members, and sources/forum/*.php Use via the $GLOBALS['FORUM_DRIVER']
usergroups (forum sources/forum_stub.php object.
driver layer). sources/users.php

Forum, members, and sources/ocf_*.php Needs require_code. Only use these


usergroups (OCF). functions if you can assume OCF is
running.
Database sources/database.php Use via the $GLOBALS['SITE_DB'] or
sources/database/*.php $GLOBALS['FORUM_DB'] objects / global
functions.
Caches sources/caches.php Use the functions, which tie into pre-
instantiated cache objects (you don't
need to instantiate anything).
CAPTCHA sources/captcha.php Needs require_code.
Comcode sources/comcode.php Needs require_code if you need
sources/comcode_text.php anything in comcode_text.php.
Configuration sources/config.php Pre-loaded and available in global scope
Encryption sources/encryption.php Needs require_code.
Feedback (ratings, sources/feedback.php Needs require_code.
comments, etc)
Files sources/files.php Needs require_code if you need
sources/files2.php anything in files2.php.
Forms sources/form_templates.php Needs require_code.
E-mail and SMS sources/mail.php Needs require_code.
Image manipulation sources/images.php Needs require_code.
Language and sources/lang.php Needs require_code if you need
internationalisation sources/lang2.php anything in lang2.php.
Filtering syntax sources/ocfiltering.php Needs require_code.
(ocFilter)
Permissions sources/permissions.php Needs require_code if you need
sources/permissions2.php anything in permissions2.php.

Copyright ocProducts Ltd. Page 54 of 74
Content submission sources/submit.php Needs require_code.
security
Archiving and sources/tar.zip Needs require_code.
compression sources/zip.php
sources/m_zip.php
Templating (Tempcode sources/tempcode.php Pre-loaded and available in global scope
engine)
Templating (template sources/templates.php Pre-loaded and available in global scope
wrapper functions)
Templating sources/templates_interfac Needs require_code.
(sophisticated es.php
interfaces)
Date and time sources/temporal.php Pre-loaded and available in global scope
Overridable text files sources/textfiles.php Needs require_code.
Themes (esp theme sources/themes.php Needs require_code if you need
image management) sources/themes2.php anything in themes2.php.
Data validation sources/type_validation.ph Needs require_code.
p
File uploads sources/uploads.php Needs require_code.
Web services sources/xmlrpc.php Needs require_code.
XHTML manipulation sources/xhtml.php Needs require_code.
URL generation sources/urls.php Pre-loaded and available in global scope
Block, page, and zone sources/zones.php Needs require_code if you need
querying and sources/zones2.php anything in zones2.php or zones3.php.
maintenance sources/zones3.php

Generally any code in a file suffixed '_action.php', and often code in a file suffixed '_2.php' (or
'_3.php') is involved in writing data. This is not a strictly enforced rule, and the main reason for the
division is performance - it takes more CPU, disk activity, and memory, if unnecessary functions are
loaded up. As writes are less common, the extra functions are usually put into a separate file. Some of
the '_2.php' files never need to be loaded up manually using 'require_code' because they are auto-
loaded by stub functions in the primary files (this is a memory optimisation for the case where a set of
functions are needed a lot but not always).

For AJAX handler scripts, less may be loaded into the global scope than indicated in the table, and less
may generally be initialised - for performance reasons.

Copyright ocProducts Ltd. Page 55 of 74
Diff tools

This is not specific to ocPortal, but you will find a good 'diff' tool is essential when programming. It
should be a key part of any programmers toolkit, and can be used for:

● Seeing what code you might have changed (e.g. if you made a quick bug fix and need to send
through a bug report saying what to change).

● Seeing what code someone else might have changed (e.g. between ocPortal versions, if you've
found there's a new bug and want to see what caused it).

● If multiple developers conflict, you can sync changes.

● Updating overridden lang files with typo-fixes from the main ocPortal.

● A whole lot more!

On Windows 'WinMerge' is a good too, on Mac 'DiffMerge', and on Linux, 'Kompare'.

Debugging, and stack traces

Some developers like to use IDE's with breakpoint support, watches, code-stepping, etc. The core
ocPortal staff do not do this, as we tend to find the IDE environment a little clunky and slow, but
others have other opinions. Netbeans is an excellent system and has recently had PHP support added.

The core ocPortal staff usually debug via just putting temporary bits of code in, like:
@ob_end_flush(); @print(gettype($var)."\n"); @var_dump($var); flush(); exit();
You don't need all that code all the time, but if you don't use "exit" then you do need "ob_end_flush".
This is because ocPortal uses an "output buffer" for it's Tempcode system, and by default any output
you do will get written into an arbitrary point in the Tempcode tree - often never to be seen at all!

In fact, rather than the ugly code above that just dumps information into the output stream, you can
do something a lot fancier:
attach_message(gettype($var).var_export($var,true),'warn');
This should show the message in a nice position in the ocPortal output.

Of course, make sure you don't leave any temporary debugging code around when you finish!

ocPortal will show a "stack trace" if it receives an error it treats fatally. These stack traces are
amazingly good at helping you find bugs, so if you get one have a close look at it. If you look down
the trace you will see it will lead straight to the place the error happened, and the parameters to the
function where the error happened. Lateral reasoning usually allows spotting of the problem in a few
seconds, although sometimes it can be more difficult.

One confusing thing about stack traces is that the error chain is included within the trace. With time
you'll learn to read past this and see where the error was actually triggered.

Copyright ocProducts Ltd. Page 56 of 74
How Tempcode works

The internals of Tempcode are enormously complex.

It has to achieve a number of key design goals:

● It has to be able to cache

● When cached it must remain 'smart' - it cannot cache statically, as things will be in cached
Tempcode that need to change over time (for example the GLOBAL template might show the
current server date and time - cacheing must be smart so that when it comes out of the cache
it still shows the correct date and time)

● It must not use too much memory

● It has to be very very fast

● It has to be simple for a programmer to use (via the PHP API)

● It has to be simple for a themer to use (via the written language)

● The template structure must be preservable, so themers can see what structures different
screens use

● The full Tempcode language has to be supported, with loop's, if tests, escaping, etc

● It must be preprocessable.

● For example, a template might include a block, which might require some CSS - this CSS
would need to be included in the HTML <head> before the block itself was output

● Shift encoding has to work (ability to define things in one place, and have them used in
another)

● The 'IF_ADJACENT' symbol has to work, so templates can detect their neighbours
(complex to implement!)

Tempcode in ocPortal 4+ works via a very elaborate concept that we call the 'closure tree'. Essentially
Tempcode objects link to form a tree structure, and each Tempcode object consists of a series of
'closures'. These closures are a pair: a PHP function name, and a set of parameters to that function.
Essentially each closure represents a template call - the PHP function name is the name of a compiled
version of that template (templates are compiled to PHP code), and the parameters are the
parameters to the template. Some complex tracking goes on to ensure that the correct template
functions are defined or loaded, as required by the closure tree. Some of these functions are actually
encoded directly within the Tempcode objects (ones that were defined at run-time, such as attached
string literals), and some are defined in the ".tcp" files.

When I say "function call" and "function", this is actually simulated for performance reasons (PHP uses
huge amounts of memory to store real functions). It actually happens via an 'eval' call. There are a
few exceptions to this where real calls are used, when a template is so commonly used that we think
it does justify getting loaded up as a real function.

Copyright ocProducts Ltd. Page 57 of 74
Tempcode in ocPortal 3 worked in an entirely different way to ocPortal 4. It was actually an
interpreted programming language, and hence was quite a lot slower (whilst PHP of course is an
interpreted language itself, it is fast because it is interpreted by assembly code). Rather than being
compiled PHP, it essentially used the equivalent of op-codes and operands. Tempcode in ocPortal 2
was similar to ocPortal 3, except a bit more primitive - ocPortal 3 would 'flatten' the template tree,
and was very clever about being able to merge certain things to increase execution speed and
memory consumption. I have just explained the history of Tempcode so I can make an important
point - Tempcode is a bit of a black-box, and the internal engine can be rewritten so long as the API,
written language, and semantics, are preserved. If the Tempcode language is ever changed, the
caches need to be cleared - to clear away any compiled Tempcode (we never store anything primarily
in compiled Tempcode - instead we store in Comcode and cache the Tempcode).

Development mode (aka debug mode)

This section is mostly intended for ocProducts staff.

If ocPortal is running out of a subversion repository, or with our own custom version of PHP, it will
automatically run inside 'development mode'. Development mode involves a number of intentionally
quirky and strict changes to how ocPortal runs, including:

● You will get error messages if you use too many queries, unless $GLOBALS['NO_QUERY_LIMIT']
is set to true.

● Cookies will be disabled, forcing use of 'keep_session' for session propagating. This helps us
make sure ocPortal doesn't need cookies to run.

● Persistent caching will randomly toggle between on and off (this can cause strange XML errors
occasionally due to a race condition in the text-based persistent cache - try refreshing the page
if you get random XML errors).

● If you create a bad link to a screen (e.g. without using 'build_url') you will get an error about
that. This is because 'keep_devtest=1' is passed through and must be passed through for any
referred (i.e. internal) link. Don't manually pass this - the code must do it itself, as it is
injected by 'build_url' to prove build_url was used.

● Form field descriptions will be forced to end with full stops (.).

● The template cache will randomly get emptied.

● True XHTML (i.e. strict XML parsing) will be used on most web browsers (put
&keep_no_xhtml=1 into the URL to prevent this as it does have a tendency to drive people
crazy). XML cleanup of data will also be enabled (e.g. RSS feeds get re-written in clean
XHTML).

● Extra error messages if you try and pass a number to 'do_lang' or 'do_lang_tempcode' (you
are supposed to convert to a string first manually).

● You'll get an error if you don't call a template ending '_SCREEN' at some point during your
code.

Copyright ocProducts Ltd. Page 58 of 74
● Relative URLs will intentionally not work, due to an intentionally broken 'base' tag. This forces
you to use absolute URLs as per coding standards. One exception is URLs like '#example',
which are broken by the 'base' tag, but then fixed at run time using Javascript - you are
allowed to use those.

You can turn off most of development mode by putting "&keep_cache=1" into the URL, but this is not
promoted as development mode was intentionally written to help us do on-the-fly testing of things
that might otherwise go unnoticed.

Custom version of PHP

Our custom version of PHP (linked to under "Programming/progmattic-interface standards") performs


2 main functions:

● It makes PHP type strict (if turned on, which ocPortal does do), so that automatic type
conversion results in a PHP notice, which causes an ocPortal stack dump.

● It turns on some very special innovative XSS security hole detection code. All PHP strings are
tagged as "escaped or non-escaped", and this tagging is maintained through all string
operations (e.g. if a string that is escaped is appended to a string that is non-escaped, the
result will be a string that is non-escaped). Functions like 'htmlentities' result in escaping being
marked, and there is also an 'ocp_mark_as_escaped' function to force this manually (useful if
you are intentionally injecting HTML literal code, e.g. from an RSS feed). If you output a non-
escaped string, a PHP warning is produced about it - because the developer has not blocked
what could well be an XSS security hole. We're very proud of this system, and how elegant it
is, and as far as we know it's not anything anyone has done before - it took quite a few
thought experiment sessions until we can up with it.

It also enforces some other coding standards, such as not allowing relative paths to files.

Engineering standards and trade-off

When coding for ocPortal, you should be careful to make sure all our design goals are maintained.
Sometimes trade-offs have to be made between design goals, sometimes an option can remove the
need for a trade-off, and sometimes an option would just bloat and confuse the product. Good balance
and thoughtfulness is required before rushing to add the latest cool ideas.

We have established the following design goals (in no particular order):


 Security - ocPortal should be completely secure and un-hackable, but also be able to track
offenders and be easily restorable if the worst happens
 Performance - ocPortal should be as efficient as possible, with both CPU and memory usage;
things like caches and optimised code, and sensible feature design, can achieve this. Tip: use
xdebug for profiling, along with WinCacheGrind.
 Great design - ocPortal should look excellent on every page
 General quality - ocPortal should not contain things such as spelling errors
 Flexibility / generality - ocPortal should be able to work in many different kinds of website.

Copyright ocProducts Ltd. Page 59 of 74
Features should be usable in different ways for different people. It should not have a restricted
layout (hence Comcode, modules, blocks, and templates)
 International - ocPortal should work well across the world, across language and culture (as
long as someone puts the effort in to do a translation)
 Compatibility - ocPortal should work well with common versions of required technologies
 Modability - ocPortal should be modular, and overrideable; easy to change without tearing the
things to pieces, preferably without even editing things (just adding)
 Usability - ocPortal should be very easy to use. The best user-experience and interaction-
design methods should be used.
 Power/features - ocPortal should be as powerful as any competing product, but that should
not be an excuse for implementing crap that bloats and over-complicates the product; we all
want loads of features, but we don't want to confuse the users or tie ourselves up so tight that
future development is very difficult
 Standards compliance - An accessible web, based on open standards, which is stable and
not tied to any specific vendor, is desirable for all; by sticking to standards, you are ensuring
ocPortal works on a wide range of technologies, and for a wide audience
 Documentation - Remember that the best documentation is when the interface describes
itself, but the written documentation must be excellent.
 Consistency - The whole system should have common ways of doing things. Everything
should seem consistent to the user, and be so behind the scenes. Consistency is piece of mind,
consistency is ease of use, consistency is simplicity, consistency is less code to test and fix. I
can't stress how important it is to be consistent.
 Independence - ocPortal should not have unnecessary dependencies, such as mySQL.
 Stability / Error-trapping / Input-validation - ocPortal should catch all errors, not let
things like PHP notices slip through. This means that programmers have to get used to a new
level of strictness, we are much less tolerant of errors slipping through because for complex
software like ocPortal we can't afford weak foundations.
 Privacy - ocPortal shouldn't spy on site users and site administrators anymore than is required
to carry out user desired features and anti-piracy measures.
 Scaleability - ocPortal must work on sites both small and huge. For example, it's wrong to put
all downloads in a list on any single screen - there could be 1000's.
 Portability - People should be able to move their websites between servers without much
difficulty.
 Simplicity - Features only needed by a minority should be 'off-by-default'

Importers

Import or forum driver?


Forum/member integration, done using a forum driver, allows ocPortal to integrate with an existing
forum/member system in a number of ways. One of the biggest is so that you don't need to
separately log in to both systems. ocPortal also uses the forum's members and usergroups, and
stores/reads posts from the forum. Forum/member integration is achieved via forum drivers. ocPortal
always uses one forum driver, and the default is the 'OCF' driver which actually allows ocPortal to use
it's own inbuilt forum (OCF).

Copyright ocProducts Ltd. Page 60 of 74
Generally ocProduct's advises people not to use forum/member integration, but rather to use the
inbuilt OCF forum instead (and therefore the 'OCF' driver). This is because it's much cleaner just to
have one piece of software. This said, people often have valid reasons for avoiding a migration.
If one wishes to use OCF but currently uses a different forum/member-system, then importing is the
answer. If one doesn't care about OCF but currently uses a different forum/member-system, a forum
driver is the answer.

Sometimes you want the advantages of migration, but don't want to move too quickly. ocProduct's
approach to supporting these kinds of migrations is to provide forum drivers, but also importers, so
people can choose to move to ocPortal slowly in two separate steps:
1. install ocPortal using a forum driver
2. import the forum into OCF and discard old forum).
At the time of writing most forum drivers have corresponding importers to support this, but a few
don't.

Writing a forum driver


There's not a lot to say about this, as there are so many existing forum drivers in ocPortal to look-
at/refer-to and they all follow a pattern. The API is mature and simple.
To make a new forum driver just copy an existing one and give it a new filename corresponding to the
software being imported. To use the driver the info.php file will need to reference the stub of the
filename you chose in it's 'forum_type' option. You shouldn't switch forum drivers on a real ocPortal
site because it breaks foreign key references in ocPortal tables (member ID's are group ID's) - but
you can do it when developing. It's probably best to install ocPortal using OCF, write your new forum
driver and switch to it whilst you debug it - then install again from scratch using your new forum
driver from the offset.

Writing an importer

The import system (the admin_import module) provides a framework for importing data into ocPortal
from other systems. The system provides both a GUI and an import API.

An importer imports data from the database of some other software into ocPortal. So it takes
database records and converts them. The end result is the data is then part of an ocPortal-powered
website.

Importers are implemented using the ocPortal 'hook' system, meaning that a new importer may be
added by just copying the importer-file into the sources_custom/hooks/modules/admin_import
directory. At the time of writing, the following importers are available:
 phpNuke 6.5 (with possibly other versions and forked-products partially compatible)
 phpBB 2
 vBulletin 3
 Invision Board 1.3.x

Copyright ocProducts Ltd. Page 61 of 74
 Invision Board 2.0.x
 Merge from another copy of the latest version of ocPortal

The importer-file consists of an object that defines:


● a probe_db_access function that tries to autodetect database connection details from the
given installation directory of the software being imported
● an info function that provides importer details through a standard data structure. These
details include:
● the name and versions of the product(s) the importer supports
● the default table prefix for the database of the product
● a list of all the available importable features in the importer, roughly ordered in the order
they should be imported in (all of which have feature_<codename> associated functions
in the importer)
● a feature dependency map (to prevent users importing in the wrong order - which they
may do as they are allowed to skip features so long as there is no defined dependency
by what they import next)
● a message to display after the import has completed. In the case of most of the existing
importers it's a standard message telling the user to go run our cleanup tools.
● miscellaneous other advanced details
● feature-import functions, which are named according to the code-names of the import features
listed within the info data structure (import_<feature>). The object only needs to define
feature-import functions for those features it can import.
● any other miscellaneous functions that the importer may use internally (somtimes importers
have their own unique situations where moderately complex conversion of data between
representation schemes is required, and support functions are often useful to keep the code
tidy)

The import system mainly provides a workflow via it's GUI, and the actual API is relatively light-
weight (although the whole ocPortal API is available to importers). The workflow is as follows:
● Finds which importer to use (options available correspond to what hooks are on the file
system)
● Finds an import session to continue, or starts a new session (described below)
● Gets path details for the system being imported
● Verifies the path details and autoprobes for database connection details
● Provides the user a UI for working through each importable feature, one-at-a-time
At the last stage the feature functions are called. The import functions themselves are passed details
that will allow them access to the imported database, and they generally just loop over the data for the
feature being imported, and use the ocPortal API's to add the data.

If you need to import a feature for which no import code exists yet then you'll need to define it. They
are defined by hook files in sources/hooks/modules/admin_import_types. Each hook file has a
function that returns a mapping between import codes and the language string identifiers that label
them. e.g. the 'filedump' code is defined in
sources/hooks/modules/admin_import_types/filedump.php and is set to be labelled to the user
with the language string 'FILE_DUMP'. If you need to create a new import code put it in the most
appropriate hook file that already exists (probably ocf_forum.php). Try and re-use an existing
language string if one exists, otherwise create one in lang/EN/import.ini.

Copyright ocProducts Ltd. Page 62 of 74
Often special ocPortal API parameters are used to disable cache-regeneration and input-validity-
checks, to allow for a smoother and more error-free import. For example, if you're adding a topic in an
importer you don't want to check poster permissions, so the ocf_add_topic function has parameters
to disable it's internal-checks.

The importer system uses a concept of 'import sessions'. These are built on top of the ocPortal login
sessions, and are an important feature in allowing you to merge multiple sites into ocPortal: they keep
the progress and "ID remap table" from each import separate. The "choose your session" interface
exists so that if your ocPortal session is lost, you can still resume a previous import.

The import API gives importers an ability to do foreign key conversions. For example, when importing
a topic an importer will likely need to convert the foreign key representing who posted that topic into
whatever it has been imported to (a member ID#5 on the import data may now be member ID#9 on
ocPortal). This API is very simple: you just set key remappings when they become known (when
importing the feature being referenced), and then look them up when importing whatever uses the
key. You can treat the foreign keys either as a dependency (giving an error if there is an inconsistency
in the data being imported) or you can handle failed lookups whatever way you wish.

The importer system is designed to be robust, and is programmed as 're-entrant' code; this means
that if installation is halted, via failure, timeout, or cancellation, it can continue from where it left off.
This is of particular use if there is an incompatibility between your data and the importer, which is not
very unlikely due to the wide variation in data for any single product across different versions and
usage patterns.

One problem with importers that import members is that almost always passwords are not available
and not compatible with ocPortal's hashing scheme. To workaround this:
1. OCF needs to be given a handler for the password hashing scheme. This is done by writing a
special 'systems/ocf_auth' hook.
2. The member must be imported with a reference code representing the hashing scheme to use
(the ocf_make_member function supports passing this in)

A common mistake when writing importers is with HTML entities. Some software stores some fields as
HTML (albeit taglessly - just with the entities), whilst ocPortal never does. You need to decode any
HTML fields using 'html_entity_decode' before passing them into the ocPortal API, like
"html_entity_decode($field_value,ENT_QUOTES,get_charset())".
If you are unsure how software stores it's data, enter test data that uses various quotation mark
symbols, so that it will jump out at you when you view the raw database.

There is a very complex situation for non-forum importers that use forum-driver functions during
import, in order to find data to associate with database records they are creating. For example, an
importer for a simple download system which stores 'usernames' of download-submitters, might wish
to try to bind these usernames to actual user-ids, using the forum-database that ocPortal normally
uses. However, by default the forum-database is tied to the local-OCF-install during installation, as
when forum data is imported, this OCF database is required (as this is where the information will be
piped to). Therefore there are two import API functions that allow switching to and from local and
M.S.N. OCF installations:

Copyright ocProducts Ltd. Page 63 of 74
● ocf_over_local (this sets ocPortal to use the local OCF for the FORUM_DB object)

● ocf_over_msn (this sets ocPortal to use the database of the regular forum for the FORUM_DB
object - which may or may not be OCF, and even if it is OCF, it might not be the local OCF)

Note that the importer system does not change the actual forum driver used. Therefore it cannot be
assumed that the global FORUM_DRIVER object is an OCF forum driver, nor can it be assumed it holds a
local database. The OCF system has been programmed to never use FORUM_DRIVER itself, it uses
OCF_DRIVER (which you may use yourself if writing a forum importer), which is always guaranteed to
be linked to OCF.
A related problem is that some parts of ocPortal's API might assume that the FORUM_DB object points
to OCF if the get_forum_type() function returns 'ocf'; or there can be problems with reference
variables that confuses forum driver objects. Therefore running these commands might be required in
some other cases - in particular when Comcode is being parsed. Don't worry too much - emulate what
the other importers do.

To learn how to write an importer consider copying an existing one (preferably one written for a
product similar to the product you are importing) and adapting it. Work through the code function by
function, adjusting field names and other code as appropriate.

Sometimes data in software being imported has no use in ocPortal, or isn't quite compatible with how
ocPortal does things. When ocProducts makes an importer we try to make a call in these situations as
to how important the feature is:
● almost always it is fairly trivial (e.g. an option that does not exist in ocPortal as we'd use a
template edit instead). In this case we would ignore the option.
● sometimes a bit of clever code can convert the data into something useful.
● if it is something that seems important, either a feature would need adding to ocPortal, or
whoever has commissioned the importer would be consulted, or the issue would be
documented. It shouldn't be ignored completely.

Always test a new importer and keep backups before actually running it on your data. It is surprisingly
easy to make a typo in an update query, for example, which will trash a whole table (and if also
accidentally executed on the source database, perhaps more disastrous).

eCommerce

For technical information about how eCommerce is achieved in ocPortal, including how products work,
see this tutorial:

http://ocportal.com/docs/tut_ecommerce.htm

Permissions

Permissions are drawn from usergroup membership. Usually a forum allows multiple usergroup
memberships per member (the inbuilt ocPortal forum, OCF, does). This allows you to more effectively

Copyright ocProducts Ltd. Page 64 of 74
mix and match permissions for individual members and cuts down the total number of usergroups
required.

The permission architecture in ocPortal is particularly rich, giving the webmaster a high degree of
control over their site, but where complexity is only added in if they need it (permissions can be
overridden on a per-page and per-category basis, but otherwise apply globally from a simple list of
settings).

Individual privileges in ocPortal are called 'specific permissions'.


Note: View permissions are handled differently to specific permissions. Instead of having an
'overriding' system, view permissions work in terms of barriers - you need to be authorised a number
of barriers (zone, page, category) before you are able to view a resource.

Permission-modules are identifiers used to identify resources for which view or specific permissions
are set. The term 'module' here is not exactly the same as a 'module' in the usual ocPortal module-
page sense. A permissions-module is usually given the same name as the page-module for viewing
the resource it identifies permissions for, but it may not be if the page-module supports multiple types
of resource. For example, the 'catalogues' page-module has support for both catalogue-permissions
and category-permissions, so the permission-modules are 'catalogues_catalogue' and
'catalogues_category'.

When specific permissions are overridden for pages, the pages that the overriding is defined for is
usually the content management page; for example, if the 'add_midrange_content' permission is
overridden for the download system as a whole, it would be overridden with the page name
'cms_downloads'.
Note the distinction between overriding for a page, and the permission-module - in this example
'add_midrange_content' has been overridden for a whole page, so no permission-module is needed to
identify a resource. If we were overriding for a category, it would then be the permission-module that
identified the type of category we were overriding for, and the ID of that category would also be
stored.
(A final point of note is that of SEO-modules and feedback-modules: these define yet another set of
'module' names: these are however totally unrelated to permissions.)

Referencing existing permissions

It is very easy to reference existing permissions from your code. Just use the
'has_specific_permission', 'has_actual_page_access', and 'has_category_access' functions. Note that
it is important to use the 'has_actual_page_access' function if you're accessing resources belonging to
a page-module if you aren't running at page-module code itself at the time (e.g. the RSS feed for the
page-module). You actually don't need to call 'has_actual_page_access' from page-module code itself
because ocPortal checks this for you.

The 'has_specific_permission' function supports checking full overrides as well as just basic global
settings. To do full override checks you can give parameters to specify what page-module you
consider your resources to belong to, as well as permission-module and category ID's.
If you are going to do override checks though, make sure that your page-modules define what they

Copyright ocProducts Ltd. Page 65 of 74
can override so that the ocPortal Permission Tree Editor knows what to make editable. For examples of
how to do this, see cms/pages/modules/cms_downloads.php and site/pages/modules/downloads.php.

Adding a new specific permission

In the install function for your page-module, add the following code to actually add the permission:

add_specific_permission('FOO_SECTION','may_foobar',false);

That code adds the permission 'may_foobar' to a section of options named 'FOO_SECTION' (a
language string for this must exist), and sets it to false for every usergroup except staff usergroups.
It'll run when your code first installs. When developing often you add permissions adhoc until you're
finished (you can't be reinstalling your module each time) - in which case temporarily also put the
code in data_custom/execute_temp.php and run that.

In the uninstall function for your page-module, add the following code:

delete_specific_permission('may_foobar');

In your modules language file, add:

PT_may_foobar=May foobar

SEO meta-data

To allow SEO (search engine optimization) meta-data in your addon, the following is roughly required
for different sections of the addon's page-module code. This example is for the download system:

This function seo_meta_set_for_implicit() implicitly (by virtue of extracting keywords from the
strings it is passed) sets the meta information for the specified resource. Add it in the model
add_download function:

seo_meta_set_for_implicit('downloads_download',strval($id),
array($name,$description,$comments),$description);

The function seo_get_fields gets templated form fields to insert into a form page, for manipulation
of seo fields. Add it in the AED CMS-module, get_form_fields function:

$fields->attach(
seo_get_fields('downloads_download',strval($id))
);

In the AED CMS-module, you need extra parameters to the call to the model edit_download function:

Copyright ocProducts Ltd. Page 66 of 74
,post_param('meta_keywords'),post_param('meta_description')

In the model edit_download function you need to take the extra parameters:

,$meta_keywords,$meta_description

and you need to use them:

seo_meta_set_for_explicit('downloads_download',strval($id),
$meta_keywords,$meta_description);

The function seo_meta_erase_storage erases a seo entry... as these shouldn't be left hanging
around once content is deleted. Add this function in the model delete_download function, you need to
clean up:

seo_meta_erase_storage('downloads_download',$id);

In the page-module you need to load up the settings when the content is viewed:

seo_meta_load_for('downloads_download',strval($id),
$title_to_use);

Feedback mechanisms

ocPortal allows webmasters to create a highly interactive site, with numerous features for user
feedback at disposal. You are able to add ratings, comments, trackbacks, and staff notes to a module.

We recognise that many websites owners will not wish to allow users to affect the state of their
website: because of this, commenting and rating may be enabled/disabled on a site-wide basis. They
are, however, enabled by default. To disable the elements of the feedback, check-boxes are given in
the 'User interaction' subsection of the 'Feature options' section of the main Admin Zone configuration
page.

In addition to site-wide control of feedback, it may also be enabled/disabled on a content entry level.
For a piece of content to support rating, for example, that content must be configured for rating, and
ocPortal must have rating enabled site-wide.

Feedback commenting is very similar to, and actually implemented as, a forum topic being attached to
a piece of content, and displayed beneath it. To allow users to comment on ocPortal content, in
addition to site-wide commenting any commenting for the content entry being enabled, the named
comment forum must exist; the default comment forum name is 'ocPortal comment topics', but this is
configurable in the main Admin Zone configuration page.

Add the following code snippets to enable your feedback system…

Copyright ocProducts Ltd. Page 67 of 74
In the install function for your module, in the table creation…

'allow_rating'=>'BINARY',
'allow_comments'=>'BINARY',
'allow_trackbacks'=>'BINARY',
'notes'=>'LONG_TEXT',

In the uninstall function for your module...

$GLOBALS['SITE_DB']->query_delete('trackbacks',
array(
'trackback_for_type'=>'<your_chosen_feedback_module_name>'
)
);
$GLOBALS['SITE_DB']->query_delete('rating',
array(
'rating_for_type'=>'<your_chosen_feedback_module_name>'
)
);

In the run function for your module…

require_code('feedback');

In the get_form_fields function header of your AED CMS module…

,$allow_rating=1,$allow_comments=1,$allow_trackbacks=1,
$notes=''

In the get_form_fields function of your AED CMS module…

$fields->attach(feedback_fields($allow_rating,$allow_comments,
$allow_trackbacks,$notes));

In the add_actualisation and edit_actualisation functions of your AED CMS module…

$allow_rating=post_param_integer('allow_rating',0);
$allow_comments=post_param_integer('allow_comments',0);
$allow_trackbacks=post_param_integer('allow_trackbacks',0);
$notes=post_param('notes');

In the fill_in_edit_form function of your AED CMS module, where the get_form_fields function is
called…

,$myrow['allow_rating'],$myrow['allow_comments'],$myrow['allow_trackbacks'],$myrow[
'notes']

In the model add/edit actualisation functions add some extra parameters...

,$allow_rating,$allow_comments,$allow_trackbacks,$notes

Copyright ocProducts Ltd. Page 68 of 74
In the model add/edit actualisation function query_insert/query_update calls…

'allow_rating'=>$allow_rating,
'allow_comments'=>$allow_comments,
'allow_trackbacks'=>$allow_trackbacks,
'notes'=>$notes,

In the model delete actualisation function…

$GLOBALS['SITE_DB']->query_delete('rating',array(
'rating_for_type'=>'<your_chosen_feedback_module_name>',
'rating_for_id'=>$id));
$GLOBALS['SITE_DB']->query_delete('trackbacks',array(
'trackback_for_type'=>'<your_chosen_feedback_module_name>',
'trackback_for_id'=>$id));

In the view function for your content…

do_rating($myrow['allow_rating'],
'<your_chosen_feedback_module_name>',$id);
do_comments($myrow['allow_comments'],
'<your_chosen_feedback_module_name>',$id,
$self_url, $self_title);
$rating_details=get_rating_text(
'<your_chosen_feedback_module_name>',$id);
$comment_details=get_comment_details(
'<your_chosen_feedback_module_name>',
$myrow['allow_comments']==1,$id);
$trackback_details=get_trackback_details(
'<your_chosen_feedback_module_name>',
strval($id),$myrow['allow_trackbacks']==1);

In the main template call to display your content on it's own screen...

'TRACKBACK_DETAILS'=>$trackback_details,
'RATING_DETAILS'=>$rating_details,
'COMMENTS_DETAILS'=>$comment_details,

Language lookups, Comcode, and attachments

The following steps are required for correct use of ocPortal's language/Comcode/attachment system:

1. Define your tables correctly - human language should almost always be defined using a
'*_TRANS' field type.
2. To input Comcode data, use the Comcode versions of the field input functions (e.g.
field_input_comcode). To allow new attachments to be added, you need to use a posting form.
3. insert/update/delete your data using the correct Comcode/language/attachment functions.
When used correctly, the special functions in ocPortal's API automatically handle Comcode
parsing, and attachment uploading. For example, to add some Comcode, you use the

Copyright ocProducts Ltd. Page 69 of 74
insert_lang_comcode function as a filter to convert between Comcode-format string, to
translate-table ID number -- and this ID number is then inserted using the normal query_insert
function. See how the news addon code does it all for a better example - it is best understood
by copying how existing addons do it.
4. Retrieve your data using the get_translated_* functions.
5. Attachment-support requires attachment hooks. These hooks work to grant users permissions
to access attachments via their permission to access the resources that reference those
attachments. For example, someone can access an attachment to a news article if they have
access to that news article, or access to anything else that also references that attachment
(such as a forum post).

Hidden features inside ocPortal

ocPortal contains a number of hidden 'values', 'keep' parameters, and empty file flags. These allow
activation of special functionality that isn't considered important/mainstream enough to warrant user-
interface space within ocPortal.

Empty files

The presence of the following empty files in the root directory have a special meaning to ocPortal:
 install_ok – don't complain if install.php is left present (DO NOT use this unless your install
is not connected to the Internet or if you definitely have an install_locked file)
 install_locked – whether to lock the installer (prevent it running)
 old_mysql – degrade mySQL functionality so that old (official unsupported) versions will
basically work with ocPortal

Hidden 'values'

Values are like hidden configuration options. They are either hidden because they are managed by
code (perhaps used for keeping track of something), or because they are obscure. To set a value,
open up OcCLE (under the ‘tools’ section in the Admin Zone or clicking the symbol at the bottom-left
of any page) and type :set_value('<name>','1'); (replace '1' if appropriate, but usually we do use '1'
to enable). In normal PHP code, you can use the same 'set_value' function, and also the 'get_value'
function. You can automatically add a new hidden value just by setting it for the first time.

The values you might want to manually change are:


 emoticon_nightmare – set to '1' for an April Fools-style emoticon joke [OCF only]
 stupidity_mode – set to 'leet' or 'bork', for a fun April Fools-style Comcode joke (clear the
Comcode cache after you're done though)
 rebrand_name – if ocPortal is called something else as far as the site staff are concerned, set
the name here

Copyright ocProducts Ltd. Page 70 of 74
 rebrand_base_url – to change where branding URLs go to (e.g. the stub that is used when
linking to documentation), set it here
 company_name – if ocPortal is being rebranded to be 'made' by another company, set the
name here (this has no effect of copyright of course, but ocProducts allows this)
 simple_mode_news – set this to '1' if the news form-fields are to be cut down
 simple_mode_downloads – ditto, for Downloads
 simple_mode_comcode_pages – ditto, for Comcode pages
 simple_mode_calendar – ditto, for calendar events
 show_gallery_counts – set this to '1' to show gallery counts in OCF member boxes
 comment_forum__galleries / comment_forum__images / comment_forum__videos /
comment_forum__downloads / comment_forum__calendar / comment_forum__news /
comment_forum__iotds / comment_forum__polls – override the comment forum for a
particular content type
 download_gallery_root – set this to a gallery name; new download galleries will be created
underneath the given gallery (defaults to the root gallery)
 ionic_on – inform the software that the IIS Ionic Rewriter plugin is installed and configured
 reverse_thumb_order – set to '1' if thumbnails automatically chosen as gallery rep-images are
to be taken from the latest image, rather than the first image
 comment_forum__catalogues_<catalogue-name> – define a comment forum for a specific
forum
 unofficial_ecommerce – set to '1' if your forum driver contains usergroup manipulation
functions (we don't support this, but wanted to be able to allow those willing to extend forum
drivers to use our full eCommerce framework)
 lots_of_data_in_* – set this to '1', with '*' replaced with a database table name, if you want
the ocFilter mechanism to work with recursive db lookups rather than one huge flat lookup
 textmate – set this to '1' if you are developing on a local machine and use the Apple Mac
TextMate editor. It will cause TextMate editing links to come up in stack traces.
 disable_iconv – set this to '1' if your iconv extension causes PHP to crash
 disable_mbstring – set this to '1' if your mbstring extension causes PHP to crash
 no_admin_menu_assumption – set this to '1' if you don't want breadcrumbs to hardcode the
default admin and CMS zone 'sectioned' navigation system
 always_banners – set this to '1' if you want to pretend the 'banner_free' permission does not
exist (hence shows banners even to admins)
 disable_allow_emails_field – set this to '1' if you do not want to give members a choice about
whether they can receive emails from the site / users
 disable_comcode_page_children – set this to '1' if you do not want the Comcode page children
feature to be active
 gallery_selectors – set this to a comma-separated list of numbers if you want to override what
"per page" jumpers are available when browsing the gallery
 ldap_login_qualifier – set this to "YOURDOMAIN\" if you are trying to use LDAP on an Active
Directory server that is not primarily on the same domain as it's users
 allow_alpha_search – set this to '1' if you'd like to pass in template data for alpha-jumping for
the member directory (uses extra server resources)
 has_low_memory_limit – set this to '1' if you're using a server that has a low memory limit
that cannot be overridden, yet PHP reports the limit as off (1&1 hosting does this).

Copyright ocProducts Ltd. Page 71 of 74
Hidden 'keep' parameters

'keep' parameters are placed inside URLs in order to give ocPortal some extra information when
loading a page. Their URL-presence is automatically relayed/preserved in all ocPortal links within the
page. To enable a 'keep' parameter, simply append &<name>=1 to the URL (replace '1' if appropriate,
but usually we do use '1' to enable). If there are no parameters in the URL and short-URLs are not
enabled, use a '?' instead of a '&'.

The 'keep' parameters available are:


 keep_noiepng – set to '1' to disable the IE 6 PNG transparency workaround
 keep_cache – set to '1' to temporarily enable cacheing
 keep_no_ext_check – set to '1' to force the validator to not check dependency files
 keep_force_htaccess – set to '1' to force the OCF login to go via htauth in precedence over
other login methods (e.g. login cookies, sessions)
 keep_textonly – set to '1' if the 'text only' stylesheet is to be used
 keep_simplified_donext – set to '1' to temporarily act as if the simplified do-next option is
turned on
 keep_pda – set to '1' to pretend you're using a PDA
 keep_has_js – set to '1' to force ocPortal to think you have Javascript (useful if the has-JS
cookie isn't saving yet you need to use a JS-based interface)
 keep_forum_root – see root below; this one works for the forum and is a 'keep' parameter due
to how the forum has links going all-over
 keep_ldap_debug – set to '1' to forcefully show LDAP errors, when supressing them might
normally be wise due to over-sensitivity
 keep_hide_mail_failure – set to '1' if you want to avoid showing e-mailing errors
 keep_huge – set to '1' if you want to override the precautionary filesize check used by the JS
validator
 keep_show_query – set to '1' if you want the SQL query used by a search to be echoed out
 keep_just_show_query – set to '1' if you want ocPortal to echo out the query as above, but
then to exit before running it
 keep_currency – set to an ISO currency code to tell ocPortal which currency you use
 keep_country – set to an ISO country code to tell ocPortal which currency you're in
 keep_cat_display_type – set to a number representing the catalogue category display-type to
try out (0=entry-tables, 1=lists, 2=matrix)
 keep_id_order – use this in conjunction with the admin_ocf_groups page to force a usergroup
reordering based on the named DB field of your choice
 keep_backup_instant – set this to '1' if the backup module should do a backup instantly, rather
than in the background (useful for debugging, as you can add 'echo's to the backup code)
 keep_backup_alien – set this to '1' if the backup module should only be backing up non-
ocPortal files (i.e. do a backup of the website, but not the software)
 keep_module_dangerous – set this to '1' to allow uninstallation of modules that are locked
 keep_preserve_ids – set this to '1' when doing an OCP-merge import, to preserve ID's in the
import (and not import duplicated ID's); this is not supported officially
 keep_theme – set this to a theme name, to temporarily try out a different theme
 keep_safe_mode – set this to '1' so that where possible, any customised files will be ignored;

Copyright ocProducts Ltd. Page 72 of 74
the default theme will be used
 keep_fatalistic – set this to '1' if terminal warning errors should be handled as terminal fatal
errors (which include stack traces); useful for debugging the origin of an error message
 keep_firephp – set this to '1' to dump information to FirePHP such as a list of permission
checks that occurred. It only works for authenticate administrators, but if the administrator is
using 'keep_su' to masquerade as another user it will work (which is particularly useful for
debugging permissions problems).
 keep_show_parse_errors – set this to '1' if you think you have a corrupt PHP file but you can't
tell which as your PHP display errors option isn't on (and hence just get blank screens). This
will allow ocPortal to generate stack traces for most corrupt files it tries to include. Note
another cause of blank screens can be the PHP memory limit being exceeded.

The following 'keep' parameters have interface triggers, but are also handy for manual activation:
 keep_markers – set this to '1' if you want template start/end markers to be output in the page
HTML source when viewed in ocPortal
 keep_novalidate – set this to '1' to force page output validation to not occur
 keep_su – set this to a username or member ID when logged in as admin, to pretend to be
that member. May choose the guest member ('Guest' on OCF by default).
 keep_theme_seed – set this to kiddie or random or a 6-character HTML colour code, to get
dynamic themegen going for your page view (this is very slow, but fun!); you must be staff,
with a confirmed session, for this to work
 keep_theme_dark – used in conjunction with keep_theme_seed; set to '1' if it is a dark seed
 keep_print – set this to '1' to render a 'printer friendly' version of the page
 keep_lang – set to a two-letter ISO language code to tell ocPortal which language to use
 keep_session – this isn't useful for manual editing, but it is used to note session-IDs when
[session-]cookies are not working
 keep_no_xhtml – do not output an XML/XHTML mime type even if in debug mode

The following aren't 'keep' parameters, but are still useful and otherwise behave in the same way:
 auth – set to '1' to make ocPortal request HTTP authentication. This is useful for RSS URLs, if
you want the feeds to be generated as authenticated.
 wide – set to '1' if you don't want to show side panels
 wide_high – set to '1' if you don't show to show panels or the TOP/BOTTOM
 root – set to a category ID for the current viewed content type to trick the breadcrumbs into
showing a 'virtual root'; this can also be activated by browsing to the category you'd like to be
virtual root (as staff) and then clicking on the final link in the breacrumb chain (the link
representing the current category)
 start – set to the start number in the result browsing
 max – set to the maximum number of results to show per-page
 page/type/id – I hope you know what these ones do ;)
 kfsXX – this isn't useful for manual editing but people ask what it's for; it's to preserve the
browse positions in forumviews, such that when returning, the browse positions are
remembered

Copyright ocProducts Ltd. Page 73 of 74
Hints for making websites for other people
ocPortal is a great platform for web developers. A web developer can build all kinds of sites using
ocPortal – from simple new media sites, to social networks, to seeminly-bespoke sites such as property
directories.

However, if you're making a website for somebody else it is is important to bear in mind they probably
do not want to learn ocPortal like you have.

Here are some hints:


● You might want to use the 'debranding' feature in the Admin Zone (Tools section)
● You might want to give the user a non-admin username (e.g. a super-moderator user instead),
and then set Admin Zone permissions so that many unnecessary pages are hidden. ocPortal is
then smart enough to automatically simplify it's interface to compensate.
● If you have to hand code a Comcode page with HTML then include the following hint in it:
{$,page hint: no_smart_conversion}
This will stop the WYSIWYG editor trying to convert the HTML back into Comcode, which can
mess up hand-crafted markup and CSS (normally WYSIWYG only guarantees to preserve
style/structure created within itself).
● When deploying make sure to get your canonical URL right. Ask the client whether they want
'www.' in their URL or not. Set up a .htaccess file (http://seoclass.org/canonical-redirects-
htaccess-and-seo) that redirects the non-canonical URL to the correct one. This will remove a
lot of headaches related to duplicate cookies, bad SEO, confusion, and ocPortal error messages.
● Always, always, test the e-mails a site gives out. It is very embarrassing if you accidentally
customise your MAIL template in a custom theme and then find e-mails are being sent out of
the Admin Zone using the default theme and ocPortal logo. Always save your MAIL.tpl in the
default theme, and make sure that the default theme's 'logo/-logo' and 'logo/trimmed-logo'
theme images correctly reflect your design. And, test it! If you made complex changes, test in
different e-mail programs/webmail-apps.
● Develop using a staging/developer website, and deploy to live/customer-demo after you've
finished all changes. This primarily involves uploading changed files. ocPortal 4.2+ has a great
tool for transferring data between ocPortal sites using XML too, if you find you are needing to
sync data.
● In particular, do not make theme image changes using ocPortal's theme editor, as these are
really painful to sync. Make use of ocPortal's automatic theme image detection by simply saving
new theme image files into appropriate directory locations; note you will need to clear the
caches if you are overriding a default image.
● Remember when uploading to a live site that you do not want to overwrite customised Comcode
pages, or other files that may have since been changed. Be careful what you upload, and keep
backups.

Conclusion

We’re glad you’ve made it to the end of our Code Book. Thanks for reading this far, and we hope you
find ocPortal an effective and enjoyable environment to code for.

Copyright ocProducts Ltd. Page 74 of 74

Potrebbero piacerti anche