<?xml version="1.0"?>
<rss version="2.0"><channel><title>Invision Community Blog: Invision Community</title><link>https://invisioncommunity.com/news/invision-community/page/16/?d=34</link><description>Invision Community Blog: Invision Community</description><language>en</language><item><title>IPS 4.0: Editor - Part 1: Content</title><link>https://invisioncommunity.com/news/invision-community/9537-ips-40-editor-part-1-content/</link><description><![CDATA[<p><strong>Introduction</strong><br><br>
The post editor is undoubtably one of the most frequently used features of the IPS Social Suite as it's the way users submit content to your community and functionality has evolved dramatically from the early days of forum software which consisted of a plain textbox in which users would type BBCode into the feature-rich WYSIWG (<em>What You See Is What You Get</em>) editors prevalent on the web today.<br><br>
For the IPS Social Suite 4.0 we really wanted to focus on making the editor as good as it can be: feature-rich, intuitive to users and highly customisable.<br><br>
Over the next 4 blog entries I'm going to cover the functionality of the editor and related features (attachments, emoticons, etc.) and the customisation options that will be available in 4.0. The theme of each blog entry will be:

Here's a screenshot of what it looks like (by default, on a desktop, more on that later):
<strong>Quotes</strong>
A feature added to IP.Board in the 3.x series was "visual quotes" - when adding a quote the box shows directly in the editor as it will show in the post, rather than as just normal text with a quote BBCode wrapped around.
In 4.0 we've rewritten how this works to use a CSS3 based solution rather than JavaScript to keep the citation header attached to the quote, which makes the feature much more reliable and easy to use when splitting quote boxes, moving the cursor before or after the quote box or dealing with embedded quotes.
<strong>Code</strong>
We've also rewritten how code can be inserted into the editor to be more reliable.
Adding code now initially brings up an editor which supports syntax highlighting:
And after inserting, you'll see the code exactly how it will be displayed after posting and you can interact with it exactly how you'd expect:
<strong>Spoilers</strong>
Spoilers are now also visual (though of course they're not true WYSIWYG as you need to be able to see what you're typing) and a built-in feature enabled by default.
When you click the spoiler button you'll be given a darkened box in which to type the content:
The spoilers display in the post as the same coloured box with a "click to view" message. When clicked, the dark box fades away revealing the content underneath.
<strong>Emoticons</strong>
We've completely overhauled emoticon management in 4.0. You can now create multiple "groups", drag and drop to reorder and quickly upload loads of emoticons at once.
When you click the emoticon button in the editor, you'll see a popup with an overview of all the groups. If your community only uses the default emoticons, this is big enough to show all the emoticons, however, if you like to install lots of emoticons, you can select any group from the dropdown menu to show all emoticons in that group, or use the search bar to find a particular emoticon.
In addition, your most recently used emoticons will show right at the top.
<strong>Embedded Media</strong>
In 3.0 we introduced a media BBCode tag. By wrapping a URL to media such as YouTube or Vimeo videos in media BBCode tags, the correct embed code would automatically be worked out and embedded into the post. In later versions, we added automatic embedding support, so just pasting the URL into the post would cause the video to be embedded.
This still works exactly the same in 4.0 with a couple improvements:
Previews
If you type the URL to a supported media in the "Link" dialog, a preview will be shown in the dialog - you can choose to insert the embedded media or just a regular link. If you insert the embedded media, it will show directly in the editor.
oEmbed
Currently URLs which gets picked up for media embedding and the output they produce are managed in the Admin CP. The idea was that this would allow admins to add support for additional services, however, it has caused some problems when for example, YouTube has added new URL formats or changed their embed code and they don't work on most communities until we release an update to change the default record for YouTube. is an API which allows you to make a call to the provider giving them the URL and they respond with the best embed code to use, if that URL can be embedded. In 4.0, if a URL for YouTube, Flickr, Vimeo, College Humor or Hulu is inserted, we make a call to their oEmbed provider to obtain the embed code, so it's always up to date.
<strong>Conclusion</strong>
This is just the first in our series of blog entries on the editor in IPS4. Please do let us know what you think in the comments, but remember there's much more to come!</p>
<ol data-ipsbbcode-list="true" style="list-style-type: decimal" href="//media.invisioncic.com/a319035/blogentry-0-0-30713900-1370445068.png"><p>
[*]Content
[*]Uploads
[*]Customisation and BBCode
[*]Special features</p>
<br><br><br><a href="http://community.invisionpower.com/uploads/blogentry-108264-0-76894200-1370431713.png"></a><img src="http://community.invisionpower.com/uploads/blogentry-108264-0-76894200-1370431713_thumb.png" data-fileid="51451" loading="lazy"><br><br><br><br><br><br><br><br><br><br><br><br><br><br><a href="http://community.invisionpower.com/uploads/blogentry-0-0-25229800-1370444893.png"></a><img src="//media.invisioncic.com/a319035/blogentry-0-0-25229800-1370444893_thumb.png" data-fileid="51456" loading="lazy"><br><br><br><a href="http://community.invisionpower.com/uploads/blogentry-0-0-08258200-1370444895.png"></a><img src="//media.invisioncic.com/a319035/blogentry-0-0-08258200-1370444895_thumb.png" data-fileid="51457" loading="lazy"><br><br><br><br><br><br><br><a href="http://community.invisionpower.com/uploads/blogentry-0-0-30713900-1370445068.png"></a><img src="//media.invisioncic.com/a319035/blogentry-0-0-30713900-1370445068_thumb.png" data-fileid="51458" loading="lazy"><br><br><br><br><br><br><br><br><a href="http://community.invisionpower.com/blogvideos/Emoticon-Management.swf" rel="external nofollow">Video Demonstration</a><br><br><br><br><br><br><a href="http://community.invisionpower.com/blogvideos/Inserting-Emoticons.swf" rel="external nofollow">Video Demonstration</a><br><br><br><br><br><br><br><br><br><br><br><br><br><a href="http://community.invisionpower.com/blogvideos/Media.swf" rel="external nofollow">Video Demonstration</a><br><br><br><br><br><br><br><a href="http://oembed.com" rel="external nofollow">oEmbed</a><br><br><br><br><br></ol>]]></description><guid isPermaLink="false">854</guid><pubDate>Thu, 06 Jun 2013 13:30:00 +0000</pubDate></item><item><title>IP.Board 3.4.5 + all applications available for beta testing</title><link>https://invisioncommunity.com/news/invision-community/9528-ipboard-345-all-applications-available-for-beta-testing/</link><description><![CDATA[<p>The following applications are available for beta testing:</p><ul><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><li>IP.Calendar 3.3.4<br></li></ul><br><br>
This round of maintenance updates represents updates to all of our applications.  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).  We would like to encourage all interested users to perform as much testing of these apps as possible.<br><br>
We will be upgrading our company forums early this coming week.<br><br>
Please report any bugs you find with the beta to our <a href="http://community.invisionpower.com/resources/bugs.html" rel="external nofollow">bug tracker</a>.<br><br>
Please pay particular attention to the following areas:<ul><li>The editor, both within IP.Board (topics/posts) and in other areas (such as IP.Content articles, IP.Blog entries, etc.).  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.  Pay attention specifically to quotes to be sure they display correctly.
</li><li>Any IP.Nexus functionality that has to do with grouped renewals.  Cancelling packages where renewals are grouped, reactivating those packages, changing those packages, etc.<br></li></ul><br><br>
If you find a bug, please be certain to report it to the <a href="http://community.invisionpower.com/resources/bugs.html" rel="external nofollow">bug tracker</a>.  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.  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.  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.  We recommended, instead, to create a copy of your live board as a test installation, and upgrade your test installation instead.<br><br>
All customers with active licenses can download the betas at: <a href="http://community.invisionpower.com/qa.php" rel="external nofollow">http://community.inv...ower.com/qa.php</a><br><br>
Thanks in advance!  We look forward to your feedback.
]]></description><guid isPermaLink="false">853</guid><pubDate>Sat, 11 May 2013 00:42:00 +0000</pubDate></item><item><title>Minor Workflow Enhancements in IP.Nexus 1.5.8</title><link>https://invisioncommunity.com/news/invision-community/8967-minor-workflow-enhancements-in-ipnexus-158/</link><description><![CDATA[<p>At IPS not only do we create market leading products, 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><br><strong>Separating Outgoing Email Addresses</strong><br><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@"  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><br><strong>Account Credit Increase Item</strong><br><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><br>
In IP.Nexus 1.5.8 we have added a new invoice item type in the ACP.<br><br><a href="//media.invisioncic.com/a319035/monthly_05_2013/blogentry-0-0-96875900-1367494881.png"><img src="//media.invisioncic.com/a319035/monthly_05_2013/blogentry-0-0-96875900-1367494881_thumb.png" data-fileid="50667" loading="lazy"></a><br><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><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/" rel="external nofollow">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><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></p>]]></description><guid isPermaLink="false">852</guid><pubDate>Fri, 10 May 2013 15:00:00 +0000</pubDate></item><item><title>IPS Converter Update</title><link>https://invisioncommunity.com/news/invision-community/8835-ips-converter-update/</link><description><![CDATA[<p>We are pleased to announce an update to our <a href="http://www.invisionpower.com/convert" rel="external nofollow">popular converter application</a> and with it support for conversions from vBulletin 5 Connect.<br><br>
This release has been particularly focused on converter stability and performance improvements. At the time of writing there are 0 open bug reports in the bug tracker.<br><br><strong>Performance Improvements</strong><br><br>
Whilst data integrity will always be our number one priority we do often review and apply performance improvements where appropriate.<br><br>
In a previous release we introduced a feature that would significantly reduce the time taken when selecting data from the source software but this was only applied to a select few products. We are happy to report this change has now been applied across all converters. For the technically curious, this eliminates the need to use expensive LIMIT clauses with high offsets when converting forums with large amounts of posts.<br><br>
Additionally we identified a few areas that would benefit from extra indexes particularly in the area of looking up relational content.<br><br><strong>Software Specific Improvements</strong><br><br>
Support for vBulletin5 Connect is now available and the following items are converted - Permissions, Groups, Members, Passwords, Forums, Topics, Posts, Attachments, BBCode, Profile Fields, Emoticons, Moderators, Friends, Ignored Users, Reputation, Ranks, Warn Logs<br><br>
The Ning converter has been greatly overhauled and now works much more reliably.<br><br><strong>Considering switching to IPS?</strong><br><br>
Now is a great time to switch to IPS. We have a full community suite of products and a great resource community in the <a href="http://community.invisionpower.com/files/" rel="external nofollow">Marketplace</a>. For a limited time through May 15 you can use the coupon code SWITCH at checkout to take 10% off your order. Feel free to email sales@invisionpower.com with questions or post in our <a href="http://community.invisionpower.com/forum/305-pre-sales-questions/" rel="external nofollow">pre-sales forum</a> to get feedback from other clients.<br></p>]]></description><guid isPermaLink="false">851</guid><pubDate>Mon, 29 Apr 2013 12:00:00 +0000</pubDate></item><item><title>IPS Marketplace Update</title><link>https://invisioncommunity.com/news/invision-community/8827-ips-marketplace-update/</link><description><![CDATA[<p>The <a href="http://community.invisionpower.com/files/" rel="external nofollow">IPS Marketplace</a> is the place to go for plugins, skins, language packs, full applications, and other resources provided by the IPS community. Some resources are free and some have a small fee. It's a great way to find ways to personalize your community and expand its functions.<br><br><br><strong>Some updates...</strong><br><br>
I wanted to share some general statistics on the Marketplace (yes, I'm channeling Apple here). Yesterday we reached a great milestone:<br><br>
Since its inception, we have paid out over $250,000 to contributors. Yes over a quarter million dollars has been paid out to those that sell resources in the Marketplace!<br><br>
What's even more exciting is the growth we are seeing. In fact over 40% of that total payout was done just this year! Because I love any excuse to play with Excel here's a chart showing growth trend:<br><br><a href="http://community.invisionpower.com/uploads/monthly_04_2013/blogentry-49-0-41848500-1366458776.png"><img src="http://community.invisionpower.com/uploads/monthly_04_2013/blogentry-49-0-41848500-1366458776_thumb.png" data-fileid="50396" loading="lazy"></a><br><br>
We also now have over 500 individual contributors that are providing both free and paid resources in the Marketplace.<br><br><strong>Improvements coming soon</strong><br><br>
We are in the process of doing some cleaning up to make resources easier to find. Expect to see new categories based on what things do (moderation tools, promotion, utilities, etc.) rather than what they are (hook, mod, app, etc.) which we think will make finding resources for your community even easier.<br><br><br>
A new ability to allow us to feature more than one resource at a time has already been launched.<br><br>
Take a moment to browse the <a href="http://community.invisionpower.com/files/" rel="external nofollow">IPS Marketplace</a> and see if there are any resources that would benefit your community. If you find one don't forget to thank the contributor!<br><br></p>]]></description><guid isPermaLink="false">850</guid><pubDate>Sat, 20 Apr 2013 11:30:00 +0000</pubDate></item><item><title>Monetize your traffic with VigLink</title><link>https://invisioncommunity.com/news/invision-community/8826-monetize-your-traffic-with-viglink/</link><description><![CDATA[<p>IPS is always looking for ways to assist our clients in monetization and promotion of their community traffic. After we saw so many clients successfully using VigLink on their community we decided to bundle it directly in the AdminCP under the Community Enhancements section. Since then many clients have reported success in using their service. Many clients have also since looked into other monetization options and have started to really grow.<br><br>
Information from VigLink:<br></p><blockquote data-ipsquote="" class="ipsQuote"><br><p>
VigLink is a content monetization service used by tens of thousands of publishers. While most monetization services focus on the traffic you receive - usually by displaying ads that distract and interrupt - VigLink is focused on the traffic that’s already [i]leaving[/i] your community. Here’s how it works: Often your communities will post links to retail sites like eBay or Amazon. With VigLink, clicks on these links are automatically monetized. There’s no change to the user experience. It’s simple: when a user clicks and purchases something, you earn a commission. Through their relationships with over </p><a href="http://www.viglink.com/tools/explorer" rel="external nofollow">30,000 merchants</a><p>, including over 90% of the top 500 affiliating retailers on the web, VigLink automatically monetizes more links than any relevant competitor. Because of their size and success they have negotiated spectacular commissions with some of the top merchants. If you or your community already posts affiliate links or uses any other form of monetization, VigLink will not interfere.</p><br><br><p>
To further increase your revenue, VigLink also offers Link Insertion, a technology that can automatically create links when brands, products, and stores are mentioned. These links are normal, everyday links, except for the fact that they can earn you money. For example, if a user posts about a camera they are thinking of buying, VigLink can detect the product mentioned and link it to a retailer that pays top commissions. Typically, publishers get more than half of their earnings from this technology.</p><br><br><p>
There are others who try to offer similar technologies, however we have selected to integrate with VigLink because of their superior technology, performance, and customer service, especially among discussion-based communities. The largest network of its kind, VigLink processes billions of page views and over three hundred million clicks every month.</p></blockquote><br><br>
If you want to give it a try simply visit your AdminCP and click Community Enhancements to turn VigLink on and start earning. IPS does benefit from this relationship however we do not take any of your commission of course.
]]></description><guid isPermaLink="false">849</guid><pubDate>Sat, 20 Apr 2013 11:21:00 +0000</pubDate></item><item><title>4.0 - Trees</title><link>https://invisioncommunity.com/news/invision-community/8812-40-trees/</link><description><![CDATA[<p><strong>Introduction</strong><br><br>
I previously wrote <a href="http://community.invisionpower.com/blog/4445/entry-8746-40-tables/" rel="external nofollow">a blog entry</a> about building tables in IPS Social Suite 4.0. Similar to tables, we also have <em>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. An example of trees would be the list of forums in IP.Board.<br><br>
Trees vary quite significantly in their individual implementations, for example:</p>
<ul><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).<br></li>
</ul><br><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><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><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><br><br><br><strong>Creating the classes</strong><br><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 <span style="font-family:'courier new'">IPSNodeModel</span> which is an abstract class that provides most of the functionality we need. <span style="font-family:'courier new'">IPSNodeModel</span> in turn extends a class called <span style="font-family:'courier new'">IPSPatternsActiveRecord</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><br>
In this class I need to define a few variables - I'll explain them below, but this is the code I'll write:<p></p>
<pre class="ipsCode">
/**
 * Custom Profile Field Group Node
 */
