Legacy Documentation
You are using the documentation for version 4.3.8. Go here for the latest version.
Extensions Development¶
- Overview
- Naming Convention
- The Base Extension Class
- Activating and Updating
- Disabling
- Settings
- Calling of the Extension
- Multiple Extensions, Same Hook
- Extension Hooks
Overview¶
Within ExpressionEngine are what is known as ‘hooks’; little snippets of code in over 100 strategic places that allow the calling of third-party scripts that can rewrite and modify the inner workings of the program. Extensions can do things like modify an entire Control Panel page, add/remove functionality, and modify the appearance of certain page elements. Extensions enable third party developers to modify aspects of ExpressionEngine without hacking the backend scripts. You can think of an Extension as a plugin. But unlike a plugin, Extensions are not used within your templates, they instead allow you to modify the core system itself.
An Extension is an add-on script that is placed in the
/system/user/addons/<package_name>/
directory and
then enabled via the Add-on Manager in the Control Panel. Extensions can
have their own settings and their own database tables, if necessary, but
neither is required. If settings are available for an Extension, a
language file is required, but unlike a module there is no control panel
for Extensions.
Naming Convention¶
Extensions have a similar naming convention to ExpressionEngine plugins
so current developers should get the hang of them quickly. There is only
a single file required for an extension and inside this file should be a
PHP class. The name of the class is used in the file name of the
extension with the addition of the suffix _ext
so that the name of the
file is the lower-cased class name with the prefix ext. and the
standard PHP suffix of .php
. So, if we have an extension named
‘Link_truncator
’, then the file name for this extension would be
‘ext.link_truncator.php
’ and the class would be called
‘Link_truncator_ext
’.
Addon Setup file¶
All addons must have an addon.setup.php
file. This one will be quite simple:
<?php
return array(
'author' => 'Me',
'author_url' => 'https://example.com/',
'name' => 'Link Truncator',
'description' => '',
'version' => '1.0.0',
'namespace' => 'MyAddon\LinkTruncator',
'settings_exist' => TRUE
);
The Base Extension Class¶
Inside an extension file should be a class, which will be called by ExpressionEngine whenever this particular extension is required. The constructor for the class will require one parameter that will receive settings for the extension and set a class variable.
class Link_truncator_ext {
var $settings = array();
/**
* Constructor
*
* @param mixed Settings array or empty string if none exist.
*/
function __construct($settings='')
{
$this->settings = $settings;
}
// END
}
// END CLASS
Besides the $settings class variable, there are five other required class variables that your extension should have. These variables output meta information about your extension to the Extensions Manager so that it can describe your extension, provide documentation, and update settings (if any).
class Link_truncator_ext {
var $name = 'Link Truncator';
var $version = '1.0';
var $description = 'Truncates long links';
var $settings_exist = 'y';
var $docs_url = ''; // 'https://ellislab.com/expressionengine/user-guide/';
var $settings = array();
/**
* Constructor
*
* @param mixed Settings array or empty string if none exist.
*/
function __construct($settings = '')
{
$this->settings = $settings;
}
}
// END CLASS
If your extension has a language file, then you the $name and $description class variables can be set in the constructor by calling the language file and variables using the Language ($LANG) class. If your plugin is likely to be used internationally and by non-English speakers this is a recommended course of action.
Activating and Updating¶
There are two required methods for your extensions class that control the activating and updating of your extension. The most important is the function used to activate the extension in ExpressionEngine. To activate an extension, you are simply inserting a query into the database with various pieces of information like the extension hook and the name of the method in your extension’s class to call for this hook.
/**
* Activate Extension
*
* This function enters the extension into the exp_extensions table
*
* @see https://ellislab.com/codeigniter/user-guide/database/index.html for
* more information on the db class.
*
* @return void
*/
function activate_extension()
{
$this->settings = array(
'max_link_length' => 18,
'truncate_cp_links' => 'no',
'use_in_forum' => 'no'
);
$data = array(
'class' => __CLASS__,
'method' => 'truncate_this',
'hook' => 'typography_parse_type_end',
'settings' => serialize($this->settings),
'priority' => 10,
'version' => $this->version,
'enabled' => 'y'
);
ee()->db->insert('extensions', $data);
}
Here is a quick run down of what each of these fields in the database table mean:
extension_id
- primary id for row in tableclass
- name of your extension’s classmethod
- method being called for this extension hookhook
- name of the extension hook in the programsettings
- serialized array of settings, usually empty by defaultpriority
- an extension hook could have many extensions being called, so there needs to be priority. 1 => First, 10 => Last.version
- version of extension when activated, used for updatingenabled
- is this extension activated
Updating an extension is extremely easy in ExpressionEngine. The user
will simply upload the new version of the extension and ExpressionEngine
will automatically update the extension the next time it is called. All
that is required is an intelligent function called
update_extension()
. The program will automatically compare the
version of the extension information in the database against the version
of the extension file, and if the extension file is a newer version it
calls this function.
/**
* Update Extension
*
* This function performs any necessary db updates when the extension
* page is visited
*
* @return mixed void on update / false if none
*/
function update_extension($current = '')
{
if ($current == '' OR $current == $this->version)
{
return FALSE;
}
if ($current < '1.0')
{
// Update to version 1.0
}
ee()->db->where('class', __CLASS__);
ee()->db->update(
'extensions',
array('version' => $this->version)
);
}
Disabling¶
When an extension is enabled for the very first time, the
activate_extension()
function is called and all of the extension
calls are inserted into the database. When an extension is disabled
though, these extension calls are not removed from the database. Instead
they are merely disabled, which allows settings to be preserved and not
removed so that they are still there if the extension is enabled again
in the future.
This causes a problem for developers who, while developing an extension,
will often enable an extension to test their code but before they have
added all of their extension calls to the activate_extension()
function. What we have done is allowed the creation of a
disable_extension()
function in an extension’s class. If this
function exists in the class, it will be called whenever your extension
is disabled. This will allow you to clear out your extension’s data and
basically start fresh every single time.
/**
* Disable Extension
*
* This method removes information from the exp_extensions table
*
* @return void
*/
function disable_extension()
{
ee()->db->where('class', __CLASS__);
ee()->db->delete('extensions');
}
Settings¶
Abstracted Settings Form and Processing¶
If you want to give your extension the ability to have settings, then we
have written an abstracted layer to make it extremely easy. First, you
have to make sure that you have your $settings_exist
class variable
set to ‘y
’. Second, you need a language file for your extension with
the file name of the language file being the extension’s lower-cased
class name with a suffix of ‘_lang.php
’. Make sure the language file
is put in the /system/user/addons/package
name/language/
directory too. And finally, you need to have a method
in your extension’s class called settings()
. This function will
return an array in a certain form that will help the Extensions Manager
automatically create a form for your settings.
// --------------------------------
// Settings
// --------------------------------
function settings()
{
$settings = array();
// Creates a text input with a default value of "EllisLab Brand Butter"
$settings['brand'] = array('i', '', "EllisLab Brand Butter");
// Creates a textarea with 20 rows and an empty default value
$settings['description'] = array('t', array('rows' => '20'), '');
// Creates a set of radio buttons, one for "Yes" (y), one for "No" (n) and a default of "Yes"
$settings['tasty'] = array('r', array('y' => "Yes", 'n' => "No"), 'y');
// Creates a set of checkboxes, one for "Lowfat" (l) and one for "Salty" (s), and a
// default of both items being checked
$settings['details'] = array('c', array('l' => "Lowfat", 's' => "Salty"), array('l', 's'));
// Creates a select dropdown with the options "France" (fr), "Germany" (de), and "United States"
// (us), with a default of "United States"
$settings['country'] = array('s', array('fr' => 'France', 'de' => 'Germany', 'us' => 'United States'), 'us');
// Creates a multi-select box with the options "Derek" (dj), "Leslie" (lc), and "Rick" (re) with
// Derek and Rick selected by default
$settings['enjoyed_by'] = array('ms', array('dj' => 'Derek', 'lc' => 'Leslie', 're' => 'Rick'), array('dj', 're'));
// General pattern:
//
// $settings[variable_name] => array(type, options, default);
//
// variable_name: short name for the setting and the key for the language file variable
// type: i - text input, t - textarea, r - radio buttons, c - checkboxes, s - select, ms - multiselect
// options: can be string (i, t) or array (r, c, s, ms)
// default: array member, array of members, string, nothing
return $settings;
}
// END
A note about the values array for the second field: The keys will be used as the value for that item while the value will be the language text for that item. If you want, the value can be the name of a language variable from your extension’s language file and the Extensions Manager will automatically retrieve it for you.
Built In Settings Form and Processing¶
Alternatively, if your settings require a special form that cannot be
created by the abstracted layer above, then ExpressionEngine permits you
to create your own settings form and processing functions within your
Extension. First, you will need to have a method in your
extension’s class called settings_form()
.
/**
* Settings Form
*
* @param Array Settings
* @return void
*/
function settings_form($current)
{
$name = 'link_truncator';
if ($current == '')
{
$current = array();
}
$defaults = array(
'max_link_length' => 20,
'truncate_cp_links' => 'n'
);
$values = array_replace($defaults, $current);
$vars = array(
'base_url' => ee('CP/URL')->make('addons/settings/' . $name . '/save'),
'cp_page_title' => 'Link Truncator Settings',
'save_btn_text' => 'btn_save_settings',
'save_btn_text_working' => 'btn_saving',
'alerts_name' => 'link-truncator-save',
'sections' => array(array())
);
$vars['sections'] = array(
array(
array(
'title' => 'max_link_length',
'fields' => array(
'max_link_length' => array(
'type' => 'text',
'value' => $values['max_link_length'],
'required' => TRUE
)
)
),
// Site short name field
array(
'title' => 'truncate_cp_links',
'desc' => 'truncate_cp_links_desc',
'fields' => array(
'truncate_cp_links' => array(
'type' => 'yes_no',
'value' => $values['truncate_cp_links'],
'required' => TRUE
)
)
)
)
);
return ee('View')->make('link_truncator:index')->render($vars);
}
View File¶
Since we’re using the Shared Form View, our view file remains quite simple. In this case we will call it Views/index.php
<div class="box">
<?php $this->embed('ee:_shared/form')?>
</div>
Save Settings¶
Lastly, you will need to have a method in your extension’s class called
save_settings()
. This function will be called when your
settings_form()
method’s form is submitted. Use it to process the
data sent and put it into the exp_extensions database table. Remember
that the data put into the database is a serialized array, so handle it
appropriately.
/**
* Save Settings
*
* This function provides a little extra processing and validation
* than the generic settings form.
*
* @return void
*/
function save_settings()
{
if (empty($_POST))
{
show_error(lang('unauthorized_access'));
}
ee()->lang->loadfile('link_truncator');
$len = ee()->input->post('max_link_length');
if ( ! is_numeric($len) OR $len <= 0)
{
ee('CP/Alert')->makeInline('link-truncator-save')
->asIssue()
->withTitle(lang('message_failure'))
->addToBody(sprintf(lang('max_link_length_range'), $len))
->defer();
}
else
{
ee('CP/Alert')->makeInline('link-truncator-save')
->asSuccess()
->withTitle(lang('message_success'))
->addToBody(lang('preferences_updated'))
->defer();
}
ee()->functions->redirect(ee('CP/URL')->make('addons/settings/link_truncator'));
}
Calling of the Extension¶
The following is an example of an ExpressionEngine Extension Hook that is available for use:
// -------------------------------------------
// 'typography_parse_type_end' hook.
// - Modify string after all other typography processing
//
if (ee()->extensions->active_hook('typography_parse_type_end') === TRUE)
{
$str = ee()->extensions->call('typography_parse_type_end', $str, $this, $prefs);
}
//
// -------------------------------------------
The first parameter of $this->extensions->call_extension
is the name
of the hook, which lets the Extension class know what extensions to
call. The other three parameters are variables taken from the function
that the hook is embedded within. They provide information and data for
the extensions being called for this hook, which allows those extensions
to have information about the script that allow them to perform certain
actions or manipulate data. When an extension is called,
ExpressionEngine loads the extension file, instantiates the extension’s
class, and then calls the method specified for this extension hook as
specified by the extension when it was activated (see above concerning
activation).
When that method is called in the extension’s class those other three parameters will be sent to the method automatically. Here is what the method might look like:
/**
* Shorten Link Text
*
* This function is a callback method for preg_replace_callback in the method below.
*
* @param array array from the preg_match
* @return string Newly truncated Link.
*/
function _shorten_link_text($matches)
{
$link_text = $matches[3];
$link_text = substr($link_text, strpos($link_text, '://') + 3);
if (strlen($link_text) >= (int) $this->settings['max_link_length'] )
{
$l = (int) $this->settings['max_link_length'] / 2;
$b_part = substr($link_text, 0, $l);
$e_part = substr($link_text, -$l);
$link_text = $b_part . '…' . $e_part;
}
return $matches[1].$link_text.'</a>';
}
/**
* Truncate This
*
* This function is the meat & potatoes of the extension, where all
* the work is done.
*
* @see https://ellislab.com/expressionengine/user-guide/development/extension_hooks/global/typography/index.html#typography-parse-type-end
*
* @param string string to look
* @param object typography object
* @param array array of preferences
* @return string
*/
function truncate_this($str, $obj, $prefs)
{
if ($this->settings['truncate_cp_links'] == 'no' && REQ == 'CP')
{
return $str;
}
if (isset($obj->EE->FRM_CORE) && $this->settings['use_in_forum'] == 'no')
{
return $str;
}
$pattern = "/(<a[^>]*\s+href\s*=\s*(\042|047)([^\\2]*?)\\2[^>]*>)\\3<\/a>/i";
$str = preg_replace_callback($pattern, array(get_class($this), '_shorten_link_text'), $str);
return $str;
}
The three parameters from the extension hook are mapped straight to the three parameters of the method being called, and so your extension can easily use those parameters and do what it needs to do. The ExpressionEngine.com Extension Hook library will have a record of all extension hooks and the parameters available to you, along with a suggestion or two about what can be done with the extension hook.
Multiple Extensions, Same Hook¶
When an extension hook is called, ExpressionEngine checks the database to see if there are any extensions available for the hook. If there are extensions, then it processes them in order based on their priority level with the lower the priority number the sooner the extension is called. Because of priority, extensions might interfere with each other, so we have provided two variables for helping with that.
$this->extensions->last_call¶
There will be rather popular hooks being used by multiple extensions and
some hooks will expect you to return data to the extension hook. Because
of that, there is a variable available from the Extensions class
($this->extensions
) that will contain the returned data of any prior
extensions for that hook. Say, there is a hook for formatting text and
an extension before yours is called. That extension will be returning
the text formatted in its own way, but then your extension is called
with the original text details being sent. In such an instance of data
being returned and possible prior extensions, there is a variable
available to retrieve that already formatted text:
$this->extensions->last_call
. This variable will return whatever the
last extension returned to this hook. If there was no prior extension,
then the value of this variable is FALSE
.
$this->extensions->end_script¶
Many extension hooks exist for the express purpose of totally
controlling a page or script in the Control Panel. They are meant for
redesigning the appearance of a form or perhaps usurping a script for
processing form data. In those instances you want your extension to be
the last thing called for that extension hook so that nothing else is
processed after that point. The $this->extensions->end_script
exists
solely for that purpose. If you set this value to TRUE, then once your
extension is done being processed the execution of the hook is finished,
as is the script that the extension hook is contained within.