Code Book, part 2 (Front-end)

Written by Chris Graham (ocProducts)
« Return to Code Book table of contents


Front-end

Design/copy Coding Standards

The developers maintain a checklist for standards. These standards used to be listed in this Code Book, but there are now too many to clearly explain here, and we needed a document we could morph and use on a routine basis for quick review.

Standard language strings you can re-use

The following language strings from global.ini and critical_errors.ini are commonly reused:
  • Nothing here: MISSING_RESOURCE, NO_ENTRIES, NO_CATEGORIES, NONE, NONE_EM
  • Actions: PROCEED, SAVE, SET, CHOOSE, USE, ADD, EDIT, DELETE, SEND, VIEW, MORE, BROWSE, _JOIN, _LOGIN
  • Choices and records: YES, NO, EXISTING, NEW, OLD
  • Users and usergroups: SUBMITTER, USERNAME, PASSWORD, GROUP, _USERGROUP, BY, BY_SIMPLE, GUEST, MEMBER
  • URLs: uploads and thumbnails: URL, IMAGE_URL, UPLOAD, THUMBNAIL
  • Resources: PAGE, ENTRY, CATEGORY
  • Table columns/fields, Identifiers: IDENTIFIER, CODENAME, TITLE, NAME
  • Table columns/fields, Properties: SIZE, DATE, DATE_TIME, TYPE, VALIDATED
  • Table columns/fields, Other: AMOUNT, FROM, TO, REASON, PARAMETER_A, PARAMETER_B
  • Rating, sorting, browsing and results: RATING, SORT, SORT_BY, RESULT, _RESULTS, START, DONE, ROOT, NEXT, PREVIOUS

Templates and themes

Template files are stored on disk, and are defined with the standard "override system" of Composr; 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 Composr compiles the templates upon first-use.

The ultimate compilation target for templates is "serialised tempcode"; if the template is cached as this, it will be loaded directly into memory, and it will then be bound with the parameters of the template (for example, a download box template might take the download name and URL as parameters).

Tempcode is Composr'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 Composr language used for controlling template output) has the 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: SUBMITTED_BY=Submitted by {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.

A full description of the Tempcode language is available in the Tempcode programming tutorial. Read the Escaping section careful because it is extremely important for security.

WCAG notes

WCAG is the standard accessibility standard. Imagine, for example, a blind user using a website.

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

The Theme Wizard

Composr's default CSS uses equations to define colours, all defined against a seed colour. This allows us to define a large harmonious colour scheme programmatically, and thus allows the Theme Wizard to work (recalculating the colours against a different seed colour). It saves themers an enormous amount of time, as manually redefining each colour would be very time consuming, let alone recreating all the theme images within the colour scheme.

Equations look like this in the CSS…

Code (CSS)

{$THEME_WIZARD_COLOR,#d91522,red_highlight_text,80% FF0000 + 20% seed}
...

span.red_alert, a.red_alert, strong.red_alert, em.red_alert {
    color: {$GET,red_highlight_text};
    ...
}
 

The equations are calculated by an inference chain system. In other words, one equation may reference a colour defined in another equation. The Theme Wizard supports various arithmetically manipulates of colours, using colour science. Any colours defined in global.css may be referenced in any of the other CSS files.

If you are creating a new theme, or just working on a particular site, there is no need to use the Theme Wizard colour system when you add new CSS rules, or change existing ones. Of course because your theme will be derived from the default theme it will still have the Theme Wizard equations in it, coded against the particular seed colour you choose when you created your theme (or the default one if you didn't use the Theme Wizard) – but that's just to start you off and does not need to be maintained by you. Just embed your own colours directly into the CSS like you normally would, and replace any default equations that you want to. Even if you want the Theme Wizard to work with your theme, the alternative "HSV-shift" Theme Wizard method doesn't require equations to be defined.

If you are adding features to the standard Composr, you must make use of Theme Wizard colours. Almost always you will find there is a colour defined that is appropriate for you to use (see the big collection of them toward the top of the global.css file). There are also some Commandr commands you can use to search for what the nearest defined colour is to an exact RGB colour. If you do need to define a new colour, do so while maintaining the tidy organisation and naming conventions currently in use.

PNG images

As a standard Composr uses 32-bit PNG files over other image types, unless animation is required (.gif used instead). Soon will be able to do animation too via APNG though.

We use PNG files because 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).

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, there is one further problem with PNG images – various web browsers, including Microsoft Edge 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 through a free compression-optimisation program called PNGGauntlet (for Windows; use OptiPNG on a Mac), 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 (check) 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. Composr has a loose framework for producing templates that use JavaScript.

JavaScript libraries

Composr has an inbuilt set of JavaScript libraries, which are split across a number of JavaScript template files in the javascript[_custom] template directory. The core library that is loaded onto every screen is named just global.js. This core library is very general and doesn't know about Composr'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 chat.js) or to specific aspects of Composr (e.g. the Comcode editing interface uses editing.js). There are a few additional general purpose JavaScript libraries:
  1. ajax – AJAX call functionality, which should be used for all AJAX code.
  2. 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. jquery – Bundled jQuery library, used on some screens (but not usually included as a cross-site dependency).
  4. 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 an example.js template, use:

