<?xml version="1.0" encoding="iso-8859-1" ?>
<rss version="2.0">
<channel>
	<title>The Development Channel</title>
	<link>http://community.invisionpower.com/blog/4445-the-development-channel/</link>
	<description>The Development Channel Syndication</description>
	<pubDate>Sat, 11 May 2013 01:04:21 +0000</pubDate>
	<webMaster>info@invisionpower.com (Invision Power Services)</webMaster>
	<generator>IP.Blog</generator>
	<ttl>60</ttl>
	<item>
		<title>IP.Board 3.4.5 + all applications available for beta testing</title>
		<link>http://community.invisionpower.com/blog/4445/entry-9528-ipboard-345-all-applications-available-for-beta-testing/</link>
		<category></category>
		<description><![CDATA[The following applications are available for beta testing:<ul class='bbc'><li>IP.Board 3.4.5</li><li>IP.Blog 2.6.3</li><li>IP.Content 2.3.6</li><li>IP.Chat 1.4.4</li><li>IP.Downloads 2.5.4</li><li>IP.Gallery 5.0.5</li><li>IP.Nexus 1.5.8</li></ul>&#160;<br />This round of maintenance updates represents updates to all of our applications.&#160; At this point in time, there are zero open bug reports in our tracker (that are not flagged to be resolved in a future major version).&#160; We would like to encourage all interested users to perform as much testing of these apps as possible.<br />&#160;<br />We will be upgrading our company forums early this coming week.<br />&#160;<br />Please report any bugs you find with the beta to our <a href='http://community.invisionpower.com/resources/bugs.html' class='bbc_url' title=''>bug tracker</a>.<br />&#160;<br />Please pay particular attention to the following areas:<ul class='bbc'><li>The editor, both within IP.Board (topics/posts) and in other areas (such as IP.Content articles, IP.Blog entries, etc.).&#160; Pay attention both to the editor itself (i.e. when typing out and formatting your post, and toggling the editor back and forth) and the final post that is submitted.</li><li>Rebuilding posts, specifically upon upgrading from an older version of the software.&#160; Pay attention specifically to quotes to be sure they display correctly.</li><li>Any IP.Nexus functionality that has to do with grouped renewals.&#160; Cancelling packages where renewals are grouped, reactivating those packages, changing those packages, etc.</li></ul>&#160;<br />If you find a bug, please be certain to report it to the <a href='http://community.invisionpower.com/resources/bugs.html' class='bbc_url' title=''>bug tracker</a>.&#160; When doing so, be sure to include as much information as possible that will allow us to reproduce the issue, including what browser you are using, what version of PHP is running on your server, whether this was an upgrade or a fresh installation, and so on.&#160; Screenshots are often helpful.<br />As with all beta releases from IPS, these releases are not supported by our technicians until the official final releases are publicly available in our client center.&#160; Please do not upgrade your live installation using these betas, as you may find no path between these builds and the final releases that we put out.&#160; We recommended, instead, to create a copy of your live board as a test installation, and upgrade your test installation instead.<br />&#160;<br />All customers with active licenses can download the betas at: <a href='http://community.invisionpower.com/qa.php' class='bbc_url' title=''>http://community.inv...ower.com/qa.php</a><br />&#160;<br />Thanks in advance!&#160; We look forward to your feedback.]]></description>
		<pubDate>Sat, 11 May 2013 00:42:00 +0000</pubDate>
		<guid>http://community.invisionpower.com/blog/4445/entry-9528-ipboard-345-all-applications-available-for-beta-testing/</guid>
	</item>
	<item>
		<title>Minor Workflow Enhancements in IP.Nexus 1.5.8</title>
		<link>http://community.invisionpower.com/blog/4445/entry-8967-minor-workflow-enhancements-in-ipnexus-158/</link>
		<category></category>
		<description><![CDATA[At IPS not only do we create market leading products,&#160;we also use them heavily ourselves. In doing so we often identify problems and needs in the software very early on. Two of the minor changes we've introduced with Nexus 1.5.8 are a direct result of management and support staff feedback during their use of the product internally.<br />&#160;<br /><strong class='bbc'>Separating Outgoing Email Addresses</strong><br />&#160;<br />In the next release of IP.Nexus we've introduced the ability to set up separate outgoing email addresses for billing and support related notifications. these in turn are also separate from the default suite outgoing address. What this means in practice is that you can set up your community with a "noreply@"&#160; address where replies are not desirable and you want to avoid having to deal with bounced emails. Your billing notifications could be set up from "billing@" and ticket notifications from "support@". Replies can then be received via email and directed to the appropriate departments automatically.<br />&#160;<br /><strong class='bbc'>Account Credit Increase Item</strong><br />&#160;<br />Businesses often have a primary contact and a separate billing contact. For accounting purposes, it is easier for them to add 12 months worth of account credit to the main account. Currently, in order to do this an invoice needs to be generated for a miscellaneous charge. The alternate contact has to then log in and pay this invoice then notify us that they've made payment. Finally, we have to look up the invoice and then manually add the credit to the account.<br />&#160;<br />In IP.Nexus 1.5.8 we have added a new invoice item type in the ACP.<br />&#160;<br /><br />&#160;<br />When an invoice containing this item type is paid, the customer's account credit will be automatically increased without further intervention from a staff member.<br />&#160;<br />These types of real world usage scenarios and feedback are vital when developing all of the products in the IPS Suite. <a href='http://community.invisionpower.com/forum/482-community-suite-feedback/' class='bbc_url' title=''>Your feedback</a> as well as our internal usage help us to build products based on what our customers actually require rather than what an isolated group of developers may think they require.<br />&#160;<br />Do you have suggestions for any other other workflow improvements such as these? However small or seemingly trivial you think they may be we are always interested in hearing ways in which we can make these tasks that little bit easier.<br />&#160;]]></description>
		<pubDate>Fri, 10 May 2013 15:00:00 +0000</pubDate>
		<guid>http://community.invisionpower.com/blog/4445/entry-8967-minor-workflow-enhancements-in-ipnexus-158/</guid>
	</item>
	<item>
		<title>4.0 - Trees</title>
		<link>http://community.invisionpower.com/blog/4445/entry-8812-40-trees/</link>
		<category></category>
		<description><![CDATA[<strong class='bbc'>Introduction</strong><br />&#160;<br />I previously wrote <a href='http://community.invisionpower.com/blog/4445/entry-8746-40-tables/' class='bbc_url' title=''>a blog entry</a> about building tables in IPS Social Suite 4.0. Similar to tables, we also have&#160;<em class='bbc'>trees</em>. Trees in many ways look and behave similarly to tables, but can be distinguished mainly by the fact that trees show a collection of objects in a fixed order (often, though not always the order can be changed by the administrator) whereas tables can show data sorted however you like at the time.&#160;An example of trees would be the list of forums in IP.Board.<br />&#160;<br />Trees vary quite significantly in their individual implementations, for example:<ul class='bbc'><li>Objects in a tree often have parent/child relationships. Sometimes this relationship is between the same type of object (for example, forums in IP.Board or categories in IP.Downloads), sometimes the relationship is between different types of object (for example, applications and modules, where modules are always the child of applications) and sometimes it's a mix of both (for example, packages in IP.Nexus are always children of package groups, but package groups may or may not be children of each other).</li><li>Objects in a tree can usually, but not always be reordered by the administrator.</li><li>Objects in a tree can sometimes be enabled and disabled without being deleted, for example applications and modules. Sometimes individual objects can't when the rest can (for example, you can't disable the "Core" application).</li><li>Trees usually have controls for adding, editing, assigning permissions, duplicating and deleting objects, though a subset of these controls, or additional controls may be available (for example, you can't edit the permissions on a package in IP.Nexus, but you can run a purchase report, which you can't do in any other trees).</li></ul>&#160;<br />For IPS Social Suite 4.0, I wanted to create a central class to create trees - currently we duplicate a lot of functionality, and all our trees display differently (in some places, quite radically so), but without loosing any of the flexibility necessary given the various differences in each implementation. Because trees are both more complicated and flexible than tables, the method for creating one might seem complicated - however, when you compare it to having to write every part manually (including all the JavaScript) as was the case in IP.Board 3, you'll be shaving hours off of development time.<br />&#160;<br />There are two ways you can create trees in IPS Social Suite 4.0. The most common way is when each item in the table has a record in the database. There are occasions when this isn't the case (for example, the developer center displays trees created from JSON objects), and the Tree class can handle these, but in this blog entry I'm going to show you the more common method.<br />&#160;<br />Just like I did for tables, I'm going to take you through how I programmed a real-world example, specifically the tree for custom profile fields.<br />&#160;<br />&#160;<br />&#160;<br /><strong class='bbc'>Creating the classes</strong><br />&#160;<br />Custom profile fields are arranged into groups, so I'm going to start by creating a tree that shows the groups. To do this, I need to create a class for the custom profile field groups, I'll set this class to extend&#160;<span  style='font-family: courier new'>\IPS\Node\Model</span> which is an abstract class that provides most of the functionality we need.&#160;<span  style='font-family: courier new'>\IPS\Node\Model</span> in turn extends a class called&#160;<span  style='font-family: courier new'>\IPS\Patterns\ActiveRecord</span> which provides functionality to make a class an Active Record (by that I mean, an object of the class corresponds to a record in the database).<br />&#160;<br />In this class I need to define a few variables - I'll explain them below, but this is the code I'll write:<pre class='prettyprint lang-auto linenums:0'>
/**
 * Custom Profile Field Group Node
 */
class _Group extends \IPS\Node\Model
{
	/**
	 * @brief	&#91;ActiveRecord] Multiton Store
	 */
	protected static $multitons;
	
	/**
	 * @brief	&#91;ActiveRecord] Default Values
	 */
	protected static $defaultValues = NULL;
	
	/**
	 * @brief	&#91;ActiveRecord] Database Table
	 */
	public static $databaseTable = 'core_pfields_groups';
	
	/**
	 * @brief	&#91;ActiveRecord] Database Prefix
	 */
	public static $databasePrefix = 'pf_group_';
	
	/**
	 * @brief	&#91;ActiveRecord] ID Database Column
	 */
	public static $databaseColumnId = 'id';
		
	/**
	 * @brief	&#91;Node] Node Title
	 */
	public static $nodeTitle = 'profile_fields';
		
}</pre><ul class='bbc'><li>$multitons and $defaultValues are required by the&#160;<span  style='font-family: courier new'>\IPS\Patterns\ActiveRecord</span>&#160;class. We don't need to do anything with them other than declare them.</li><li>$databaseTable tells the&#160;<span  style='font-family: courier new'>\IPS\Patterns\ActiveRecord</span>&#160;class what database table the records we need are in.</li><li>$databasePrefix tells the&#160;<span  style='font-family: courier new'>\IPS\Patterns\ActiveRecord</span>&#160;class the prefix used on all database columns. This isn't necessary, but since all the columns in my table start with "pf_group_", putting it here will save me typing it out every time.</li><li>$databaseColumnId tells the&#160;<span  style='font-family: courier new'>\IPS\Patterns\ActiveRecord</span>&#160;class which column contains the primary key.</li><li>$nodeTitle tells the&#160;<span  style='font-family: courier new'>\IPS\Node\Model&#160;</span>class what language string to use as the title on my page that shows the tree.</li></ul>&#160;<br />&#160;<br />Now I need to create a controller to display my tree - the developer center will set up the structure of this for me, then I just fill in the name of the class I just created:<pre class='prettyprint lang-auto linenums:0'>
namespace IPS\core\modules\admin\membersettings;

/**
 * Profile Fields and Settings
 */
class _profiles extends \IPS\Node\Controller
{
	/**
	 * Node Class
	 */
	protected $nodeClass = '\IPS\core\ProfileFields\Group';

}
</pre>Now I have a page which looks like this:<br />&#160;<br /><br />&#160;<br />&#160;<br />&#160;<br /><strong class='bbc'>Customising the rows</strong><br />&#160;<br />You'll notice I now have two rows (because I have two rows in my database), but both are blank. This is because the&#160;<span  style='font-family: courier new'>\IPS\Node\Model&#160;</span>class doesn't know what to use for the record title. Let's fix that by adding a method to our class:<pre class='prettyprint lang-auto linenums:0'>
	/**
	 * &#91;Node] Get Node Title
	 *
	 * @return	string
	 */
	protected function get__title()
	{
		$key = "core_pfieldgroups_{$this-&gt;id}";
		return \IPS\Lang::i()-&gt;$key;
	}

</pre>That code might seem a bit confusing, but note:<ul class='bbc'><li>$this-&gt;id gets the value of the "pf_group_id" column in the database. This is because the&#160;<span  style='font-family: courier new'>\IPS\Patterns\ActiveRecord</span>&#160;class provides us with a <span  style='font-family: courier new'>__get</span> method for retrieving the database row values. This is handy if we want to be able to modify the value returned for whatever reason, as we can override that method.</li><li>We're retrieving the value for a language key rather than some kind of title field in the database. This is because in IPS Social Suite 4, data like this will be translatable, so if you have more than one language you can display different values depending on the user's language choice.</li></ul>&#160;<br />So now we have this (I've clicked the dropdown arrow so you can see it's contents):<br />&#160;<br /><br />&#160;<br />Most of this is okay, but permissions aren't relevant for custom profile field groups, so let's get rid of that. The&#160;<span  style='font-family: courier new'>\IPS\Node\Model&#160;</span>class has methods for checking if the user has permission to each button it displays (which by default check ACP restrictions, which I'll explain more about later) - we can simply override the method which checks for that button so it always returns false:<pre class='prettyprint lang-auto linenums:0'>
	/**
	 * &#91;Node] Does the currently logged in user have permission to edit permissions for this node?
	 *
	 * @return	bool
	 */
	public function canManagePermissions()
	{
		return false;
	}

</pre>And now it's gone:<br />&#160;<br /><br />&#160;<br />&#160;<br />&#160;<br /><strong class='bbc'>Making the buttons work</strong><br />&#160;<br />The first buttons I need to make work are the add on the "root" row, and the edit on each row below that. I'm going to ignore the add button on each record row for now as that is for adding a child record and we haven't got to that yet - it will start working automatically when we add support for children.<br />&#160;<br />These buttons will display a form.&#160;In a <a href='http://community.invisionpower.com/blog/4445/entry-8660-40-forms/' class='bbc_url' title=''>previous blog entry</a> I talked about our form helper class. I'm going to use this to build the add/edit form.<br />&#160;<br />To do this, I'll add two methods to my class to display the form and to save it's values - here they are:<pre class='prettyprint lang-auto linenums:0'>
	/**
	 * &#91;Node] Add/Edit Form
	 *
	 * @param	\IPS\Helpers\Form	$form	The form
	 * @return	void
	 */
	public function form( &$form )
	{
		$form-&gt;add( new \IPS\Helpers\Form\Translatable( 'pfield_group_title', NULL, TRUE, array( 'app' =&gt; 'core', 'key' =&gt; ( $this-&gt;id ? "core_pfieldgroups_{$this-&gt;id}" : NULL ) ) ) );
	}
	
	/**
	 * &#91;Node] Save Add/Edit Form
	 *
	 * @param	array	$values	Values from the form
	 * @return	void
	 */
	public function saveForm( $values )
	{
		if ( !$this-&gt;id )
		{
			$this-&gt;save();
		}
		
		\IPS\Lang::saveCustom( 'core', "core_pfieldgroups_{$this-&gt;id}", $values&#91;'pfield_group_title'] );
	}
</pre>Most of that should be self explanatory - however in the blog entry about forms I didn't mention the Translatable class. If you have one language installed, this will just display a normal text field, however, if you have more than one, it will show one for each language. It then returns an array, which we can pass to<span  style='font-family: courier new'>&#160;\IPS\Lang::saveCustom()</span>&#160;to save the values.<br />&#160;<br />This is what our form might look like if I have several languages installed:<br /><br />&#160;<br />Or, more commonly, if I just have one:<br /><br />&#160;<br />&#160;<br />Now the add and edit forms are working, but since these are very small forms (they only have one input field) I'd like them to display in a modal popup rather than take the user to a new page (if the user has JavaScript disabled, a new page will do). To do this, I just add a property to my class:<pre class='prettyprint lang-auto linenums:0'>
	/**
	 * @brief	&#91;Node] Show forms modally?
	 */
	public static $modalForms = TRUE;

</pre>Next we have the copy and delete buttons. These will actually work by themselves (the central class will handle copying and deleting the records from the database), however, since we have translatable values, we need to make sure those too are copied and deleted appropriately. To do this, I'll override the two methods which handle copying and deleting:<pre class='prettyprint lang-auto linenums:0'>
	/**
	 * &#91;ActiveRecord] Duplicate
	 *
	 * @return	void
	 */
	public function __clone()
	{
		$oldId = $this-&gt;id;
		parent::__clone();
		\IPS\Lang::saveCustom( 'core', "core_pfieldgroups_{$this-&gt;id}", \IPS\Db::i()-&gt;buildAndFetchAll( array( 'select' =&gt; '*', 'from' =&gt; 'core_sys_lang_words', 'where' =&gt; array( 'word_key=?', "core_pfieldgroups_{$oldId}" ) ), 'lang_id', 'word_custom' ) );
	}
	
	/**
	 * &#91;ActiveRecord] Delete Record
	 *
	 * @return	void
	 */
	public function delete()
	{
		parent::delete();
		\IPS\Lang::deleteCustom( 'core', 'core_pfieldgroups_' . $this-&gt;id );
	}

</pre><strong class='bbc'>Search</strong><br />&#160;<br />You'll notice that the system has automatically added a search box at the top of the table. In order to make this work, we need to add a simple search method:<pre class='prettyprint lang-auto linenums:0'>
	/**
	 * Search
	 *
	 * @param	string		$column	Column to search
	 * @param	string		$query	Search query
	 * @param	string|null	$order	Column to order by
	 * @return	array
	 */
	public static function search( $column, $query, $order )
	{	
		if ( $column === '_title' )
		{
			$return = array();			
			foreach ( \IPS\Lang::i()-&gt;searchCustom( 'core_pfieldgroups_', $query ) as $key =&gt; $value )
			{
				try
				{
					$return&#91; $key ] = self::load( $key );
				}
				catch ( \Exception $e ) { }
			}
			return $return;
		}
		return parent::search( $column, $query, $order );
	}

</pre><strong class='bbc'>Making the rows re-orderable</strong><br />&#160;<br />The last thing I need to do to finish the handling of groups is make it so we can drag and drop to reorder them. To do this I just add another property to my class telling&#160;<span  style='font-family: courier new'>\IPS\Patterns\ActiveRecord</span>&#160;which column contains the order ID:<pre class='prettyprint lang-auto linenums:0'>
	/**
	 * @brief	&#91;Node] Order Database Column
	 */
	public static $databaseColumnOrder = 'order';
</pre>&#160;&#160;<br /><strong class='bbc'>ACP Restrictions</strong><br />&#160;<br />It's important of course to make sure we honour ACP restrictions. The easiest way to do this is to create individual ACP restrictions for add/edit/delete (this can be done in the developer center) with a common prefix, and then specify like so:<pre class='prettyprint lang-auto linenums:0'>
	/**
	 * @brief	&#91;Node] ACP Restrictions
	 */
	protected static $restrictions = array(
		'app'		=&gt; 'core',
		'module'	=&gt; 'membersettings',
		'prefix'	=&gt; 'profilefieldgroups_',
	);

</pre>The system will now look for ACP restrictions with the keys "profilefieldgroups_add",&#160;"profilefieldgroups_edit" and&#160;"profilefieldgroups_delete" when performing those actions.<br />&#160;<br />&#160;<br /><strong class='bbc'>Children</strong><br />&#160;<br />Let's recap what we have so far with a video:<br /><a href='http://screencast.com/t/TUBuuYBSBON' class='bbc_url' title='External link' rel='nofollow external'>http://screencast.com/t/TUBuuYBSBON</a><br />Now that we have groups sorted, we're going to create another class for the actual fields which will show as children. The process is almost exactly the same as for groups. Since the process is the same,&#160;I won't go through the process step-by-step, but here is the class I've written if you're interested:<br /><br />&#160;<br />The only changes are:<ul class='bbc'><li>I've declared two additional properties specifying the class name of the parent ("\IPS\core\ProfileFields\Group") and which column contains the parent ID.</li><li>I've declared an additional method to fetch an icon for the row so we can see what type of field this is.</li><li>Just like we overwrote canManagePermissions for groups, I've also overridden canAdd in the same way, as you cannot add children to profile fields.</li></ul>&#160;<br />Now all we need to do is link them up. To do this, I add a property to my group class telling&#160;<span  style='font-family: courier new'>\IPS\Node\Model</span>&#160;the name of the class which contains children:<pre class='prettyprint lang-auto linenums:0'>
	/**
	 * @brief	&#91;Node] Subnode class
	 */
	public static $subnodeClass = 'IPS\core\ProfileFields\Field';


</pre>The system will now automatically change the behaviour of our page in the following ways:<ul class='bbc'><li>When clicking on a group, it will expand out to show the fields under it.</li><li>The search box will include fields as well as groups in its results.</li><li>When clicking the "Add" button for a group, it will show the field to add a field to that group.</li><li>When clicking the "Copy" button for a group, you'll have the option to copy children too or not.</li><li>When clicking the "Delete" button for a group, you'll have the option to move children elsewhere or delete them too.</li><li>(This is my favourite feature) In addition to being able to drag and drop fields to reorder, you can drag a field out of one group and into another.</li></ul>Here's a video:<br /><a href='http://screencast.com/t/5fQwgle3EX' class='bbc_url' title='External link' rel='nofollow external'>http://screencast.com/t/5fQwgle3EX</a><br />&#160;]]></description>
		<pubDate>Wed, 17 Apr 2013 20:00:00 +0000</pubDate>
		<guid>http://community.invisionpower.com/blog/4445/entry-8812-40-trees/</guid>
	</item>
	<item>
		<title>4.0 Developer Center</title>
		<link>http://community.invisionpower.com/blog/4445/entry-8810-40-developer-center/</link>
		<category></category>
		<description><![CDATA[A few weeks ago, I posted a <a href='http://community.invisionpower.com/blog/4445/entry-8685-40-developer-center-database-schema-management/' class='bbc_url' title=''>blog entry</a> mentioning a new feature in 4.0 which aims to make development of applications within the IPS Social Suite (both for us and third party modification authors) easier. We focussed on managing the database schema in that blog entry and I'd now like to take you through the other features.<br />&#160;<br />&#160;<br /><strong class='bbc'>Modules</strong><br />&#160;<br /><br />&#160;<br />Two tabs (one for admin modules and one for front modules) allow you to view all modules and sections in your application. You can add modules (which will both insert it into the database and create the relevant files in the filesystem), change the default section for a module (which previously required a defaultSection.php file) and create new sections.<br />&#160;<br />When you're creating a new section, the form looks like this (this is for creating a section for an admin module):<br /><br />&#160;<br />The "Type" field controls the code that will be placed in the file created for the section - the options are:<ul class='bbc'><li>"Blank" - which will create the class with no other logic, so the section will be blank.</li><li>"Table" which will create a class with a boilerplate for displaying a <a href='http://community.invisionpower.com/blog/4445/entry-8746-40-tables/' class='bbc_url' title=''>table</a>.</li><li>"Node Controller" - which will create a boilerplate for displaying a tree of containers such as IP.Board forums, IP.Downloads categories, IP.Nexus groups, etc. We've not posted how this class works, but a future blog entry will give more details.</li></ul>No matter which type you select, the system will automatically generate a file, with a basic class structure already filled in, including ACP restrictions checks, etc.<br />&#160;<br />The "Menu Tab" field is admin specific and controls under which tab in the Admin CP the section should show (for example, we have some stuff from the "core" app under the "Look & Feel" tab). Previously, one would have to edit the menu.xml file for the module to add sections, and making sections appear under tabs other the default application tab was very difficult - the new system does it for you.<br />&#160;<br />The "ACP Restriction" field is also admin specific and allows you to select an existing ACP restriction which will control who can see the section. It also has a special "Create Restriction" option, which will cause the system to create a restriction, and use that. Previously, one would have to edit the permissions.xml file to create restrictions and then assign them in the menu.xml file.<br />&#160;<br />Of course - you can completely bypass this feature and manually create your module folders and section files, but the addition of this feature makes the process much quicker.<br />&#160;<br />&#160;<br />&#160;<br /><strong class='bbc'>Admin CP Menu</strong><br />&#160;<br />This tab contains&#160;a graphical representation of the data which was previously stored in menu.xml files.<br />&#160;<br /><br />&#160;<br /><br />&#160;<br />&#160;<br />&#160;<br /><br /><strong class='bbc'>Admin CP Restrictions</strong><br />&#160;<br />This tab contains a graphical representation of the data which was previously stored in permissions.xml files.<br />&#160;<br /><br />&#160;<br />&#160;<br />&#160;<br /><strong class='bbc'>Extensions</strong><br />&#160;<br />Extensions are ways in which applications interact with one another. Previously, you would drop extension files in your applications "/extensions" folder, though there wasn't much reasoning to the structure of the directory, it was difficult to know what extensions were available, and sometimes understanding an extensions requirements was difficult.<br />&#160;<br />In 4.0, the extensions directory is more structured - the format is owner app &gt; extension type &gt; extension file (so admin/group_form.php for example, is now core/GroupForm/*.php) so this tab provides a GUI for managing your applications extensions. Applications can also specify a boilerplate file for an extension, so you can see what extensions are available, and clicking the "add" button will create a file with a basic structure to get you going.<br />&#160;<br /><br />&#160;<br />&#160;<br />&#160;<br /><strong class='bbc'>Settings</strong><br />&#160;<br />In 4.0, developers have much more control over how settings are presented, rather than all being dumped in the central "System Settings" table. With this, much of the data that was previously needed in settings.xml is no longer required, so we've simplified the process of creating setting to just providing a key and a default value.<br />&#160;<br /><br />&#160;<br />&#160;<br />&#160;<br /><strong class='bbc'>Versions</strong><br />&#160;<br />The versions tab shows all of the application's versions and the database queries that the upgrader will run when upgrading to that version. It's sort of a combination of the versions.xml file and the setup folder.<br />&#160;<br /><br /><br />&#160;<br />Queries are automatically added as you modify the database schema. Naturally you can also manually add queries, or specify a custom PHP script to run in that upgrade step.]]></description>
		<pubDate>Mon, 15 Apr 2013 14:00:00 +0000</pubDate>
		<guid>http://community.invisionpower.com/blog/4445/entry-8810-40-developer-center/</guid>
	</item>
	<item>
		<title>4.0 - Login Handlers</title>
		<link>http://community.invisionpower.com/blog/4445/entry-8747-40-login-handlers/</link>
		<category></category>
		<description><![CDATA[<em class='bbc'>Login Handlers</em> are the different methods for logging into the&#160;IPS Social Suite. We currently support:<ul class='bbc'><li><em class='bbc'>"</em>Internal", which is for accounts created natively through the suite.</li><li>Facebook</li><li>Twitter</li><li>Microsoft (this is currently referred to as "Windows Live", though they rebranded to "Microsoft Account" a short while ago)</li><li>LDAP</li><li>"IPS Connect", which is our SSO solution for connecting your site with other IPS Social Suite installations or third-party applications.</li><li>A generic handler for any MySQL database you have access to.</li></ul>In 4.0 we've made a number of changes to the Login Handlers which I wanted to mention.<br />&#160;<br />&#160;<br />&#160;<br /><strong class='bbc'>Improved Password Encryption</strong><br />&#160;<br />We currently use a salted md5 hash for hashing passwords. md5 has been a popular password hashing technique for years - however, it is not the most secure hashing method.<br />&#160;<br />md5 is designed to be&#160;computationally efficient (meaning generating a hash is quick). The problem with this is that if a server were ever compromised to the point that someone were able to gain access to a database containing passwords hashed using md5, and someone were to use a program to generate and hash different strings repeatedly until a match were found, the password could be worked out. One particularly well-known program claims to be able to make 5.6 billion md5 hashes per second with a relatively modern GPU. Even with our hashing method which includes multi-level hashing and a salt, this means, assuming an 8-character long password using only alphanumeric characters were used, a password could be calculated in about 3 days.<br />&#160;<br />While I'm unaware of any cases of this actually happening, we want to make sure that our products are as secure as they can be. For this reason, in 4.0, we're migrating to <a href='http://en.wikipedia.org/wiki/Blowfish_(cipher)' class='bbc_url' title='External link' rel='nofollow external'>Blowfish</a>. Blowfish is a more&#160;cryptographically secure technique for generating hashes that is deliberately slow, meaning that even if your database were ever compromised, the passwords will still be secure.<br />&#160;<br />&#160;<br /><strong class='bbc'>New Login Handlers</strong><br />&#160;<br />In addition to the Login Handlers mentioned above, we've added support for Google and LinkedIn.<br />&#160;<br />&#160;<br /><strong class='bbc'>Improved Facebook and Twitter support</strong><br />&#160;<br />Currently, although you can log in with Facebook and Twitter, they're not treated on the back-end as true Login Handers. This is because of how Login Handlers in 3.x were designed (which was before such 3rd party login services were popular) in that they assumed you would provide a username (or email) and password directly into a form, and subsequently didn't accommodate the OAuth-style login processes.<br />&#160;<br />Since we've rewritten the way Login Handlers are designed, this means we can treat Facebook and Twitter (and Google and LinkedIn which both also use OAuth) exactly the same as the rest.<br />&#160;<br />Practically, this means you'll see Facebook and Twitter in the Login Handlers section of the Admin CP, and manage them as you would any other login method.<br />&#160;<br />&#160;<br /><strong class='bbc'>Updated Microsoft Support</strong><br />&#160;<br />Microsoft now support OAuth for login through them so we've updated to use that. In addition to being necessary for when they stop supporting the old way, it's much easier to set up for the administrator.]]></description>
		<pubDate>Mon, 08 Apr 2013 13:15:39 +0000</pubDate>
		<guid>http://community.invisionpower.com/blog/4445/entry-8747-40-login-handlers/</guid>
	</item>
	<item>
		<title>IP.Board 3.4.4 Beta Refreshed</title>
		<link>http://community.invisionpower.com/blog/4445/entry-8802-ipboard-344-beta-refreshed/</link>
		<category></category>
		<description><![CDATA[We've just rebuilt IP.Board 3.4.4 for further beta testing.<br />&#160;<br />Thanks to everyone who has tested this release so far and for reporting the bugs you've found. We've fixed a good portion of these and would like for you to update your test installations with the latest release.<br />&#160;<br />All customers with an active IP.Board license can download the beta at:&#160;<a href='http://community.invisionpower.com/qa.php' class='bbc_url' title=''>http://community.inv...ower.com/qa.php</a><br />&#160;<br />Once you've uploaded all the files to your server, there's no need to run the upgrade system as the version numbers haven't changed. You will need to rebuild your languages and skins. There's instructions <a href='http://www.invisionpower.com/support/kb/_/manually-rebuild-skins-and-languages-from-xml-r42' class='bbc_url' title='External link' rel='nofollow external'>here</a> on how to do that.<br />&#160;<br />As always, please report any bugs you find with the beta to our&#160;<a href='http://community.invisionpower.com/resources/bugs.html' class='bbc_url' title=''>bug tracker</a>.<br />&#160;<br />Please pay particular attention to using the editor with IP.Board 3.4.4.&#160; A very large focus was placed on resolving some of the outstanding bugs and complaints with the editor, and we would appreciate any testing you can perform in this area.&#160; Create new posts, edit existing posts, toggle between the editor modes - if you find any bugs,&#160;<a href='http://community.invisionpower.com/resources/bugs.html' class='bbc_url' title=''>let us know</a>.<br />&#160;<br />As with all beta releases from IPS, IP.Board 3.4.4 is not supported by our technicians until it has been officially publicly released.&#160; Please do not upgrade your live installation using this beta, as you may find no path between this build and the final release that we put out.&#160; We recommended, instead, to create a copy of your live board as a test installation, and upgrade your test installation instead.<br />&#160;<br />Thanks!<br /><br />&#160;]]></description>
		<pubDate>Wed, 03 Apr 2013 15:44:40 +0000</pubDate>
		<guid>http://community.invisionpower.com/blog/4445/entry-8802-ipboard-344-beta-refreshed/</guid>
	</item>
	<item>
		<title>IP.Board 3.4.4 available for beta testing</title>
		<link>http://community.invisionpower.com/blog/4445/entry-8791-ipboard-344-available-for-beta-testing/</link>
		<category></category>
		<description><![CDATA[IP.Board 3.4.4 is now available for beta testing!<br />&#160;<br />We have been hard at work on IP.Board 3.4.4, and following a good week of testing here on our company forums, we have built a downloadable IP.Board 3.4.4 package for you to test on your own servers.&#160; We appreciate any testing you can perform.<br />&#160;<br />Please report any bugs you find with the beta to our <a href='http://community.invisionpower.com/resources/bugs.html' class='bbc_url' title=''>bug tracker</a>.<br />&#160;<br />Please pay particular attention to using the editor with IP.Board 3.4.4.&#160; A very large focus was placed on resolving some of the outstanding bugs and complaints with the editor, and we would appreciate any testing you can perform in this area.&#160; Create new posts, edit existing posts, toggle between the editor modes - if you find any bugs, <a href='http://community.invisionpower.com/resources/bugs.html' class='bbc_url' title=''>let us know</a>.<br />&#160;<br />As with all beta releases from IPS, IP.Board 3.4.4 is not supported by our technicians until it has been officially publicly released.&#160; Please do not upgrade your live installation using this beta, as you may find no path between this build and the final release that we put out.&#160; We recommended, instead, to create a copy of your live board as a test installation, and upgrade your test installation instead.<br />&#160;<br />All customers with an active IP.Board license can download the beta at: <a href='http://community.invisionpower.com/qa.php' class='bbc_url' title=''>http://community.invisionpower.com/qa.php</a><br />&#160;<br />Thanks in advance!&#160; We look forward to your feedback.]]></description>
		<pubDate>Fri, 29 Mar 2013 20:43:59 +0000</pubDate>
		<guid>http://community.invisionpower.com/blog/4445/entry-8791-ipboard-344-available-for-beta-testing/</guid>
	</item>
	<item>
		<title>IP.Board 3.4.4 Editor Testing</title>
		<link>http://community.invisionpower.com/blog/4445/entry-8755-ipboard-344-editor-testing/</link>
		<category></category>
		<description><![CDATA[The eagle eyed among you may have spotted that we've just upgraded our company forums to IP.Board 3.4.4.<br />&#160;<br />We routinely do this during a development cycle so that we can get some extended testing prior to a beta release. When we write new features and fix bugs we do test ourselves but of course we can't replicate the testing hundreds of active users with all the different browser and operating system combinations can offer.<br />&#160;<br />The focus of 3.4.4 has been to further stabilise the editor. We've made great improvements since the initial release of 3.4.0 but we're aware that there are a handful of issues remaining which we want to get licked for this release.<br />&#160;<br />If you have a few moments spare, we'd appreciate it if you could test out the editor, either by creating a post in the <a href='http://community.invisionpower.com/forum/15-test-posting-messages/' class='bbc_url' title=''>test forum</a> or just by being more aware of any quirks or issues when making posts normally.<br />&#160;<br />Anything you spot, can you please report into our <a href='http://community.invisionpower.com/resources/bugs.html/_/ip-board/' class='bbc_url' title=''>bug tracker</a> with as much detail as you can.<br />&#160;<br />Thanks!<br />&#160;<br />&#160;]]></description>
		<pubDate>Mon, 25 Mar 2013 13:50:46 +0000</pubDate>
		<guid>http://community.invisionpower.com/blog/4445/entry-8755-ipboard-344-editor-testing/</guid>
	</item>
	<item>
		<title>4.0 - Tables</title>
		<link>http://community.invisionpower.com/blog/4445/entry-8746-40-tables/</link>
		<category></category>
		<description><![CDATA[There's a table in the Admin CP of the IPS Social Suite that I really like - the members table. It has some really cool options - you can reorder the data just by clicking on a column head; you can quickly search for a member by typing a name into a search box at the top; there's some filter options to quickly show banned, locked, spam and validating members; and there's an advanced search form to search for members based on practically any criteria.<br />&#160;<br />It would be great if these features were available elsewhere. So <a href='http://community.invisionpower.com/blog/4445/entry-8660-40-forms/' class='bbc_url' title=''>much like we did for forms</a>, we decided to create a central helper class for building tables.<br />&#160;<br />To demonstrate how it works, I'm going to go through, step by step, how I recreated the Admin CP members table in IPS 4.<br />&#160;<br />&#160;<br />It starts with one line to create the table, and another to pass it to the output class:<pre class='prettyprint lang-auto linenums:0'>
		/* Create the table */
		$table = new \IPS\Helpers\Table\Db( 'core_members', 'app=core&module=members&section=members' );
		
		/* Display */
		\IPS\Output::i()-&gt;output	= \IPS\Output::i()-&gt;getTemplate( 'global' )-&gt;block( 'members', $table );

</pre>&#160;<br />&#160;<br />With just those two lines, you'll see this:<br /><br />&#160;<br />Some things to note:<ul class='bbc'><li>We're calling <span  style='font-family: courier new'>\IPS\Helpers\Table\Db</span> - the "Db" part indicates that the source of data for our table is a database table. There are other classes to use, for example, a JSON document as the data source.</li><li>We pass it the name of our database table (or for the other classes, whatever the data source is) and the query string part of the URL where we're going to be displaying this (which we need to build the links and AJAX calls).</li><li>I'm passing it to the output through a template called "block" which simply adds the dark-blue bar at the top, which isn't actually part of the table itself, and some padding. The "members" parameter is the key for the langauge string to use in that dark-blue bar.</li><li>I'm passing <span  style='font-family: courier new'>$table</span> directly to the template - the helper class has a <span  style='font-family: courier new'>__toString</span> method which renders the table, so the output class thinks it's been given a normal string.</li></ul>&#160;<br />&#160;<br />&#160;<br />The first obvious thing is that we're showing all the columns in the database table, which obviously we don't want. So let's add another line to specify which columns we want:<pre class='prettyprint lang-auto linenums:0'>
$table-&gt;include = array( 'name', 'email', 'joined', 'member_group_id', 'ip_address' );
</pre>&#160;<br />&#160;<br />In this example, I'm giving the helper class a list of columns to include - I could alternatively pass a list of columns to exclude, if that would be more appropriate.<br /><br />The output is now this:<br /><br />&#160;<br />Some things to note:<ul class='bbc'><li>It's worked out pagination itself. When you click a pagination link, the contents of the table will update with AJAX, including changing your browser's URL (unless you have JavaScript disabled of course, in which case it will work like a normal link). Pagination defaults to 25 results per page, but you can change that just by changing a property in the class.</li><li>All the columns are clickable, which will resort the results. You can sort any column ascending or descending. Resorting will also update with AJAX (including changing your browser's URL), unless JavaScript is disabled.</li></ul>&#160;<br />&#160;<br />&#160;<br />I want the headers to display something more meaningful than the column name. The system will automatically look for language strings which match the column name - you can also optionally specify a prefix, and it'll look for langauge strings which match that followed by the column name.<br />&#160;<br />Let's specify a prefix:<pre class='prettyprint lang-auto linenums:0'>
$table-&gt;langPrefix = 'members_';
</pre>&#160;<br />And I'll then create some language strings that match that (so "members_name", "members_email", etc.).<br />&#160;<br />The output is now this:<br /><br />&#160;<br />&#160;<br />&#160;<br />&#160;<br />Next - we need to change how we display some of those values. The joined date and the group are displaying the raw values from the database, but we want something more meaningful than that.<br />&#160;<br />To format the values, we simply create an array of lambda functions - one for each we want to format:<br />&#160;<pre class='prettyprint lang-auto linenums:0'>
		$table-&gt;parsers = array(
			'joined'			=&gt; function( $val, $row )
			{
				return \IPS\DateTime::ts( $val )-&gt;localeDate();
			},
			'member_group_id'	=&gt; function( $val, $row )
			{
				return \IPS\Member\Group::load( $val )-&gt;formattedName();
			}
		);

</pre>&#160;<br />&#160;<br />I'm also going to add one additional line to specify the "main" column, which applies some additional styles:<pre class='prettyprint lang-auto linenums:0'>
$table-&gt;mainColumn = 'name';</pre>&#160;<br />&#160;<br />The output is now this:<br /><br />&#160;<br />Some things to note:<ul class='bbc'><li>I'm using the <span  style='font-family: courier new'>\IPS\DateTime </span>class to format the joined date. The ts method in this is a factory method which takes a UNIX timestamp and returns an object of <span  style='font-family: courier new'>\IPS\DateTime</span>. <span  style='font-family: courier new'>\IPS\DateTime</span> extends <a href='http://www.php.net/manual/en/class.datetime.php' class='bbc_url' title='External link' rel='nofollow external'>DateTime</a>, so all the features of that class are available to us. The localeDate method returns a string with the date formatted appropriately according to user's locale.</li><li>The<span  style='font-family: courier new'> \IPS\Member\Group::load</span> call being executed for each result may look like it might be resource intensive, but it caches objects it creates, so it's only actually "loading" each group once.</li></ul>&#160;<br />&#160;<br />&#160;<br />&#160;<br />Now &#160;I want to add a column with the user's photo. There isn't a single "photo" column in the database we can use for this (since the photo could be one they uploaded, a photo from their Facebook account if they're using Facebook Connect, a Gravatar image, or some other things), we need to use a method in the <span  style='font-family: courier new'>\IPS\Member </span>class.<br />&#160;<br />This isn't a problem. I can simply add an element to our list of fields to include and add that into the parsers.<pre class='prettyprint lang-auto linenums:0'>
$table-&gt;include = array( 'photo', 'name', 'email', 'joined', 'member_group_id', 'ip_address' );
</pre><pre class='prettyprint lang-auto linenums:0'>
		$table-&gt;parsers = array(
			'photo'				=&gt; function( $val, $row )
			{
				return \IPS\Member::constructFromData( $row )-&gt;photo('mini');
			},

</pre>&#160;<br />I'll also want to specify that we cannot use the photo column for sorting:<br />&#160;<pre class='prettyprint lang-auto linenums:0'>
$table-&gt;noSort	= array( 'photo' );
</pre>&#160;<br />&#160;<br />The output is now this:<br /><br />&#160;<br />&#160;<br />Some things to note:<ul class='bbc'><li>Since this isn't a value which exists in the database, the value of <span  style='font-family: courier new'>$val</span> in the lambda function will be <span  style='font-family: courier new'>NULL</span>, however, <span  style='font-family: courier new'>$row</span> has all the data for that record.</li><li>We're not using <span  style='font-family: courier new'>\IPS\Member::load</span> to get the member object, since that would execute an additional query for every result, which would be resource intensive, and unnecessary since we already have that data. Instead, we use the <span  style='font-family: courier new'>constructFromData</span> method and pass it the row from the database.</li></ul>&#160;<br />&#160;<br />&#160;<br />Next, I want to specify the default sorting. This is done with just two lines of code:<pre class='prettyprint lang-auto linenums:0'>
		$table-&gt;sortBy = $table-&gt;sortBy ?: 'joined';
		$table-&gt;sortDirection = $table-&gt;sortDirection ?: 'desc';
</pre>&#160;<br />&#160;<br />The output is now this:<br /><br />&#160;<br />&#160;<br />&#160;<br />&#160;<br />Now, I want to add a quick search box. All we need to do is specify which column the quick search should look at:<pre class='prettyprint lang-auto linenums:0'>
$table-&gt;quickSearch = 'name';
</pre>&#160;<br />&#160;<br />The output is now this:<br /><br />&#160;<br />Some things to note:<ul class='bbc'><li>As you type, results are obtained with AJAX.</li><li>You can page through your results (the number of pages will update automatically) and reorder your results by clicking the headers without loosing your search.</li></ul>&#160;<br />&#160;<br />&#160;<br />I also want to allow more advanced search options - like to search by email address, or joined date. To do this, I create a new array:<pre class='prettyprint lang-auto linenums:0'>
		$table-&gt;advancedSearch = array(
			'member_id'			=&gt; \IPS\Helpers\Table\SEARCH_CONTAINS_TEXT,
			'email'				=&gt; \IPS\Helpers\Table\SEARCH_CONTAINS_TEXT,
			'ip_address'		=&gt; \IPS\Helpers\Table\SEARCH_CONTAINS_TEXT,
			'member_group_id'	=&gt; array( \IPS\Helpers\Table\SEARCH_SELECT, array( 'options' =&gt; $groups ), function( $val )
			{
				return array( 'member_group_id=? OR ? IN(mgroup_others)', $val, $val );
			} ),
			'joined'			=&gt; \IPS\Helpers\Table\SEARCH_DATE_RANGE,
			);

</pre>&#160;<br />&#160;<br />To explain what's going on here:<ul class='bbc'><li>The keys are the columns we're letting the user search on.</li><li>The values are usually a constant indicating the type of search that is appropriate for that column.</li><li>The member_group_id element is a bit more complicated. It has to specify an array of options (I've omitted the code to generate <span  style='font-family: courier new'>$groups</span> in this snippet, but it'll be at the end of this blog entry), and, because we need to search both primary and secondary groups based on the value, there's a lambda function to get the proper WHERE clause for the query.</li></ul>&#160;<br />Now, next to the quick search box there's a button which will bring up a modal popup (or just take you to a new page if JavaScript is disabled) which looks like this:<br /><br />&#160;<br />Some things to note:<ul class='bbc'><li>The date entry boxes use the HTML5 date input type:<br />
<br />
If your browser doesn't support that, there's a JavaScript fallback:<br />
<br />
And if you're really awkward and are using a browser that doesn't support&#160;the HTML5 date input type and have JavaScript disabled, you'll see a regular text box where you can enter a date in practically any format, and it'll work it out.</li><li>After performing the search, you can reorder your results by clicking the headers without loosing your search.</li></ul>&#160;<br />&#160;<br />&#160;<br />&#160;<br />Now, I want to add some filters so you can quickly see banned, spam, locked and validating members. To do this, you create an array simply specifying the WHERE clause to use in the query for each filter:<pre class='prettyprint lang-auto linenums:0'>
		/* Filters */
		$table-&gt;filters = array(
			'members_filter_banned'		=&gt; 'member_banned=1',
			'members_filter_locked'		=&gt; 'failed_login_count&gt;=' . (int) \IPS\Settings::i()-&gt;ipb_bruteforce_attempts,
			'members_filter_spam'		=&gt; '(members_bitoptions & ' . \IPS\Member::$bitOptions&#91;'bw_is_spammer'] . ') != 0',
			'members_filter_validating'	=&gt; 'v.lost_pass=0 AND v.vid IS NOT NULL'
		);

</pre>&#160;<br />For this though, I'll also need to join the core_validating database table, so we add one more line for that:<pre class='prettyprint lang-auto linenums:0'>
$table-&gt;joins = array( array( 'from' =&gt; array( 'core_validating' =&gt; 'v' ), 'where' =&gt; 'v.member_id=_0.member_id' ) );
</pre>&#160;<br />&#160;<br />The output is now this:<br /><br />&#160;<br />Some things to note:<ul class='bbc'><li>The helper class will add the "All" filter automatically.</li><li>It's getting the word to use for the filter by looking for a language string with the same key as the key in the array passed.</li><li>Like everything else, clicking a filter updates the results with AJAX and the filter is retained in searches.</li></ul>&#160;<br />&#160;<br />&#160;<br />&#160;<br />Finally, the last thing I need to do is add a column with some buttons. You can specify a normal array for buttons to show in the header, and a lambda functions to return an array for buttons to show for each row:<pre class='prettyprint lang-auto linenums:0'>
		$table-&gt;rootButtons = array(
			'add'	=&gt; array(
				'icon'		=&gt; array( 'icons/add.png', 'core' ),
				'title'		=&gt; 'members_add',
				'link'		=&gt; 'app=members&module=members&section=members&do=add',
			)
		);
		$table-&gt;rowButtons = function( $row )
		{
			return array(
				'edit'	=&gt; array(
					'icon'		=&gt; array( 'icons/edit.png', 'core' ),
					'title'		=&gt; 'edit',
					'link'		=&gt; 'app=members&module=members&section=members&do=edit&id=' . $row&#91;'member_id'],
				),
				'delete'	=&gt; array(
					'icon'		=&gt; array( 'icons/delete.png', 'core' ),
					'title'		=&gt; 'delete',
					'link'		=&gt; 'app=members&module=members&section=members&do=delete&id=' . $row&#91;'member_id'],
					'class'		=&gt; 'delete',
				),
			);
		};

</pre>&#160;<br />&#160;<br />&#160;<br />&#160;<br />Our finished table looks like this:<br /><br />&#160;<br />And behaves like this:<br /><a href='http://screencast.com/t/KMFq8zCE' class='bbc_url' title='External link' rel='nofollow external'>http://screencast.com/t/KMFq8zCE</a><br />&#160;<br />&#160;<br />&#160;<br />To recap, here's the code, in it's entirety to generate that table:<br />&#160;<pre class='prettyprint lang-auto linenums:0'>
		/* Create the table */
		$table = new \IPS\Helpers\Table\Db( 'core_members', 'app=core&module=members&section=members' );
		$table-&gt;langPrefix = 'members_';
				
		/* Columns we need */
		$table-&gt;include = array( 'photo', 'name', 'email', 'joined', 'member_group_id', 'ip_address' );
		$table-&gt;mainColumn = 'name';
		$table-&gt;noSort	= array( 'photo' );
		
		/* Default sort options */
		$table-&gt;sortBy = $table-&gt;sortBy ?: 'joined';
		$table-&gt;sortDirection = $table-&gt;sortDirection ?: 'desc';
		
		/* Filters */
		$table-&gt;joins = array( array( 'from' =&gt; array( 'core_validating' =&gt; 'v' ), 'where' =&gt; 'v.member_id=_0.member_id' ) );
		$table-&gt;filters = array(
			'members_filter_banned'		=&gt; 'member_banned=1',
			'members_filter_locked'		=&gt; 'failed_login_count&gt;=' . (int) \IPS\Settings::i()-&gt;ipb_bruteforce_attempts, /*@todo*/
			'members_filter_spam'		=&gt; '(members_bitoptions & ' . \IPS\Member::$bitOptions&#91;'bw_is_spammer'] . ') != 0',
			'members_filter_validating'	=&gt; 'v.lost_pass=0 AND v.vid IS NOT NULL'
		);
		
		/* Groups for advanced filter (need to do it this way because array_merge renumbers the result */
		$groups = array( '' =&gt; 'any_group' );
		foreach ( \IPS\Member\Group::groups() as $k =&gt; $v )
		{
			$groups&#91; $k ] = $v;
		}
		
		/* Search */
		$table-&gt;quickSearch = 'name';
		$table-&gt;advancedSearch = array(
			'member_id'			=&gt; \IPS\Helpers\Table\SEARCH_CONTAINS_TEXT,
			'email'				=&gt; \IPS\Helpers\Table\SEARCH_CONTAINS_TEXT,
			'ip_address'		=&gt; \IPS\Helpers\Table\SEARCH_CONTAINS_TEXT,
			'member_group_id'	=&gt; array( \IPS\Helpers\Table\SEARCH_SELECT, array( 'options' =&gt; $groups ), function( $val )
			{
				return array( 'member_group_id=? OR ? IN(mgroup_others)', $val, $val );
			} ),
			'joined'			=&gt; \IPS\Helpers\Table\SEARCH_DATE_RANGE,
			);
		
		/* Custom parsers */
		$table-&gt;parsers = array(
			'photo'				=&gt; function( $val, $row )
			{
				return \IPS\Member::constructFromData( $row )-&gt;photo('mini');
			},
			'joined'			=&gt; function( $val, $row )
			{
				return \IPS\DateTime::ts( $val )-&gt;localeDate();
			},
			'member_group_id'	=&gt; function( $val, $row )
			{
				return \IPS\Member\Group::load( $val )-&gt;formattedName();
			}
		);
		
		/* Specify the buttons */
		$table-&gt;rootButtons = array(
			'add'	=&gt; array(
				'icon'		=&gt; array( 'icons/add.png', 'core' ),
				'title'		=&gt; 'members_add',
				'link'		=&gt; 'app=members&module=members&section=members&do=add',
			)
		);
		$table-&gt;rowButtons = function( $row )
		{
			return array(
				'edit'	=&gt; array(
					'icon'		=&gt; array( 'icons/edit.png', 'core' ),
					'title'		=&gt; 'edit',
					'link'		=&gt; 'app=members&module=members&section=members&do=edit&id=' . $row&#91;'member_id'],
				),
				'delete'	=&gt; array(
					'icon'		=&gt; array( 'icons/delete.png', 'core' ),
					'title'		=&gt; 'delete',
					'link'		=&gt; 'app=members&module=members&section=members&do=delete&id=' . $row&#91;'member_id'],
					'class'		=&gt; 'delete',
				),
			);
		};
		
		/* Display */
		\IPS\Output::i()-&gt;output	= \IPS\Output::i()-&gt;getTemplate( 'global' )-&gt;block( 'members', $table );

</pre>&#160;<br />&#160;]]></description>
		<pubDate>Fri, 22 Mar 2013 16:30:00 +0000</pubDate>
		<guid>http://community.invisionpower.com/blog/4445/entry-8746-40-tables/</guid>
	</item>
	<item>
		<title>Use a third party library/framework, or build it in-house?</title>
		<link>http://community.invisionpower.com/blog/4445/entry-8670-use-a-third-party-libraryframework-or-build-it-in-house/</link>
		<category></category>
		<description><![CDATA[One question I have seen surface in the past (and present), revolves around how we decide when to use a third party library or framework, and how we decide when to develop something in-house entirely.&#160; For instance, in the 4.0 Suite we will utilize <a href='http://jquery.com/' class='bbc_url' title='External link' rel='nofollow external'>jQuery</a> (a third party javascript framework), however we will build our underlying PHP framework in-house.&#160; How did we decide to go that route?&#160; There are several PHP frameworks on the web, many of which having licenses compatible with our commercial license, so why didn't we choose one of those to kickstart 4.0 development?<br />&#160;<br />This is, admittedly, often a difficult question to answer.&#160; The truth is, we evaluate each scenario on a case-by-case basis and make decisions based on what is best for us and our clients.&#160; Sometimes these decisions may not be obvious, however you should know that much thought has gone on behind the scenes here at IPS to ensure we are making the choices that we feel are best for our products.<br />&#160;<br />Javascript frameworks are almost an essential tool with today's fast-paced browser development and web advances.&#160; Browser updates from some vendors are almost weekly.&#160; We went from HTML 4 to XHTML 1 to HTML 5 within but a few years.&#160; And while everything a javascript framework does can be replicated in-house, it would consume a lot of development time that we would have to spend in order to keep pace with all of these changes, purely for compatibility reasons (e.g. no new functionality added, just to keep things working and up to date).&#160; We have long-ago determined that using a well-maintained javascript framework to facilitate javascript development is virtually a necessity, otherwise you quickly get bogged down trying to maintain javascript code just to retain compatibility with current browsers and newly available functionality.<br />&#160;<br />What about PHP frameworks though?&#160; There are many out there (<a href='http://ellislab.com/codeigniter' class='bbc_url' title='External link' rel='nofollow external'>CodeIgniter</a>, <a href='http://framework.zend.com/' class='bbc_url' title='External link' rel='nofollow external'>Zend Framework</a>, etc.) and many are relatively robust, well tested and quite extensible.&#160; Why have we chosen to write our own underlying framework given this information?&#160; In researching whether to maintain our own framework or use an existing one, we had to weigh many pros and cons.&#160; For instance, one pro using third party PHP frameworks would be that we can skip all of the development of underlying classes (controllers, autoloaders, database connector and so forth) and jump into the higher level development.&#160; This is surely an important consideration to take into account.<br />&#160;<br />On the other hand, using third party PHP frameworks ties us into that framework, and we expect the underlying codebase in the 4.0 series to last several years once it is released.&#160; What if the framework we choose to utilize is no longer maintained 2 years from now?&#160; What if a security issue arises in the third party framework, but it is not rectified quickly?&#160; We certainly can't leave our clients vulnerable to known security vulnerabilities while we wait for a third party to patch it.&#160; What if the framework developers release an important update, but it renders APIs incompatible with our implementation of the framework?&#160; We could find ourselves in a situation where we either can't update the framework easily, or we would need to recode many of the underlying usages of the framework in order to update.&#160; Additionally, frameworks often have many, many capabilities, many which we may not need or use.&#160; This can make our release larger than it needs to be, and/or cause our software to consume more resources than it would otherwise, if those features which we aren't using were not present.&#160; Of course, licensing concerns are also present - we have to be certain that any third party code we use is released under a license that is compatible with our commercial license.&#160; Finally, if we utilized a third party PHP framework, we would either have to (1) rewrite ALL of our code (just think of every database query that may be run - these would need to be passed through the framework rather than through our own database driver), or (2) write an abstraction layer on top of the framework to translate the requests we currently send to low-level classes so that they are compatible with the framework.&#160; No easy task, either way.<br />&#160;<br />By writing our own framework we ultimately have better control of our software.&#160; We can tailor every class to our needs, ensuring that it is as efficient as possible within the confines of what we wish to accomplish, while still making these classes robust enough to handle everything we want to throw at it.&#160; We can ensure we do not have unnecessary classes and code, or features which aren't (and never will be) used.&#160; If a security issue is found, we have full control over the underlying code base and can address the issue quickly without waiting on a third party to release an update, or rewriting underlying API calls in our software if the framework changes how a class must be called.&#160; If we wish to implement new functionality, we can implement these changes directly in low-level classes efficiently.&#160; We do not have to work within the third party framework's design, artificially requiring us to utilize more resources (e.g. by extending a class vs implementing our changes into the base class to start with).<br />&#160;<br />By writing our own framework, we face the "con" of spending the time up front to develop all of these low level classes we will need, however we feel the "pros" that this affords us in the long run are worth the time and trouble.&#160; It is a decision every developer or development company has to make as they approach a product, and everyone has different view points.&#160; The take away here, however, should be that we have indeed looked into available options, weighed the pros and cons against our goals and needs, and have determined after careful evaluation that sometimes it is best to use an existing framework, and sometimes we just need to roll our own.]]></description>
		<pubDate>Sun, 17 Mar 2013 21:30:26 +0000</pubDate>
		<guid>http://community.invisionpower.com/blog/4445/entry-8670-use-a-third-party-libraryframework-or-build-it-in-house/</guid>
	</item>
</channel>
</rss>