Skip to content
Apr 19 / Scotty

#iagreewithnick

Popularity: 3% [?]

Apr 17 / Scotty

Limesurvey Ticks All My Boxes.

I recently had to source and implement a means of creating, distributing, and reporting customer satisfaction surveys. I had a look around and after a bit of research narrowed things down to SurveyMonkey, Google Forms, and LimeSurvey. I settled for LimeSurvey in the end because:

  • It is free – as a LAMP based web app, all you need is a web server to install it to and you get full functionality. SurveyMonkey and its ilk charge to be able to have more than ten question surveys; LimeSurvey allows you as many surveys with as many questions to as many recipients as you like.
  • It is extremely customizeable and flexible. 20 question types, skip logic, and a built in template editor.
  • Did I mention it’s free?
  • Google Forms is a great solution for simple, generic looking surveys with simpler question types, but LimeSurvey is a fully integrated survey application with comprehensive reporting, token based email invites / reminders, and plenty more besides. After a straightforward install and short learning period, I had knocked out a great looking survey in no time, with the template matching the company colours and badged with the logo. The surveys are hosted on a subdomain of our website and the emails appear to originate from our domain, so the overall feel is very professional. Anyone looking to implement something similar could do a lot worse than to get yourself over to LimeSurvey for the download.

    Popularity: 8% [?]

    Apr 14 / Scotty

    Twitter Updates for 2010-04-14

    Powered by Twitter Tools

    Popularity: 2% [?]

    Apr 10 / Scotty

    Twitter Updates for 2010-04-10

    Powered by Twitter Tools

    Popularity: 2% [?]

    Apr 8 / Scotty

    Astro resumption

    It’s been ages since I’ve got my ’scope out, but it’s ben gnawing at me for a while. Last night I decided to get it set up in my garden for the first time since moving house. I had one of those nights that reminds me why I got a scope in the first place – I saw not only Saturn and Mars, as well as several Messier objects, through the scope, but also spotted several satellites, a shooting star, and an Iridium flare (see Heavens Above to find out when you can see one) whist setting up, which was lucky.

    I found that Google Sky Map on my HTC Hero is a great aid for setting up, in order to refresh my memory of where all the stars are when aligning the scope. I can instantly identify where the alignment stars and Polaris are as well as pick out which planets are in a good enough place for me to spot (I have several buildings around blocking large chinks of the sky).

    I captured some video on my SP900NC using WxAstroCapture, and was hoping to use AviStack to stack the images but it tells me that the CODEC is unsupported – anyone got any ideas?

    The quality was really poor though, I need to work on my collimation (Thierry Legault’s fantastic collimation guide is always my first point of reference). There was a lot of crap on my collector plate which I have cleaned off, which I am hoping will mean a much improved view next time.

    I have also LX-Modded my SPC900NC, but do not have a proper parallel port to make use of it – I am going to have to make up a USB-Serial adapter as described by Martin Meijer (I think the Shoestring Astronomy LXUSB is a bit expensive for what it does for my liking). Once I have done this I will post my results.

    Popularity: 4% [?]

    Apr 8 / Scotty

    Wordpress PHP Fatal Error

    Update – it seems that Tweet Blender or Twitter Tools does not want to play ball – when I hit the big blue Publish button, i get a PHP fatal error (Cannot redeclare class services_json).

    On further investigation, it looks like this is down to Wordpress rather than any of the plugins. Also, the afore mentioned Alex King strikes again – with an explanation for the problem and a fix, Here – which I have put into place. Thanks again, Alex!

    Popularity: 2% [?]

    Apr 8 / Scotty

    WP Update

    I’ve been putting it off for a while but I’ve now moved the back end DB to MySQL 5 from the old 4 DB at my hosts, allowing me to upgrade WP. Anyone else using wordpress, I’m sure I don’t need to underscore the importance of staying up-to-date – see Scobleizer for an example.

    Plugins like WP Security Scan and Akimset are great ways to help improve your blog’s security. Also, Lostinsearch.com have some great tips. If anybody has any other tips, plugins, or even tales of what can happen with poor WP security, let us know below.

    Anyways I took the time to change theme and have a freshen up of the blog, the old theme was starting to bug me, as well as adding some cool Twitter integration – via Tweet Blender, and Twitter Tools (anyone have any good suggestions for other Twitter plugins?)

    Thanks to Alex King for making a load of cool plugins, see here for a list, there’s some great stuff there.

    Anyways I’m gonna try and post a bit more frequently (the blogger’s lament – if I had a pound for every time I’d read that on a blog (and another pound for each time I saw that written followed by a massive gap with no posts!) )… so watch this space.

    Popularity: 2% [?]

    Dec 17 / Scotty

    Asterisk to Voiceflex – an example on the NSLU2

    I have had numerous comments and requests regarding Asterisk configuration for Voiceflex. I have neglected my blog somewhat lately, but when I googled for “BCM50 SIP” the other day, I was suprised to see my own blog high up the listings! I have decided to revisit this, as it has obviously generated a lot of interest.

    I have a bash script that I used on the Linksys NSLU2 (“the SLUG”) in order to quickly configure Asterisk to act as a gateway between a Nortel BCM50 and Voiceflex’s SIP trunks. Although there is no longer a need to use the NSLU2 in this fashion, as Voiceflex can dispense with SIP authentication when the BCM50 is on a static IP, I will post the scripts, as well as the relevant Asterisk config files, as they may be useful for anybody that wants to configure Asterisk with SIP trunks.

    Firstly,  the Asterisk configs: obviously, do not use these as-is, you will need to change the details to suit!

    sip.conf

    [general]
    
    context=default
    bindport=5060
    bindaddr=0.0.0.0
    srvlookup=yes
    domain=asterisk
    
    disallow=all
    allow=alaw,g729,g723
    language=en
    relaxdtmf=yes
    trustrpid=no
    useragent=Asterisk PBX
    promiscredir=yes
    canreinvite=yes
    
    domain=146.101.248.200,incoming-voiceflex
    domain=192.168.40.6,incoming-voiceflex
    domain=10.10.10.10,incoming-bcm
    domain=10.10.11.11,default
    
    #include /etc/asterisk/sip_nat.conf
    
    #include /etc/asterisk/sip_accounts.conf
    
    [sip-bcm]
    type=peer
    host=10.10.10.10
    context=incoming-bcm
    
    #include /etc/asterisk/sip_og_context.conf
    
    ; sip ext for xlite, for testing
    [200]
    type=friend
    regexten=200
    secret=donttell
    context=default
    host=10.10.10.10
    canreinvite=no
    insecure=port,invite

    sip_nat.conf

    externip=11.11.11.11
    localnet=192.168.192.0/24
    nat=yes

    sip_accounts.conf

    ;sip_accounts.conf generated by autoasterflex
    register => 12345678:shhhsecret@sip.voiceflex.com
    
    registertimeout=20
    registerattempts=0
    
    [authentication]
    auth=12345678:shhhsecret@146.101.248.200
    auth=12345678:shhhsecret@voiceflex
    auth=12345678:shhhsecret@sip.voiceflex.com

    sip_og_context.conf

    [voiceflex]
    type=friend
    callerid=01234567890
    fromuser=01234567890
    defaultuser=12345678@voiceflex
    disallow=all
    allow=alaw,ulaw,g729
    fromdomain=voiceflex
    secret=shhhsecret
    host=sip.voiceflex.com
    insecure=invite,port
    context=incoming-voiceflex

    extensions_incoming_voiceflex.conf

    [incoming-voiceflex]
    exten => s,1,Dial(SIP/666666@sip-bcm,,r);
    exten => 666666,1,Dial(SIP/666666@sip-bcm,,r);

    extensions.conf

    ; extensions.conf - asterisk dialplan
    
    [general]
    static=yes
    writeprotect=no
    autofallthrough=yes
    clearglobalvars=no
    priorityjumping=no
    
    [globals]
    
    [incoming-bcm]
    exten => 200,1,Dial(SIP/200,,r);
    exten => _9.,1,Dial(SIP/${EXTEN:1}@voiceflex,,r);
    exten => _8.,1,SIPDtmfMode(inband);
    exten => _8.,2,Dial(SIP/${EXTEN:1}@voiceflex,,r);
    
    ;this context must be absent prior to  running autoasterflex
    ;[incoming-voiceflex]
    ;exten => s,1,Dial(SIP/234567@sip-bcm,,r);
    ;exten => 234567,1,Dial(SIP/234567@sip-bcm,,r);
    
    [default]
    exten => _9.,1,Dial(SIP/${EXTEN:1}@voiceflex,,r);
    exten => 300,1,Dial(SIP/234567@sip-bcm,,r);
    exten => 234567,1,Dial(SIP/234567@sip-bcm,,r);
    
    #include /etc/asterisk/extensions_incoming_voiceflex.conf

    Finally, here is a little shell script that I was using, in order to configure an NSLU2 with Asterisk already installed, to configure the SIP trunks. USE WITH CAUTION – answering “y” to “Change IP & DNS settings (y/n) ?” will re-write the network interface config (/etc/network/interfaces) and DNS client config (/etc/resolf.conf) on the machine that you run it on (if it is Linux based.) I would advise anybody using the script to exercise care doing this, and only to do so if you have read and understand the script.

    autoasterflex.sh

    #!/bin/bash
    
    #autoasterflex
    
    echo "Caller ID of incoming SIP Trunk?"
    read CALLERID
    echo "SIP Account number?"
    read SIPACCT
    echo "SIP Account password?"
    read SIPPASS
    echo "PBX IP Address?"
    read BCMIP
    echo "WAN IP Address?"
    read EXTERNIP
    echo "Local network? (eg 192.168.0.1/24)"
    read LOCALNET
    echo "Digits to send to pbx as received digits?"
    read DDI
    echo "Change IP & DNS settings (y/n) ?"
    read CHANGEIP
    
    if [ "$CHANGEIP" == "y" ]; then
    	echo "Slug IP?"
    	read SLUGIP
    	echo "Slug Netmask?"
    	read SLUGSN
    	echo "Slug Default Gateway?"
    	read SLUGDGW
    	echo "DNS Server?"
    	read SLUGDNS
    fi
    
    # modify existing sip_og_context.conf
    sed -i "s/callerid=.*/callerid=${CALLERID}/" /etc/asterisk/sip_og_context.conf
    sed -i "s/fromuser=.*/fromuser=${CALLERID}/" /etc/asterisk/sip_og_context.conf
    sed -i "s/defaultuser=.*/defaultuser=${SIPACCT}@voiceflex/" /etc/asterisk/sip_og_context.conf
    sed -i "s/secret=.*/secret=${SIPPASS}/" /etc/asterisk/sip_og_context.conf
    
    # delete existing si_accounts.conf, create a new one
    rm -f /etc/asterisk/sip_accounts.conf
    echo ";sip_accounts.conf generated by autoasterflex" >> /etc/asterisk/sip_accounts.conf
    echo "register => ${SIPACCT}:${SIPPASS}@sip.voiceflex.com" >> /etc/asterisk/sip_accounts.conf
    echo "" >> /etc/asterisk/sip_accounts.conf
    echo "registertimeout=20" >> /etc/asterisk/sip_accounts.conf
    echo "registerattempts=0" >> /etc/asterisk/sip_accounts.conf
    echo "" >> /etc/asterisk/sip_accounts.conf
    echo "[authentication]" >> /etc/asterisk/sip_accounts.conf
    echo "auth=${SIPACCT}:${SIPPASS}@146.101.248.200" >> /etc/asterisk/sip_accounts.conf
    echo "auth=${SIPACCT}:${SIPPASS}@voiceflex" >> /etc/asterisk/sip_accounts.conf
    echo "auth=${SIPACCT}:${SIPPASS}@sip.voiceflex.com" >> /etc/asterisk/sip_accounts.conf
    
    #delete existing sip_nat.conf,  create a new one
    rm -f /etc/asterisk/sip_nat.conf
    echo "externip=${EXTERNIP}" >> /etc/asterisk/sip_nat.conf
    echo "localnet=${LOCALNET}" >> /etc/asterisk/sip_nat.conf
    echo "nat=yes" >> /etc/asterisk/sip_nat.conf
    
    #check extensions.conf has #include /etc/asterisk/extensions_incoming_voiceflex.conf, append if absent
    INCVFLEXCONFPRESENT=`grep '/etc/asterisk/extensions_incoming_voiceflex.conf' /etc/asterisk/extensions.conf`
    if [ "" == "$INCVFLEXCONFPRESENT" ]; then
    	echo ""  >> /etc/asterisk/extensions.conf
    	echo "#include /etc/asterisk/extensions_incoming_voiceflex.conf" >> /etc/asterisk/extensions.conf
    fi
    
    #delete existing extensions_incoming_voiceflex.conf, recreate
    rm -f /etc/asterisk/extensions_incoming_voiceflex.conf
    echo "[incoming-voiceflex]" >> /etc/asterisk/extensions_incoming_voiceflex.conf
    echo "exten => s,1,Dial(SIP/${DDI}@sip-bcm,,r);" >> /etc/asterisk/extensions_incoming_voiceflex.conf
    echo "exten => ${DDI},1,Dial(SIP/${DDI}@sip-bcm,,r);" >> /etc/asterisk/extensions_incoming_voiceflex.conf
    
    # modify sip.conf
    sed -i "s/domain=.*,incoming-bcm/domain=${BCMIP},incoming-bcm/" /etc/asterisk/sip.conf
    sed -i "s/host=.*/host=${BCMIP}/" /etc/asterisk/sip.conf
    
    if [ "$CHANGEIP" == "y" ]; then
    	#recreate /etc/network/interfaces
    	rm -f /etc/network/interfaces
    	echo "# /etc/network/interfaces" >> /etc/network/interfaces
    	echo "# recreated by autoasterflex" >> /etc/network/interfaces
    	echo "#" >> /etc/network/interfaces
    	echo "# the loopback interface" >> /etc/network/interfaces
    	echo "auto lo" >> /etc/network/interfaces
    	echo "iface lo inet loopback" >> /etc/network/interfaces
    	echo "#" >> /etc/network/interfaces
    	echo "# the interface used by default during boot" >> /etc/network/interfaces
    	echo "auto eth0" >> /etc/network/interfaces
    	echo "#" >> /etc/network/interfaces
    	echo "# static entry for eth0" >> /etc/network/interfaces
    	echo "iface eth0 inet static" >> /etc/network/interfaces
    	echo "    address ${SLUGIP}" >> /etc/network/interfaces
    	echo "    netmask ${SLUGSN}" >> /etc/network/interfaces
    	echo "    gateway ${SLUGDGW}" >> /etc/network/interfaces
    
    	#recreate /etc/resolf.conf
    	rm -f /etc/resolv.conf
    	echo "search workgroup" >> /etc/resolf.conf
    	echo "nameserver ${SLUGDNS}" >> /etc/resolv.conf
    fi
    
    #done
    echo "Changes done. execute shutdown -r now to restart with new settings."

    I hope this helps.

    Popularity: 100% [?]

    Dec 16 / Scotty

    CakePHP ExtendAssociations – HABTM Update by HABTM primary key

    For a few years now I have been using Brandon Parise’s great CakePHP behaviour, ExtendAssociations, which I originally found here at the Bakery. Lately I have been developing an app that has a HABTM relationship that has extra data stored in the relationship, and also can have multiple entries for the same association.
    To explain further, I have a Sale model, that can be associated to multiple Items. each association stores the sale price and the quantity of the item. Also, say for example a Sale has 4 of Item X associated to it at £99, this cound be added to at a later date with 2 Item X at £130 for example. Since each HABTM association cannot be identified uniquely by the tuple of sale_id and item_id, the HABTM table must have a primary key field for itself.

    The original ExtendAssociations update action did not provide an update action. I provided one a while ago here, but this is not sufficient for the scenario mentioned above, so here is another action which can update an HABTM association using the HABTM join table primary key as a reference:

    function habtmUpdatePrimary(&$model, $assoc, $ids = array(), $extra = array() ) {
    		if(!is_array($ids)) {
    			$extra = array($extra);
    			$ids = array($ids);
    		}
    
    		// make sure the association exists
    		if(isset($model->hasAndBelongsToMany[$assoc])) {
    			// get association data
    			$joinTable = $model->hasAndBelongsToMany[$assoc]['joinTable'];
    			$associationForeignKey = $model->hasAndBelongsToMany[$assoc]['associationForeignKey'];
    			$foreignKey = $model->hasAndBelongsToMany[$assoc]['foreignKey'];
    
    			$joinClass = array($model->name, $assoc);
    			sort($joinClass);
    			$joinClass = Inflector::pluralize($joinClass[0]) . $joinClass[1];
    			$joinTablePrimaryKey = $model->$joinClass->primaryKey;
    
    			if( !empty($joinTable) && !empty($joinTablePrimaryKey) ) {
    
    				$success = true;
    				foreach($ids as  $index => $id)
    				{
    					// execute SQL query for each $id
    					$sql = array();
    					foreach( $extra[$index] as $key => $value ) {
    					    if ($value != null) {
    							$sql[] = $key . " = '". addslashes($value) . "'";
    						} else {
                    			$sql[] = $key . " = NULL";
                  			}
              			}
    					$result = $model->query( "UPDATE `$model->tablePrefix.$joinTable` set ".implode( "," , $sql )." WHERE $joinTablePrimaryKey = $id");
    					if (!$result) $success = false;
    				}
    				return $success;
    			} else {
    				// invalid join table name or primary key field name
    				return false;
    			}
    		} else {
    			// association doesn't exist, return false
    			return false;
    		}
    	}

    Here is the associated action to delete a HABTM by join table primary key:

    function habtmDeletePrimary(&$model, $assoc, $ids) {
    if(!is_array($ids))
    $ids = array($ids);
    
    // make sure the association exists
    if(isset($model->hasAndBelongsToMany[$assoc])) {
    
    //get extra data
    $joinTable = $model->hasAndBelongsToMany[$assoc]['joinTable'];
    $foreignKey = $model->hasAndBelongsToMany[$assoc]['foreignKey'];
    $associationForeignKey = $model->hasAndBelongsToMany[$assoc]['associationForeignKey'];
    
    $joinClass = array($model->name, $assoc);
    sort($joinClass);
    $joinClass = Inflector::pluralize($joinClass[0]) . $joinClass[1];
    $joinTablePrimaryKey = $model->$joinClass->primaryKey;
    
    $sql = "DELETE FROM `$model->tablePrefix.$joinTable` WHERE ";
    $first = true;
    foreach($ids as $id)
    {
    $sql .= ($first?"":" OR ") . "`$joinTablePrimaryKey` = '$id'";
    $first = false;
    }
    
    // execute SQL
    $success = $model->query($sql);
    return $success;
    }
    
    // association doesn't exist, return false
    return false;
    }

    Here is the full code for the version of ExtendAssociations that I currently use – any comments / criticisms / improvements welcome. I would again like to thank Brandon Parise for graciously publishing the original Behaviour, nice work!

    /**
    * Extend Associations Behavior
    * Extends some basic add/delete function to the HABTM relationship
    * in CakePHP.  Also includes an unbindAll($exceptions=array()) for
    * unbinding ALL associations on the fly.
    *
    * Now also adds / updates extra data (ie fields in the HABTM join table other than the keys),
    * as well as handling HABTMs that have multiple entries for the same association
    * and their own primary key.
    *
    * This code is loosely based on the concepts from:
    * http://rossoft.wordpress.com/2006/08/23/working-with-habtm-associations/
    *
    * @author Brandon Parise <brandon@parisemedia.com>, updated and extended by Scott Donnelly <scott@donnel.ly>
    * @package CakePHP Behaviors
    *
    */
    class ExtendAssociationsBehavior extends ModelBehavior {
    /**
    * Model-specific settings
    * @var array
    */
    var $settings = array();
    
    /**
    * Setup
    * Noething sp
    *
    * @param unknown_type $model
    * @param unknown_type $settings
    */
    function setup(&$model, $settings = array()) {
    // no special setup required
    $this->settings[$model->name] = $settings;
    }
    
    /**
    * Add an HABTM association
    *
    * @param Model $model
    * @param string $assoc
    * @param int $id
    * @param mixed $assoc_ids
    * @return boolean
    */
    function habtmAdd(&$model, $assoc, $id, $assoc_ids , $extra = array() ) {
    if(!is_array($assoc_ids)) {
    $assoc_ids = array($assoc_ids);
    $extra = array($extra);
    }
    
    // make sure the association exists
    if(isset($model->hasAndBelongsToMany[$assoc])) {
    
    // get association data
    $joinTable = $model->hasAndBelongsToMany[$assoc]['joinTable'];
    $associationForeignKey = $model->hasAndBelongsToMany[$assoc]['associationForeignKey'];
    $foreignKey = $model->hasAndBelongsToMany[$assoc]['foreignKey'];
    
    $success = true;
    $n = 0;
    
    if( !empty( $joinTable ) ) {
    foreach($assoc_ids as $assoc_id)
    {
    // execute SQL query for each $assoc_id
    $values = array("'" . $id . "'", "'" . $assoc_id . "'");
    $cols = array("`" . $foreignKey . "`", "`" . $associationForeignKey . "`");
    foreach( $extra[$n] as $key => $value )
    {
    array_push($cols, '`' . $key . '`');
    array_push($values, "'" . addslashes($value) . "'");
    }
    
    $n++;
    $result = $model->query( "INSERT INTO `$model->tablePrefix.$joinTable` (" . implode( ", ", $cols) . ") VALUES (" . implode( "," , $values ) . ")");
    if (!$result) $success = false;
    }
    } else {
    return false;
    }
    return $success;
    }
    
    // association doesn't exist, return false
    return false;
    }
    
    /**
    * Delete an HABTM Association
    *
    * @param Model $model
    * @param string $assoc
    * @param int $id
    * @param mixed $assoc_ids
    * @return boolean
    */
    function habtmDelete(&$model, $assoc, $id, $assoc_ids) {
    if(!is_array($assoc_ids))
    $assoc_ids = array($assoc_ids);
    
    // make sure the association exists
    if(isset($model->hasAndBelongsToMany[$assoc])) {
    
    //get extra data
    $joinTable = $model->hasAndBelongsToMany[$assoc]['joinTable'];
    $foreignKey = $model->hasAndBelongsToMany[$assoc]['foreignKey'];
    $associationForeignKey = $model->hasAndBelongsToMany[$assoc]['associationForeignKey'];
    
    //build SQL query
    $sql = "DELETE FROM `$model->tablePrefix.$joinTable` WHERE `$foreignKey` = '$id'";
    if($assoc_ids[0] != '*') {
    $sql .= " AND (";
    $first = true;
    foreach($assoc_ids as $assoc_id)
    {
    $sql .= ($first?"":" OR ") . "`$associationForeignKey` = '$assoc_id'";
    $first = false;
    }
    $sql .= ")";
    }
    
    // execute SQL
    $success = $model->query($sql);
    return $success;
    }
    
    // association doesn't exist, return false
    return false;
    }
    
    /**
    * Delete an HABTM Association, specified by primary key id, rather than foreignkey / associationforeignkey
    *
    * @param Model $model
    * @param string $assoc
    * @param mixed $ids
    * @return boolean
    */
    function habtmDeletePrimary(&$model, $assoc, $ids) {
    if(!is_array($ids))
    $ids = array($ids);
    
    // make sure the association exists
    if(isset($model->hasAndBelongsToMany[$assoc])) {
    
    //get extra data
    $joinTable = $model->hasAndBelongsToMany[$assoc]['joinTable'];
    $foreignKey = $model->hasAndBelongsToMany[$assoc]['foreignKey'];
    $associationForeignKey = $model->hasAndBelongsToMany[$assoc]['associationForeignKey'];
    
    $joinClass = array($model->name, $assoc);
    sort($joinClass);
    $joinClass = Inflector::pluralize($joinClass[0]) . $joinClass[1];
    $joinTablePrimaryKey = $model->$joinClass->primaryKey;
    
    $sql = "DELETE FROM `$model->tablePrefix.$joinTable` WHERE ";
    $first = true;
    foreach($ids as $id)
    {
    $sql .= ($first?"":" OR ") . "`$joinTablePrimaryKey` = '$id'";
    $first = false;
    }
    
    // execute SQL
    $success = $model->query($sql);
    return $success;
    }
    
    // association doesn't exist, return false
    return false;
    }
    
    /**
    * update HABTM Associations, including extra data, referenced by joined models primary keys
    *
    * @param Model $model
    * @param string $assoc
    * @param int $id
    * @param int $assoc_ids
    * @param mixed $extra
    * @return boolean
    */
    function habtmUpdate(&$model, $assoc, $id, $assoc_ids, $extra = array() ) {
    if(!is_array($assoc_ids)) {
    $assoc_ids = array($assoc_ids);
    $extra = array($extra);
    }
    
    // make sure the association exists
    if(isset($model->hasAndBelongsToMany[$assoc])) {
    
    // get association data
    $joinTable = $model->hasAndBelongsToMany[$assoc]['joinTable'];
    $associationForeignKey = $model->hasAndBelongsToMany[$assoc]['associationForeignKey'];
    $foreignKey = $model->hasAndBelongsToMany[$assoc]['foreignKey'];
    
    if( !empty( $joinTable ) ) {
    
    $success = true;
    foreach($assoc_ids as $index => $assoc_id)
    {
    // build query for each $assoc_id
    $sql = array();
    foreach( $extra[$index] as $key => $value ) {
    if ($value != null) {
    $sql[] = $key . " = '". addslashes($value) . "'";
    } else {
    $sql[] = $key . " = NULL";
    }
    }
    // update each association with the new values
    $result = $model->query( "UPDATE `$model->tablePrefix.$joinTable` set ".implode( "," , $sql )." WHERE $foreignKey = $id AND $associationForeignKey = '$assoc_id'");
    if (!$result) $success = false;
    }
    
    return $success;
    } else {
    // join table not specified, return false
    return false;
    }
    } else {
    // association doesn't exist, return false
    return false;
    }
    }
    
    /**
    * retrieve HABTM Associations, accessed by join table primary key
    *
    * @param Model $model
    * @param string $assoc
    * @param int $id
    * @return boolean
    */
    function habtmFetch(&$model, $assoc, $ids) {
    if(!is_array($ids))
    $ids = array($ids);
    
    // make sure the association exists
    if(isset($model->hasAndBelongsToMany[$assoc])) {
    
    // get association data
    $joinTable = $model->hasAndBelongsToMany[$assoc]['joinTable'];
    $associationForeignKey = $model->hasAndBelongsToMany[$assoc]['associationForeignKey'];
    $foreignKey = $model->hasAndBelongsToMany[$assoc]['foreignKey'];
    
    $joinClass = array($model->name, $assoc);
    sort($joinClass);
    $joinClass = Inflector::pluralize($joinClass[0]) . $joinClass[1];
    $joinTablePrimaryKey = $model->$joinClass->primaryKey;
    
    $n = 0;
    if( !empty( $joinTable ) )
    {
    $whereClause = "(";
    foreach($ids as $id)
    {
    // craft where clause
    if ($n > 0) $whereClause .= " OR ";
    $whereClause .= "$joinTablePrimaryKey = $id";
    $n++;
    }
    $whereClause .=")";
    
    $res = $model->query( "SELECT * FROM `$model->tablePrefix.$joinTable` WHERE $whereClause");
    }
    return $res;
    }
    // association doesn't exist, return false
    return false;
    }
    
    /**
    * Update HABTM Associations, accessed by join table primary key
    *
    * @param Model $model
    * @param string $assoc
    * @param int $id
    * @param mixed $extra
    * @return boolean
    */
    function habtmUpdatePrimary(&$model, $assoc, $ids = array(), $extra = array() ) {
    if(!is_array($ids)) {
    $extra = array($extra);
    $ids = array($ids);
    }
    
    // make sure the association exists
    if(isset($model->hasAndBelongsToMany[$assoc])) {
    // get association data
    $joinTable = $model->hasAndBelongsToMany[$assoc]['joinTable'];
    $associationForeignKey = $model->hasAndBelongsToMany[$assoc]['associationForeignKey'];
    $foreignKey = $model->hasAndBelongsToMany[$assoc]['foreignKey'];
    
    $joinClass = array($model->name, $assoc);
    sort($joinClass);
    $joinClass = Inflector::pluralize($joinClass[0]) . $joinClass[1];
    $joinTablePrimaryKey = $model->$joinClass->primaryKey;
    
    if( !empty($joinTable) && !empty($joinTablePrimaryKey) ) {
    
    $success = true;
    foreach($ids as  $index => $id)
    {
    // execute SQL query for each $id
    $sql = array();
    foreach( $extra[$index] as $key => $value ) {
    if ($value != null) {
    $sql[] = $key . " = '". addslashes($value) . "'";
    } else {
    $sql[] = $key . " = NULL";
    }
    }
    $result = $model->query( "UPDATE `$model->tablePrefix.$joinTable` set ".implode( "," , $sql )." WHERE $joinTablePrimaryKey = $id");
    if (!$result) $success = false;
    }
    return $success;
    } else {
    // invalid join table name or primary key field name
    return false;
    }
    } else {
    // association doesn't exist, return false
    return false;
    }
    }
    
    /**
    * Delete All HABTM Associations
    * Just a nicer way to do easily delete all.
    *
    * @param Model $model
    * @param string $assoc
    * @param int $id
    * @return boolean
    */
    function habtmDeleteAll(&$model, $assoc, $id) {
    return $this->habtmDelete($model, $assoc, $id, '*');
    }
    
    /**
    * Find
    * This method allows cake to do the dirty work to
    * fetch the current HABTM association.
    *
    * @param Model $model
    * @param string $assoc
    * @param int $id
    * @return array
    */
    function __habtmFind(&$model, $assoc, $id) {
    // temp holder for model-sensitive params
    $tmp_recursive = $model->recursive;
    $tmp_cacheQueries = $model->cacheQueries;
    
    $model->recursive = 1;
    $model->cacheQueries = false;
    
    // unbind all models except the habtm association
    $this->unbindAll($model, array('hasAndBelongsToMany' => array($assoc)));
    $data = $model->find(array($model->name.'.'.$model->primaryKey => $id));
    
    $model->recursive = $tmp_recursive;
    $model->cacheQueries = $tmp_cacheQueries;
    
    if(!empty($data)) {
    // use Set::extract to extract the id's ONLY of the $assoc
    $data[$assoc] = array($assoc => Set::extract($data, $assoc.'.{n}.'.$model->primaryKey));
    }
    
    return $data;
    }
    
    /**
    * UnbindAll with Exceptions
    * Allows you to quickly unbindAll of a model's
    * associations with the exception of param 2.
    *
    * Usage:
    *   $this->Model->unbindAll(); // unbinds ALL
    *   $this->Model->unbindAll(array('hasMany' => array('Model2')) // unbind All except hasMany-Model2
    *
    * @param Model $model
    * @param array $exceptions
    */
    function unbindAll(&$model, $exceptions = array()) {
    $unbind = array();
    foreach($model->__associations as $type) {
    foreach($model->{$type} as $assoc=>$assocData) {
    // if the assoc is NOT in the exceptions list then
    // add it to the list of models to be unbound.
    if(@!in_array($assoc, $exceptions[$type])) {
    $unbind[$type][] = $assoc;
    }
    }
    }
    // if we actually have models to unbind
    if(count($unbind) > 0) {
    $model->unbindModel($unbind);
    }
    }
    }
    $model->tablePrefix.

    Popularity: 48% [?]

    Aug 9 / Scotty

    BCM50 SIP DTMF update 2

    The BCM50 sends SIP INFO messages for DTMF. using Asterisk’s SIPDtmfMode() application, the DTMF send mode can be forced to in-band. Since the call has been set up as G711, in-band DTMF will still be intelligible by the far end. I have tested this in our lab set-up, and it works fine, so that is issue 1 out of the way. As for issue 2, I have placed a call to a number which I know will not answer (Looks like I’m the only mug that works on a Saturday :-) ). The call rang out for over 2 minutes before receiving a SIP 486 (user busy) from Voiceflex, with their Asterisk server setting X-Asterisk-HangupCause as “Network out of order”. 2 minutes seems like plenty of time to be ringing to me, most likely this is limited by a max ringing timeout timer somewhere along the line, either at Voiceflex or on the PSTN.

    Popularity: 14% [?]