Code (PHP)

require_javascript('example');
 
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

Alternatively, you can load JavaScript from a template with a command like:

Code

{$REQUIRE_JAVASCRIPT,example}

You may add new JavaScript templates as needed. The instructions are the same as integrating a third-party library:
See the Integration of Composr and other installed scripts/applications tutorial.

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.

In Composr, JavaScript libraries are automatically minified to save on bandwidth, unless &keep_no_minify=1 is in the URL (very useful for debugging).

Useful Composr JavaScript functions

  • Escaping: escape_html, escape_comcode
  • Cookies: set_cookie, read_cookie
  • Introspection: get_elements_by_class_name, abstract_get_computed_style, browser_matches
  • Calling up dependencies: load_snippet, require_javascript
  • Viewport: get_window_width, get_window_height, get_window_scroll_width, get_window_scroll_height, get_window_scroll_x, get_window_scroll_y
  • Element positioning: find_pos_x, find_pos_y, find_width, find_height
  • Tooltips: activate_tooltip
  • Misc: set_opacity, add_event_listener_abstract, cancel_bubbling, keep_stub
  • HTML manipulation: get_inner_html, set_inner_html
  • AJAX: ajax.js: do_ajax_request
  • Animation: toggleable_tray, smooth_scroll, transitions.js: fade_transition

JavaScript event handlers

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

Code (HTML)

<a href="{BASE_URL*}/popup.htm" onclick="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.

Sometimes it is not appropriate to add an event directly to an HTML node, usually for one of these reasons:
  • You can't always make the assumption that a node will only have a single event handler
  • Sometimes event handlers need putting on generic events such as onload
  • Sometimes you want to add event handlers from inside JavaScript code, on-the-fly
