Complete Idiots Guide to using Composer with CakePHP

I recently wanted to use Miles Johnson’s Forum Plugin for CakePHP in a project, and when I came to install it, saw that he recommends (insists on, in fact) using Composer to do this, to take care of ensuring all the dependencies are installed. Never having used Composer before, I took a look at his post on using Composer with CakePHP, which probably counts as an idiots guide for most people, but still wasn’t basic enough for me. Therefore, I thought I would write a complete idiots guide!

The steps I had to go through to get it working were as follows:

  • Install Composer – I am on Windows, so just downloaded and ran the Windows installer, which seemed to work fine
  • Ensure that the openssl extension is enabled in php.ini, i.e. uncomment this line: extension=php_openssl.dll
  • Create a composer.json file in your app directory and add the dependencies, e.g. for the Forum plugin:
    {
       "config": {
          "vendor-dir": "Vendor"
       },
       "require": {
          "mjohnson/forum": "5.*"
       }
    }
  • Run composer install in the app directory. This should install all of the dependencies (in the Vendor or Plugin directories of your app, as appropriate) and create an autoload.php file in the Vendor directory
  • Add the following at the start of your Config/core.php file to make use of Composer’s autoloading capabilities:
    require_once dirname(__DIR__) . '/Vendor/autoload.php';

That should deal with all of the dependencies, in this case for the Forum Plugin. It is still necessary to load the Plugins in the Config/bootstrap.php file. For the Forum Plugin, this is does as follows:

CakePlugin::load('Utility', array('bootstrap' => true, 'routes' => true));
CakePlugin::load('Admin', array('bootstrap' => true, 'routes' => true));
CakePlugin::load('Forum', array('bootstrap' => true, 'routes' => true));

IOS 7 status bar with fixed footer navbar in Cordova/JQuery Mobile

To avoid your content scrolling underneath the new IOS 7 status bar, you have a few choices but in most situations, org.apache.cordova.statusbar is the best solution.

From the CLI:

$ cordova plugin add org.apache.cordova.statusbar

And you can then turn the transparent IOS7 status bar into one which behaves as it does in IOS6 with the following in your config.xml:

<preference name="StatusBarOverlaysWebView" value="false" />
<preference name="StatusBarBackgroundColor" value="#000000" />

However, if you have a fixed navbar in the footer such as this to approximate the look of a tab bar in the new IOS7  Human Interface Guidelines:

<div data-role="footer" data-id="allot_footer" data-position="fixed">
    <div data-role="navbar" data-iconpos="bottom">
        <ul>
            <li><a href="#browse" data-id="footer_browse" data-theme="d" data-icon="grid">Browse</a></li>
            <li><a href="#find" data-id="footer_find" data-theme="d" data-icon="search">Find</a></li>
        </ul>
    </div>
</div>

then you’ll find that the statusbar plugin pushes the navbar down off the bottom of the page by the height of the status bar. So, for a fixed bottom navbar, the solution I’ve adopted inn the end is to forget the statusbar plugin and to allow the status bar to overlay my content. However, to create a bit of space at the top of the page for it to sit, I used this Gist from Shazron to arrive at:

function onDeviceReady() {
    if (parseFloat(window.device.version) >= 7.0) {
        $("div.ui-header").css("padding-top", "20px"); 
        }
    ...
    }

Note that you’ll need to install org.apache.cordova.device using the CLI:

$ cordova plugin add org.apache.cordova.device

It now looks fine on startup but if you need to scroll your content, it’ll still get overlayed by the IOS7 status bar so make your header fixed too:

<div data-role="header" data-id="allot_header" data-position="fixed" data-theme="d">
    <h1>Browse</h1>
</div>

Hoping that IOS7 issues will all become a distant memory very soon, but this is what works for me at the moment.

QuestionMark Perception’s GetScheduleListByParticipant method in QMWISe

PLEASE NOTE: I’m looking further into why this may be happening after a very useful chat with Questionmark’s Richard Ham so watch this space…

If you’re using QuestionMark Perception’s GetScheduleListByParticipant method through their QMWISe API and the Questionmark Basic LTI Community Connector (I don’t know about the LTI Connector for Questionmark OnDemand) then this is a quick heads up about a problem that I’ve just encountered in v5.4. We use this method to work out whether students are currently scheduled for a given assessment through a (rather inelegant) check of whether the AssessmentID they are trying to access is the same as the AssessmentID of one of the assessments they are scheduled for and, if that schedule is time-limited, whether it is currently open.

