Table of Contents
System services provide many features, and allow these features to be implemented in many different ways. System services are not provided by Pines Core. They are, however, well defined and must conform to certain guidelines to ensure that any component designed to use them will work with any implementation.
The configurator service allows the user and other components to edit/save configuration and enable/disable components. In Pines, a component is disabled by prepending a period (.) to its directory's name. This is the same way the directory is hidden on Linux file systems.
The configurator itself has the following methods:
disable_component
Disables a component. This is accomplished by prepending a dot to its directory's name. Accepts the name of the component as the only argument.
enable_component
Enables a component. This is accomplished by removing the dot from the beginning of its directory's name. Accepts the name of the component as the only argument.
list_components
Creates and attaches a module which lists configurable components. It also returns the created module.
// Enable the component com_good.
$pines->configurator->enable_component('com_good');
// Disable the component com_bad.
$pines->configurator->disable_component('com_bad');
The configurator also provides a class for retrieving and
manipulating a component's configuration. This class is the
configurator_component class. It provides
the static method factory, which accepts
the component's name as the only argument and returns a new
instance of configurator_component for the
provided component. The class constructor also accepts this
argument, but using the factory method is
recommended.
The component configurator class has these properties:
defaults
The configuration defaults.
config
The current configuration.
config_keys
The current configuration in an array with key => values.
info
The info object of the component.
name
The component.
The component configurator class provides these methods:
get_full_config_array
Get a full config array. (With defaults replaced.)
is_configurable
Check if a component is configurable.
is_disabled
Check if a component is disabled.
print_form
Print a form to edit the configuration.
print_view
Print a view of the configuration.
save_config
Write the configuration to the config file.
set_config
Set the current config by providing an array of key => values. Any value not provided will be set back to default.
// Load the component's configuration.
$conf = configurator_component::factory('com_example');
// Set the new configuration.
$conf->set_config(array(
'host' => $host,
'user' => $user,
'password' => $password,
'database' => $database
));
// Save the new configuration to disk.
if (!$conf->save_config())
pines_error('Couldn\'t save config.');
When the configuration is saved, the configurator creates a
config.php file in the component's directory
(if it does not already exist), and saves a script to the file,
which returns the configuration values as an array of associative
arrays. Each associative array contains an entry called "name" and
an entry called "value". The config service uses this file when
building the component's configuration, replacing values from
defaults.php with the values from this
file.
The editor service provides a more friendly HTML editor for the user. When editing some sort of content, which is to be displayed in a page or email, a textarea is usually not the best solution. The editor service provides a method which allows the system administrator to choose between multiple HTML editors to provide for the system's users.
Once the editor is loaded, it will transform any textareas with the class "peditor", "peditor-simple", or "peditor-email" into HTML editors. If it provides a simplified editor, textareas with the class "peditor-simple" will use this editor instead. If it provides an email editor, textareas with the class "peditor-email" will use this editor instead. Email editors will format content for emails. This includes full URLs for links and images, no stylesheets, etc.
The editor has a method, load, which
loads the editor. This method should be called from a view, to
ensure it is only called when outputting HTML. The editor also has a
method, add_css, which takes one argument,
the URL of a stylesheet to use for styling WYSIWYG
content.
<?php
/**
* Prints editors.
*
* @license http://www.gnu.org/licenses/agpl-3.0.html
* @author Hunter Perrin <hunter@sciactive.com>
* @copyright SciActive.com
* @link http://sciactive.com/
*/
/* @var $pines pines *//* @var $this module */
defined('P_RUN') or die('Direct access prohibited');
$this->title = 'Editors';
// Load the editor.
$pines->editor->load();
// Load a custom stylesheet.
$pines->editor->add_css($pines->config->location.'components/com_example/includes/custom.css');
?>
<div>Regular Editor</div>
<div><textarea rows="3" cols="35" class="peditor"></textarea></div>
<br />
<div>Simple Editor</div>
<div><textarea rows="3" cols="35" class="peditor-simple"></textarea></div>
<br />
<div>Email Editor</div>
<div><textarea rows="3" cols="35" class="peditor-email"></textarea></div>
Go brew some coffee or drink an energy drink, cause this is without a doubt, the most complex and important part of Pines! Understanding the entity manager lets you build complex relationships between data as easily as setting the value of a property.
The entity manager service provides database abstraction for
Pines. Data in Pines is based on objects called entities. An
entity is an object which can hold any type of data available in
PHP (except resources), including other entities. All entity
classes (and therefore all entities) inherit the
entity class.
Components can freely access the database if they wish, however it is strongly discouraged, as this prevents portability and extensibility. Using entities to store data has several benefits for both developers and site operators.
Site operators can choose which database backend to use, based on their own needs.
Moving from one database to another, backing up and restoring a database, and mirroring databases are much easier through an entity manager.
Data storage is much easier and faster to code.
Data querying is much easier and faster to code.
A component can store data in another component's entities and extend its functionality.
The user manager provides access control for all entities automatically.
Entities are not strictly structured, so data of any type
can be added and saved just by assigning a variable on the entity
and calling save. This makes data
manipulation in Pines very easy.
All entities are given a globally unique identifier (GUID), which is an integer (but may not be in the future). No entities in the same Pines installation will ever have the same GUID. The entity manager also provides UIDs, which can be used to number certain types of entities (or practically anything else). UIDs can be used to provide a more visibly pleasing identifier for entities.
Entities are organized with tags. For example, if a component, com_blog, wanted to store posts using an entity, it may add the tags "com_blog" and "post" to all new instances. This allows components to select and differentiate their entities from other components' entities.
An entity's class is usually named by appending to their
component's name. In the previous example, the class would
probably be called com_blog_post. When
selecting entities with the entity manager, the class used to
retrieve them must be specified, or the
entity class will be used. If an entity is
referenced in another entity's variable, the class is saved along
with the GUID. When this variable is accessed later, the entity
manager will retrieve the referenced entity using the saved
class.
![]() | Caution |
|---|---|
|
When changing an entity's class name, any entities referencing it must be resaved after setting the reference again using the new class name. |
To create an entity, call the
factory static method of an entity's
class. The entity class takes a list of
tags as arguments. However, if an author chooses, the
factory method of an inheriting class can
take any other arguments. It will usually take only one argument,
a GUID. It would return the entity with the GUID or, if it didn't
exist or no GUID was provided, a new entity. The
factory method is used instead of the
new keyword to provide entities a chance to set
up hooking on their objects. To check that an entity is a new
entity (it hasn't been saved), check that the GUID has not been
set.
// Using the entity class.
$new_entity = entity::factory();
// Using a custom class.
$blog_post = com_blog_post::factory();
// Check that the entity is new.
if (isset($new_entity->guid))
pines_notice('This entity is not a new entity!');
Much like blogs in many blogging systems, entities are organized using tags. Entity tags are not supposed to be added by the user, as this could allow them to create or manipulate users, groups, configurations, etc. It may seem inviting to use the entity tags to store user provided data, but don't! Though not strictly necessary, it is highly recommended to give every entity your component creates a tag identical to your component's name, such as 'com_xmlparser'. You don't want to accidentally get another component's entities.
Be very cautious when saving an entity in another entity's variable. If the referenced entity is newly created and does not have a GUID, the entity manager will not be able to retrieve it later. Always save the referenced entity first.
$entity = entity::factory(); $entity->foo = entity::factory(); $entity->save(); // foo hasn't been saved yet! $entity->foo->bar = 'It works!'; $entity->foo->save(); $guid = $entity->guid; unset($entity); $entity = $pines->entity_manager->get_entity($guid); isset($entity->foo->guid); // False echo $entity->foo->bar; // Outputs nothing.
$entity = entity::factory(); $entity->foo = entity::factory(); $entity->foo->bar = 'It works!'; $entity->foo->save(); // Saving the referenced entity first! :) $entity->save(); // now foo has been saved. $guid = $entity->guid; unset($entity); $entity = $pines->entity_manager->get_entity($guid); isset($entity->foo->guid); // True echo $entity->foo->bar; // Outputs 'It works!'.
![]() | Caution |
|---|---|
|
Since the reference entity's class name is stored in the reference on the entity's first save and used to retrieve the reference entity using the same class, if you change the class name in an update, you need to reassign the reference entity and save to storage. |
When an entity is loaded, it does not request its referenced
entities from the entity manager. This is done the first time the
variable/array is accessed. The referenced entity is then stored
in a cache, so if it is altered elsewhere, then accessed again
through the variable, the changes will not be there. Therefore, you should take
great care when accessing entities from multiple variables. If you
might be using a referenced entity again later in the code
execution (after some other processing occurs), it's recommended
to call clear_cache.
As mentioned before, entities are organized using tags. To
add, remove, and check tags, the methods
add_tag,
remove_tag, and
has_tag are used, respectively. Each
takes any number of tags or an array of tags as
arguments.
$entity = entity::factory();
$entity->add_tag('com_foobar', 'foo', 'bar');
$entity->has_tag('foo'); // True
$entity->remove_tag('foo', 'bar');
$entity->has_tag('foo'); // False
To clear the cache of referenced entities, so that the next
time one is accessed it will be pulled from the database, use the
clear_cache method.
$entity = entity::factory(); $entity->foo = entity::factory(); $entity->foo->bar = 'Old value.'; $entity->foo->save(); $entity->save(); $entity = $pines->entity_manager->get_entity($entity->guid); $inst_of_foo = $pines->entity_manager->get_entity($entity->foo->guid); $inst_of_foo->bar = 'New value.'; $inst_of_foo->save(); echo $entity->foo->bar; // Outputs 'Old value.' $entity->clear_cache(); echo $entity->foo->bar; // Outputs 'New value.'
Much like clearing the entity cache, you may need to refresh
the entity's own data. Use the refresh
method for this.
$entity = entity::factory(); $entity->foo = 'Old value.'; $entity->save(); $inst_of_ent = $pines->entity_manager->get_entity($entity->guid); $inst_of_ent->foo = 'New value.'; $inst_of_ent->save(); echo $entity->foo; // Outputs 'Old value.' $entity->refresh(); echo $entity->foo; // Outputs 'New value.'
As you've already seen in the examples, to save an entity,
use the save method. Likewise, to delete
the entity, use the delete method. You
can also call the save_entity,
delete_entity, and
delete_entity_by_id methods of the entity
manager itself. Entities use these methods, which allows hooking
of all entity saves and deletes.
$entity = entity::factory(); // Save the entity. $entity->save(); // or $pines->entity_manager->save_entity($entity); // Delete the entity. $entity->delete(); // or $pines->entity_manager->delete_entity($entity); // or $pines->entity_manager->delete_entity_by_id($entity->guid);
Several methods are provided by entities to find and compare them with other data. Entities cannot simply be checked using the == operator. It will almost always fail because of things like entity caching and sleeping references. Instead, you can use the following entity methods.
is
Perform a less strict comparison of two entities. To return true, the entity and the object passed must meet the following criteria:
| They must be entities. |
| They must have equal GUIDs. (Or both can have no GUID.) |
| If they have no GUIDs, their data must be equal. |
equals
Perform a more strict comparison of two entities. To return true, the entity and the object passed must meet the following criteria:
| They must be entities. |
| They must have equal GUIDs. (Or both can have no GUID.) |
| They must be instances of the same class. |
| Their data must be equal. |
in_array
Check whether the entity is in an array. Takes two
arguments, the array and a boolean
$strict. If $strict is
false, the function uses is to
compare, and if it's true, the function uses
equals.
array_search
Search an array for the entity and return the
corresponding key. Takes two arguments, the array and a
boolean $strict. If
$strict is false, the function uses
is to compare, and if it's true,
the function uses equals. This
method may return 0, which evaluates to false, so you should
use in_array if you are only
checking whether the entity is in the array.
$entity = entity::factory();
$entity->is($entity); // True
$array = array('foo' => null, 'bar' => $entity);
echo $entity->array_search($array); // Outputs 'bar'
The entity manager provides three methods to sort entities. Each one uses a different method of sorting.
hsort
Sort an array of entities hierarchically by a specified property's value.
psort
Sort an array of entities by parent and a specified property's value. If they have the same parent, their own value is used.
sort
Sort an array of entities by a specified property's value.
$cats = $pines->entity_manager->get_entities(
array('class' => com_foobar_category),
array('&',
'tag' => array('com_foobar', 'category')
)
);
$case_sensitive = false;
$reverse = false;
// Sort categories hierarchically by their name.
$pines->entity_manager->hsort($cats, 'name', 'parent', $case_sensitive, $reverse);
// Sort categories by their parent's name.
$pines->entity_manager->psort($cats, 'name', 'parent', $case_sensitive, $reverse);
// Sort categories by their name.
$pines->entity_manager->sort($cats, 'name', $case_sensitive, $reverse);
The real power behind the entity manager is the entity querying system. After all, what's the use of having complex data and relationships if you can't find it?
The simplest way to get an entity is usually its
factory static method. Almost all
entities provided by SciActive maintained components use a
factory that takes a GUID as an argument.
However, the author of an entity's class can use whatever they
like as arguments. For example, the user
class takes either a GUID or a username. Usually the method will
return a brand new entity if the queried entity is not found.
![]() | Tip |
|---|---|
|
If you're using a user provided value as the GUID,
casting it to an int will ensure it is evaluated as a GUID.
Also, non-numeric values will be evaluated as 0, resulting in
a new entity, since no entity has GUID 0. Then you can
determine if it was found by checking that
|
// Most entities use a GUID.
$baz = com_foobar_baz::factory((int) $_REQUEST['id']);
if (!isset($baz->guid)) {
pines_notice('The specified baz cannot be found!');
return;
}
// When selecting a user, you can use a GUID or a username.
$cron_user = user::factory('cron');
if (!isset($cron_user->guid)) {
pines_notice('Can\'t find the cron user! Have you created one yet?');
return;
}
// If the script made it to this point, both $baz and $cron_user were found!
pines_notice('Hooray! I found the baz you wanted and the cron user!');
The really powerful way of querying entities is the entity
manager's get_entities and
get_entity methods.
The first argument to these functions is an options array.
It is an associative array of key value pairs signifying options.
The options array can be empty, in which case the
entity class will be used for returned
entities. The following is a list of options which all entity
managers support.
Table 4.1. Entity Querying Options
| Option | Type | Default | Description |
|---|---|---|---|
| class | string | "entity" | The class used to create each entity. It must
have a factory static method
that returns a new instance. |
| limit | int | null | The limit of entities to be returned. Not needed
when using get_entity, as it
always returns only one. |
| offset | int | 0 | The offset from the oldest (or newest if reversed) matching entity to start retrieving. |
| reverse | bool | false | If true, entities will be retrieved from newest to oldest. Therefore, offset will be from the newest entity. |
| sort | string | "guid" | How to sort the entities. Accepts "guid", "cdate", and "mdate". |
| skip_ac | bool | false | If true, the user manager will not filter returned entities according to access controls. |
![]() | Note |
|---|---|
|
When using skip_ac, any referenced entities, when accessed, will also be retrieved using skip_ac. |
A component or entity manager implementation is free to add extra options, but it should consider prepending the component's name to the name of the option. For example, if your component, com_entityversions, wants to add the option "date", it should use "com_entityversions_date".
Every argument following the options array is a selector. A selector is an associative array of criteria. An entity must pass each selector's criteria to be returned. The first member of the array (the value at index 0) is the type of selector, which determines how the criteria are matched against an entity's data.
Table 4.2. Entity Selector Types
| Type | Name | Description |
|---|---|---|
| & | And | All values in the selector must be true. |
| | | Or | At least one value in the selector must be true. |
| !& | Not And | All values in the selector must be false. |
| !| | Not Or | At least one value in the selector must be false. |
The following members of the array are the criteria of the
selector. They must be in the form $selector['name'] =
$value, or $selector['name'] = array($value1,
$value2,...). They can be negated by prepending a bang (!)
to the name, such as $selector['!tag'] =
'user'.
Table 4.3. Entity Selector Criteria
| Name | Value | Condition | Example Selector | Matching Entity |
|---|---|---|---|---|
| guid | A GUID. | True if the entity's GUID is equal. | array('&', 'guid' =>
12) | $entity->guid =
12; |
| tag | A tag. | True if the entity has the tag. | array('&', 'tag' =>
'com_foobar') | $entity->add_tag('com_foobar'); |
| isset | A name. | True if the named variable exists and is not null. | array('&', 'isset' =>
'foo') | $entity->foo =
0; |
| data | An array with a name, then value. | True if the named variable is defined and equal. | array('&', 'data' => array('foo',
false)) | $entity->foo =
0; |
| strict | An array with a name, then value. | True if the named variable is defined and identical. | array('&', 'strict' => array('foo',
0)) | $entity->foo =
0; |
| array | An array with a name, then value. | True if the named variable is an array containing the value. Uses in_array(). | array('&', 'array' => array('foo',
'bar')) | $entity->foo =
array('bar', 'baz'); |
| match | An array with a name, then regular expression. | True if the named variable matches. Uses preg_match(). | array('&', 'match' => array('foo',
'/bar/')) | $entity->foo =
'foobarbaz'; |
| gt | An array with a name, then value. | True if the named variable is greater than the value. | array('&', 'gt' => array('foo',
5)) | $entity->foo =
6; |
| gte | An array with a name, then value. | True if the named variable is greater than or equal to the value. | array('&', 'gte' => array('foo',
6)) | $entity->foo =
6; |
| lt | An array with a name, then value. | True if the named variable is less than the value. | array('&', 'lt' => array('foo',
7)) | $entity->foo =
6; |
| lte | An array with a name, then value. | True if the named variable is less than or equal to the value. | array('&', 'lte' => array('foo',
6)) | $entity->foo =
6; |
| ref | An array with a name, then either a entity, or a GUID. | True if the named variable is the entity or an array containing the entity. | array('&', 'ref' => array('foo',
12)) | $entity->foo =
com_foobar_baz::factory(12); |
So putting it all together, you can specify any of the options, and any number of selectors to find the exact entities you want.
// Get all entities using the entity class.
$entities = $pines->entity_manager->get_entities();
// Get only the first entity.
$entity = $pines->entity_manager->get_entity();
// Get the last entity.
$entity = $pines->entity_manager->get_entity(array('reverse' => true));
// Get all baz entities, using the com_foobar_baz class.
$entities = $pines->entity_manager->get_entities(
array('class' => com_foobar_baz),
array('&',
'tag' => array('com_foobar', 'baz')
)
);
// Get the five newest baz entities.
$entities = $pines->entity_manager->get_entities(
array('class' => com_foobar_baz, 'reverse' => true, 'limit' => 5),
array('&',
'tag' => array('com_foobar', 'baz')
)
);
// Get baz entities with names.
$entities = $pines->entity_manager->get_entities(
array('class' => com_foobar_baz),
array('&',
'tag' => array('com_foobar', 'baz'),
'isset' => 'name'
)
);
// Get baz entities without names.
$entities = $pines->entity_manager->get_entities(
array('class' => com_foobar_baz),
array('&',
'tag' => array('com_foobar', 'baz'),
'!isset' => 'name'
)
);
// Get baz entities with either first names or last names.
$entities = $pines->entity_manager->get_entities(
array('class' => com_foobar_baz),
array('&',
'tag' => array('com_foobar', 'baz')
),
array('|',
'isset' => array('first_name', 'last_name')
)
);
// Get baz entities created since yesterday.
$entities = $pines->entity_manager->get_entities(
array('class' => com_foobar_baz),
array('&',
'tag' => array('com_foobar', 'baz'),
'gt' => array('p_cdate', strtotime('-1 day'))
)
);
// Get baz entities with names, who either make not greater than 8 dollars pay or are under 22.
$entities = $pines->entity_manager->get_entities(
array('class' => com_foobar_baz),
array('&',
'tag' => array('com_foobar', 'baz'),
'isset' => 'name'
),
array('!|', // at least one must be false
'gte' => array('age', 22),
'gt' => array('pay', 8)
)
);
// Get baz entities named Clark, James, Chris, Christopher, Jake, or Jacob.
$entities = $pines->entity_manager->get_entities(
array('class' => com_foobar_baz),
array('&',
'tag' => array('com_foobar', 'baz')
),
array('|',
'strict' => array(
array('name', 'Clark'),
array('name', 'James')
),
'match' => array(
array('name', '/Chris(topher)?/'),
array('name', '/Ja(ke|cob)/')
)
)
);
// Get all baz entities, regardless of whether the logged in user has access to them.
$entities = $pines->entity_manager->get_entities(
array('class' => com_foobar_baz, 'skip_ac' => true),
array('&',
'tag' => array('com_foobar', 'baz')
)
);
All entity managers provide a method for exporting and importing entities. This makes it very easy to backup, restore, duplicate, and move an entire database of entities, even with different database backends.
The entity manager has the following methods for exporting and importing entities:
export
Export entities to a file.
export_print
Export entities to the client as a downloadable file.
import
Import entities from a file.
@set_time_limit(3600); // Make sure the script doesn't terminate before the entities are exported/imported. // Export entities to a file. $pines->entity_manager->export($filename); // Export entities to the client. $pines->entity_manager->export_print(); // Import entities from a file. $pines->entity_manager->import($filename);
The format of an entity export file is simple, and readable by all entity managers. The file extension, PEX, stands for Pines Entity eXchange. Here is an example of an exported entity file.
# Pines Entity Export
# com_pgentity version 1.0.0
# sciactive.com
#
# Generation Time: Tue, 05 Jul 2011 09:56:14 -0700
# Pines Version: 0.94.0beta
#
# UIDs
#
<com_sales_sale>[1]
#
# Entities
#
{1}[com_user,user,enabled]
p_cdate="d:1309884676.464271068572998046875;"
p_mdate="d:1309884676.464293003082275390625;"
abilities="a:2:{i:0;s:10:\"system\/all\";i:1;s:14:\"com_user\/login\";}"
groups="a:0:{}"
inherit_abilities="b:1;"
address_type="s:2:\"us\";"
addresses="a:0:{}"
attributes="a:0:{}"
username="s:5:\"admin\";"
salt="s:32:\"7d5bc9dc81c200444e53d1d10ecc420a\";"
password="s:32:\"f066801b0b70491f397ad37b51b7e278\";"
name_first="s:6:\"Hunter\";"
name_middle="s:0:\"\";"
name_last="s:6:\"Perrin\";"
name="s:13:\"Hunter Perrin\";"
email="s:20:\"hunter@sciactive.com\";"
phone="s:0:\"\";"
fax="s:0:\"\";"
timezone="s:0:\"\";"
address_1="s:0:\"\";"
address_2="s:0:\"\";"
city="s:0:\"\";"
state="s:0:\"\";"
zip="s:0:\"\";"
address_international="s:0:\"\";"
UIDs provide an easier way for users to identify entities. UIDs are just sequential numbers and can be used for anything you like, not just entities. As opposed to a GUID, which is a unique ID for all entities, a UID is only unique for its own sequence. This allows them to stay small in a database filled with other entities.
UIDs can be named anything. A good naming convention, in order to avoid conflicts, is to use your component's name, a slash, then a descriptive name of the sequence being identified for non-entity sequences, and the name of the entity's class for entity sequences. E.g. "com_example/widget_hits" or "com_hrm_employee".
The entity manager has the following methods for handling UIDs:
delete_uid
Delete a unique ID from the system.
get_uid
Get the current value of a unique ID.
new_uid
Increment or create a unique ID and return the new value.
rename_uid
Rename a unique ID.
set_uid
Set the value of a unique ID.
// Create a new entity.
$entity = com_foobar_baz::factory();
// Now give it a more friendly ID than GUID.
$entity->id = $pines->entity_manager->new_uid('com_foobar_baz');
$entity->save();
![]() | Caution |
|---|---|
|
If a UID is incremented for an entity, and the entity cannot be saved, there is no safe, and therefore, no recommended way to decrement the UID back to its previous value. |
The icon service provides a set of icons available to use
through CSS classes. Pines Icons come in two sizes, 16x16 and 32x32.
To use an icon, you add the class of the icon, such as
picon-document-edit, to an HTML element. This
will use the icon's 16x16 size. To use the 32x32 size, add another
class, picon-32.
The icon service has one method,
load, which loads the icon CSS. This method
should be called from a view, to ensure it is only called when
outputting HTML.
<?php
/* @var $pines pines *//* @var $this module */
defined('P_RUN') or die('Direct access prohibited');
// Load the icons CSS.
$pines->icons->load();
?>
<div style="float: left; width: 32px; height: 32px; margin: 1em;" class="picon-32 picon-document-sign"></div>
<p>Your order can't be shipped until your contract is signed.</p>
<button type="button" class="btn">
<span class="picon-dialog-ok" style="background-repeat: no-repeat; padding-left: 18px;"> </span>Go
</button>
The log manager service provides an easy and simple logging
feature for Pines. The default log manager can use the hook system
to log all calls to an object under $pines. The
log manager has no restriction on the backend it can use, so log
managers can be made for flat files, MySQL, PostgreSQL, MSSQL,
Oracle, etc.
The log manager has one method, log,
which takes two arguments, the log entry and the level of the entry.
The level can be one of the following, ordered in increasing
severity.
debug
Provide information that could be useful while debugging.
info
Provide information that might be useful for a user. This is the default.
notice
Provide notice of an event or action that has occurred that the user may want to know.
warning
Warn the user of something that may be wrong or bad, but not necessarily an error.
error
Provide notice of an error that has occurred.
fatal
Provide notice of an error, from which the script cannot recover.
$user = user::factory();
$user->username = 'bob';
$user->password('changeme');
$pines->log_manager->log('Trying to save a new user.'); // The log level defaults to info.
if ($user->save())
$pines->log_manager->log("Saved user {$user->username}.", 'notice');
else
$pines->log_manager->log("Couldn't save user {$user->username}.", 'error');
The template service is a special service, which does not have to declare itself as the template service. Instead, Pines reads the configuration and determines the template. The template builds the layout and design of the page and inserts the content from any modules attached to the page. It also has the responsibility of building the output of the menus. Templates will be discussed in much greater depth in Templates in Part II.
The template only needs two methods.
menu
Formats a menu so it can be printed into content.
url
Creates a URL, utilizing the Pines URL rewriting setup. Has the following arguments:
The name of the component to be requested by the URL.
The name of the action to be requested by the URL.
An associative array of parameters to be used as query data in the URL.
Boolean determining if the URL should be a full URL. Otherwise, it will be a URL relative to the server of the Pines installation.
// A URL suitable for a link on a page in Pines.
$link = $pines->template->url('com_example', 'widget/edit', array('id' => $widget->guid));
// or you can use the shortcut function:
$link = pines_url('com_example', 'widget/edit', array('id' => $widget->guid));
// A URL suitable for a link in an external page or an email.
$link = $pines->template->url('com_example', 'widget/edit', array('id' => $widget->guid), true);
// The URL of the home page of the Pines installation.
$link = pines_url();
Templates also have some properties which define how output is constructed/viewed.
editor_css
The location of a stylesheet to be used by WYSIWYG editors.
format
The format of the template. This format is used by Pines to find the view when a module is being rendered.
The template format uses dashes to separate its parts, and describes the language, format, version, etc. of the view. The following are some example formats and a description of their language and what they're for.
html-desktop-5
HTML 5 for desktops.
html-mobile-4-01
HTML 4.01 for mobile screens.
html-desktop-xhtml-1-0-strict
XHTML 1.0 Strict for desktops.
xml-rss-2-0
RSS 2.0.
For more information on templates, you can see Templates in Part II.
The uploader service provides a convenient way for the user to upload and manage files on the server. The intent of the uploader service is to allow files to be uploaded and selected using an Ajax enabled interface for ease of use.
Once the uploader is loaded, it will transform any text inputs
with the class "puploader" into Ajax file uploaders/selectors. If
you would like the user to be able to select folders, add the
"puploader-folders" class. If you would like the user to be able to
select multiple files, add the "puploader-multiple" class. Add the
class "puploader-temp" to emulate a single file upload. Only one
file will be allowed, and it will be placed into a temporary
directory. Note that the check method is not needed when you are
using a temp file. To use the uploader in a view, call the
load method. This method should be called
from a view, to ensure it is only called when outputting
HTML.
<?php
/**
* Prints an uploader.
*
* @license http://www.gnu.org/licenses/agpl-3.0.html
* @author Hunter Perrin <hunter@sciactive.com>
* @copyright SciActive.com
* @link http://sciactive.com/
*/
/* @var $pines pines *//* @var $this module */
defined('P_RUN') or die('Direct access prohibited');
$this->title = 'Uploader';
// Load the uploader.
$pines->uploader->load();
?>
<div>Choose or Upload a File on the Server</div>
<div><input class="puploader" name="upload_file" type="text" value="" /></div>
<div>Upload a New File</div>
<div><input class="puploader puploader-temp" name="temp_file" type="text" value="" /></div>
The value of the field when the form is submitted will be the
relative URL of the selected file. When selecting multiple files,
they will be separated by two forward slashes "//". After receiving
the path of the file, it should always be checked using the
check method of the uploader service
(unless it is a temp upload). This will verify that the user didn't
maliciously alter the URL. It can then be translated to a real file
path using the real method, and back to a
URL again with the url method. The
url method takes an extra argument, which
determines whether a full URL should be returned instead of a
relative URL.
$file_url = $_REQUEST['upload_file'];
// The first step is always checking the file URL.
if (!$pines->uploader->check($file_url)) {
pines_notice('Invalid file path submitted!');
return;
}
// Now get the real file path.
$file_path = $pines->uploader->real($file_url);
// And the full URL of the file.
$file_url_full = $pines->uploader->url($file_path, true);
![]() | Warning |
|---|---|
|
Using a submitted URL without first checking it could allow a user to bypass safety features and exploit a server, potentially giving them access to confidential information. |
When you accept a temp upload, use the
temp method to get the real file path to
the uploaded file.
$file = $_REQUEST['temp_file'];
// Get the path to the temporary location of the file.
$file_path = $pines->uploader->temp($file);
if (!$file_path) {
pines_notice('Invalid file path submitted!');
return;
}
// Now you can move the file into a permanent location with rename().
The user manager service is one of Pines' most powerful features. It has many features and responsibilities, designed to separate and protect data. Fully understanding the user manager will allow you to create complex systems which function safely in a multi-user environment.
The user manager has several methods to help access and organize user and group data.
fill_session
Fill the $_SESSION['user'] variable
with the logged in user's data. It also sets the default
timezone used by date functions to the user's timezone.
get_groups
Get the system's groups. It takes one argument, which, when true, will cause it to return both disabled and enabled groups, rather than just enabled groups.
get_users
Get the system's users. It takes one argument, which, when true, will cause it to return both disabled and enabled users, rather than just enabled users.
group_sort
Sort an array of groups hierarchically. An additional property of the groups can be used to sort them under their parents. It takes up to four arguments:
The array of groups.
The name of the property to sort groups by. Null for no additional sorting.
Boolean, determines whether to sort case sensitively.
Boolean, determines whether to reverse the sort order.
login
Logs the given user into the system. Takes only one argument, the user.
logout
Logs the current user out of the system.
print_login
Creates and attaches a module which lets the user log in. Takes up to two arguments, the position of the new module, and a URL to which the user will be redirected after logging in.
punt_user
Kick the user out of the current page. This method completely terminates execution of the script when it is called. Code after this method is called will not run. Though not required, user managers will usually give the user an opportunity to log in and return to the page, if they are not currently logged in. This method takes up to two arguments, a message to display to the user, and the URL they should use to retry after logging in. If no message is given, a generic message will be displayed. To prevent a message, use an empty string.
Two of the methods of the user manager,
gatekeeper and
punt_user, have shortcut functions to make
calling them easier. Unlike most shortcut functions in Pines, which
have "pines_" prepended to their names, these two functions have
identical names as their method counterparts.
Access control is one of the features heavily considered since the beginning of Pines' development. As such, the user manager has some unique and powerful systems of access control.
This section discusses the different types of access control that the user manager service provides. A component is not required to utilize these systems, but they will certainly make designing a safe, functional, multi-user component much easier. When developing Pines, SciActive always considers good access control very important. If you plan on contributing code to SciActive maintained components or Pines Core, please take the time to learn and understand these access control systems very well.
Abilities define what a user is allowed to do. Abilities can be given to both users and groups. When a user is in a group or groups that have abilities and the user is set to inherit abilities, the user manager will act as though the user has the sum of his own abilities and the abilities of all his groups.
The user manager has to be told what abilities a component
checks for. This is done in the component's
info.php file. The info array has an entry
called "abilities", which is an array of arrays. Each array in
the abilities array contains three values, the name of the
ability, the title or common name, and the description of the
ability.
'abilities' => array(
array('view', 'View Log', 'Let the user view the Pines log.'),
array('clear', 'Clear Log', 'Let the user clear (delete) the pines log.')
),
Once a user has been granted abilities, the
gatekeeper method can be used to check
them. To check an ability, append a slash and the name of the
ability to the name of the component, and call
gatekeeper. If no user is given as the
second argument, the currently logged in user will be used.
There is one special ability called "system/all" which has the
effect of granting the user all abilities.
punt_user$user = user::factory('hunter');
// Check that the user has the ability.
if (!$pines->user_manager->gatekeeper('com_logger/view', $user)) {
pines_notice('Hunter can\'t view the Pines log.');
return;
}
// Use shortcuts and check the current user. Punt them if they don't have the ability.
if (!gatekeeper('com_logger/view'))
punt_user(null, pines_url('com_logger', 'view'));
// Checking two abilities. Custom punt user message.
if (!gatekeeper('com_example/viewwidgets') && !gatekeeper('com_example/viewownwidgets'))
punt_user('You need the ability to see widgets to access this page.');
// Use depend to check multiple abilities. Same as above.
if (!pines_depend('ability', 'com_example/viewwidgets|com_example/viewownwidgets'))
punt_user('You need the ability to see widgets to access this page.');
Entity access controls determine what a user can do, if anything, with an entity. When a component retrieves entities from the entity manager, the user manager filters out any entities the user does not have read access to before the entities are returned from the method. Likewise, when a component tries to save or delete an entity, the user manager will only allow it if the user has appropriate permission.
Every entity which is created while a user is logged in
will be owned by that user, and belong to that user's primary
group. If an entity should belong to another user, it must be
saved, assigned to the other user, then resaved. An entity
belongs to a user when the user is placed in the entity's
user variable. Likewise, an entity belongs to
a group when the group is placed in the entity's
group variable.
Access controls are stored in a variable called
ac on every entity. This variable is an
object with the following three variables.
Table 4.4. Entity Access Control Variables
| Value | Default Value | Description |
|---|---|---|
user | 3 | Controls the entity's owner's access. |
group | 3 | Controls the access of all users in both the entity's group and all ancestor groups. |
other | 0 | Controls all other users' access. |
Each of these three variables can be one of the following numeric values.
Table 4.5. Entity Access Control Values
| Value | Access Level |
|---|---|
| 0 | No access to the entity is granted. |
| 1 | Access is granted to read the entity. |
| 2 | Access is granted to read and edit the entity. |
| 3 | Access is granted to read, edit, and delete the entity. |
![]() | Caution |
|---|---|
|
If your component allows the user to edit an entity's
|
The user manager's
check_permissions method can be used to
determine whether the current user has certain access to an
entity. It takes two arguments, the entity and the type of
access to check. The type of access is given as an integer, and
returns true if the user has at least that level of access.
Different situations use different checks to determine the
user's access.
Table 4.6. Entity Access Checking Situations
| Situation | Check Performed/Value Returned |
|---|---|
| No user is logged in. | Always true, should be managed with abilities. |
| The entity has no user and no group. | Always true. |
| The user has the "system/all" ability. | Always true. |
| The entity is the user. | Always true. |
| The entity is the user's primary group. | Always true. |
| The entity is a user or group. | Always true. |
Its user variable is the
user. (It is owned by the user.) | Check user AC. |
Its group variable is the
user's primary group. | Check group AC. |
Its group variable is one of
the user's secondary groups. | Check group AC. |
Its group variable is a
descendant of one of the user's groups. | Check group AC. |
| None of the above. | Check other AC. |
A component can change the access control variable to allow or prevent access.
// Create a new baz entity.
$entity = com_foobar_baz::factory();
$entity->foo = 'bar';
// Allow all users to see it, but only the current user to edit or delete it.
$entity->ac = (object) array(
'user' => 3,
'group' => 1,
'other' => 1
);
$entity->save();
It is the responsibility of the user manager to prevent your component from accessing an entity in a way the user is not permitted. Therefore, you don't need to add anything to your code. Entities that the current user does not have proper access to won't be returned when you query the entity manager. When you try to access a referenced entity, and the user manager blocks it due to access controls, it will appear as if the variable is null. If you wish to bypass the entity access control feature, you can use the "skip_ac" option in your entity queries.
// Get all baz entities, regardless of whether the logged in user has access to them.
$entities = $pines->entity_manager->get_entities(
array('class' => com_foobar_baz, 'skip_ac' => true),
array('&',
'tag' => array('com_foobar', 'baz')
)
);
![]() | Note |
|---|---|
|
When you request entities using the "skip_ac" option, any referenced entities will also be retrieved using the "skip_ac" option. |
The user class is very similar to the entity class. Their
interfaces both extend
data_object_interface, so they
share many of the same methods. Here is a list of the methods they
share.
array_search
delete
equals
in_array
is
refresh
save
Although the user's information doesn't have to be stored in an entity, users must act like entities in the following ways.
Users must have unique GUIDs, just like entities.
The user class must provide a
factory method.
When requesting entities, users must be returned if they match as well.
Users must be able to store data, including entity references, just like entities.
Users must work with the "ref" selector in the entity manager.
What this means is that even though users are not necessarily entities, they can be treated as if they were, except for tags. It is up to the user manager implementation whether to use entities to store users. The SciActive maintained user manager uses entities to handle users and groups.
Users have several useful methods:
add_group
Add the user to a (secondary) group. Takes one argument, the group. Returns true if the user is already in the group, the resulting array of groups if the user was not.
check_password
Check the given password against the user's. Takes one argument, the password in question. Returns true if the passwords match, otherwise false.
del_group
Remove the user from a (secondary) group. Takes one argument, the group. Returns true if the user wasn't in the group, the resulting array of groups if the user was.
disable
Disable the user.
enable
Enable the user.
get_timezone
Returns the user's timezone. Takes a boolean argument (defaults to false), whether to return an object of the DateTimeZone class, instead of an identifier string.
in_group
Check whether the user is in a (primary or secondary) group. Takes one argument, the group or the group's GUID.
is_descendant
Check whether the user is in a (primary or secondary) group that is a descendant of the provided group. Takes one argument, the group or the group's GUID.
password
Change the user's password. Takes one argument, the new password.
print_form
Print a form to edit the user. Returns the form's module.
The group class is also very similar to the entity class.
Its interface also extends
data_object_interface. It shares
the same methods with entities that the
user class shares. It also must act like
entities in the same way users do. Again, this does not include
support of tags.
Groups are hierarchical, and all children of a group have
the group in their parent variable. This makes
traversal up the hierarchy very easy. However, traversal down the
hierarchy is not as easy. It requires the use of the
get_children or
get_descendants methods.
Groups can also have a logo. This allows an easy visual representation of a user's position in the group hierarchy. This feature is especially useful for systems which use sports teams, subsidiary companies, store locations, countries, guilds, etc. If the current group doesn't have an associated logo, the user manager should traverse up the hierarchy until it finds a group with a logo, or return the default logo if it can't find one.
Groups have several useful methods:
disable
Disable the group.
enable
Enable the group.
is_descendant
Check whether the group is a descendant of the provided group. Takes one argument, the group or the group's GUID.
get_children
Gets an array of the group's child groups.
get_descendants
Gets an array of the group's descendant groups. Takes a boolean argument (defaults to false), whether to include itself in the returned array.
get_level
Get the number of parents the group has. If the group is a top level group, this will return 0. If it is a child of a top level group, this will return 1. If it is a grandchild of a top level group, this will return 2, and so on.
get_logo
Find the location of the group's current logo image. Takes a boolean argument (defaults to false), whether to return a full URL, instead of a relative one. Returns the URL of the logo image.
get_users
Gets an array of users in the group. Some user managers may return only enabled users, so don't rely on this returning all users. Takes a boolean argument (defaults to false), whether to include users in all descendant groups too.
print_form
Print a form to edit the group. Returns the form's module.