In these circumstances you can add the event to an HTML node indirectly by using the add_event_listener_abstract function. This function works consistently across browsers (so long as you don't try and use this inside the event handler code).

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

You should use 'CDATA' for your <script> tag, like:

Code (HTML)

<script>// <![CDATA[
        ...
//]]></script>
 

It is important that:
  • You use the "//" bits. Usually Composr will be running as HTML even though it is marked-up as XHTML (this is a little known fact but is the case for almost websites out there, whatever they are running). HTML interprets everything in the script tag as CDATA automatically so everything inside there is parsed as JavaScript; the "//" bits are simply JavaScript comments to stop the CDATA bits being interpreted as JavaScript code.
  • You should actually use CDATA so the document does conform as XHTML.
  • You should not put it all on one line, because then the JavaScript line-comment will end up commenting all the code out and nothing will happen.
  • Don't use any Composr JavaScript except for JavaScript in the main global.js library, unless you have put it inside an onload event handler, like:

    Code (JavaScript)

    add_event_listener_abstract(window,'load',function() { ... });
     
  • This is because Composr does not want to pre-load any more JavaScript library code than is necessary via loading it in <head> (it stops the page rendering at all until the files have been downloaded), and we can't assume the order JavaScript will be loaded in compared to the order in which inline code is executed (it changes randomly, and between browsers). By putting our code inside an onload handler we force it to only run after all our dependent JavaScript files have loaded.

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:

Code (HTML)

<script>// <![CDATA[
/* Store a template parameter into a global variable.
Placing on the window object is the same as setting a global variable.
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):

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

Code (HTML)

<a href="#" onclick="output_my_parameter('{MY_TEMPLATE_PARAMETER;*}');">Click me</a>
 
In the JavaScript library:

Code (JavaScript)

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

Writing AJAX functionality

AJAX requests allow dynamic updating of webpages by making calls to the web server during the viewing of a web page.

AJAX requests can be either synchronous or asynchronous. A synchronous request runs linearly: JavaScript makes the call and then the browser will wait for the response before resuming execution. An asynchronous request involves a later (at an unknown point) 'call back' to a response handler (the "method") that we would write specially. Technically a synchronous-AJAX request is a contradiction (the 'A' in AJAX means 'asynchronous'), but the way people use the term AJAX means it's best we not worry about this. It's rare for us to use synchronous requests as it can make the browser freeze while it waits.

Detailed steps for writing code involving AJAX

  1. Write normal Composr screen:

    We'll assume you already have some working PHP code that calls a template.
  2. Load Composr JavaScript library from your PHP code:

    For adding AJAX functionality, we can use the functions defined in the ajax.js template (in particular, do_ajax_request). For using those functions, we should include ajax as a JavaScript dependency using this PHP code:

    Code (PHP)

    require_javascript('ajax');
     
  3. Write custom JavaScript code:

    We need our own JavaScript template file to implement our AJAX-tied behaviour, because the code is probably going to be too long to include in-line with the HTML. For our purposes we'll decide it is a JavaScript template named example.js.

    In our JavaScript template file, we typically define basically two functions if we are writing asynchronous AJAX. One function is for initiating the AJAX event (can be any name you like) and another is to handle the response of the AJAX script (has any name, but it must be passed to the Composr do_ajax_request function). If you are writing synchronous AJAX you can pass the null value instead of a function, and the do_ajax_request function will respond with the result.
  4. Load custom JavaScript code:

    Our JavaScript code must be loaded up from the PHP code by:

    Code (PHP)

    require_javascript('example');
     
  5. Write entry-point script:

    To handle the AJAX request on the server-side we will need to call a new entry-point script. We'll place in the data/ folder – or, if you are writing third-party functionality, it should go into data_custom/ instead.

    In the data/<script-name>.php, we require_code a file that will contain a new PHP function to implement our server-side handler code, and then call that function (and nothing else). We'll describe that in the next step.

    Note that the Composr entry-point script code is 99% boilerplate. Only the final few lines are customised, the rest is copy&pasted.

    Use entry-point scripts properly

    Don't try and omit making an entry-point script by calling index.php directly.

    You really do not want all the peripheral implications of a full page load (zone permissions, global HTML wrapper, etc). In other words, you are not going through a zone, you are not using the do_site methodology of loading up a Composr page.
  6. Write server-side handler code:

    It is a Composr convention to define our server-side handler function in sources/ajax.php, but we don't have to (and shouldn't if it's not a very core AJAX function to Composr). Remember if you are writing third-party functionality it should go into a file under sources_custom/ instead.

    Our PHP function should return a response in XML (i.e. XML format, with a text/xml mime type) – or, for the very simplest use-cases, in text format (i.e. with a text/plain mime type). In other words, run the correct header command before anything else. You can only return XHTML if you do it under an text/plain mime type.

    Note that you must use XML format in any of these situations:
    • You need to implement error handling and just returning a blank response upon some kind of known error would be insufficient.
    • You need to return structured data.
    If in doubt, use XML.

    Returning errors

    The only time in which XHTML is returned with a text/html mime type is if there was some kind of error and Composr outputs an error screen, in which case it would be automatically accompanied with an HTTP status code of '500', and any non-200 status code would prompt the AJAX framework to put out an error message.

    If you are writing your own webmaster-friendly error handling then you should put out errors in the XML frame Composr uses (described below), or you should encode your own error passing system into the pay-load data.


    The XML frame you should output from PHP should be structured as follows:

    Code (XML)

    <?xml version="1.0" encoding="charset"?>
    <request>
            <result>
                    (Your pay-load data goes here. It is likely this is XML,
                    with your own specific set of tags,
                    but it can be XML-encoded plain-text if you like.)
            </result>
            <message>
                    <error>(Some kind of error message that will be put in an alert)</error>
                    (Some kind of informational message that will be put in an alert)
            </message>
            <method>
                    (JavaScript function name that overrides what was passed to do_ajax_request)
            </method>
    </request>
     

    This example of the full usage. However normally you don't return any method or messages, so you will see real code is usually much simpler, like:

    Code (PHP)

    <?php
    header('Content-Type: text/xml; charset=' . get_charset());
    prepare_for_known_ajax_response(); // Make sure input data is converted, as JavaScript always uses utf-8 even if we are not set to use it
    echo '<?xml version="1.0" encoding="' . get_charset() . '"?' . '>';
    echo '<request><result>';
    $out=do_something_and_get_tempcode();
    $out->handle_symbol_preprocessing();
    $out->evaluate_echo();
    echo xmlentities($out);
    echo '</result></request>';
     

Once the server has returned a result to the client, it will be processed for handling.
  • For synchronous requests, the result object is returned from do_ajax_request linearly (asynchronous requests will return nothing from do_ajax_request). Code may then use the responseText and responseXML properties of this as required.
  • For asynchronous requests, the response handler (the method) function is called, with two parameters: ajax_result_frame (the full XML), and ajax_result (the pay-load XML). It is the responsibility for the method function to process the XML in ajax_result (ajax_result_frame is unlikely to be used by the method) – the code can assume the XML will be valid because HTTP status error handling and XML frame error handling will already have occurred.
There is an implied format contract between the PHP code we wrote and the JavaScript we wrote. In other words, we are not likely to have incompatibilities between the data of the server (PHP) and client (JavaScript) as you will have designed them together. Therefore you don't need to write any special error handling code to make sure the structure returned is correct, unless you've written in your own extra error handling into the pay-load.

Example:
  • Template – themes/default/javascript/ajax_people_lists.js
  • Data file – data/namelike.php (calls namelike_script() defined in sources/ajax.php)
  • Ajax php script – source/ajax.php

Debugging

Modern web browsers have excellent debugging capabilities for AJAX.

You can open up development tools and inspect the requests and responses of all AJAX requests that are happening.

Object-based callbacks (advanced)

You can either pass a function to the do_ajax_request function as your method, or you can pass an object that has a response function in it. The latter is useful if you want to make your response a neat part of an OOP approach.

Boxes

Composr uses boxes to show featured areas, including featured content and categories. The concept of boxes comes up in a few system layers:
  • CSS: The .box CSS class (and associated CSS classes)
  • Templates: The HTML markup referencing the above
  • Tempcode: The Tempcode BOX directive, for automatically generating box markup (not used much in v9+ due to it adding an unnecessary abstraction layer, but was common in previous versions when the markup was more complex due to a need to support old web browsers)
  • Comcode: The Comcode box tag, used for generating a box from Comcode
  • Content API: The render_<content-type>_box functions, and associated templates that they call up

The rest of this section will focus on how the content API uses boxes…

Almost every kind of content (and category) has an content type hook defined. That content type hook will specify how a box of that content (or category) will be displayed. The convention is that the content type hooks glue themselves to the render_<content-type>_box functions, which glue themselves to particular templates.

So, how do the templates work? Any content type will probably have two main templates used to display it:
  1. A box template (<content_type>_BOX.tpl) [what we are discussing here]
  2. A screen template (<content_type>_SCREEN.tpl)

Box templates are used on category listings, previews, search results, and when picking out content to display with an award. Box templates generally hold the following criteria:
  • Must be surrounding with standard box markup, or mainly consist of a table
  • If it is a box, must have a level 3 header within the box (boxes must not be presented as list elements – they are headed sections in boxes, and CSS may change the appearance of them if required due to the consistency of the box markup)
  • Will typically involve a title/thumbnail/caption, but it really depends what the content is; the SIMPLE_PREVIEW_BOX.tpl template may be used for a foundation for very simple box layouts where no dedicated template is needed
  • Must support display in both category and standalone contexts
    • Category contexts will not mention the content type or show breadcrumbs
    • Standalone contexts will make it clear enough what the content actually is, rather than merely showing a title/thumbnail/caption; for hierarchically stored content breadcrumbs should be included
  • Must primarily support a fairly minimal raw context, but possibly with the option to have additional attached bits such as edit links
  • Box templates should look neat, not too generic
  • Box templates should be flexible enough to show in a carousel, in a grid, or as standalone/sequential boxes
  • Box templates for categories should say how many child entries are inside the category

Category listings

As mentioned in the section above, category listings consist of a run of boxes. Composr has a standard block for displaying runs of boxes, the main_multi_content block. Where possible the module category listing screens will simply glue themselves to an appropriate call to that block with minimal other code (code for breadcrumbs, loading up metadata, checking permissions, providing a wrapper template, etc).

By gluing listings to a block, pagination can work via AJAX because a block is defined neatly as an autonomous region of a page. It also saves lots of code duplication – we need the blocks anyway so that webmasters have fluidity in how they can lay content out, so it makes sense to centralise around them. Lastly, it creates important consistency and maintains important building blocks as high quality.

However, things aren't always so simple. There are some exceptions because some content listings are not as simple as querying a category/Selectcode-string and displaying the contents. Here are the main cases:
  • the news system allows some quite complex rules about what to display, and uses multiple display types simultaneously. Therefore we have a custom main_news and a main_news block (and also a side_news_categories for organising by date).
  • the gallery system has to aggregate both images and videos. Therefore we have a custom main_gallery_embed block.
  • the calendar system display is organised by date. Therefore we have a custom side_calendar block.
  • the catalogue system comes with a range of available display types, not just boxes. This is because we need to give catalogue authors a wide-palette due to lack of ability to code in any assumptions. Therefore we have a custom main_cc_embed block.
  • the Personal Galleries feature may mean that empty galleries may be left around, and also download galleries need to be skipped.
  • the gallery provides a flow-mode display type which is inherently module-based.
  • other modules use very fine-tuned listings for display categories/content. For example, the chat lobby shows rooms as a compact list.
  • other modules make custom content queries which don't match up to what the parameters for existing blocks can achieve. For example, the authors module shows what downloads and news belongs to a particular author.

This abstraction quandary comes up in other places too. Often programmers will try and abstract everything and end up with a mess. For example, Composr CRUD modules define the form structure manually. We could try and automatically generate forms via the semantic database structure we already have – but for usability we need much more control over how each form will look in every case.

There is a constant battle between abstraction, and intuitive interfaces. Not everything can be simplified down to an instance of a common pattern. Therefore Composr abstracts the patterns in layers, and then module/block code will interface with the layer that is compatible with its requirements and fill in the gaps. This minimises code, while maximises the user experience, and allows new unplanned experiences to be created by webmasters who wish to interface with the APIs at various levels. In general, even if a block is not used to display listings, the code required is still minimal due to use of the render_<content-type>_box functions.

Why doesn't Composr just use blocks for everything?

Some people might also wonder why we have modules to display categories at all. Why is it not entirely fluid, done through putting the blocks where you want them? There are three main answers to this:
  1. Content needs a 'home' URL that the system knows, for many reasons (e.g. RSS linkage, XML sitemaps, notification URLs, …). It can't reliably second guess this via looking where blocks are.
  2. Simplicity! Composr is about having a good website quick, you shouldn't have to understand how to lay out your own skeleton, or do it for each project. Power should be something that is available, but the defaults should be simple.
  3. It is difficult to see how blocks could be used to navigate a category tree – the architecture for this would be really quite complex, considering there is no direct/easy association between URL and the parameters a block takes.

Some people might wonder why entry viewing isn't done via blocks, or really any other code other than category viewing. The reason is that we don't want a huge collection of blocks and lots of extra internal code complexity. So, we solve the issue of wanting to allow fluid site structure via the main_include_module block, which allows you to take a module screen and use it as if it was a block. Again, we try and represent what is commonly required in the default Composr structure, and coded in the simplest possible way, while also making the system fluid and malleable enough to make changes.

Making theme changes outside Composr

When you add/edit theme images inside Composr, it makes up a filename. But instead if you make the themes/<theme>/images_custom naming structure match that of themes/default/images, then Composr automatically finds them. This is better when working collaboratively with git, or if synching development and live sites, because a new database record is not needed to track the theme image reference.

Generally you should rarely use the Composr inbuilt theming tools when acting as a programmer. Just put the files in the right place and use your text editor to alter the CSS and templates. This makes for a more integrated, programmer-friendly, workflow.

The only caveat with theme images is that if Composr has already searched for it a theme image in your theme, and only found the default one, you'd need to empty the theme_images table to make it search again. That's safe as long as you only add/edit via filesystem and never from inside Composr.

Feedback

Please rate this tutorial:

Have a suggestion? Report an issue on the tracker.


Meta-analysis of the code-base

Language string word count

Here's a script for if you want to know some basic stats about Composr's English language pack:

Code (PHP)

<?php /*

 Composr
 Copyright (c) ocProducts, 2004-2017

 See text/EN/licence.txt for full licencing information.

 Meta Script:
   Count the number of words in Composr currently
*/


if (!isset($_GET['path'])) {
    exit('Must give a \'path\' parameter');
}
chdir($_GET['path']);

$count = 0;
$strings = 0;
$files = 0;
$path = 'lang/EN/';
$dh = opendir($path);
while (($f = readdir($dh)) !== false) {
    if (substr($f, -4) == '.ini') {
        $files++;
        $lines = file($path . $f);
        foreach ($lines as $line) {
            $bits = explode('=', $line, 2);
            if (count($bits) == 2) {
                $count += count(preg_split('/\s+/', $bits[1]));
                $strings++;
            }
        }
    }
}

header('Content-type: text/plain');

echo number_format($count) . ' words, in ' . number_format($strings) . ' language strings, in ' . number_format($files) . ' files.';
 

Top functions

Here's a script for if you you to found out what are the most common functions used in the Composr PHP code:

Code (PHP)

<?php /*

 Composr
 Copyright (c) ocProducts, 2004-2017

 See text/EN/licence.txt for full licencing information.

 Meta Script:
   Find top functions used in a codebase
*/


header('Content-type: text/plain');

if (isset($_GET['path'])) {
    chdir($_GET['path']);
}

$out = do_dir('.');
$calls = array();
foreach ($out as $file) {
    $code = file_get_contents($file);
    $matches = array();
    $num_matches = preg_match_all('#(\w+)\(#', $code, $matches);
    for ($i = 0; $i < $num_matches; $i++) {
        $call = $matches[1][$i];
        if ($call != 'array') {
            if (!isset($calls[$call])) {
                $calls[$call] = 0;
            }
            $calls[$call]++;
        }
    }
}

arsort($calls);

var_dump($calls);

function do_dir($dir)
{
    $out = array();
    $_dir = ($dir == '') ? '.' : $dir;
    $dh = opendir($_dir);
    if ($dh) {
        while (($file = readdir($dh)) !== false) {
            if (($file{0} != '.') && ($file != 'exports')) {
                if ((is_file($_dir . '/' . $file)) && (substr($file, -4) == '.php')) {
                    $out[] = $_dir . '/' . $file;
                } elseif ((is_dir($_dir . '/' . $file)) && (strpos($file, '_custom') === false) && ($file != 'tracker')) {
                    $out = array_merge($out, do_dir($dir . (($dir != '') ? '/' : '') . $file));
                }
            }
        }
    }
    return $out;
}