class _Group extends IPSNodeModel
{
	/**
	 * @brief	[ActiveRecord] Multiton Store
	 */
	protected static $multitons;
	
	/**
	 * @brief	[ActiveRecord] Default Values
	 */
	protected static $defaultValues = NULL;
	
	/**
	 * @brief	[ActiveRecord] Database Table
	 */
	public static $databaseTable = 'core_pfields_groups';
	
	/**
	 * @brief	[ActiveRecord] Database Prefix
	 */
	public static $databasePrefix = 'pf_group_';
	
	/**
	 * @brief	[ActiveRecord] ID Database Column
	 */
	public static $databaseColumnId = 'id';
		
	/**
	 * @brief	[Node] Node Title
	 */
	public static $nodeTitle = 'profile_fields';
		
}</pre>
<p></p>
<ul><li>$multitons and $defaultValues are required by the <span style="font-family:'courier new'">IPSPatternsActiveRecord</span> class. We don't need to do anything with them other than declare them.
</li>
<li>$databaseTable tells the <span style="font-family:'courier new'">IPSPatternsActiveRecord</span> class what database table the records we need are in.
</li>
<li>$databasePrefix tells the <span style="font-family:'courier new'">IPSPatternsActiveRecord</span> 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 <span style="font-family:'courier new'">IPSPatternsActiveRecord</span> class which column contains the primary key.
</li>
<li>$nodeTitle tells the <span style="font-family:'courier new'">IPSNodeModel </span>class what language string to use as the title on my page that shows the tree.<br></li>
</ul><br><br><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:<p></p>
<pre class="ipsCode">
namespace IPScoremodulesadminmembersettings;

/**
 * Profile Fields and Settings
 */
class _profiles extends IPSNodeController
{
	/**
	 * Node Class
	 */
	protected $nodeClass = 'IPScoreProfileFieldsGroup';

}
</pre>
<p><br>
Now I have a page which looks like this:<br><br><a href="http://community.invisionpower.com/uploads/monthly_04_2013/blogentry-108264-0-49672000-1366021606.png"><img src="http://community.invisionpower.com/uploads/monthly_04_2013/blogentry-108264-0-49672000-1366021606_thumb.png" data-fileid="50239" loading="lazy"></a><br><br><br><br><strong>Customising the rows</strong><br><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 <span style="font-family:'courier new'">IPSNodeModel </span>class doesn't know what to use for the record title. Let's fix that by adding a method to our class:</p>
<p></p>
<pre class="ipsCode">
	/**
	 * [Node] Get Node Title
	 *
	 * @return	string
	 */
	protected function get__title()
	{
		$key = "core_pfieldgroups_{$this-&gt;id}";
		return IPSLang::i()-&gt;$key;
	}

</pre>
<p><br>
That code might seem a bit confusing, but note:</p>
<ul><li>$this-&gt;id gets the value of the "pf_group_id" column in the database. This is because the <span style="font-family:'courier new'">IPSPatternsActiveRecord</span> 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.<br></li>
</ul><br><br>
So now we have this (I've clicked the dropdown arrow so you can see it's contents):<br><br><a href="http://community.invisionpower.com/uploads/monthly_04_2013/blogentry-108264-0-76532900-1366021607.png"><img src="http://community.invisionpower.com/uploads/monthly_04_2013/blogentry-108264-0-76532900-1366021607_thumb.png" data-fileid="50240" loading="lazy"></a><br><br>
Most of this is okay, but permissions aren't relevant for custom profile field groups, so let's get rid of that. The <span style="font-family:'courier new'">IPSNodeModel </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:<p></p>
<pre class="ipsCode">
	/**
	 * [Node] Does the currently logged in user have permission to edit permissions for this node?
	 *
	 * @return	bool
	 */
	public function canManagePermissions()
	{
		return false;
	}

</pre>
<p><br>
And now it's gone:<br><br><a href="http://community.invisionpower.com/uploads/monthly_04_2013/blogentry-108264-0-31267900-1366021613.png"><img src="http://community.invisionpower.com/uploads/monthly_04_2013/blogentry-108264-0-31267900-1366021613_thumb.png" data-fileid="50241" loading="lazy"></a><br><br><br><br><strong>Making the buttons work</strong><br><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><br>
These buttons will display a form. In a <a href="http://community.invisionpower.com/blog/4445/entry-8660-40-forms/" rel="external nofollow">previous blog entry</a> I talked about our form helper class. I'm going to use this to build the add/edit form.<br><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:</p>
<p></p>
<pre class="ipsCode">
	/**
	 * [Node] Add/Edit Form
	 *
	 * @param	IPSHelpersForm	$form	The form
	 * @return	void
	 */
	public function form( &amp;$form )
	{
		$form-&gt;add( new IPSHelpersFormTranslatable( 'pfield_group_title', NULL, TRUE, array( 'app' =&gt; 'core', 'key' =&gt; ( $this-&gt;id ? "core_pfieldgroups_{$this-&gt;id}" : NULL ) ) ) );
	}
	
	/**
	 * [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();
		}
		
		IPSLang::saveCustom( 'core', "core_pfieldgroups_{$this-&gt;id}", $values['pfield_group_title'] );
	}
</pre>
<p><br>
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'"> IPSLang::saveCustom()</span> to save the values.<br><br>
This is what our form might look like if I have several languages installed:<br><a href="//media.invisioncic.com/a319035/monthly_04_2013/blogentry-0-0-63654900-1365676775.png"><img src="//media.invisioncic.com/a319035/monthly_04_2013/blogentry-0-0-63654900-1365676775_thumb.png" data-fileid="50123" loading="lazy"></a><br><br>
Or, more commonly, if I just have one:<br><a href="//media.invisioncic.com/a319035/monthly_04_2013/blogentry-0-0-32384000-1365676818.png"><img src="//media.invisioncic.com/a319035/monthly_04_2013/blogentry-0-0-32384000-1365676818_thumb.png" data-fileid="50124" loading="lazy"></a><br><br><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:</p>
<p></p>
<pre class="ipsCode">
	/**
	 * @brief	[Node] Show forms modally?
	 */
	public static $modalForms = TRUE;

