HTML Logo by World Wide Web Consortium ( Click to learn more about our commitment to accessibility and standards.

Moving forward with Composr

ocPortal has been relaunched as Composr CMS, which is now in beta. ocPortal 9 will be superseded by Composr 10.

Head over to for our new site, and to our migration roadmap. Existing ocPortal member accounts have been mirrored.

ocPortal Code Book

For ocPortal version 9.0
« Return to Code Book table of contents

Back-end (continued)

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 very fast, they do not know how to directly access the actual data structures used, and have an overhead in their interprocess connection system, 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 not enabled (by default it isn't):
  • via the eAccelerator/mmcache/APC/xcache PHP extensions (requires PHP extension)
  • via the memcached server daemon (requires PHP extension to access it)
  • via file writing (for testing only – not 'concurrent' safe for use on live sites)

The usage of the cache is very simple. You can:
  • read a named (nameable by any data structure, like an array) entry (persistant_cache_get)
  • write one named entry (persistant_cache_set)
  • delete one named entry (persistant_cache_delete)
  • delete them all (persistant_cache_empty)
As you can see, each entry is given a name. The name identifies the data being cached. It can be a string, but usually it is actually an array, specifying a set of parameters that define what is being cached; usually the first entry in the array is a standard string to identify what's being cached, and the rest detail the situation the cache entry is valid for (e.g. a theme name the cache entry is built assuming, a list of usergroups the cache entry is valid for).

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 (i.e. do not assume all users will use the same theme by caching theme-tainted data without using the theme name as part of the cache identifier)
  • 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 put user-specific stuff in templates 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). If you can't do this, and the data does contain user-specific data, you'll need to cache against each user, and that is very inefficient
  • 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 because they are relatively rare, compared to total hits a site gets; whilst it is preferable to be able to delete specific objects, that's not always possible (e.g. with Comcode pages, they are cached against theme, but having a load of loop code to selectively choose what to decache would be excessive)
  • make use of cache layers: e.g. we didn't optimise some functions in ocPortal because higher level block caching would often make my optimisation almost pointless
  • cache what is used most: we want to support lots of users, so we need regular website access use cases to be most efficient – making admin actions more efficient is almost pointless
  • 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 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!

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.

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
Web page display sources/site.php Pre-loaded and available in global scope
Forum, members, and usergroups (forum driver layer) sources/forum/*.php
Use via the $GLOBALS['FORUM_DRIVER'] object.
Forum, members, and usergroups (OCF) sources/ocf_*.php Needs require_code. Only use these functions if you can assume OCF is running.
Database sources/database.php
Use via the $GLOBALS['SITE_DB'] or $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 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, comments, etc) sources/feedback.php Needs require_code.
Files sources/files.php
API docs (was created before the API was split into the above files)
Needs require_code if you need anything in files2.php.
Forms (for full tabular form interfaces, not for any time you need a standalone input field) 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 internationalisation sources/lang.php
API docs (was created before the API was split into the above files)
Needs require_code if you need anything in lang2.php.
Filtering syntax (ocFilter) sources/ocfiltering.php Needs require_code.
Permissions sources/permissions.php
API docs (was created before the API was split into the above files)
Needs require_code if you need anything in permissions2.php.
Content submission security sources/submit.php Needs require_code.
Archiving and compression sources/tar.php
sources/m_zip.php (use the PHP API, this implements that if the PHP zip extension is not available)
Needs require_code.
Templating (Tempcode engine) sources/tempcode.php Pre-loaded and available in global scope
Templating (template wrapper functions) sources/templates.php Pre-loaded and available in global scope
Templating (sophisticated interfaces) sources/templates_confirm_screen.php
API docs (was created before the API was split into the above files)
Needs require_code.
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 image management) sources/themes.php
API docs (was created before the API was split into the above files)
Needs require_code if you need anything in themes2.php.
Data validation sources/type_validation.php Needs require_code.
File uploads sources/uploads.php Needs require_code.
Web services sources/xmlrpc.php Needs require_code.
XHTML manipulation sources/xhtml.php
API described in support.php (has since been split)
Needs require_code.
URL generation sources/urls.php Pre-loaded and available in global scope
Block, page, and zone querying and maintenance sources/zones.php
sources/zones3.php (see zones.php API doc – has since been split)
Needs require_code if you need anything in zones2.php or zones3.php.
Add/edit/delete forms sources/aed_module.php Needs require_code.

Note that this is used to provide a base class. Many ocPortal modules use it, but as a base class you should never need to override this file (because you can just override or add methods when you inherit from it).

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.

Database structure

We describe ocPortal's database structure in a separate document.

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'.

Storing extra member fields

Custom profile fields allow webmasters to add new fields to member records, extending the system. This ability is often needed by programmers too, if their functionality requires extra data to be stored for individual members. It is ideal for this to be done using custom profile fields, edited on the same edit-profile screen as their existing fields. However, it is not possible to predict what the ID numbers of custom profile fields will be (necessary for recall), so ocPortal provides a special mechanism for programmers that allows definition and recall of customer profile fields, under identifiers of their choosing.

To do this:
  1. Add a custom profile field in your module's installation code, via a regular install_create_custom_field function call on the forum driver. This is a special function created purely for the purpose of creating system member profile fields. It works on any forum driver, not just OCF – for OCF it sets OCF custom profile fields, and on most forum drivers it also sets custom profile fields, but on a few it might just alter the database structure directly.
  2. Pass in the name of the custom profile field as '<your-chosen-name>' (e.g. 'example'). It will be stored with an 'ocp_' prefix, which indicates it is a system field.
  3. OCF-only. Define a language string 'SPECIAL_CPF__ocp_<your-chosen-name>' that contains the true human-readable name. You can define this in your own language file.
  4. OCF-only. Write a new 'systems/ocf_cpf_filter' hook. If you want your field to be editable, you need to include it as a key in the returned array of the to_enable function (see existing hooks for examples). You can use one hook file for lots of fields. Your function should 'require_lang' the language file you defined your strings in.
Retrieve your field using the get_ocp_cpf function, or in templates with the '{$CPF_VALUE}' symbol, omitting the 'ocp_' prefix (i.e. just use the name you set).

OCF stores the data in two tables (you should not need to know this, but it may be useful if you are manually checking the database during the debugging process):
  • f_custom_fields – this stores the field definitions, one field definition per row. Think of it being like a schema.
  • f_member_custom_fields – this stores the actual field values, each row represents a member and the field values are spread across normal database columns (aka fields).

Showing new links on member profiles

To add new links to member profiles, write a 'modules/members' hook. Look at existing hooks as examples.

Running extra startup code

To run extra startup code write a 'systems/startup' hook. You can set it up to execute at a number of points in the bootstrapping process.

Adding to the Admin Zone/CMS menus

To do this write a 'systems/do_next_menus' hook. Look at existing hooks as examples.

Adding a new symbol, to refer to custom data and functions within templates

To add a new symbol you'll need to write a 'systems/symbols' hook. This is a very useful technique for extending the Tempcode system.

Running scheduled scripts

To run a scheduled script you'll need to do it via writing a 'systems/cron' hook. For it to work, CRON must be set up on the server and tied into ocPortal (described in the ocPortal documentation). Look at existing hooks as examples. Alternatively, if you do not want to bother setting up CRON, or want to see the script output (e.g. errors) when debugging, just call up http://yourbaseurl/data/cron_bridge.php manually in the browser.

Running temporary code

If there's some code you want to run once (e.g. if you are running a test, or running a quick bit of code to fix some data) you may find our data_custom/execute_temp.php useful.

The advantages to this file are…
  • It automatically loads up extra code useful for doing common structural changes (e.g. menu changes, module management). In other words you don't have to call up so much code yourself.
  • It is configured to output plain text (text/plain mime-type), so you don't have to template any debug output you put out. If you see if output something other than plain text then that's probably because your code generated an error, which will cause a text/html mime-type.
  • It exists for no other purpose than running temporary code. You won't break anything by putting your code in there.

Just write your code into the execute_temp function, load up the file by URL, and the code will run. Then delete your code when you're done (actually that's optional, unless the code is dangerous in some way and should never be run again by a hacker – so you can keep the code around a bit if you want to use it again later).

Tip: Often when a new version of a module is being written the upgrade code for that module will go through a number of iterations (e.g. maybe a new config option is added, at a late stage). The result of that is the automatic upgrade process won't work because it can only track full module upgrade jumps (i.e. it has no way of tracking small additions and changes to the upgrade code within a single version). If you need a little bit of new upgrade code to run copy&paste it into execute_temp.php to force it to run that way.

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:

PHP code

@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:

PHP code


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 (a fatal error is one that terminates normal execution – because execution cannot finish). These stack traces are amazingly good at helping you find bugs, so if you get one have a close look at it.

A stack trace indicates the function call chain from the call of the script handling your current URL (at the bottom) to where ocPortal failed (near the top). In other words, as you look through the trace you are looking back through each function call all the way back to the top level script that the URL is calling. It doesn't show what the code has finished doing, only what it was doing when it failed and the function call chain that led to that action. At any level you can see the file the call was on, the line number, the function called from that line, and the arguments passed to that function (although large arguments like big arrays and Tempcode are omitted).

Lateral reasoning usually allows spotting of the source of a bug in a few seconds, although sometimes it can be more difficult. Often the bug is caused by unexpected parameters, and you can see them reflected in the stack trace and laterally work out why those parameters were passed and thus work out how to solve the problem.

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. Typically anything above the fatal_exit or ocportal_error_handler function calls relates to the error handling process itself.

Tip: If you are getting a 'The requested resource does not exist.' error then you can find a stack trace for when the error is generated by adding "&keep_fatalistic=1" to the URL. This is useful because there is otherwise no way to find where these errors come from (it would have required too many new language strings to be specific about them all).

How Tempcode works

Thumbnail: The Tempcode object structure

The Tempcode object structure

The internals of Tempcode are enormously complex.

It has to achieve a number of key design goals (these are implemented in ocPortal's Tempcode engine – you don't need to worry about them, and that's part of the magic of Tempcode – for documentation see the ocPortal Tempcode programming tutorial):
  • 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
    • The Shift encoding feature ('SHIFT_ENCODE' directive to set, 'SHIFT_ENCODING' symbol to get) has to work. Shift encoding provides the ability to define things in one template, and have them used in another: clearly something rather difficult for a template engine to implement.
    • 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 via parameters. If you pass some Tempcode to a template as a parameter, that parameter is encoded into the Tempcode tree of the result of the template call.

Each Tempcode object consists of a series of 'closures' (stored as a 'seq_part'). 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. A Tempcode object represents more than one template call because Tempcode gets attached together using the 'attach' method. This is why we say 'seq' – as in, the sequence of attachments stored inside a particular Tempcode object.

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 via code_to_preexecute (ones that were defined at run-time, such as attached string literals), and some are defined in the ".tcp" files.

Even more detail (you probably will want to skip this)

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.

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

This section is mostly intended for ocProducts staff.

If ocPortal is running out of a git 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.
  • 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.
  • 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.
  • To save time you can define language strings inline in PHP code, and then ocPortal will automatically find the closest matching loaded language file to the name of the page you are running, and then move your inline definition into that file whilst removing it from your PHP file. For example, you can write: do_lang('EXAMPLE=This is an example'); and as soon as it runs for the first time the definition will be shifted into a proper language file. Note that if the call does not get executed, nothing is done – but that's a good way to see if you're not testing your code fully.

You can turn off most of development mode by putting "&keep_no_dev_mode=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. 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.
  • 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'


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).

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
  • 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.

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:
  • 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).


For technical information about how eCommerce is achieved in ocPortal, including how products work, see the eCommerce tutorial. This tutorial goes into a lot of programming detail.

For reference, the described eCommerce hooks have the following methods:
  • product_info
    This is a custom function for hooks to report their products' information. It is an array, where each entry has a key that is the product codename and a value that is an array that gives the attributes of one product (the product type, the price, the name of the custom function, an array of product-type-specific details which is often empty, and the product title). See other hooks for examples. You need to implement this for the hook to be usable in any way. Once implemented you will be able to use the ocPortal 'purchase' module to see your hook's products, and select them for purchase.
    Valid product types are:
    1. PRODUCT_PURCHASE_WIZARD (a one-off purchase)
    2. PRODUCT_INVOICE (an invoice payment) – you are very unlikely to need this one
    3. PRODUCT_SUBSCRIPTION (a subscription payment – the product details array should contain 'length' and 'length_units' values to define how the subscription will work). 'length_units' can be 'd', 'w', 'm' or 'y'. An example product details array (this would be for a subscription that recurred every 1 month):
    4. array('length'=>1,'length_units'=>'m')
    5. PRODUCT_OTHER (something that is not purchased via the purchase wizard, but done manually – usually it is something the website staff are adding manually to show up on the eCommerce charts, not something actually related to the website itself) – you are very unlikely to need this one
    6. PRODUCT_CATALOGUE (a shopping cart product) – you are very unlikely to need this one
    7. PRODUCT_ORDERS (a full shopping cart order) – you are very unlikely to need this one
  • get_needed_fields – This function will return field Tempcode, for inclusion in a purchase form.
  • set_needed_fields – This function processes the data entered from the purchase form (get_needed_fields), or if there was no purchase form it may act on URL parameters that might be present. Often this function is used to save data into the database prior to purchase (after purchase the data would then be retrieved/processed/marked-active). You can assume this function always runs in a screen after get_needed_fields, and therefore you can use the normal functions like post_param to read in the data. This function returns the purchase-ID, which henceforth is used to refer to the sale (and thus, retrieve it's data). The purchase-ID is often a database key, for the case of a row being added – but also often it instead is more implicit (e.g. a member ID, or a chosen codename).
  • get_identifier_manual_field_inputter – This optional function (often not used) will return field Tempcode for allowing an administrator to select an identifier when manually triggering a purchase. Often identifiers are automatically worked out (in-fact, often they are member ID's of the purchasers), so this method allows a more user-friendly open selection for the identifier for the case where the identifier likely is not auto-computable (i.e. when the administrator is purchasing on behalf of a third-party).
  • get_agreement – This function will return license text to be approved by the buyer. Some product do not need license agreements and hence do not need this method.
  • get_message – This function gets a message that will be displayed inside the purchase wizard. Often a very general description of the hook's products will be shown from here.
  • is_available – This function returns the status of product: whether it is available or not. For example, often products are not available to Guests (because there is no member ID to save a purchase against), or a product may not be available due to it being out of stock.
  • A user defined function – A user defined function to run while finishing an order transaction (it will be called automatically). It's name is that you added as an array element in the product details array returned from the get_products() function. Note this is a function, not a class method.
  • get_product_dispatch_type – This optional function will return the dispatch type for a product. It will return "manual" or "automatic" – most products are "automatic" (they require no manual effort to be dispatched – the code sorts it out automatically).
  • enable_manual_transaction – This optional function will return whether manual transactions (from the Admin Zone) are allowed or not. If enabled, uncompleted transaction's orders will display in Admin zone->Usage->Ecommerce >Manual transaction screen. Administrator can trigger the transaction as complete if he/she got an external payment via direct cash or cheque etc.


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 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. There are view permissions and specific permissions (what other software calls a 'privilege'). Specific permissions can be overridden on a per-page and per-category basis, but otherwise apply globally from a simple list of settings.

Note: View permissions are handled differently to specific permissions. Instead of having an 'overriding'/inheritance 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 simply string codes (e.g. 'downloads') used to specify what kind of resources (e.g. download categories) identifiers are referencing – in order to specify which view permissions or specific permissions are set. For view permissions it's simplest: permissions are set against a permission-module (e.g. 'download') and an identifier (e.g. '3') and looked up correspondingly.
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 global specific permissions are overridden for pages, the page that the overriding is defined for is usually the name of the content management page; for example, if the 'add_midrange_content' specific permission is overridden (this permission controls whether a member may submit a 'medium-impact entry', like a download or image) for the download system as a whole, it would be overridden with the page name 'cms_downloads'.
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. E.g. if we were overriding for a download category '3' (permissions module is 'downloads'), we'd be referencing '3' and 'downloads'.
Note the distinction between overriding for a page, and for overriding for a permission-module – for many permissions webmasters can override for both, meaning page names and also permission-modules/identifiers together need to be passed to the API functions. If the permissions tree editor allows an override to be set, you must make your permission calls respect this by referencing the correct permission-modules and page names. It is the get_sp_overrides function of a module (e.g. 'cms_downloads') that defines what permissions may be overridden.

Specific permissions are checked and set purely through the permissions API ('has_specific_permission'), using permission-modules (e.g. 'downloads') and identifiers (e.g. '3'). There are no hooks needed, just:
  • the name of the specific permission
  • the name of the page that controls it's overridden value (if there is one, e.g. 'cms_downloads')
  • the combination of permission-module and identifier (if it can be overridden per-category)

View permissions are checked using the has_category_access function, or other similar functions if zone or page view permissions are being checked.

(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 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:

PHP code


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:

PHP code


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:

PHP code


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:

PHP code


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

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

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:

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

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 (roughly, this code is liable to go out of date a bit as we evolve the system)…

In the install function for your module, in the table creation…

PHP code


In the uninstall function for your module…

PHP code


In the run function for your module…

PHP code


In the get_form_fields function header of your AED CMS module…

PHP code


In the get_form_fields function of your AED CMS module…

PHP code


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

PHP code


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

PHP code


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

PHP code


In the model add/edit actualisation function query_insert/query_update calls…

PHP code


In the model delete actualisation function…

PHP code


In the view function for your content…

PHP code


(the above advice is currently out-dated, the parameters required by the API have changed, and there is a new function, embed_feedback_systems, that encapsulates all of the above)

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

PHP code


AED modules

Most ocPortal add/edit/delete interfaces are done via an AED module, as this saves code and makes it easy to be consistent. AED modules get the following automatic functionality:
  • Add/edit forms
  • 'Choose what to edit' forms
  • Permission checking
    • specific permissions for add/edit/delete
    • ownership checks for edit/delete
    • view permissions for edit (you can't edit what you can't view)
    • unsetting of validation if the user can't bypass validation
  • sending out new-content emails to staff
  • Standard SEO fields
  • Standard award fields
  • Standard 'you should log in' messages
  • Standard 'maximum file size' messages
  • Standard 'please take care' messages
  • Standard 'Do next' screens after performing an action
  • Point awarding for content submissions

There are lots of AED module's to look at as examples in the CMS zone. This section describes how they work in general terms.

Each AED module extends the 'standard_aed_module' base class. This class has many internal methods that abstract the interface and actualisation functions of the AED functionality, and it actually takes hold of the whole AED process, limiting the burden on us just to defining a few special methods/properties. We inherit from the AED class in our module to use its functionality. To do this it must first require up that class, just before the module is defined:

PHP code


Rather than an AED module having a 'run' method like most modules, they instead are given a 'run_start' method. This method is called automatically from the base class's own 'run' method, which handles screen types that are standard to all AED modules. The AED module defines the default screen type to be the 'ad' screen type, unless you define a 'misc' method within your own code.

AED module's define entry-points in a get_entry_point method like other module's do, however they also tie into the base class's function to return a compound result, as follows:

PHP code

function get_entry_points()

(in this example the AED module adds one extra entry-point, 'misc').

The standard entry-points defined by an AED module are:
  • ad, The UI to add a resource.
  • _ad, The actualiser to add a resource.
  • ed, To choose a resource to edit/delete.
  • _ed, The UI to edit/delete a resource.
  • __ed, The actualiser to edit/delete a resource.
(additional sets if a category or alternate AED module has been chained on, via cat_aed_module [ac, _ac, ec, _ec, __ec]/alt_aed_module [av, _av, ev, _ev, __ev]).

AED modules define the following standard methods:
  • get_form_fields, To get form fields for the add/edit forms; this function takes parameters, but they must all be optional – the parameters will only be passed for an edit form (see below).
  • fill_in_edit_form, To pass in existing field values to 'get_form_fields' for an edit form; this function is passed the ID of the record being edited/deleted, which allows you to load up the data from the database in order to pass it through to 'get_form_fields'.
  • add_actualisation, To handle 'add' actions (usually ties into a model function).
  • edit_actualisation, To handle 'edit' actions (usually ties into a model function); this function is passed the ID of the record being edited.
  • delete_actualisation, To handle 'delete' actions (usually ties into a model function); this function is passed the ID of the record being deleted.

Often they also define the following:
  • ed, If the auto-generated "choose what to edit" screen needs customising.
  • nice_get_ajax_tree, If we are using tree list control in edit page, we need to define nice_get_ajax_tree function in our module. That tree list is using for selecting the editable data. We need to give the name of an 'systems/ajax_tree' hook file which create the data for tree list to the ocPortal form_input_tree_list function. The hook file will return XML data for the tree list and the built-in ocPortal AJAX functions (called up by 'form_input_tree_list') will receive that data and fill it in tree list. In every node expansion in the tree list another AJAX request is generated to expand the particular node's ID. The AJAX script will find the child data for that parent ID and return it's child data.
  • get_submitter, If there is a complex calculation required for who submitted a resource (required for the purposes of permission checking).
  • do_next_manager, If the standard do-next results screen is too basic (e.g. if extra icons are needed).
  • misc, The function to show the screen when the module is loaded up. If we do not define this, 'misc' acts the same as 'ad'. If you define a misc method (which many AED modules do do) then you must call it yourself from your 'run_start' method. Often 'misc' methods are used with do-next-manager interfaces, to provide a front end for choosing different options.

AED modules usually define the following standard properties:
  • lang_type, the stem for languages strings the module would use. E.g. if you say 'FOO', the 'ADD_FOO', 'EDIT_FOO', and 'DELETE_FOO' language strings would all need to exist.
  • archive_entry_point, an entry-point where a user can browse everything of the resource-type handled by the AED module.
  • view_entry_point, an entry-point where a resource handled by the AED module can be viewed, with '_ID' in place of where the ID value would be.
  • permissions_require, a value of either 'low', 'mid', or 'high' – for the kind of permissions used for resources being edited.
  • permission_module, the permission category code that identifies the module w.r.t. category access checks.
  • permissions_cat_require (usually the same as permission_module).
  • permissions_cat_name, the name of the field which holds the value that permissions are checked with respect to (usually the category field).
  • array_key, the name of the ID field.
  • orderer, the name of the field used to order results in the 'choose what to edit' list and also the title field.
  • title_is_multi_lang, whether the title field (orderer above usually) needs to be dereferenced as a language code (i.e. it goes through the 'translate' table).
  • table, the name of the database table used.

Often they also define the following:
  • user_facing, whether regular users are likely to have access to the module (adds in extra user-friendly references that might seem a bit odd for a regular administrative module).
  • send_validation_request, whether to send out e-mail notifications to staff when non-validated resources are submitted.
  • upload, if set it should be 'image' or 'file', and has the effect of extra 'maximum file size' messages being output, as well as other extra mechanism preparation.
  • javascript, Javascript code to be included when add/edit forms are shown; it is common to put code in here to handle field alternation (standard_alternate_fields), automatic field disabling based on values in other fields (e.g. not allowing specifying of the fine details to something if that something is not entered at all), or extra custom field value validations.
  • non_integer_id, whether the ID field for the table is a string (a 'codename' in ocPortal terminology).
(there are also many more to choose from – see what is defaulted at the top of aed_module.php).

Often the following are overridden at run-time, from the run_start method:
  • add_text, a message shown on the add screen (usually set via do_lang_tempcode).
  • edit_text, a message shown on the edit screen (usually set via do_lang_tempcode).
  • cat_aed_module, a reference to a second AED module instance used for managing add/edit/delete of categories; AED modules can only add/edit/delete one resource set, but you can use this to pair up add/edit/delete of entries (the main module) to add/edit/delete of categories (the referenced one). Once you set 'cat_aed_module', the main module will automatically relay ac/ec/_ec/__ec requests to the cat module, but continue to handle ad/ed/_ed/__ed requests itself.

Attachment-supporting fields: Language lookup, 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 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).

Non-bundled addons

If you are working with the ocPortal git repository, you can commit addons into git without them having to be a part of ocPortal.

Only do this if the addon would generally be useful to a decent number of people – things specific to one website, or one specific kind of website, should not be in git.

To add the addon as non-bundled:
  • Decide on the informal and formal names for your addon (e.g. "Fun time" and "fun_time")
  • Save files into *_custom directories wherever possible.
  • Create a section in data_custom/addon_files.txt and list all the files relating to your addon.
  • Add a row to data_custom/addon_details.csv
  • Ideally put a screenshot in data_custom/addon_screenshots
  • 'Add' the files to git, commit and push

Your addon can then be managed through the bugfix push system, and will be pushed when the addons are bulk-uploaded (done as a part of the new version release process).