01
Jul '19

by john

When moving to a different hosting provider, or doing some housekeeping with shared databases, sometimes it’s necessary to change the wordpress table prefix (the ‘wp_’ bit, by default) to something else. The table rename is easy enough, but after the rename, when trying to use your admin panel you will likely be greeted with a message saying:

Sorry, you are not allowed to access this page.

This is a small undocumented knock-on from the table name change, as there is also an option value that you need to change too. The _options table has an option called ‘wp_user_roles’, which also needs to have its prefix changed to match the tables. Do that and all should come good.

In case you’re interestered this option defines the user roles available to your users. Without it users can’t have assigned roles, and hence they have no permissions for any administrative function.

05
Mar '19

by john

I spent some time messing about with Bootstrap popovers, and other similar tools but was finding that they tended to be overly complicated to use and skin, and also mostly wouldn’t allow form elements to be inside them. I found myself with a task where I was very tight on space and needed to add several textareas to my form. A popover was the obvious choice, but many relied on convoluted attributes on the trigger element or involved javascript handlers. Even more importantly most tend to destroy the popover when it is closed, which is clearly no use I want a form element in there.

After a bit of playing around I came up with this script myself:

CSS:

.popover-container {
    position: relative;
}

.popover-content {
    z-index: 10000;
    border: 1px solid #555555;
    padding: 8px;
    margin-top: 10px;
    width: 300px;
    position: absolute;
    left: -1000000px;
}

.popover-content.open {
    left: -150px;
}

.popover-content.open:after {
    content: '';
    display: block;
    position: absolute;
    top: -6px;
    left: 150px;
    width: 10px;
    height: 10px;
    background: #FFFFFF;
    border-right:1px solid #555555;
    border-top:1px solid #555555;
    -moz-transform:rotate(-45deg);
    -webkit-transform:rotate(-45deg);
}

JS:

$().ready(function() {

    $('.popover-container').on('click', 'button', function() {
        var parent = $(this).parents('.popover-container');
        var content = parent.find('.popover-content');

        if(content.hasClass('open')) {
            closePopover(content);
        }
        else {
            //Clear down any others first
            $('.popover-content.open').each(function() {
                closePopover($this);
            });

            content.addClass('open');

            var offset = 150 - (parent.width() / 2) - 2;

            content.css('left', '-' + offset + 'px');
        }
    });

    //Close on blur
    $('.popover-container').on('blur', '.popover-content', function() {
        closePopover($(this));
    });

    //Close on escape key
    $(document).keyup(function(e) {
        if (e.keyCode === 27) {
            $('.popover-content.open').each(function() {
                closePopover($(this));
            });
        }
    });
});

function closePopover(element) {
    element.removeClass('open');
    element.css('left', '');
}

Example HTML:

<div class="popover-container">

    <button type="button" class="btn btn-xs btn-default">
        Show Popover
    </button>

    <div class="popover-content">
        <div class="form-group">
            <p>This is my popover</p>
            <textarea name="my-textarea"></textarea>
        </div>
    </div>
</div>

Note that the CSS and JS above are based around the popover being 300px wide. You can change this to whatever you like, but bear in mind that the centering (the 150px offsets) needs to be adjusted in a corresponding way.

17
Jan '19

by john

In my latest projects I’ve found myself increasingly putting input forms within bootstrap modals. It cleanly separates active and static elements, it maintains a tidy UI, and it reduces page load time to name but a few benefits.

A key drawback however is that it’s very easy to click away from a modal by accident and lose your work. To deal with this I created a quick script that listens for changes in any input elements within a modal, and catches the modal close event accordingly:

var is_clicked = false;
var dirty = false;

$().ready(function() {

    /*
     * Keeps the change listener inactive until at least one element has been clicked on 
     */
    $('.modal').on('click', 'input, select, textarea', function(e) {
        is_clicked = true;
    });

    /*
     * input element change listener
     */
    $('.modal').on('change', 'input, select, textarea', function(e) {

        if(is_clicked) {
            dirty = true;
        }
    });

    /*
     * Handles the modal hide event
     */
    $('.modal').on('hide.bs.modal', function(e) {
        //Prevent date picker submits from trying to close parent modals
        if($(e.target).hasClass('date-box'))
            return false;

        if(dirty) {
            if(confirm('You have unsaved changes. Are you sure you wish to close this dialog?')) {
                is_clicked = false;
                dirty = false;
                return true;
            }

            return false;
        }

        return true;
    });
});