</pre>
<p><br>
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:</p>
<p></p>
<pre class="ipsCode">
	/**
	 * [ActiveRecord] Duplicate
	 *
	 * @return	void
	 */
	public function __clone()
	{
		$oldId = $this-&gt;id;
		parent::__clone();
		IPSLang::saveCustom( 'core', "core_pfieldgroups_{$this-&gt;id}", IPSDb::i()-&gt;buildAndFetchAll( array( 'select' =&gt; '*', 'from' =&gt; 'core_sys_lang_words', 'where' =&gt; array( 'word_key=?', "core_pfieldgroups_{$oldId}" ) ), 'lang_id', 'word_custom' ) );
	}
	
	/**
	 * [ActiveRecord] Delete Record
	 *
	 * @return	void
	 */
	public function delete()
	{
		parent::delete();
		IPSLang::deleteCustom( 'core', 'core_pfieldgroups_' . $this-&gt;id );
	}

</pre>
<p><br><strong>Search</strong><br><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:</p>
<p></p>
<pre class="ipsCode">
	/**
	 * 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 ( IPSLang::i()-&gt;searchCustom( 'core_pfieldgroups_', $query ) as $key =&gt; $value )
			{
				try
				{
					$return[ $key ] = self::load( $key );
				}
				catch ( Exception $e ) { }
			}
			return $return;
		}
		return parent::search( $column, $query, $order );
	}

</pre>
<p><br><strong>Making the rows re-orderable</strong><br><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 <span style="font-family:'courier new'">IPSPatternsActiveRecord</span> which column contains the order ID:</p>
<p></p>
<pre class="ipsCode">
	/**
	 * @brief	[Node] Order Database Column
	 */
	public static $databaseColumnOrder = 'order';
</pre>
<p><br><br><strong>ACP Restrictions</strong><br><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:</p>
<p></p>
<pre class="ipsCode">
	/**
	 * @brief	[Node] ACP Restrictions
	 */
	protected static $restrictions = array(
		'app'		=&gt; 'core',
		'module'	=&gt; 'membersettings',
		'prefix'	=&gt; 'profilefieldgroups_',
	);

</pre>
<p><br>
The system will now look for ACP restrictions with the keys "profilefieldgroups_add", "profilefieldgroups_edit" and "profilefieldgroups_delete" when performing those actions.<br><br><br><strong>Children</strong><br><br>
Let's recap what we have so far with a video:<br><a href="http://screencast.com/t/TUBuuYBSBON" rel="external nofollow">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, I won't go through the process step-by-step, but here is the class I've written if you're interested:<br><a href="http://community.invisionpower.com/applications/core/interface/file/attachment.php?id=50242">Field.php</a><br><br>
The only changes are:</p>
<ul><li>I've declared two additional properties specifying the class name of the parent ("IPScoreProfileFieldsGroup") 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.<br></li>
</ul><br><br>
Now all we need to do is link them up. To do this, I add a property to my group class telling <span style="font-family:'courier new'">IPSNodeModel</span> the name of the class which contains children:<p></p>
<pre class="ipsCode">
	/**
	 * @brief	[Node] Subnode class
	 */
	public static $subnodeClass = 'IPScoreProfileFieldsField';


</pre>
<p><br>
The system will now automatically change the behaviour of our page in the following ways:</p>
<ul><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.<br></li>
</ul><br>
Here's a video:<br><a href="http://screencast.com/t/5fQwgle3EX" rel="external nofollow">http://screencast.com/t/5fQwgle3EX</a><br>]]></description><guid isPermaLink="false">848</guid><pubDate>Wed, 17 Apr 2013 20:00:00 +0000</pubDate></item><item><title>4.0 Developer Center</title><link>https://invisioncommunity.com/news/invision-community/8810-40-developer-center/</link><description><![CDATA[<p>A few weeks ago, I posted a <a href="http://community.invisionpower.com/blog/4445/entry-8685-40-developer-center-database-schema-management/" rel="external nofollow">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><br><br><strong>Modules</strong><br><br><a href="http://community.invisionpower.com/uploads/monthly_04_2013/blogentry-108264-0-12737500-1365428265.png"><img src="http://community.invisionpower.com/uploads/monthly_04_2013/blogentry-108264-0-12737500-1365428265_thumb.png" data-fileid="50010" loading="lazy"></a><br><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><br>
When you're creating a new section, the form looks like this (this is for creating a section for an admin module):<br><a href="http://community.invisionpower.com/uploads/monthly_04_2013/blogentry-108264-0-61264400-1365427490.png"><img src="http://community.invisionpower.com/uploads/monthly_04_2013/blogentry-108264-0-61264400-1365427490_thumb.png" data-fileid="50009" loading="lazy"></a><br><br>
The "Type" field controls the code that will be placed in the file created for the section - the options are:</p>
<ul><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/" rel="external nofollow">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.<br></li>
</ul><br>
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><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 &amp; 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><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><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><br><br><br><strong>Admin CP Menu</strong><br><br>
This tab contains a graphical representation of the data which was previously stored in menu.xml files.<br><br><a href="http://community.invisionpower.com/uploads/monthly_04_2013/blogentry-108264-0-29421900-1365428781.png"><img src="http://community.invisionpower.com/uploads/monthly_04_2013/blogentry-108264-0-29421900-1365428781_thumb.png" data-fileid="50011" loading="lazy"></a><br><br><a href="http://community.invisionpower.com/uploads/monthly_04_2013/blogentry-108264-0-17161400-1365428782.png"><img src="http://community.invisionpower.com/uploads/monthly_04_2013/blogentry-108264-0-17161400-1365428782_thumb.png" data-fileid="50012" loading="lazy"></a><br><br><br><br><br><strong>Admin CP Restrictions</strong><br><br>
This tab contains a graphical representation of the data which was previously stored in permissions.xml files.<br><br><a href="http://community.invisionpower.com/uploads/monthly_04_2013/blogentry-108264-0-01212100-1365428873.png"><img src="http://community.invisionpower.com/uploads/monthly_04_2013/blogentry-108264-0-01212100-1365428873_thumb.png" data-fileid="50013" loading="lazy"></a><br><br><br><br><strong>Extensions</strong><br><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><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><br><a href="http://community.invisionpower.com/uploads/monthly_04_2013/blogentry-108264-0-76563600-1365429816.png"><img src="http://community.invisionpower.com/uploads/monthly_04_2013/blogentry-108264-0-76563600-1365429816_thumb.png" data-fileid="50014" loading="lazy"></a><br><br><br><br><strong>Settings</strong><br><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><br><a href="http://community.invisionpower.com/uploads/monthly_04_2013/blogentry-108264-0-14033100-1365433111.png"><img src="http://community.invisionpower.com/uploads/monthly_04_2013/blogentry-108264-0-14033100-1365433111_thumb.png" data-fileid="50017" loading="lazy"></a><br><br><br><br><strong>Versions</strong><br><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><br><a href="http://community.invisionpower.com/uploads/monthly_04_2013/blogentry-108264-0-96533200-1365433582.png"><img src="http://community.invisionpower.com/uploads/monthly_04_2013/blogentry-108264-0-96533200-1365433582_thumb.png" data-fileid="50018" loading="lazy"></a><br><a href="http://community.invisionpower.com/uploads/monthly_04_2013/blogentry-108264-0-83518600-1365433583.png"><img src="http://community.invisionpower.com/uploads/monthly_04_2013/blogentry-108264-0-83518600-1365433583_thumb.png" data-fileid="50019" loading="lazy"></a><br><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><guid isPermaLink="false">847</guid><pubDate>Mon, 15 Apr 2013 14:00:00 +0000</pubDate></item><item><title>4.0 - Login Handlers</title><link>https://invisioncommunity.com/news/invision-community/8747-40-login-handlers/</link><description><![CDATA[<p><em>Login Handlers</em> are the different methods for logging into the IPS Social Suite. We currently support:</p><ul><li><em>"</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.<br></li></ul><br>
In 4.0 we've made a number of changes to the Login Handlers which I wanted to mention.<br><br><br><br><strong>Improved Password Encryption</strong><br><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><br>
md5 is designed to be 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><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)" rel="external nofollow">Blowfish</a>. Blowfish is a more 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><br><br><strong>New Login Handlers</strong><br><br>
In addition to the Login Handlers mentioned above, we've added support for Google and LinkedIn.<br><br><br><strong>Improved Facebook and Twitter support</strong><br><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><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><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><br><br><strong>Updated Microsoft Support</strong><br><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><guid isPermaLink="false">846</guid><pubDate>Mon, 08 Apr 2013 13:15:39 +0000</pubDate></item><item><title>IP.Board 3.4.4 Beta Refreshed</title><link>https://invisioncommunity.com/news/invision-community/8802-ipboard-344-beta-refreshed/</link><description><![CDATA[<p>We've just rebuilt IP.Board 3.4.4 for further beta testing.<br><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><br>
All customers with an active IP.Board license can download the beta at: <a href="http://community.invisionpower.com/qa.php" rel="external nofollow">http://community.inv...ower.com/qa.php</a><br><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" rel="external nofollow">here</a> on how to do that.<br><br>
As always, please report any bugs you find with the beta to our <a href="http://community.invisionpower.com/resources/bugs.html" rel="external nofollow">bug tracker</a>.<br><br>
Please pay particular attention to using the editor with IP.Board 3.4.4.  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.  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" rel="external nofollow">let us know</a>.<br><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.  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.  We recommended, instead, to create a copy of your live board as a test installation, and upgrade your test installation instead.<br><br>
Thanks!<br><br></p>]]></description><guid isPermaLink="false">845</guid><pubDate>Wed, 03 Apr 2013 15:44:40 +0000</pubDate></item><item><title>Tier II Support Vacancy</title><link>https://invisioncommunity.com/news/invision-community/8800-tier-ii-support-vacancy/</link><description><![CDATA[<p>IPS is seeking a knowledgeable and experienced individual to join its support team in an advanced support capacity. <br><br>
Successful applicants will be responsible for answering general support tickets, including providing customers with information, troubleshooting issues not resolved as part of the general product support process and performing maintenance. You would be expected where appropriate to interact with other technicians and developers to resolve issues. <br><br>
Requirements: <br><br>
* Must be familiar with IPS applications. <br>
* Must have advanced knowledge of PHP and MySQL. <br>
* Must be able to effectively work remotely. <br>
* English must be your primary language and you must possess strong verbal and written communication skills. <br><br>
Preferable, but not required: <br><br>
* Knowledge of server administration and experience working for a web hosting company. <br>
* Knowledge of additional web technologies such as JavaScript, CSS, XML, etc. <br>
* Experience of working in customer support. <br>
* Experience working with the codebase or creating hooks/apps in the IPS Suite <br><br>
Working hours are flexible and pay will be based on knowledge and experience. Due to the nature of the position, we require all applicants to be physically located in the United States. No exceptions to United States residency requirements. <br><br>
Please contact  <a href="mailto:%22hr@invisionpower.com%22">hr@invisionpower.com</a> for more information on this position. Please include your salary requirements, availability and an overview of your experience. <br><br>
We look forward to hearing from you!<br></p>]]></description><guid isPermaLink="false">844</guid><pubDate>Tue, 02 Apr 2013 02:51:19 +0000</pubDate></item><item><title>IP.Board 3.4.4 available for beta testing</title><link>https://invisioncommunity.com/news/invision-community/8791-ipboard-344-available-for-beta-testing/</link><description><![CDATA[<p>IP.Board 3.4.4 is now available for beta testing!<br><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.  We appreciate any testing you can perform.<br><br>
Please report any bugs you find with the beta to our <a href="http://community.invisionpower.com/resources/bugs.html" rel="external nofollow">bug tracker</a>.<br><br>
Please pay particular attention to using the editor with IP.Board 3.4.4.  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.  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" rel="external nofollow">let us know</a>.<br><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.  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.  We recommended, instead, to create a copy of your live board as a test installation, and upgrade your test installation instead.<br><br>
All customers with an active IP.Board license can download the beta at: <a href="http://community.invisionpower.com/qa.php" rel="external nofollow">http://community.invisionpower.com/qa.php</a><br><br>
Thanks in advance!  We look forward to your feedback.</p>]]></description><guid isPermaLink="false">843</guid><pubDate>Fri, 29 Mar 2013 20:43:59 +0000</pubDate></item><item><title>IP.Board 3.4.4 Editor Testing</title><link>https://invisioncommunity.com/news/invision-community/8755-ipboard-344-editor-testing/</link><description><![CDATA[<p>The eagle eyed among you may have spotted that we've just upgraded our company forums to IP.Board 3.4.4.<br><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><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><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/" rel="external nofollow">test forum</a> or just by being more aware of any quirks or issues when making posts normally.<br><br>
Anything you spot, can you please report into our <a href="http://community.invisionpower.com/resources/bugs.html/_/ip-board/" rel="external nofollow">bug tracker</a> with as much detail as you can.<br><br>
Thanks!<br><br></p>]]></description><guid isPermaLink="false">842</guid><pubDate>Mon, 25 Mar 2013 13:50:46 +0000</pubDate></item><item><title>4.0 - Tables</title><link>https://invisioncommunity.com/news/invision-community/8746-40-tables/</link><description><![CDATA[<p>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><br>
It would be great if these features were available elsewhere. So <a href="http://community.invisionpower.com/blog/4445/entry-8660-40-forms/" rel="external nofollow">much like we did for forms</a>, we decided to create a central helper class for building tables.<br><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><br><br>
It starts with one line to create the table, and another to pass it to the output class:</p>
<p></p>
<pre class="ipsCode">
		/* Create the table */
		$table = new IPSHelpersTableDb( 'core_members', 'app=core&amp;module=members&amp;section=members' );
		
		/* Display */
		IPSOutput::i()-&gt;output	= IPSOutput::i()-&gt;getTemplate( 'global' )-&gt;block( 'members', $table );