The problem is that, for reasons that are probably die to the way that PHP deals with a SOAP return , GetScheduleListByParticipant returns a different object structure depending on whether the one or more schedules are returned. For one schedule (with print_r()):

stdClass Object
(
    [Schedule] => stdClass Object
        (
            [Schedule_ID] => 111111111
            [Assessment_ID] => 1111111111111111 
            [Participant_ID] => 0 
            [Group_ID] => 111111111 
            [Schedule_Name] => Quiz 1 
            [Restrict_Times] => 1 
            [Restrict_Attempts] => 
            [Max_Attempts] => 0 
            [Monitored] => 0 
            [Schedule_Starts] => 2013-12-03T00:00:00 
            [Schedule_Stops] => 2013-12-04T00:00:00 
        )
)

Whereas with more than one schedule:

stdClass Object
(
    [Schedule] => Array
        (
            [0] => stdClass Object
               (
                   [Schedule_ID] => 1111111
                   [Assessment_ID] => 1111111111111111
                   [Participant_ID] => 0
                   [Group_ID] => 111111111
                   [Schedule_Name] => Quiz 1
                   [Restrict_Times] =>
                   [Restrict_Attempts] =>
                   [Max_Attempts] => 0
                   [Monitored] => 0
                   [Schedule_Starts] => 0001-01-01T00:00:00
                   [Schedule_Stops] => 0001-01-01T00:00:00
               )
            [1] => stdClass Object
               (
                   [Schedule_ID] => 2222222
                   [Assessment_ID] => 2222222222222222
                   [Participant_ID] => 0
                   [Group_ID] => 222222222
                   [Schedule_Name] => Quiz 2
                   [Restrict_Times] => 1
                   [Restrict_Attempts] =>
                   [Max_Attempts] => 0
                   [Monitored] => 0
                   [Schedule_Starts] => 2013-12-03T00:00:00
                   [Schedule_Stops] => 2013-12-04T00:00:00
               )
            [n] => and so on...

The real problem with is when you compare desired AssessmentID with scheduled AssessmentID in a for loop:

$schedules = get_schedule_list_by_participant($participant_id)) //method which calls GetScheduleListByParticipant
foreach($schedules->Schedule){
    ... //check each schedule
    }

It works fine until you come across a student with only one schedule, at which point it breaks as it tries to iterate through the properties of the only schedule returned. Until we can work out something else, the simplest solution is to check the number of schedules returned before deciding how to evaluate them:

if(count($schedules->Schedule)>1){ 
    foreach($schedules->Schedule as $schedule){
        ... //check each schedule
        }
    }
elseif(count($schedules->Schedule)==1){ 
    ... //check this schedule
    }

Haven’t checked whether behaviour is the same with the other QMWISe methods but I wouldn’t be surprised….

CKEditor: Use config.allowedContent/extraAllowedContent to stop tags being stripped

I was having an issue with certain tags, e.g. span and div, being stripped out of html by CKEditor. Whether or not I should be using these tags is another issue – in this particular situation it was useful to use span tags to give a class to snippets of text that would be styled in different ways, to indicate different types of text (printed, handwritten or engraved) on historical slides in CSlide.

It turns out that CKEditor (4.1 onwards) has the Advanced Content Filter (ACF), which is a highly configurable filter that allows only certain types of content to be entered into the editor, and removes any html tags that are not allowed by the configuration. The default behaviour (Automatic mode) results in all of the enabled editor features, e.g. styling buttons, formats, etc, adding their rules (i.e. the html associated with the feature) to the filtering rules to allow that type of content. The alternative to this is Custom mode, where the allowed content is defined in the config, using config.allowedContent – see the CKEditor guide on defining allowed content rules.

Turning off the ACF

I wouldn’t advise this (it is much better to configure the ACF), but if you really don’t want CKEditor to do any filtering, you can turn it off by adding the following line to config.js:

config.allowedContent = true;

extraAllowedContent

As well as fully defining the ACF rules using config.allowedContent, it is also possible to extend the current rules using config.extraAllowedContent, and this was the most straightforward solution to my issue. extraAllowedContent allows you to add rules to the current config, which is an easy way to extend, for example, the automatic mode configuration. In my case, I was able to allow span tags, and define a set of classes that these span tags would be allowed to have:

config.extraAllowedContent = 'span(engraved,printed,manuscript,slide_text,label_text)';

This setting means that any other class associated with span tags will be removed (though the span tag will remain). Using ‘span(*)’ allows span tags with any class.

Ajax form submission from a JQuery UI modal dialog in CakePHP

I just wanted to share with you two approaches to bringing up an add or edit form in a JQuery UI modal dialog and closing the dialog on a successful submit or keeping it open and displaying errors in case of validation or other save problems.

A non-Cake approach

Ingredients

CakePHP 2+
JQuery 1.9+
JQuery UI
JQuery Form Plugin

The Views

Main page

This is the page from which we want to launch our modal add or edit dialog:

<!-- overlayed element -->
<div id="dialogModal">
     <!-- the external content is loaded inside this tag -->
     <div class="contentWrap"></div>
</div>
...
<div class="actions">
    <ul>
        <li>
            <?php echo $this->Html->link(__('Add user', true), array("controller"=>"users", "action"=>"add"), array("class"=>"overlay", "title"=>"Add User"));
        </li>
    </ul>
</div>
...
<script>
$(document).ready(function() {
    //prepare the dialog
    $( "#dialogModal" ).dialog({
        autoOpen: false,
        show: {
            effect: "blind",
            duration: 500
            },
        hide: {
            effect: "blind",
            duration: 500
            },
        modal: true
        });
    //respond to click event on anything with 'overlay' class
    $(".overlay").click(function(event){
        event.preventDefault();
        $('#contentWrap').load($(this).attr("href"));  //load content from href of link
        $('#dialogModal').dialog('option', 'title', $(this).attr("title"));  //make dialog title that of link
        $('#dialogModal').dialog('open');  //open the dialog
        });
    });
</script>

Our add or edit form

This is shown in the dialog

echo $this->Form->create('User');
echo $this->Form->input('name');
echo $this->Form->end('Submit');
<script>
$('#UserAddForm').ajaxForm({ 
    target: '#contentWrap', 
    resetForm: false, 
    beforeSubmit: function() { 
        $('#contentWrap').html('Loading...'); 
        }, 
    success: function(response) { 
        if (response=="saved")) { 
            $('#dialogModal').dialog('close');  //close containing dialog    
            location.reload();  //if you want to reload parent page to show updated user
            } 
        } 
    });