In reality I tend to use pnotify for my confirmation dialogs, but to keep this example as simple as possible I put in the native javascript call.

01
Nov '18

by john

Ok many of you may already know this one, but it’s a relatively new one to me, and since non-HTML5 browsers are now mostly gone the way of the dinosaur, I’m much more comfortable relying on its elements for my core functionality. If you are still on IE9 or below then you deserve all you get!

This is as simple as I can make an AJAX form submission handler, that will also deal with files. It uses the HTML5 FormData object to serialise all the form data, including files, and allow us to pass it with the AJAX POST request.

$('.my-form').on('submit', function(e) {

    var form_data = new FormData($(this)[0]);

    $.ajax({
        type: 'POST',
        url: $(this).attr('action'),
        data: form_data,
        dataType: 'json',
        cache: false,
        contentType: false,
        processData: false,
        success: function(response) {
            // Wahey!
        },
        error: function() {
            // Oh dear
        }
    });

    return false;
});

The ‘cache’, ‘contentType’ and ‘processData’ flags ARE necessary to make this work properly. The ‘dataType’ is optional, but if your server doesn’t send back JSON in response to an AJAX request as standard then you need to have a word with yourself!

Also notice that the FormData object needs the DOM version of the form, not the jQuery object, hence the need for “$(this)[0]” in the constructor.

Job. Done.

25
Jul '18

by john

I know I know. Rely too much on traits and you’re asking for trouble. But when used correctly, for self-contained bolt-on functionality they really can be fantastic and save loads of time.

There only condition for this logic is that any record with which you are using this must have a ‘logo’ field. Yeah I know I could have done a more advanced version with a separate logo table and a polymorphic relationship, but it’s not like a standard system will have 2 dozen records that all require logos against them so I’ve kept it simple.

I’ve used the word ‘logo’ here, but it’s a generic image storing trait and could be used for anything. You’ll also notice an optional config option of ‘app.directory.logo’, which, if provided, will be used as a relative path below the application’s public folder, otherwise ‘/logos’ will be used.

This assumes that you have an image referenced as ‘logo_file’ in your request and have triggered a save on the parent record.

namespace App\Trt;

trait Logo
{
    /**
     * Custom boot method
     */
    public static function bootLogo()
    {
        /*
         * Trigger on save
         */
        static::saved(function ($record) {

            try {
                if(request()->hasFile('logo_file') && request()->file('logo_file')->isValid()) {
                    $logo_file = request()->file('logo_file');
                    $logo_folder = public_path(config('app.directory.logo', '/logos'));

                    if(is_dir($logo_folder) && is_writable($logo_folder)) {
                        //Prepending the record ID to ensure uniqueness
                        //Could also prepend parent record type / sanitised model name if it's to be used for multiple records
                        $file_name = $record->id . '_' . $logo_file->getClientOriginalName();

                        $record->clearLogo(false);

                        if($logo_file->move($logo_folder, $file_name)) {
                            $record->logo = $file_name;

                            //Direct update here so we don't trigger any callbacks
                            \DB::table($record->getTable())->where('id', $record->id)->update(['logo' => $file_name]);
                        }
                    }
                    else {
                        \Log::error('Logo folder is not writable ' . $logo_folder);
                    }
                }
                elseif(request()->clear_logo) {
                    $record->clearLogo();
                }
            }
            catch(\Exception $e) {

            }
        });

        /*
         * Trigger on delete
         */
        static::deleting(function ($record) {
            $record->clearLogo(false);
        });
    }

    /**
     * Check for logo
     * @return boolean
     */
    public function hasLogo() {
        return ($this->logo && is_file($this->logoPath()));
    }

    /**
     * Logo URL
     * @return string|false
     */
    public function logoURL() {
        return $this->logo ? url(config('app.directory.logo', '/logos') . '/' . $this->logo) : false;
    }

    /**
     * Logo Path
     * @return string|false
     */
    public function logoPath() {
        return $this->logo ? public_path(config('app.directory.logo', '/logos') . '/' . $this->logo) : false;
    }

