The config service loads configuration for both Pines and any
components/templates. The configuration for Pines is always loaded,
and components' configuration is loaded the first time it is
accessed, during each script run. Config will start by loading the
defaults.php file (if it exists) in the
component's directory. The return value from this file is an array
which is used to load configuration variables. Each array entry
should be an associative array containing the following
entries:
Table 3.3. Config Array Entries
| Key | Required | Description |
|---|---|---|
| name | Yes | The configuration option's name. This is used to access it from code. |
| cname | Yes | A common name. This is displayed to the user when setting the option. |
| description | Yes | A description of the configuration option. |
| value | Yes | The default value of the configuration option. |
| options | No | An array of the possible values of the option. |
| peruser | No | A boolean determining whether the option can be set on a per user basis. |
![]() | Tip |
|---|---|
|
Calling a function in a |
Config will then load the config.php file
(if it exists) in the component's directory. This file is generated
by the configurator service and stores user specified configuration.
The file is structured the same, except that each entry only
contains "name" and "value".
The configuration object is built using these files, and placed into a property (named after the component) on the first attempted access of the property. Pines' system configuration options are placed in the config object itself.
When the user changes a config option, they may only change the option to a value of the same type as the default value. For example, if the option's default value is an integer, the value can only be changed to another integer.
// Access Pines' "system_name" configuration option: echo $pines->config->system_name; // Access a component's "website_name" configuration option: echo $pines->config->com_example->website_name; // Access a template's "tagline" configuration option: echo $pines->config->tpl_green->tagline;
![]() | Caution |
|---|---|
|
Configuration options are not sanitized. When you are
inserting them into HTML, they should be escaped with
|
The depend service manages and runs dependency checkers. It includes several dependency checkers by default.
Table 3.4. Built In Dependency Checkers
| Name | Description |
|---|---|
| ability | Check that the current user has certain abilities. |
| action | Check the currently running or originally requested action. |
| class | Check whether classes exist. |
| clientip | Check the client's IP address. |
| component | Check whether components/templates are installed, enabled, and are certain versions. |
| extension | Check whether PHP extensions are installed, enabled, and are certain versions. |
| function | Check whether functions exist. |
| host | Check the requested server hostname. Uses the hostname provided by the client in the HTTP request. |
| option | Check the currently running or originally requested component. |
| php | Check PHP's version. |
| pines | Check Pine's version. |
| request | Check the requested component and action. (Combination of "option" and "action".) |
| service | Check the available system services. |
To check a dependency, use the
check() method. It will return true if the
value passes the check, or false if it does not. To retrieve a
checker's help array, with information about the checker, use the
help() method.
if (!$pines->depend->check('component', 'com_example>=2.0|com_sample')) {
pines_notice('This feature is only available when you have com_example 2.0 or higher, or com_sample.');
return;
}
if (!$pines->depend->check('extension', 'mysql|mysqli')) {
pines_notice('It appears the MySQL extension is not installed. Please install it to continue.');
return;
}
$help_array = $pines->depend->help('extension');
Adding a new dependency checker is easy. Add a new entry to the checkers variable, using the name of the checker as the key, and a callback to the checker function as the value. The callback function should return true if the check passes and false if it does not. The first argument to a checker is a value to check. However, the second argument, if set to true, means the callback function should return a help array.
This should usually be done using an init script. That will ensure the checker is available by the time an action is run.
function com_example__check_something($value, $help = false) {
if ($help) {
$return = array();
$return['cname'] = 'Barbecue Checker';
$return['description'] = <<<'EOF'
Check that something is a barbecue.
EOF;
$return['syntax'] = <<<'EOF'
Any value will do, but to pass, it must be barbecue.
EOF;
$return['examples'] = <<<'EOF'
stick in the mud
: Check if "stick in the mud" == "barbecue".
EOF;
$return['simple_parse'] = false;
return $return;
}
return ($value == 'barbecue');
}
$pines->depend->checkers['something'] = 'com_example__check_something';
// Call the new checker like this:
// $pines->depend->check('something', 'a value'); // Would return false.
![]() | Caution |
|---|---|
|
Since unprivileged users are often allowed to run custom dependency checkers, it is important to not allow disclosure of sensitive data using a checker. For example, an unprotected file checker could be used to find files on the host machine. |
![]() | Caution |
|---|---|
|
Be careful that a checker does not recursively call itself, which could result in an infinite loop. |
All the built in dependency checkers use
simple_parse() to understand simple logic.
The syntax is simple, including and "&", or "|", not "!", and
grouping using parentheses "()". Here's an example of how to use
simple_parse() in the example
checker.
function com_example__check_something($value, $help = false) {
global $pines;
if ($help) {
$return = array();
$return['cname'] = 'Barbecue Checker';
$return['description'] = <<<'EOF'
Check that something is a barbecue.
EOF;
$return['syntax'] = <<<'EOF'
Any value will do, but to pass, it must be barbecue.
EOF;
$return['examples'] = <<<'EOF'
stick in the mud|hot dogs
: Check if "stick in the mud" or "hot dogs" == "barbecue".
EOF;
$return['simple_parse'] = true;
return $return;
}
if (
strpos($value, '&') !== false ||
strpos($value, '|') !== false ||
strpos($value, '!') !== false ||
strpos($value, '(') !== false ||
strpos($value, ')') !== false
)
return $pines->depend->simple_parse($value, 'com_example__check_something');
return ($value == 'barbecue');
}
$pines->depend->checkers['something'] = 'com_example__check_something';
// Call the new checker like this:
// $pines->depend->check('something', 'a value|barbecue'); // Would return true.
The checker's help array must contain the following values.
Table 3.5. Dependency Checker Help Array Values
| Name | Description |
|---|---|
| cname | A common name to present to users. |
| description | A description of the checker. This is formatted with Markdown. |
| syntax | An explanation of the checker's syntax. This is formatted with Markdown. |
| examples | Examples of values that can be checked with the checker. This is formatted with Markdown. |
| simple_parse | A boolean. Whether this checker uses the simple_parse feature. |
If you'd like to include your checker in your component's class, it is better to consider the resources used by including a callback directly to your component's method. Component classes are loaded as soon as the object is first accessed. Instead of using a callback to the method, you can use a shortcut function to save Pines from loading your component's class during each script run.
// Using an extra function to call the real checker keeps the com_example
// class from being loaded on each script run.
function com_example__check_something($value) {
global $pines;
return $pines->com_example->something($value);
}
$pines->depend->checkers['something'] = 'com_example__check_something';
![]() | Note |
|---|---|
|
Many things use the dependency checker, including menu entries, the package manager, conditional groups, and conditional configuration just to name a few. |
The hook service provides method hooking for the entire
$pines object and most classes. Method hooking
allows a component to intercept function calls to alter arguments,
alter return values, and/or change the actual function being
called.
Hook uses a complex technique to override an object and
allow all its public methods to be hooked. When
hook_object() is called and passed an
object, it begins by using PHP's reflection API to analyze the
object and build a new object. The class used to build the new
object is the hook_override__NAMEHERE_
class. Hook will create a new class based on this class by
replacing "_NAMEHERE_" with the name of the class. Each method on
the original object is recreated in the new class. The replacement
methods, when called will run the callbacks associated with that
hook and the original method on the original object. Once the new
class is complete, it is loaded, and an object is created using
it. The new object stores the original object, and replaces the
variable holding that object. The new object now resides in place
of the original object.
When a variable is requested from the object, the new object will pass the request directly to the original object. When the object is requested as a string, invoked, or cloned it will also request it from the original object. When the object is cloned, it will hook the clone as well.
![]() | Caution |
|---|---|
|
Calls from within a method of the original class to another method of the class will not be intercepted. This includes all calls to private methods. Static methods cannot be hooked either. |
![]() | Caution |
|---|---|
|
Functions which return or check the class name of the
object will use the class name of the new object, and could
therefore return unwanted results. For example, a hooked user
object would return false with |
To hook an object, so calls to its methods can be
intercepted, use hook_object(). The
prefix will be used when setting up callbacks for the
hooks.
$widget = new com_example_widget; $pines->hook->hook_object($widget, 'com_example_widget->', false);
![]() | Note |
|---|---|
|
|
To set up a callback, use
add_callback(). A callback is called
either before a method runs or after. The callback is passed an
array of arguments or return value which it can freely manipulate.
If the callback runs before the method and sets the arguments
array to false (or causes an error), the method will not be run.
Callbacks before a method are passed the arguments given when the
method was called, while callbacks after a method are passed the
return value (in an array) of that method.
The callback can receive up to 5 arguments, in this order:
&$arguments
An array of either arguments or a return value.
$name
The name of the hook.
&$object
The object on which the hook caught a method call.
&$function
A callback for the method call which was caught. Altering this will cause a different function/method to run.
&$data
An array in which callbacks can store data to communicate with later callbacks.
A hook is the name of whatever method it should catch. A hook can also have an arbitrary name, but be wary that it may already exist and it may result in your callback being falsely called. In order to reduce the chance of this, always use a plus sign (+) and your component's name to begin arbitrary hook names. E.g. "+com_games_player_bonus".
If the hook is called explicitly, callbacks defined to run before the hook will run immediately followed by callbacks defined to run after.
A negative $order value means the
callback will be run before the method, while a positive value
means it will be run after. The smaller the order number, the
sooner the callback will be run. You can think of the order value
as a timeline of callbacks, zero (0) being the actual method being
hooked.
Additional identical callbacks can be added in order to have a callback called multiple times for one hook.
The hook "all" is a pseudo hook which will run regardless of what was actually caught. Callbacks attached to the "all" hook will run before callbacks attached to the actual hook.
function com_example__callback() {
// Do something.
}
// Run the callback before something() is called on com_example.
// Note the single quotes. Double quotes would be parsed by PHP and not work.
$pines->hook->add_callback('$pines->com_example->something', -10, 'com_example__callback');
// Run the callback before delete() is called on com_example_widget.
$pines->hook->add_callback('com_example_widget->delete', -1, 'com_example__callback');
// Run the callback after every method call is caught.
$pines->hook->add_callback('all', 10, 'com_example__callback');
To manually run callbacks (without an originating function
call), use
run_callbacks().
The info service provides information about Pines and
installed components/templates. When Info is loaded, the
info.php file in the
system folder is retrieved. This file is used
to fill variables in the info object. Similarly, when a component's
variable is accessed, Info will look for an info.php file in the
component's directory. It will use this file to create an object for
that component. The template variable will
contain the info of the current template. The info file should
return an associative array with the following entries:
Table 3.6. Info Array Entries
| Key | Required | Description |
|---|---|---|
| name | Yes | The component's name. This is the name displayed to the user. |
| author | Yes | The component's author. |
| version | Yes | The component's version. |
| license | No | The URL or name of the license under which the component is released. |
| website | No | The URL of the component's website. |
| services | No | An array of the names of services the component provides. |
| short_description | No | A short description (one line) of the component. |
| description | No | A description of the component. |
| depend | No | An associative array of dependencies which the component requires. The keys are the names of the dependency checker to use, and the values are the values to check. |
| conflict | No | An associative array of dependencies which the component conflicts with. The keys are the names of the dependency checker to use, and the values are the values to check. |
| recommend | No | An associative array of dependencies which the component recommends. The keys are the names of the dependency checker to use, and the values are the values to check. |
| abilities | No | An array of abilities used by the component. Each array has three entries, a name, a title, and a description, in that order. |
defined('P_RUN') or die('Direct access prohibited');
return array(
'name' => 'System Configurator',
'author' => 'SciActive',
'version' => '1.0.0',
'license' => 'http://www.gnu.org/licenses/agpl-3.0.html',
'website' => 'http://www.sciactive.com',
'services' => array('configurator'),
'short_description' => 'Manages system configuration',
'description' => 'Allows you to edit your system\'s configuration and the configuration of any installed components.',
'depend' => array(
'pines' => '<2',
'component' => 'com_jquery&com_ptags&com_pgrid&com_pform'
),
'recommend' => array(
'service' => 'entity_manager&user_manager'
),
'abilities' => array(
array('edit', 'Edit Configuration', 'Let the user change (and see) configuration settings.'),
array('editperuser', 'Edit Per User Configuration', 'Let the user change (and see) per user/group configuration settings.'),
array('view', 'View Configuration', 'Let the user see current configuration settings.'),
array('viewperuser', 'View Per User Configuration', 'Let the user see current per user/group configuration settings.')
),
);
The menu service builds and renders menus. In Pines, menus are
built using arrays, and these arrays are usually stored in JSON
files. Menu will begin by loading the menu.json
file in the system directory. This will set up
the main menu and its submenus. It will then load any
menu.json files found in all components'
directories. The JSON structure of a menu file is an array of
objects (because JSON doesn't support associative arrays) which are
each placed into the menu_arrays variable in the
menu object. Each entry is either a top level menu, or a menu entry.
Top level menus require a position in which to place them, as well
as several other values:
Table 3.7. Top Level Menu Array Entries
| Key | Required | Description |
|---|---|---|
| path | Yes | The name of the menu's path. |
| text | Yes | The text to title the menu's module. |
| position | Yes | The page position in which to place the menu. |
| sort | No | Boolean value of whether the menu should be sorted alphabetically. |
| depend | No | An associative array of dependencies required to show the menu. |
Regular menu entries have these values:
Table 3.8. Regular Menu Array Entries
| Key | Required | Description |
|---|---|---|
| path | Yes | The path of this menu entry. Paths are hierarchical like file paths, using forward slashes as separators. |
| text | Yes | The text to show in the menu entry. |
| sort | No | Boolean value of whether the menu should be sorted alphabetically. |
| href | No | A URL, or an array of arguments to pass to
pines_url(), which the entry will
link to. |
| target | No | The HTML target of the link. Defaults to "_self". |
| onclick | No | JavaScript which will run when the entry is clicked. |
| depend | No | An associative array of dependencies required to show the entry. |
An extra dependency called "children" can be used on menu entries in order to show the menu only if it has visible children.
To add a menu entry using code, you can insert it into the
menu_arrays variable.
// Using pines_url().
$pines->menu->menu_arrays[] = array(
'path' => 'main_menu/example',
'text' => 'Example',
'href' => array('com_example', 'something', array('id' => $some_id)) // These are used as arguments for pines_url().
);
// Using a URL. This is placed under the above entry. It only appears when the user is logged in. It opens in a new window.
$pines->menu->menu_arrays[] = array(
'path' => 'main_menu/example/google',
'text' => 'Google',
'href' => 'http://google.com',
'target' => '_blank',
'depend' => array(
'ability' => ''
)
);
The system init script i80menus.php is responsible for loading
the JSON files, by calling add_json_file()
for the system menu file, then each component's menu file. This
means that menu entries are not loaded until after all init/kill
scripts and requested actions have run. This means that if you need
to alter a component's menu entries (as opposed to just adding
submenus and entries), you must hook Menu's
add_json_file() or
render() method.
Once all menu entries have been loaded,
render() will remove entries when their
dependencies are not met. It will convert the entries into
multi-dimensional arrays for each menu using their path values. It
then passes these arrays to the menu()
method of the current template and places the return value into a
module in the menu's requested position.
[
{
"path": "main_menu/other/example",
"text": "Example",
"depend": {
"children": true
}
},
{
"path": "main_menu/other/example/widgets",
"text": "Widgets",
"href": ["com_example","widget/list"],
"depend": {
"ability": "com_example/listwidgets"
}
},
{
"path": "main_menu/other/example/newwidget",
"text": "New Widget",
"href": ["com_example","widget/edit"],
"depend": {
"ability": "com_example/newwidget"
}
}
]
The page service manages modules, notices, and errors, and renders content to be output to the client by the template. Modules, which are objects that use views to generate content, are attached to Page in specific positions. A template's info object will have a list of positions the template supports. Page uses PHP's output buffering system to retrieve the output from modules and templates. Page also controls the title of the page.
Page has several methods for manipulating the page title.
title
Append text to the title.
title_set
Replace the title.
title_pre
Prepend text to the title.
get_title
Retrieve the title.
Page has several methods for displaying notices and errors to the user. Page only manages and organizes them, as it is ultimately the responsibility of the template to display the messages to the user.
notice
Add a notice to be displayed to the user.
get_notice
Get the array of notices.
error
Add an error to be displayed to the user.
get_error
Get the array of errors.
Modules are segments of the page, which must be attached to
the page in order to have their view content rendered and sent to
the client. When a module is attached to Page, a position must be
specified. Though module attachments are usually handled by the
module when it is created, you can manually attach a module to Page.
The second argument of attach_module() is
the position in which to place the module. You can also manually
detach the module. If the module is attached to the same position
twice, it must likewise be removed twice.
$module = new module; // Attach the module. $pines->page->attach_module($module, 'content'); // Detach the module. $pines->page->detach_module($module, 'content');
The template will need to place the module content in the
appropriate place on the page. To do this, templates use
render_modules(), providing a position. If
needed, the template can also provide a model, which will be used to
format the content as necessary.
<div id="left">
<?php echo $pines->page->render_modules('left', 'module_sidebar'); ?>
</div>
Under special circumstances, you may need to only output
certain data to the client. For example, when you are returning JSON
data to an Ajax request. To do this, set the
override variable to true, then pass the content
to override_doc().
// Turn override on. $pines->page->override = true; // Output JSON data. $pines->page->override_doc(json_encode($var)); // If you need to retrieve the data: $data = $pines->page->get_override_doc();
The page is rendered when the system init script
i90render.php calls
render(). If the page is overridden, the
override document is returned. Otherwise, each module is rendered
individually, then the template's template.php
file is run. Because each module is rendered before the page, a
module can cause another module to be attached to the page. However,
that module will ultimately be rendered during the page's rendering.
Therefore, if that module then causes another module to be attached
in a position that has already been rendered, the third module will
not be output to the user.
![]() | Note |
|---|---|
|
In order to allow it to be hooked,
|