</pre>
<p><br><br><br>
With just those two lines, you'll see this:<br><a href="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-20828300-1363955563.png"><img src="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-20828300-1363955563_thumb.png" data-fileid="49530" loading="lazy"></a><br><br>
Some things to note:</p>
<ul><li>We're calling <span style="font-family:'courier new'">IPSHelpersTableDb</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.<br></li>
</ul><br><br><br><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:<p></p>
<pre class="ipsCode">
$table-&gt;include = array( 'name', 'email', 'joined', 'member_group_id', 'ip_address' );
</pre>
<p><br><br><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><a href="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-48276100-1363955564.png"><img src="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-48276100-1363955564_thumb.png" data-fileid="49531" loading="lazy"></a><br><br>
Some things to note:</p>
<ul><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.<br></li>
</ul><br><br><br><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><br>
Let's specify a prefix:<p></p>
<pre class="ipsCode">
$table-&gt;langPrefix = 'members_';
</pre>
<p><br><br>
And I'll then create some language strings that match that (so "members_name", "members_email", etc.).<br><br>
The output is now this:<br><a href="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-75840800-1363955565.png"><img src="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-75840800-1363955565_thumb.png" data-fileid="49532" loading="lazy"></a><br><br><br><br><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><br>
To format the values, we simply create an array of lambda functions - one for each we want to format:<br></p>
<p></p>
<pre class="ipsCode">
		$table-&gt;parsers = array(
			'joined'			=&gt; function( $val, $row )
			{
				return IPSDateTime::ts( $val )-&gt;localeDate();
			},
			'member_group_id'	=&gt; function( $val, $row )
			{
				return IPSMemberGroup::load( $val )-&gt;formattedName();
			}
		);

</pre>
<p><br><br><br>
I'm also going to add one additional line to specify the "main" column, which applies some additional styles:</p>
<p></p>
<pre class="ipsCode">
$table-&gt;mainColumn = 'name';</pre>
<p><br><br><br>
The output is now this:<br><a href="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-14380100-1363955567.png"><img src="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-14380100-1363955567_thumb.png" data-fileid="49533" loading="lazy"></a><br><br>
Some things to note:</p>
<ul><li>I'm using the <span style="font-family:'courier new'">IPSDateTime </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'">IPSDateTime</span>. <span style="font-family:'courier new'">IPSDateTime</span> extends <a href="http://www.php.net/manual/en/class.datetime.php" rel="external nofollow">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'"> IPSMemberGroup::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.<br></li>
</ul><br><br><br><br><br>
Now  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'">IPSMember </span>class.<br><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.<p></p>
<pre class="ipsCode">
$table-&gt;include = array( 'photo', 'name', 'email', 'joined', 'member_group_id', 'ip_address' );
</pre>
<p></p>
<p></p>
<pre class="ipsCode">
		$table-&gt;parsers = array(
			'photo'				=&gt; function( $val, $row )
			{
				return IPSMember::constructFromData( $row )-&gt;photo('mini');
			},

</pre>
<p><br><br>
I'll also want to specify that we cannot use the photo column for sorting:<br></p>
<p></p>
<pre class="ipsCode">
$table-&gt;noSort	= array( 'photo' );
</pre>
<p><br><br><br>
The output is now this:<br><a href="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-28450300-1363955568.png"><img src="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-28450300-1363955568_thumb.png" data-fileid="49534" loading="lazy"></a><br><br><br>
Some things to note:</p>
<ul><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'">IPSMember::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.<br></li>
</ul><br><br><br><br>
Next, I want to specify the default sorting. This is done with just two lines of code:<p></p>
<pre class="ipsCode">
		$table-&gt;sortBy = $table-&gt;sortBy ?: 'joined';
		$table-&gt;sortDirection = $table-&gt;sortDirection ?: 'desc';
</pre>
<p><br><br><br>
The output is now this:<br><a href="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-47370100-1363955569.png"><img src="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-47370100-1363955569_thumb.png" data-fileid="49535" loading="lazy"></a><br><br><br><br><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:</p>
<p></p>
<pre class="ipsCode">
$table-&gt;quickSearch = 'name';
</pre>
<p><br><br><br>
The output is now this:<br><a href="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-81749200-1363955570.png"><img src="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-81749200-1363955570_thumb.png" data-fileid="49536" loading="lazy"></a><br><br>
Some things to note:</p>
<ul><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.<br></li>
</ul><br><br><br><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:<p></p>
<pre class="ipsCode">
		$table-&gt;advancedSearch = array(
			'member_id'			=&gt; IPSHelpersTableSEARCH_CONTAINS_TEXT,
			'email'				=&gt; IPSHelpersTableSEARCH_CONTAINS_TEXT,
			'ip_address'		=&gt; IPSHelpersTableSEARCH_CONTAINS_TEXT,
			'member_group_id'	=&gt; array( IPSHelpersTableSEARCH_SELECT, array( 'options' =&gt; $groups ), function( $val )
			{
				return array( 'member_group_id=? OR ? IN(mgroup_others)', $val, $val );
			} ),
			'joined'			=&gt; IPSHelpersTableSEARCH_DATE_RANGE,
			);

</pre>
<p><br><br><br>
To explain what's going on here:</p>
<ul><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.<br></li>
</ul><br><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><a href="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-86746200-1363955571.png"><img src="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-86746200-1363955571_thumb.png" data-fileid="49537" loading="lazy"></a><br><br>
Some things to note:<ul><li>The date entry boxes use the HTML5 date input type:<br><a href="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-89216400-1363955572.png"><img src="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-89216400-1363955572_thumb.png" data-fileid="49538" loading="lazy"></a><br>
If your browser doesn't support that, there's a JavaScript fallback:<br><a href="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-10122500-1363955663.png"><img src="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-10122500-1363955663_thumb.png" data-fileid="49541" loading="lazy"></a><br>
And if you're really awkward and are using a browser that doesn't support 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.<br></li>
</ul><br><br><br><br><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:<p></p>
<pre class="ipsCode">
		/* Filters */
		$table-&gt;filters = array(
			'members_filter_banned'		=&gt; 'member_banned=1',
			'members_filter_locked'		=&gt; 'failed_login_count&gt;=' . (int) IPSSettings::i()-&gt;ipb_bruteforce_attempts,
			'members_filter_spam'		=&gt; '(members_bitoptions &amp; ' . IPSMember::$bitOptions['bw_is_spammer'] . ') != 0',
			'members_filter_validating'	=&gt; 'v.lost_pass=0 AND v.vid IS NOT NULL'
		);

</pre>
<p><br><br>
For this though, I'll also need to join the core_validating database table, so we add one more line for that:</p>
<p></p>
<pre class="ipsCode">
$table-&gt;joins = array( array( 'from' =&gt; array( 'core_validating' =&gt; 'v' ), 'where' =&gt; 'v.member_id=_0.member_id' ) );
</pre>
<p><br><br><br>
The output is now this:<br><a href="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-88662700-1363955649.png"><img src="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-88662700-1363955649_thumb.png" data-fileid="49539" loading="lazy"></a><br><br>
Some things to note:</p>
<ul><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.<br></li>
</ul><br><br><br><br><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:<p></p>
<pre class="ipsCode">
		$table-&gt;rootButtons = array(
			'add'	=&gt; array(
				'icon'		=&gt; array( 'icons/add.png', 'core' ),
				'title'		=&gt; 'members_add',
				'link'		=&gt; 'app=members&amp;module=members&amp;section=members&amp;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&amp;module=members&amp;section=members&amp;do=edit&amp;id=' . $row['member_id'],
				),
				'delete'	=&gt; array(
					'icon'		=&gt; array( 'icons/delete.png', 'core' ),
					'title'		=&gt; 'delete',
					'link'		=&gt; 'app=members&amp;module=members&amp;section=members&amp;do=delete&amp;id=' . $row['member_id'],
					'class'		=&gt; 'delete',
				),
			);
		};

