Extending the core: changing Scalar using ‘hooks’
Step 1: Enable hooks
Change false to true in /system/application/config/config.php.
$config['enable_hooks'] = TRUE;
Step 2: Edit hooks.php
Add the following to /system/application/config/hooks.php.
$hook['post_controller_constructor'][] = array(
'class' => '',
'function' => 'my_hook',
'filename' => 'custom.php',
'filepath' => 'hooks/my_hook',
'params' => array()
);
According to the CI hooks documentation, there are a few different strings that can be used in place of "post_controller_constructor". However, due to how Scalar's Controllers are architected, with _remap(), "post_controller_constructor" is the best solution.
Step 3: Add hook files
For the purposes of this demo, let's assume you wish to add both a CSS file and a JS file. Either way you'll need to create a new directory named after the "filepath" you define above ("hooks/my_hook"). Then, make sure to add a custom.php file inside, as that was also defined above.
/system/application/hooks/my_hook // The new directory as defined in Step 2
/system/application/hooks/my_hook/custom.php // Required as defined in Step 2
/system/application/hooks/my_hook/custom.js // Optional JS file, can be named anything
/system/application/hooks/my_hook/custom.css // Optional CSS file, can be named anything
Keep in mind that Scalar already includes jQuery, so you can use that right out of the box in custom.js without needing to add anything else.
Step 4: Edit custom.php
Open custom.php and add a function that matches what was set in the "function" field in Step 2 (in this case, "my_hook"). Then, put the CI instance into a variable ("$ci"). From here you can do anything you can typically do in PHP, though for this step let's stick to adding the CSS and JS files. A more thorough example is included further down.
<?php
function my_hook() {
$ci =& get_instance();
$ci->template->add_js('system/application/hooks/my_hook/custom.js');
$ci->template->add_css('system/application/hooks/my_hook/custom.css');
}
?>
Note that adding the CSS file in this way will place the CSS file at the top of the stack of CSS files in Scalar's front-end. This means that Scalar's default stylesheets will take precedence! If this poses a problem, go ahead and remove the add_css(...) line above, adding the stylesheet, instead, in custom.js.
Step 5: Edit custom.js
Now that the JS file is loaded, you can hand off most of the work to Javascript or jQuery. For example, you can load in custom.css (if you decided to skip it in Step 4), set a custom JS file for CKEditor (Scalar's WYSIWYG editor), and load in more JS files. Keep in mind Scalar's pageLoadComplete event, under which you should include any DOM manipulation.
$(document).ready(function() {
var approot = $('link#approot').attr('href');
// Add stylesheet to bottom of stack (if skipped adding in Step 2)
$('head').append('<link type="text/css" rel="stylesheet" href="'+approot+'hooks/my_hook/custom.css" />');
// Custom CKEditor settings
if (window.CKEDITOR) CKEDITOR.config.customConfig = approot+'hooks/my_hook/ckeditor_custom.js';
// Wait until Scalar has finished its DOM changes to do ours
$('body').on('pageLoadComplete', function() {
$('#mainMenuInside .home_link > a').eq(0).html('Start page');
}
});
More with custom.php
As mentioned, there is more that you can do inside custom.php. You can, as you would expect, add your own functions, access models and views via the CI instance, etc. Below is an example of some common tasks. Keep in mind that there might be better ways to do some tasks than use a hook. For example, both CSS and JS files can be added on a per-book bases in the Dashboard. So we recommend first becoming familiar with Scalar's Dashboard, our Advanced Topics, and the various config files kept in /system/application/config.
<?php
function my_hook() {
$ci =& get_instance();
// Add a custom Layout
$my_layout = array('name'=>'My Layout','description'=>'<b>A custom layout only available in this Scalar install.</b> Javascript included somewhere will be invoked if this layout is mentioned in the HTML header.','image'=>'hooks/my_hook/custom_layout.png');
$ci->config->config['views']['my_layout'] = $my_layout;
$ci->data['views']['my_layout'] = $my_layout;
// Don't continue if on the edit page or annotation editor page
$ignore_pages = array('.edit', '.annotation_editor');
foreach ($ignore_pages as $ignore) {
if ($ignore == substr($ci->uri->uri_string, strlen($ignore)*-1)) return;
}
// Don't continue if in the Dashboard
if ('system' == $ci->router->fetch_class()) return;
// Only continue if in a specific book
if ('my-book' != $ci->data['book']->slug) return;
// Remove Scalar's main stylesheet and add our own
$ci->template->block('system/application/views/melons/cantaloupe/css/common.css', 'css');
$ci->template->add_css('system/application/hooks/my_hook/custom.css');
}
?>
More with custom.js
Once in Javascript there are a few built-in variables that can be leveraged for minor adjustments to the front-end look-and-feel. Of course, you can adjust just about anything. You'll want to make sure to wrap any DOM manipulation in a ready event handler, and, because Scalar does a fair amount of manipulation itself, also include Scalar's pageLoadComplete event in most cases.
$(document).ready(function() {
var approot = $('link#approot').attr('href');
var parent = $('link#parent').attr('href');
var base = $('base').attr('href');
var slug = base.replace(parent,'');
// Custom CKEditor settings
if (window.CKEDITOR) {
CKEDITOR.config.customConfig = approot+'hooks/my-hook/ckeditor_custom.js';
};
$('body').on('pageLoadComplete', function() {
// Custom scripts for individual pages
if ('my-custom-page' == slug) $.getScript(approot+'hooks/my-book/custom_script.js');
});
// Built in variables for minor adjustments to front-end
scalarMediaHideSourceFileTab = true;
scalarMediaDetailsHideSourceFileLink = true;
scalarMediaDetailsAddDescription = false;
scalarMediaDetailsHideCitationsSectionIfNoCitations = true;
// Overwrite the "Details" tab underneath each media element
customAddMetadataTableForNodeToElement = function(node, element, linkify) {
var propOrder = [
['dcterms:title','Title'],
['dcterms:description','Description'],
['dcterms:creator','Creator'],
['dcterms:date','Creation date'],
['dcterms:temporal','Timeline date'],
['dcterms:source','Source'],
['dcterms:rights','Rights'],
['dcterms:provenance','Provenance'],
['vra:culturalContext','Culture/community'],
['dcterms:language','Language'],
['dcterms:type','Type'],
['dcterms:format','Material'],
['dcterms:coverage','Location'],
['dcterms:spatial','Coordinates'],
['dcterms:identifier','Identifier']
]
var obj = $.extend({}, node.current.auxProperties, {
'dcterms:title':[node.getDisplayTitle()],
'dcterms:description':[node.current.description],
'dcterms:source':[node.current.source],
'art:sourceLocation':[node.current.sourceLocation]
});
var $table = $('<table></table>').appendTo(element);
for (var j = 0; j < propOrder.length; j++) {
if ('undefined' == typeof(obj[propOrder[j][0]])) continue;
var values = obj[propOrder[j][0]];
for (var k = 0; k < values.length; k++) {
var value = (null == values[k]) ? '' : values[k];
if (!value.length) continue;
$table.append( '<tr><td><span title="'+propOrder[j][0]+'">' + propOrder[j][1] + '</span></td><td>' + linkify(value) + '</td></tr>');
};
};
$table.append('<tr><td>Scalar URL</td><td><a href="'+node.url+'">'+node.url+'</a> (version '+node.current.number+')</td></tr>');
$table.append('<tr><td>Source URL</td><td><a href="'+node.current.sourceFile+'" target="_blank">'+node.current.sourceFile+'</a> ('+node.current.mediaSource.contentType+'/'+node.current.mediaSource.name+')</td></tr>');
};
});