</script>

Controller

For our add or edit action

function add() {
    ...
    if (!empty($this->request->data)) {
        $this->User->create();
        if ($this->User->save($this->request->data)){ 
            return "saved"; 
            } 
        }
    ...
    }

 

A more Cakey approach

With thanks to RXC on StackOverflow

Ingredients

CakePHP 2+
JQuery 1.9+
JQuery UI

The Views

Main page

This is the same as above

Our add or edit form

echo $this->Form->create('User');
echo $this->Form->input('name');
echo $this->Js->submit('Save', array(  //create 'ajax' save button
    'update' => '#contentWrap'  //id of DOM element to update with selector
    ));
if (false != $saved){ //will only be true if saved OK in controller from ajax save above
    echo "<script>
        $('#dialogModal').dialog('close');  //close containing dialog         
        location.reload();  //if you want to reload parent page to show updated user
    </script>";
    }
echo $this->Form->end();
echo $this->Js->writeBuffer(); //assuming this view is rendered without the default layout, make sure you write out the JS buffer at the bottom of the page

Controller

For our add or edit action

function add() {
    ...
    $this->set('saved', false); //false by default - controls closure of overlay in which this is opened
    if (!empty($this->request->data)) {
        $this->User->create();
        if ($this->User->save($this->request->data)){ 
            $this->set('saved', true); //only set true if data saves OK
            } 
        }
    ...
    }

Hope that’s helpful. Would be very interested in alternatives that have worked for you…and corrections to inevitable errors above.

Google Maps Autocomplete in Bootstrap Modal

I’ve just spent a while struggling to get a Google Maps API v3 Places Autocomplete search text box working in a Bootstrap JS Modal. It seemed as though the autocomplete wasn’t working at all, which I initially thought meant that I had messed up my JQuery selectors. However, it turns out that it the autocomplete was working (I could use the down arrow to toggle through the results), but the results were being shown behind the modal box. The z-index of the modal box is 1040 by default, whereas that of the .pac-container div that shows the autocomplete results is 1000 (defined inline).