</pre>
<p><br><br><br><br><br>
Our finished table looks like this:<br><a href="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-04317600-1363955651.png"><img src="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-04317600-1363955651_thumb.png" data-fileid="49540" loading="lazy"></a><br><br>
And behaves like this:<br><a href="http://screencast.com/t/KMFq8zCE" rel="external nofollow">http://screencast.com/t/KMFq8zCE</a><br><br><br><br>
To recap, here's the code, in it's entirety to generate that table:<br></p>
<p></p>
<pre class="ipsCode">
		/* Create the table */
		$table = new IPSHelpersTableDb( 'core_members', 'app=core&amp;module=members&amp;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) IPSSettings::i()-&gt;ipb_bruteforce_attempts, /*@todo*/
			'members_filter_spam'		=&gt; '(members_bitoptions &amp; ' . IPSMember::$bitOptions['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 ( IPSMemberGroup::groups() as $k =&gt; $v )
		{
			$groups[ $k ] = $v;
		}
		
		/* Search */
		$table-&gt;quickSearch = 'name';
		$table-&gt;advancedSearch = array(
			'member_id'			=&gt; IPSHelpersTableSEARCH_CONTAINS_TEXT,
			'email'				=&gt; IPSHelpersTableSEARCH_CONTAINS_TEXT,
			'ip_address'		=&gt; IPSHelpersTableSEARCH_CONTAINS_TEXT,
			'member_group_id'	=&gt; array( IPSHelpersTableSEARCH_SELECT, array( 'options' =&gt; $groups ), function( $val )
			{
				return array( 'member_group_id=? OR ? IN(mgroup_others)', $val, $val );
			} ),
			'joined'			=&gt; IPSHelpersTableSEARCH_DATE_RANGE,
			);
		
		/* Custom parsers */
		$table-&gt;parsers = array(
			'photo'				=&gt; function( $val, $row )
			{
				return IPSMember::constructFromData( $row )-&gt;photo('mini');
			},
			'joined'			=&gt; function( $val, $row )
			{
				return IPSDateTime::ts( $val )-&gt;localeDate();
			},
			'member_group_id'	=&gt; function( $val, $row )
			{
				return IPSMemberGroup::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&amp;module=members&amp;section=members&amp;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&amp;module=members&amp;section=members&amp;do=edit&amp;id=' . $row['member_id'],
				),
				'delete'	=&gt; array(
					'icon'		=&gt; array( 'icons/delete.png', 'core' ),
					'title'		=&gt; 'delete',
					'link'		=&gt; 'app=members&amp;module=members&amp;section=members&amp;do=delete&amp;id=' . $row['member_id'],
					'class'		=&gt; 'delete',
				),
			);
		};
		
		/* Display */
		IPSOutput::i()-&gt;output	= IPSOutput::i()-&gt;getTemplate( 'global' )-&gt;block( 'members', $table );

</pre>
<p><br><br></p>]]></description><guid isPermaLink="false">841</guid><pubDate>Fri, 22 Mar 2013 16:30:00 +0000</pubDate></item><item><title>Use a third party library/framework, or build it in-house?</title><link>https://invisioncommunity.com/news/invision-community/8670-use-a-third-party-libraryframework-or-build-it-in-house/</link><description><![CDATA[<p>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.  For instance, in the 4.0 Suite we will utilize <a href="http://jquery.com/" rel="external nofollow">jQuery</a> (a third party javascript framework), however we will build our underlying PHP framework in-house.  How did we decide to go that route?  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><br>
This is, admittedly, often a difficult question to answer.  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.  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><br>
Javascript frameworks are almost an essential tool with today's fast-paced browser development and web advances.  Browser updates from some vendors are almost weekly.  We went from HTML 4 to XHTML 1 to HTML 5 within but a few years.  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).  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><br>
What about PHP frameworks though?  There are many out there (<a href="http://ellislab.com/codeigniter" rel="external nofollow">CodeIgniter</a>, <a href="http://framework.zend.com/" rel="external nofollow">Zend Framework</a>, etc.) and many are relatively robust, well tested and quite extensible.  Why have we chosen to write our own underlying framework given this information?  In researching whether to maintain our own framework or use an existing one, we had to weigh many pros and cons.  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.  This is surely an important consideration to take into account.<br><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.  What if the framework we choose to utilize is no longer maintained 2 years from now?  What if a security issue arises in the third party framework, but it is not rectified quickly?  We certainly can't leave our clients vulnerable to known security vulnerabilities while we wait for a third party to patch it.  What if the framework developers release an important update, but it renders APIs incompatible with our implementation of the framework?  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.  Additionally, frameworks often have many, many capabilities, many which we may not need or use.  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.  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.  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.  No easy task, either way.<br><br>
By writing our own framework we ultimately have better control of our software.  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.  We can ensure we do not have unnecessary classes and code, or features which aren't (and never will be) used.  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.  If we wish to implement new functionality, we can implement these changes directly in low-level classes efficiently.  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><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.  It is a decision every developer or development company has to make as they approach a product, and everyone has different view points.  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.</p>]]></description><guid isPermaLink="false">840</guid><pubDate>Sun, 17 Mar 2013 21:30:26 +0000</pubDate></item><item><title>4.0 Developer Center - Database Schema Management</title><link>https://invisioncommunity.com/news/invision-community/8685-40-developer-center-database-schema-management/</link><description><![CDATA[<p>When developing, modifying the database schema (such as adding a column to a table) can be surprisingly time consuming. Currently, we have to:</p>
<ul><li>Make the change locally
</li>
<li>Change the installer
</li>
<li>Add the query to make the change to the upgrader for whatever version we're working on
</li>
<li>Let the other developers know so they can run the query to make the change in their installs.<br></li>
</ul><br><br>
This can cause issues, especially at the last step - we currently use a large .sql file in the trunk directory of our repository which we add queries to that we need everyone else to run - sometimes, one of us doesn't notice this has been updated. I'm sure also, third party modification authors are familiar with this annoyance.<br><br><br>
In 4.0, one of the things I really wanted to do was build a central "Developer Center" from which both us and modification authors can manage aspects of the application without digging into installer/upgrader files, XML files and manually copying and pasting things when creating a new module or extension.<br><br>
Part of this developer centre is a GUI for modifying the database schema. When you make changes, the changes will automatically be ran against your local database and added into the installer and upgrader. Internally, we'll also then have a special script which runs when we SVN update to copy these changes over when another developer makes a change.<br><br><br>
The first page in the Database Schema Management will be a list of tables:<br><a href="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-57588700-1362520004.png"><img src="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-57588700-1362520004_thumb.png" data-fileid="48949" loading="lazy"></a><br><br>
You can add a table to the list either by creating a new table, importing a table which is already in the database or uploading a .sql file containing a CREATE TABLE statement:<br><a href="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-48685100-1362520043.png"><img src="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-48685100-1362520043_thumb.png" data-fileid="48951" loading="lazy"></a><br><a href="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-61571100-1362520041.png"><img src="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-61571100-1362520041_thumb.png" data-fileid="48950" loading="lazy"></a><br><a href="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-24438100-1362520044.png"><img src="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-24438100-1362520044_thumb.png" data-fileid="48952" loading="lazy"></a><br><br>
When you edit a table, you can manage the columns, indexes and rows which are inserted by default:<br><a href="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-70405800-1362520446.png"><img src="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-70405800-1362520446_thumb.png" data-fileid="48953" loading="lazy"></a><br><br>
If you try to edit a table and your local database does not match what the schema has, you'll be shown the conflicts:<br><a href="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-23092100-1362520496.png"><img src="http://community.invisionpower.com/uploads/monthly_03_2013/blogentry-108264-0-23092100-1362520496_thumb.png" data-fileid="48954" loading="lazy"></a><br><br><br><br>
This, plus the other features in the Developer Center, which we'll talk about in later blog entries, make the process of developing applications easier for both us, and third party authors.<br><br><br><br><br>
Fun fact: I'm writing this blog entry from the home of IPS in Lynchburg, VA rather than my usual office in the UK  :smile:
]]></description><guid isPermaLink="false">839</guid><pubDate>Mon, 11 Mar 2013 19:00:00 +0000</pubDate></item><item><title>4.0 - Forms</title><link>https://invisioncommunity.com/news/invision-community/8660-40-forms/</link><description><![CDATA[<p>Forms are an ubiquitous aspect of any web application. In the IPS Social Suite, particularly in the Admin CP, I often find myself copying and pasting code in various places to create a form. We've had the <span style="font-family:'courier new'">ipsRegistry::getClass('output')-&gt;formInput()</span> and similar methods since 3.0, but you still have to copy all the HTML to display the rows, and write all the code to validate it yourself.<br><br>
Copying and pasting code is something all developers hate. It's a red flag that you're probably doing something wrong. In IPS 4.0, we've written a central form building helper class to alleviate this.<br><br>
Just as a reminder: Everything in this blog is a work in progress - naturally someone with a much keener design sense than I will be going over the interface - I'm just demonstrating the functionality, not a finished product  :smile:<br><br><br><strong>The Basics</strong><br><br>
Let's say I want to create a form with a single text input field. I simply initiate the form, add an object representing the input, and then display the form (it has a __toString method) - like so:</p>
<p></p>
<pre class="ipsCode">
$form = new IPSHelpersForm();
$form-&gt;add( new IPSHelpersFormText( 'name', 'default value' ) );
IPSOutput::i()-&gt;output .= $form;
</pre>
<p><br><br><br>
The helper will automatically look for language strings with the same name as the form element and use them. If there is a language string with the same name and then "_desc" - it'll use that as the description.<br><br>
So the above code, in the ACP, produces something like this:<br><a href="http://community.invisionpower.com/uploads/monthly_02_2013/blogentry-108264-0-04016100-1361532131.png"><img src="http://community.invisionpower.com/uploads/monthly_02_2013/blogentry-108264-0-04016100-1361532131_thumb.png" data-fileid="48572" loading="lazy"></a><br><br><br><strong>Required</strong><br><br>
Let's say I want the field to be required - I just pass a third argument indicating so. When the form is submitted, if no value has been provided, it will automatically display an inline error:<br><br></p>
<p></p>
<pre class="ipsCode">
new IPSHelpersFormText( 'name', '', TRUE )
</pre>
<p><br><br><br><a href="http://community.invisionpower.com/uploads/monthly_02_2013/blogentry-108264-0-33018700-1361532262.png"><img src="http://community.invisionpower.com/uploads/monthly_02_2013/blogentry-108264-0-33018700-1361532262_thumb.png" data-fileid="48573" loading="lazy"></a><br><a href="http://community.invisionpower.com/uploads/monthly_02_2013/blogentry-108264-0-02496100-1361532263.png"><img src="http://community.invisionpower.com/uploads/monthly_02_2013/blogentry-108264-0-02496100-1361532263_thumb.png" data-fileid="48574" loading="lazy"></a><br><br><br><strong>Options</strong><br><br>
The fourth argument I can pass is an array with options dependent on the type of input field I'm adding. These options may change the way a field is displayed or add additional validation.<br><br>
So for a text field, I can specify the minimum and maximum length (which, naturally takes multibyte languages into consideration):<br><br></p>
<p></p>
<pre class="ipsCode">
new IPSHelpersFormText( 'name', '', TRUE, array( 'minLength' =&gt; 5 ) )
</pre>
<p><br><br><a href="http://community.invisionpower.com/uploads/monthly_02_2013/blogentry-108264-0-98043300-1361532410.png"><img src="http://community.invisionpower.com/uploads/monthly_02_2013/blogentry-108264-0-98043300-1361532410_thumb.png" data-fileid="48577" loading="lazy"></a><br><br><br>
For a number field, I might specify the number of decimal points to round to, which it will do on the fly:<br></p>
<p></p>
<pre class="ipsCode">
new IPSHelpersFormNumber( 'name', 0, TRUE, array( 'decimals' =&gt; 2 ) )
</pre>
<p><br><br><a href="http://screencast.com/t/tydAiR3I" rel="external nofollow">Watch Video</a><br><br><br>
Or I could add an "Unlimited" checkbox, which is quite common for Admin CP settings:</p>
<p></p>
<pre class="ipsCode">
new IPSHelpersFormNumber( 'name', 0, TRUE, array( 'unlimited' =&gt; TRUE ) )</pre>
<p><br><a href="http://community.invisionpower.com/uploads/monthly_02_2013/blogentry-108264-0-02179600-1361532780.png"><img src="http://community.invisionpower.com/uploads/monthly_02_2013/blogentry-108264-0-02179600-1361532780_thumb.png" data-fileid="48580" loading="lazy"></a><br><br><br><br><strong>Custom Validation</strong><br><br>
If the built-in options don't provide enough validation for a given need, you can pass a lambda function as a 5th argument:<br></p>
<p></p>
<pre class="ipsCode">
new IPSHelpersFormText( 'name', '', TRUE, array(), function( $val )
{
	if ( $val === 'Bad Value' )
	{
		throw new IPSHelpersFormException( 'That value is not allowed.' );
	}
} )
</pre>
<p><br><br><a href="http://community.invisionpower.com/uploads/monthly_02_2013/blogentry-108264-0-97789800-1361532953.png"><img src="http://community.invisionpower.com/uploads/monthly_02_2013/blogentry-108264-0-97789800-1361532953_thumb.png" data-fileid="48581" loading="lazy"></a><br><br><br><strong>Uploads</strong><br><br>
Of course - simple input fields aren't all that can be done. How does drag and drop uploading sound?<br></p>
<p></p>
<pre class="ipsCode">
new IPSHelpersFormUpload( 'name', NULL, FALSE, array( 'multiple' =&gt; TRUE ) )
</pre>
<p><br><br><a href="http://screencast.com/t/uCs9Pn0lWs" rel="external nofollow">Watch Video</a><br><br>
(By the way, you'll notice a stripy bar flashes up briefly - this is a real progress bar, but because I'm uploading to localhost it's filling faster than it displays - in practice, you'll see a nice smooth-filling progress bar)  <br><br><br><strong>Getting the values</strong><br><br>
The form helper will by default (you can override it of course) submit to the same page it's on. You can check if it has been submitted (and passed validation) and obtain the values if so simply by calling the values() method - here's an example:<br></p>
<p></p>
<pre class="ipsCode">
$form = new IPSHelpersForm();