    /**
     * Clear Logo
     * @param boolean [$save=true] Save
     * @return boolean
     */
    public function clearLogo($save = true) {
        if($this->logo) {
            if($this->hasLogo())
                unlink($this->logoPath());

            $this->logo = null;

            if($save)
                $this->save();

            return true;
        }

        return false;
    }
}

And that’s it!

01
Jun '18

by john

I’m sure we’ve all been there. You create a nice data structure for a utility and the client says “Ok, but I also need to be able to add a note to it”. You add a note field and then they say “Ok, but I also need to add a note over here”. You add another note field over there and then they say “But I need to be able to add new notes and keep the old ones”. You scream internally, wish they’d told you that in the first place, and start building a more flexible solution.

Well here is my take on it. It’s a separate notes table with a polymorphic association to any and all other records. Their models simply need to use this ‘Notable’ trait. The notes will be created automatically with any save action on the parent model, and have a simple functions to retrieve all notes against a parent, or clear them. Simples.

This assumes that you have either a single ‘note’ parameter in your request, or a ‘notes’ array, and have triggered a save on the parent record.

Table structure:

CREATE TABLE `notes` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `parent_id` int(11) NOT NULL,
  `parent_type` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `content` text COLLATE utf8_unicode_ci NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`)
);

Trait:

namespace App\Trt;

/**
 * Notable trait
 */
trait Notable
{
    /**
     * Custom boot method
     */
    public static function bootNotable()
    {
        /*
         * Trigger on create
         */
        static::saved(function ($record) {
            //We check the class name here as otherwise it will get in an infinite loop of trying to add notes below notes
            if(class_basename(get_class($record)) != 'Note' && (request()->note || request()->notes)) {

                if(request()->note) {
                    $record->notes()->create(['content' => request()->note]);
                }

                if(request()->notes && is_array(request()->notes)) {
                    foreach(request()->notes as $note) {
                        $record->notes()->create(['content' => $note]);
                    }
                }
            }
        });

        /*
         * Trigger on delete
         */
        static::deleting(function ($record) {
            $record->clearNotes();
        });
    }

    /**
     * Note relationship
     */
    public function notes()
    {
        return $this->morphMany('\App\Model\Note', 'parent');
    }

    /**
     * Clear all notes
     */
    public function clearNotes() {
        $this->notes()->delete();
    }
}

I tend to include the ID of the current user in the note too, so that I can display the user name against the note text. Simple case of adding the column to the table and the fillable array, adding the relationship function, and replacing:

$record->notes()->create(['content' => request()->note]);

with:

$record->notes()->create(['content' => request()->note, 'user_id' => \Auth::user()->id]);
15
Apr '18

by john

One of my projects features a survey / audit system for which I was tasked to provide an offline facility by allowing the download of fillable PDF forms, and then the subsequent re-upload and processing of them.

For the form generation and download I built a wrapper around TCPDF, which I may blog about one day, but it was an extremely involved process and today is not that day!

The import and processing routine I implemented using TCPDF (specifically this PDFTK library aliased as ‘PDFTK’ in my app configuration), and ended up being nicely self-contained:

/*
 * Read fields from PDF file
 * @param string $path Path to PDF
 * @return array
 */
public static function read($path) {
    $pdf = new \PDFRead($path, ['_command' => '/usr/bin/pdftk']);

    $ignore_fields = ['Validate', 'Submit', 'Reset', 'Print'];
    $ignore_values = ['Select...', 'Off'];

    if($data = $pdf->getDataFields()) {
        $values = collect($data->__toArray())->mapWithKeys(function($data_field) use ($ignore_fields, $ignore_values) {
            if(in_array($data_field['FieldName'], $ignore_fields))
                return [];

            if(isset($data_field['FieldValue'])) {
                $value = $data_field['FieldValue'];

                if($data_field['FieldType'] != 'Text' && in_array($data_field['FieldValue'], $ignore_values))
                    $value = '';
            }
            else
                $value = '';

            return [$data_field['FieldName'] => $value];
        });

        $parsed_values = [];

        foreach($values as $key => $value) {
            if(preg_match('/(.*)\[([0-9\_]+)\]/', $key, $matches)) {
                
                if(!isset($parsed_values[$matches[1]]))
                    $parsed_values[$matches[1]] = [];

                $parsed_values[$matches[1]][$matches[2]] = $value;
            }
            else
                $parsed_values[$key] = $value;
        }

        return $parsed_values;
    }
}

Pass any filled PDF form through that function and it will return an array in the same format as you would get from any normal form submission.
27
Mar '18

by john

So this one had me puzzled for rather a while. We all know about the bootstrap event ‘shown.bs.modal’ that fires when a modal has been loaded. However, when the modal content is loaded via AJAX, I’ve found that can still be triggered a little bit in advance of contained elements loading in the DOM.

To give a concrete example, I encountered this when trying to activate datatables on a table that I was embedding in a modal. My script was set up as follows:

$('.modal').on('shown.bs.modal', function (e) {
    $('.table-class').DataTable({
        //Datatable options
    });
});

This tended to work about 50% of the time, and after a lot of thought I realised that the times it didn’t run were due to the table element not being loaded into the DOM before the DataTable call is made. Even though the Bootstrap event had been fired, the DOM was yet to catch up.

Now there are various ways around this, most obviously by using ‘setTimeout’ to add a short delay before triggering subsequent logic, although this technique has always struck me as rather imprecise and risky. Fortunately, jQuery provides the ‘ready’ function which gives us all we need to make this logic work perfectly:

$('.modal').on('shown.bs.modal', function (e) {
    $('.table-class').ready(function() {
        $('.table-class').DataTable({
            //Datatable options
        });
    });
});

All we are doing here is adding a subsequent observer to the element so that we can be sure the DOM is aware of it before we try to run any further logic. Simple as that!

12
Mar '18

by john

Bit of a follow-on here from my post on record versioning. Versioning a record is all well and good, but often you would like to version the reccord’s relations too in order to make the record meaningful, provide context, provide metadata etc.

In the code below, the archive function on the parent model is triggered to create the archive (versioned) record. Note that I have added the with(‘notes’) call, which will add all related records from the ‘notes’ relation into the resulting array. As such this relation has been serialiasied with the parent record.

When the archive (versioned) record is deserialised we will then find that the ‘notes’ are attached to the copy of the parent model, but as simple arrays rather than objects. The custom ‘hydrateArchive’ function simply takes these arrays and turns them back into real models.

Parent Model

/**
 * Archive
 * @return \App\Model\Archive
 */
public function archive() {
    return $this->archives()->create([
        'data' => serialize($this->with('notes')->find($this->id)->toArray())
    ]);
}

/**
 * Custom archive hydrate
 *
 * @return \App\Model\Base
 */
public function hydrateArchive() {
    if(!$this->notes)
        return $this;

    $this->notes = collect($this->notes)->map(function ($data) {
        return new \App\Model\Note($data);
    });

    return $this;
}

Archive Record

/**
 * Deserialise
 * @return \App\Model\Base
 */
public function deserialise() {
    $deserialised = with(new $this->parent_type)->newFromBuilder(unserialize($this->data));
    return $deserialised->hydrateArchive();
}
05
Feb '18

by john

So we all know about Laravel’s built-in ‘creating’, ‘created’, ‘updating’, ‘updated’ etc model events. Seriously useful events for all manner of purposes.

Recently I found myself needing to add a couple more; one to trigger when a model was loaded (for logging purposes), and one before a model was updated (so that I could archive a copy of it before the data was changed).

I came up with the following code and inserted it into an abstract base model class, which all the other models extend.

protected $observables = ['loaded', 'beforeUpdate'];

/**
 * Register a loaded model event with the dispatcher.
 *
 * @param  \Closure|string  $callback
 * @return void
 */
public static function loaded($callback)
{
    static::registerModelEvent('loaded', $callback);
}

/**
 * Register a before update model event with the dispatcher.
 *
 * @param  \Closure|string  $callback
 * @return void
 */
public static function beforeUpdate($callback)
{
    static::registerModelEvent('beforeUpdate', $callback);
}

/**
 * Create a new model instance.
 *
 * @param  array  $attributes
 * @return \Illuminate\Database\Eloquent\Model|static
 */
public function newFromBuilder($attributes = array(), $connection = null)
{
    $instance = parent::newFromBuilder($attributes);

    $instance->fireModelEvent('loaded', false);

    return $instance;
}

/**
 * Trigger before update
 *
 * @param  array  $attributes
 * @return \Illuminate\Database\Eloquent\Model|static
 */
public function update(array $attributes = [], array $options = [])
{
    $this->fireModelEvent('beforeUpdate', false);

    return parent::update($attributes);
}