Therefore, the solution (easy when you know how) is to change the z-index of the .pac-container div to greater than 1040, so that it appears on top of the modal. Since the z-index of .pac-container is defined inline, it is necessary to use !important to override this inline style with from an internal/external stylesheet:

div.pac-container {
   z-index: 1050 !important;
}

Moving/copying large MySQL databases between servers with or without phpMyAdmin

I don’t suppose I’m alone in restricting the majority of my interactions with MySQL to what you can do through the wonderful phpMyAdmin. However, one place where it really lets you down is when you are trying to move large databases between servers. Exporting doesn’t seem to cause any problems (with the 100MB or so that I’ve tried so far anyway) but importing is where things start to go wrong.

phpMyAdmin seems to default to a maximum file upload size of 8MB but this, and other settings, are actually set in php.ini. You can zip (or gzip, etc.) your exported SQL file to bring down its size and/or change:

post_max_size = 8M
upload_max_filesize = 2M

in php.ini (just make sure you’re editing the right one – you will often find more than one) to values appropriate for your file.

However, you may still run into problems with execution timeouts and out of memory errors. You can fiddle around trying to change these settings in php.ini:

max_execution_time = 30 (in seconds)
memory_limit = 128M

If this fails, or if you’re just more of a GUI kind of person, and before you take the desperate step of trying to import table by table (if you do go down this route, make sure you have exported with ‘Disable foreign key checks’ ticked with InnoDB tables, to prevent missing foreign key errors), take a look at the free, cross-platform MySQL Workbench (http://www.mysql.com/products/workbench/). We already use this as a great visual DB modelling tool which takes care of all the complexities of foreign key constraints but I have only just realised today that it’s also a great way to import large databases. Simply connect to your target MySQL DB and:

  1. Click ‘Data Import/Restore’ under ‘Management’ in the ‘Navigator’ pane;
  2. Choose ‘Import from Self-Contained File’ and browse to your sql file;
  3. Choose the pre-existing database name (from ‘Default Target Schema’) if you have already created it or allow it to create it for your if you have exported with the DB creation code in there.

It takes a while, and the progress bar doesn’t move ’til it’s finished but it does the job beautifully.

 

CKEditor: Custom format options

I wanted to just allow a single “Heading” paragraph format in CKEditor, rather than having multiple heading options. I found that it was possible to change the available options in editing “config.format_tags” in ckeditor/config.js. For example, the following would show just the paragraph (Normal) and h1 (Heading 1) format options:

config.format_tags = 'p;h1';

However, having “Heading 1” as the only heading option isn’t particularly satisfactory, so I wanted to rename it to something else. This requires defining a custom format type, as follows:

config.format_tags = 'p;Heading';
config.format_Heading = { element : 'h1', name: 'Heading' };

Each of the format_tags needs a corresponding format_(tagName) entry and the default ones are predefined (http://docs.ckeditor.com/source/plugin28.html#CKEDITOR-config-cfg-format_tags). Overwrite these defaults in the config.js file does not seem to work, so I had to define a new format type, ‘Heading’, using the h1 element and named ‘Heading’.

MSDLT Update May-Oct 2013

Teaching Excellence Project Award for mobile technology

New user-friendly interface for My Workspace Resources for Biomedical Sciences students in Sakai/WebLearn

Bringing Biochemistry TLTP software up to date

And progress with: online maths assessment; project choosing; clinical sign-off and the History of Medical Sciences website

Get your copy here: ETSGMSDLTNewsMaytoOct2013.

Google Maps API v3: Capturing viewport change – use “idle” not “bounds_changed”

When wanting to capture a change in the viewport (e.g. zoom, pan), when using the Google Maps API v3, the obvious event to listen for seems to be “bounds_changed”. However, when dragging the map to pan, this event is fired repeatedly, which, in our case, meant that there hundreds of requests being sent when a user panned.

The solution is to listen for the “idle” event instead, which is only fired when the user has stopped panning/zooming:

google.maps.event.addListener(map, 'idle', function() {
   //Do something when the user has stopped zooming/panning
});