$form-&gt;add( ... );

if ( $values = $form-&gt;values() )
{
	// $values contains the values from the form
}
else
{
	IPSOutput::i()-&gt;output .= $form;
}

</pre>
<p><br><br><br>
The values will be returned in a way that is appropriate to the input type. For example, a text input field will return a string, number input will return either an integer or float, an upload field will return an <span style="font-family:'courier new'">IPSFile</span> object (or array of them, if you're accepting multiple files), etc.</p>]]></description><guid isPermaLink="false">838</guid><pubDate>Mon, 25 Feb 2013 14:00:00 +0000</pubDate></item><item><title>Some development decisions made prior to 4.0</title><link>https://invisioncommunity.com/news/invision-community/8651-some-development-decisions-made-prior-to-40/</link><description><![CDATA[<p>Development is much more than just opening a text editor and writing some code.  This is how most developers start, of course, and may be what an individual developer's day to day duties entail at a large corporation, however when viewing the "big picture" there are many other decisions that must be made in the course of building a software package for release that clients often don't realize were even considered.  We thought you might be interested in hearing about some of those decisions that were made regarding 4.0, prior to ever writing a single line of code.<br><br><br><strong>Minimum Supported Versions</strong><br>
One decision that has to be made relatively early in the development process is what dependencies will the software require.  For software developed in PHP, this typically means what version of PHP will be the minimum, and (often) what will be the minimum required version of MySQL.<br><br><a href="http://www.php.net/ChangeLog-5.php#5.3.0" rel="external nofollow">PHP 5.3.0</a> was released in June of 2009, and includes functionality that we intend to utilize within the 4.0 Suite.  Subsequently, this was an easy decision to make.<br><br>
We were leaning towards requiring MySQL 5.5, which was also released in 2009.  We feel that 3 years is long enough on the web to incorporate updated software within most hosting environments.  Upon investigating the functionality we intend to utilize in MySQL, however, we have determined that <a href="http://dev.mysql.com/doc/relnotes/mysql/5.0/en/news-5-0-3.html" rel="external nofollow">MySQL 5.0.3</a> and above includes everything we need, so MySQL 5.0.3 will be our minimum required version.<br><br><br><strong>Profiling and Benchmarking</strong><br>
If you are a PHP developer and have ever attempted to profile your code, you probably realize it can be challenging.  Many IDEs support some level of profiling built in, however it is almost always manually invoked.  <a href="http://xdebug.org/" rel="external nofollow">xdebug</a> is a powerful tool for profiling, but again it requires you to enable profiling, run a request, and then view the results.  In a live environment this is nearly impossible as profiling adds a lot of overhead to page loads and creates hundreds of profiling files quickly.<br><br>
Our goal is to be able to profile changes as they are committed, right away.  We have decided to install <a href="http://www.zend.com/en/products/server/" rel="external nofollow">Zend Server</a> to the server that will host our staging environment for 4.0, which supports automatic rule-based built in profiling.  This will allow us to easily profile requests automatically, even those that we may not "know" how to trigger manually (e.g. a specific task in a specific circumstance takes a long time to execute).<br><br><br><strong>Improving automatic deployments</strong><br>
While automatically building and deploying the software to our staging environment itself is not a challenge per-se, a lot of times changes occur that require some sort of manual intervention to enact.  We may have exported updated skin templates, language files, or there may be SQL queries that need to be run.  We are developing an automatic deployment routine that can handle all of this easily and programatically, ensuring our staging environment is truly always up to date with the latest changes.<br><br><br><strong>Website and forum organization</strong><br>
Not directly related to 4.0, another area we had to consider was our organization on the website and on our community here.  This extend to all areas of the community, from the organization of our feedback and support forums, to our marketplace categorization, to our bug tracker categorization.  The approach with 4.0 will be that we have one suite with modular applications that can be enabled.  We need to market the software this way, and we need to ensure that the logical work flow for our clients reflects how our suite is designed.  You will be seeing changes to categorization throughout our sites based on 4.0 in due course as a result.<br><br><br><strong>Loose ends</strong><br>
And beyond all of the stuff directly related to 4.0, we felt it prudent to tackle a lot of loose ends before we jumped into a major overhaul of our entire software line up.  There were a lot of smaller tasks that have been pushed off for a while but which we've finally sorted through which we feel will afford us less distractions as we work on the next release.  Most of these tasks were minor, but not unimportant.  Examples include:</p><ul><li>Moving our documentation to the main website
</li><li>Improving minor areas of IP.Downloads and IP.Nexus to allow clients to better help themselves, or to allow a smoother work flow within the administration areas our employees utilize every day
</li><li>Scripts on our end that allow us to better manage certain services
</li><li>Cleaning up old content in our installation, such as removing unused IP.Content blocks and deleting left over files on our server<br></li></ul><br><br>
None of this directly affects 4.0, however the less distracted we get with minor tasks during 4.0 development, the better we can focus and deliver the product in a timely manner and with less chance of distraction-related bugs.<br><br><br>
Of course hundreds of other decisions have gone into 4.0 as well, some of which we will talk about in future blog entries.  We thought you might be interested in hearing about some of the smaller things us developers do here at IPS, however, besides developing great software products to power your communities!
]]></description><guid isPermaLink="false">837</guid><pubDate>Sun, 24 Feb 2013 12:30:00 +0000</pubDate></item><item><title>4.0 - Prepared Statements</title><link>https://invisioncommunity.com/news/invision-community/8644-40-prepared-statements/</link><description><![CDATA[<p>In 4.0, we have made changes to the database class to make use of prepared statements.<br><br><br>
For insert and update queries, the syntax is the same as it always has been:</p><p></p><pre class="ipsCode">
IPSDb::i()-&gt;insert( 'table', array( 'foo' =&gt; 'bar' ) );
</pre><p><br><br>
However, where previously the database class would try to work out the type of variable passed to it - it now binds these to a prepared statement.<br><br>
The real usefulness of this change though, is apparent when you need to use a where clause. Where previously you'd have to do something like this:</p><p></p><pre class="ipsCode">
$this-&gt;DB-&gt;buildAndFetch( array( 'select' =&gt; '*', 'from' =&gt; 'table', 'where' =&gt; "foo='" . $this-&gt;DB-&gt;addSlashes( $foo ) . "'" ) );
</pre><p><br>
You can now do:</p><p></p><pre class="ipsCode">
IPSDb::i()-&gt;buildAndFetch( array( 'select' =&gt; '*', 'from' =&gt; 'table', 'where' =&gt; array( 'foo=?', $foo ) ) );
</pre><p><br>
We calculate the datatype based on the variable datatype, so previously where you had to do things like $this-&gt;DB-&gt;setDataType( 'foo', 'string' ) when you wanted to store a value like '01' - ew) - you can now just cast the variable to whatever datatype you like.<br>
For example, if you wanted to ensure that the variable was cast as a string to avoid issues where a user name of '007' was detected as an integer and converted to '7' then you'd use:</p><p></p><pre class="ipsCode">
IPSDb::i()-&gt;buildAndFetch( array( 'select' =&gt; '*', 'from' =&gt; 'table', 'where' =&gt; array( 'foo=?', (string) $foo ) ) );</pre><p><br>
Not only is this easier to type and to read, it ensures that the database class always takes care of escaping things properly.</p>]]></description><guid isPermaLink="false">836</guid><pubDate>Fri, 22 Feb 2013 19:12:07 +0000</pubDate></item><item><title>Betas for ALL applications available</title><link>https://invisioncommunity.com/news/invision-community/8658-betas-for-all-applications-available/</link><description><![CDATA[<p>We have built public beta releases of our entire product line up for testing.  <strong>Please be aware that beta releases are not supported by IPS technical support.  We do not recommend running them on a live site, you may be unable to upgrade from a beta release to the final version, and our only course of support is to recommend you restore a backup of your site if you face any issues.</strong><br><br>
We appreciate clients who wish to participate in beta testing programs.  We would recommend cloning your live site, or installing these betas to a new test environment, in order to test the bug fixes and functionality improvements.  Please report bugs in the <a href="http://community.invisionpower.com/resources/bugs.html" rel="external nofollow">bug tracker</a>.  As of the time of this post, our company forums are running all of these latest versions.<br><br>
IP.Board 3.4.3</p><ul><li><a href="http://community.invisionpower.com/resources/bugs.html/_/ip-board/?sort_col=record_updated&amp;sort_order=desc&amp;per_page=25&amp;filters%5B62%5D%5B0%5D=3.4.3&amp;reverse_filters%5B62%5D=1" rel="external nofollow">Bug fixes</a><br></li></ul><br><br>
IP.Blog 2.6.2<ul><li><a href="http://community.invisionpower.com/resources/bugs.html/_/ip-blog/?sort_col=record_updated&amp;sort_order=desc&amp;per_page=25&amp;filters%5B62%5D%5B0%5D=2.6.2&amp;reverse_filters%5B62%5D=1" rel="external nofollow">Bug fixes</a><br></li></ul><br><br>
IP.Calendar 3.3.2<ul><li><a href="http://community.invisionpower.com/resources/bugs.html/_/ip-calendar/?sort_col=record_updated&amp;sort_order=desc&amp;per_page=25&amp;filters%5B62%5D%5B0%5D=3.3.2&amp;reverse_filters%5B62%5D=1&amp;filters%5B64%5D%5BUnconfirmed%5D=0" rel="external nofollow">Bug fixes</a><br></li></ul><br><br>
IP.Chat 1.4.3<ul><li><a href="http://community.invisionpower.com/resources/bugs.html/_/ip-chat/?sort_col=record_updated&amp;sort_order=desc&amp;per_page=25&amp;filters%5B62%5D%5B0%5D=1.4.3&amp;reverse_filters%5B62%5D=1&amp;filters%5B64%5D%5BUnconfirmed%5D=0" rel="external nofollow">Bug fixes</a><br></li></ul><br><br>
IP.Content 2.3.5<ul><li><a href="http://community.invisionpower.com/resources/bugs.html/_/ip-content/?sort_col=record_updated&amp;sort_order=desc&amp;per_page=25&amp;filters%5B62%5D%5B0%5D=2.3.5&amp;reverse_filters%5B62%5D=1" rel="external nofollow">Bug fixes</a><br></li></ul><br><br>
IP.Downloads 2.5.3<ul><li><a href="http://community.invisionpower.com/resources/bugs.html/_/ip-downloads/?sort_col=record_updated&amp;sort_order=desc&amp;per_page=25&amp;filters%5B62%5D%5B0%5D=2.5.3&amp;reverse_filters%5B62%5D=1" rel="external nofollow">Bug fixes</a>
</li><li><a href="http://community.invisionpower.com/blog/4445/entry-8645-ipdownloads-multiple-featured-files/" rel="external nofollow">Multiple featured files now supported</a>
</li><li><a href="http://community.invisionpower.com/blog/4445/entry-8634-more-client-control-over-marketplace-purchases/" rel="external nofollow">Ability for clients to manage renewals for purchased files now supported</a><br></li></ul><br><br>
IP.Gallery 5.0.4<ul><li><a href="http://community.invisionpower.com/resources/bugs.html/_/ip-gallery/?sort_col=record_updated&amp;sort_order=desc&amp;per_page=25&amp;filters%5B62%5D%5B0%5D=5.0.4&amp;reverse_filters%5B62%5D=1" rel="external nofollow">Bug fixes</a>
</li><li><a href="http://community.invisionpower.com/blog/4445/entry-8657-minor-enhancement-to-ipgallery-feed-blocks-in-ipcontent/" rel="external nofollow">Ability to create Gallery IP.Content feeds filtering on "featured" status now supported</a><br></li></ul><br><br>
IP.Nexus 1.5.7<ul><li><a href="http://community.invisionpower.com/resources/bugs.html/_/ip-nexus/?sort_col=record_updated&amp;sort_order=desc&amp;per_page=25&amp;filters%5B62%5D%5B0%5D=1.5.7&amp;reverse_filters%5B62%5D=1" rel="external nofollow">Bug fixes</a>
</li><li><a href="http://community.invisionpower.com/blog/4445/entry-8635-ipnexus-integration-with-the-bulk-mailer/" rel="external nofollow">Integration with the bulk mailer supported</a>
</li><li><a href="http://community.invisionpower.com/blog/4445/entry-8633-minor-ipnexus-157-enhancement/" rel="external nofollow">Ability to leave a customer note when voiding account added</a><br></li></ul><br><br><br>
You can download beta releases for which your license gives you access to at our <a href="http://community.invisionpower.com/qa.php" rel="external nofollow">QA page</a>.  Thanks in advance to all who decide to test!
]]></description><guid isPermaLink="false">835</guid><pubDate>Thu, 21 Feb 2013 17:46:00 +0000</pubDate></item><item><title>Minor enhancement to IP.Gallery feed blocks in IP.Content</title><link>https://invisioncommunity.com/news/invision-community/8657-minor-enhancement-to-ipgallery-feed-blocks-in-ipcontent/</link><description><![CDATA[<p>This is just a quick update to let everyone know of a small enhancement you can expect to see in the next IP.Gallery release.<br><br>
We recently noticed that you could not create IP.Content feed blocks to pull featured images from IP.Gallery.  We felt that this was a small and easily correctable oversight, so we added a filter option when creating IP.Gallery feed blocks in IP.Content to allow you to choose whether to pull only featured images or not.  You can expect to see this new option beginning with the IP.Gallery 5.0.4 release.<br><br>
Adding filters to IP.Content feed blocks is a generally easy process, depending upon what you are trying to filter by.  Naturally, some types of filters can make the block much more complex, but with the example above of filtering by retrieving featured images, we needed only 2 language strings and 10 distinct lines of code. <br><br>
What types of block filters have you always felt would be easy but useful additions?</p>]]></description><guid isPermaLink="false">834</guid><pubDate>Thu, 21 Feb 2013 17:45:00 +0000</pubDate></item><item><title>4.0 - Dev Introduction</title><link>https://invisioncommunity.com/news/invision-community/8642-40-dev-introduction/</link><description><![CDATA[<p>A <a href="http://community.invisionpower.com/blog/1174/entry-7634-license-changes-ipboard-34-and-the-future/" rel="external nofollow">while back</a>, we casually mentioned in a blog entry that 4.0 would be next major version after 3.4. <a href="http://community.invisionpower.com/blog/1174/entry-8650-ips-social-suite-40" rel="external nofollow">Development of 4.0</a> is underway and we're going to be using this new blog to talk about development as we go.<br><br>
As Brandon <a href="http://community.invisionpower.com/blog/4445/entry-8636-welcome-to-the-development-channel/" rel="external nofollow">mentioned</a> a couple of days ago - the format of these entries is going to be developer-specific. If what we're saying doesn't make much sense right now, we will still be putting announcements up in our main blog when they're finished and ready for everyone to see.<br><br>
Because of that, it's also worth bearing in mind that everything is subject to change. I'm going to be posting code samples, screenshots and so forth - but everything in this blog is a work in progress - not the final product - and that will probably show.<br><br>
With that out the way - let's talk about 4.0! :D<br><br><br><br><strong>The file structure</strong><br><br>
Currently, applications are mostly self contained in their folders (which is either /admin/applications, /admin/applications_addon/ips or /admin/applications_addon/other) however, other files are dotted around in /interface, /public, etc. In 4.0, applications will be completely self-contained within a single /applications directory.<br><br>
An application directory will look something like this:</p><ul><li>extensions
</li><li>dev<br></li><ul data-ipsbbcode-list="true"><li>
</li><li>css
</li><li>html
</li><li>img
</li><li>js
</li><li>lang</li><br><br><ul></ul><li>admin
</li><li>front (it's "front" rather than "public" now)</li><br><br></ul></ul>[*]interface
[*]modules[*]setup
[*]sources
[*]tasks
[*]xml
<br>
You will notice the inclusion of a /dev folder. This will not actually be shipped in production, but rather replaces /cache/lang_cache/master_lang and so forth in the 3.x line.<br><br>
Outside of the applications directory, there will be a "system" directory, which contains core framework classes.<br><br><br><strong>Namespaces and autoloading</strong><br><br>
In 4.0, we'll be making use of PHP namespaces and using an autoloader. The best way to demonstrate how this works is with a few examples:<ul><li><span style="font-family:'courier new'">classDb</span> (/ips_kernel/classDb.php) is now <span style="font-family:'courier new'">IPSDb</span> and located in /system/Db/Db.php
</li><li><span style="font-family:'courier new'">output</span> (/admin/sources/classes/output/publicOutput.php) is now <span style="font-family:'courier new'">IPSOutput</span> and located in /system/Output/Output.php
</li><li><span style="font-family:'courier new'">class_forums</span> (/admin/applications/forums/sources/classes/forums/class_forums.php) is now <span style="font-family:'courier new'">IPSforumsForum</span> and located in /applications/forums/sources/Forum/Forum.php
</li><li><span style="font-family:'courier new'">IPSDispatcherFront </span><span style="font-family:arial"><span style="font-family:'courier new'">and </span></span><span style="font-family:'courier new'"><span style="font-family:arial"><span style="font-family:'courier new'">IPSDispatcherAdmin </span></span></span><span style="font-family:arial"><span style="font-family:'courier new'"><span style="font-family:arial"><span style="font-family:'courier new'">are two new classes (with similar functionality to </span></span></span></span><span style="font-family:'courier new'"><span style="font-family:arial"><span style="font-family:'courier new'"><span style="font-family:arial"><span style="font-family:'courier new'">ipsController</span></span></span></span></span><span style="font-family:arial"><span style="font-family:'courier new'"><span style="font-family:arial"><span style="font-family:'courier new'"><span style="font-family:arial"><span style="font-family:'courier new'"> in 3.x) and both </span></span></span></span></span></span>extend <span style="font-family:'courier new'">IPSDispatcherDispatcher</span><span style="font-family:arial"><span style="font-family:'courier new'"> - all 3 are located in /system/Dispatcher/ in individual files.</span></span><br></li></ul><br><br><strong>Better framework design</strong><br><br>
Where appropriate, classes are being refactored to make better use of appropriate design patterns. One lovely side-effect of this is ipsRegistry no longer exists.<br><br>
Instead of, for example <span style="font-family:'courier new'">ipsRegistry::DB()</span> you now use <span style="font-family:'courier new'">IPSDb::i()</span><span style="font-family:arial"><span style="font-family:'courier new'"> - the Db class uses a multiton design pattern (I didn't pass any arguments in that example, which doing will cause the Db class to load conf_global.php and create the default database connection, but I could have passed it a key) - the i method in this case will create the database connection if it doesn't already exist.</span></span><br><br>
To give another example - <span style="font-family:'courier new'">IPSMember</span><span style="font-family:arial"><span style="font-family:'courier new'"> (the new </span></span><span style="font-family:'courier new'"><span style="font-family:arial"><span style="font-family:'courier new'">IPSMember</span></span></span>) uses an Active Record pattern. So there's no more of this:<br><span style="font-family:'courier new'"></span><span style="font-family:arial"><span style="font-family:'courier new'"></span></span><span style="font-family:'courier new'"><span style="font-family:arial"><span style="font-family:'courier new'">IPSMember::isInGroup( 1, 4 );</span></span></span><br>
It's now, the much more logical:<br>
IPSMember::load( 1 )-&gt;isInGroup( 4 );<br><br><br><strong>Monkey patching hooks</strong><br><br>
One of the great things about the IPS Community Suite is hooks - you can easily create a class and instruct the framework to use that instead of a core class.<br><br>
Now, I don't know about you, but I really, really, really hate having to do this:<br><span style="font-family:'courier new'">$class = IPSLib::loadLibrary( '/path/to/file', 'myClass' );</span><br><span style="font-family:'courier new'">$object = new $class;</span><br><br>
Especially if what you want to call is a static method, in which case it can't be done. You want to of course, just be able to do:<br><span style="font-family:'courier new'">$object = new myClass;</span><br>
or:<br><span style="font-family:'courier new'">myClass::myStaticMethod();</span><br><br>
There is a concept in software engineering to do this sort of thing, called <a href="http://en.wikipedia.org/wiki/Monkey_patch" rel="external nofollow">monkey patching</a>, and by clever use of the autoloader, we've managed to make this work. loadLibary and loadActionOverloader are no more.<br><br><br><br><br>
These points are of course, just the beginning of 4.0. Stay tuned for more :smile:
]]></description><guid isPermaLink="false">833</guid><pubDate>Thu, 21 Feb 2013 13:30:00 +0000</pubDate></item><item><title>IPS Social Suite (4.0)</title><link>https://invisioncommunity.com/news/invision-community/8650-ips-social-suite-40/</link><description><![CDATA[<p>While we <a href="http://community.invisionpower.com/blog/1174/entry-7634-license-changes-ipboard-34-and-the-future/" rel="external nofollow">introduced some of our basic plans for 4.0</a> many months ago, we wanted to touch base again on some of these plans and expand upon some of our motivations behind decisions we have or will make for our upcoming 4.0 software release.<br><br>
Before we get too far, let me just state now that there is no expected (or even estimated) release date for 4.0 yet.  While we always have internal guidelines, timeframes and milestones, we do not communicate these publicly until we are absolutely sure they are as accurate as possible.  The 4.0 Suite will be a major overhaul, effectively a rewrite of most areas from the ground up, and there are many factors that can affect delivering within expected due dates.<br><br>
You may also have noticed the title of this blog entry uses a term for the suite you may not have seen used previously.  We have decided to name the 4.0 Suite (which we will often refer to as "4.0 Suite" or "Social Suite", informally), officially as the IPS Social Suite.  We feel that as we expand our line up and remove community-related dependencies, it is important that our main product release reflect the fact that our software can power more than just traditional communities.<br><br>
Formalities out of the way now, here are a few of our driving motivations behind 4.0...<br><br><span style="font-size:18px"></span><strong><span style="font-size:18px">Modernize the interface</span></strong><span style="font-size:18px"></span><br>
The skin delivered for 3.0 and again for 3.2 was great, but several years have since passed.  It is time we modernize the user interface in our software lineup once again.  Features have been added, trends on the web have shifted, and technologies have advanced.  Some specific points you may be interested to know:</p><ul><li>All areas of the suite will support the mobile interface.
</li><li>We are heavily investigating using a "response design" for 4.0.
</li><li>We will be switching to jQuery
</li><li>We will be embracing HTML 5 fully<br></li></ul><br><br><span style="font-size:18px"></span><strong><span style="font-size:18px">Modernize the underlying codebase</span></strong><span style="font-size:18px"></span><br>
While 4.0 will not technically be a "complete rewrite", most of the underlying codebase will be rewritten in some manner, and all of the code will at least be updated to work within the new framework we are developing.  There is a lot to go over for those of you who may be interested in the developer side of things, and I'll point you to the right place later in this entry, but as a general outline here are some things you can expect to see:<ul><li>PHP 5.3 will be the minimum supported version of PHP
</li><li>MySQL 5.0.3 will be the minimum supported version of MySQL
</li><li>IP.Board will fully utilize namespaces in PHP
</li><li>The entire directory structure, class naming structure and more will be completely overhauled
</li><li>Applications will truly be self-contained within their own folders (currently javascript, skin and language files, for instance, are scattered throughout miscellaneous directories)
</li><li>The entire code base will be modernized.  More use of formal design patterns will be employed, where appropriate.  Dumping ground classes (such as IPSLib) will be avoided at all costs.  More consistency between how applications implement functionality will be seen.  Naming conventions will be more consistent.
</li><li>The way hooks work will be completely rewritten, making things simpler for us and for developers, and making hook usage behind the scenes more reliable (no more loadLibrary calls - everything is handled automatically by the framework instead).<br></li></ul><br><br><span style="font-size:18px"></span><strong><span style="font-size:18px">Make things more consistent</span></strong><span style="font-size:18px"></span><br>
We are also working towards making all of our applications more consistent.  The approach to this actually has much more to do with planning and how we approach new functionality than it does any specific technical aspect of software development.  In a nutshell, we will have one "suite" release moving forward starting at 4.0.  Every application will be on the same versioning system and share the same version number, and every release will include every application (although you will only have access to the applications you have purchased, of course).  What we will do as we implement new features is implement new functionality suite-wide from the start.  If we were to add a new feature to 4.0, we would not add the functionality to the forums and then roll this change out to other applications as they see updates.  Instead, we will be implementing changes suite-wide from the start, which has several benefits:<ul><li>From a user standpoint, the software will be more consistent.  You won't have situations where a feature is available in Application X but not in Application Y.
</li><li>From a technological standpoint, we will be forced to implement functionality in an optimal manner where it can be utilized by all areas of the suite.  There will be less application-dependencies for features that are intended to be suite-wide.  There will be much, much less duplicated code as features will be designed from the start to work in multiple areas.
</li><li>Point #2 above will also benefit modders - APIs will be much more robust, yet more generic and reusable, for features implemented suite-wide rather than features implemented for one application and then shared across others.<br></li></ul><br><br>
Beyond changing our approach to functional changes in the software itself, we will also be focusing on consistency while redesigning the interface, and throughout every facet of development of 4.0.<br><br><br><span style="font-size:18px"></span><strong><span style="font-size:18px">Want to hear more specifics?</span></strong><span style="font-size:18px"></span><br>
We have <a href="http://community.invisionpower.com/blog/1174/entry-8637-ips-development-channel-blog/" rel="external nofollow">recently launched our new development blog</a> where we will routinely be posting about the nitty-gritty of our day to day development duties here at IPS.  While this blog is not at all intended to be specific to the development of 4.0, you will find us posting about upcoming changes and decisions made in 4.0 quite regularly.  We welcome you to follow <a href="http://community.invisionpower.com/blog/4445-the-development-channel/" rel="external nofollow">The Development Channel</a> blog if you are interested in reading about these changes.  If not, don't worry - any major announcements will be blogged about here in our company blog as well in due course.<br><br>
If you aren't sure, just to be clear....development of 4.0 is definitely underway.  A lot of planning and discussion took place prior to ever writing a single line of code for 4.0, however we are definitely working on 4.0 now and you will likely see blog entries about upcoming changes before long.  Stay tuned!
]]></description><guid isPermaLink="false">832</guid><pubDate>Wed, 20 Feb 2013 14:00:00 +0000</pubDate></item><item><title>IP.Downloads Multiple Featured Files</title><link>https://invisioncommunity.com/news/invision-community/8645-ipdownloads-multiple-featured-files/</link><description><![CDATA[<p>Through use of our own marketplace, we often identify small but useful changes to IP.Downloads that benefit sites that both use IP.Downloads to allow distribution of free files as well as those that sell files through a marketplace.  While working to improve the user interface of the home page in IP.Downloads, we decided that allowing multiple featured files would be a small change but would help improve the interaction and discoverability of useful content in the marketplace.  After all, every other marketplace has more than one file featured, right?<br><br>
IP.Downloads 2.5.3 will now allow you to feature more than one file.  Up to 20 of the most recent files that are marked as featured will display on the homepage in a carousel-style panel.  The act of featuring a file has not changed (the only thing that has changed here is that other files which are featured are no longer automatically unfeatured).  Only the homepage interface has really changed.<br><br>
Here is a short video to show you what it looks like with two featured files.  Note that if you only have one featured file, it will display as it does now in IP.Downloads 2.5.2.<br><br><a href="http://screencast.com/t/A0KX6uWs" rel="external nofollow">http://screencast.com/t/A0KX6uWs</a><br><br>
You will note that you can manually cycle through featured files, or they will cycle automatically.  If you mouse over the featured file panel, the automatic cycling pauses.<br><br>
We hope you find this small change useful, and more consistent with other applications that allow featuring (such as IP.Blog, where you can feature more than one entry, and IP.Gallery, where you can feature more than one image).  Let us know what you think in the comments below!</p>]]></description><guid isPermaLink="false">831</guid><pubDate>Sat, 16 Feb 2013 13:30:00 +0000</pubDate></item></channel></rss>
