-
Posts
70,143 -
Joined
-
Last visited
-
Days Won
649
Content Type
Downloads
Release Notes
IPS4 Guides
IPS4 Developer Documentation
Invision Community Blog
Development Blog
Deprecation Tracker
Providers Directory
Projects
Release Notes v5
Invision Community 5 Bug Tracker
Forums
Events
Store
Gallery
Everything posted by Matt
-
Since IP.Board 2, we've had a "kernel" of classes which IP.Board uses but do not depend on IP.Board to use. A selection of these kernel classes includes DB management, file uploads, emailing, RSS parsing and reading, XML parsing and reading and our proprietry archive format XMLArchive. For example, you could use "classUpload.php" and "classImage.php" in your own modifications or extensions to handle uploads and GD thumbnail generation. You would not have to initialize the registry or do anything else other than just include the file and use it. As we took the plunge and went ahead with making PHP 5 a requirement, we were able to make some long-awaited improvements. Here are the high-lights. Custom Fields Josh has completely re-written custom field handling within IP.Board and this class ties it altogether. Josh uses just about every PHP 5 trick in the book including abstract classes, interfaces and ArrayAccess to provide a simple clean interface that can be used in almost any project. It doesn't have to be limited to just IP.Board's custom profile fields. Graphing Remco has extended this class to include many new graph types including "funnel", "bubble" and "radar" graphs so that presenting data is more relevant. As IP.Board 3.0.0 develops, we'll be making good use of these new methods for statistic reporting within the ACP. GD and ImageMagick Brandon virtually started from scratch with our GD image creation. He also wrote a class for imagemagick offering more choice for those who have it installed. It's now very simple to resize an image or add a watermark. $image->init( array( 'image_path' => "/path/to/images/", 'image_file' => "image_filename.jpg" ) ); //Set max width and height $image->resizeImage( 600, 480 ); // Add a watermark $image->addWatermark( "/path/to/watermark/trans.png" ); $image->displayImage(); $image = new classImageGd(); XML Parsing and Creating We've had a class for XML parsing and creating since IP.Board 2 but it was a kludge of PHPs then primative XML handling extension and a hand-rolled class for when the XML engine wasn't available. These methods functioned well but were known to be memory intensive. Thankfully, PHP 5 has much better native XML handling which I took full advantage of when rewriting this class. I looked at simpleXML intially but found it too, well, simple for our needs so I went ahead and used the full DOM methods. This gives us full control over both reading and creating XML documents. Here's how simple it is to create a new XML document: $xml->newXMLDocument(); /* Create a root element */ $xml->addElement( 'productlist', '', array( 'name' => 'myname', 'version' => '1.0' ) ); /* Add a child.... */ $xml->addElement( 'productgroup', 'productlist', array( 'name' => 'thisgroup' ) ); $xml->addElementAsRecord( 'productgroup', array( 'product', array( 'id' => '1.0' ) ), array( 'description' => array( 'This is a description' ), 'title' => array( 'Baked Beans' ), 'room' => array( '103', array( 'store' => 1 ) ) ) ); $xml->addElementAsRecord( 'productgroup', array( 'product', array( 'id' => '2.0' ) ), array( 'description' => array( 'This is another description' ), 'title' => array( 'Green Beans' ), 'room' => array( '104', array( 'store' => 2 ) ) ) ); $xmlData = $xml->fetchDocument(); $xml = new classXML( 'utf-8' ); This will produce this document: <productgroup name="thisgroup"> <product id="1.0"> <description>This is a description</description> <title>Baked Beans</title> <room store="1">103</room> </product> <product id="2.0"> <description>This is another description</description> <title>Green Beans</title> <room store="2">104</room> </product> </productgroup> </productlist> <productlist name="myname" version="1.0"> This is how you'd parse that document: /* Grabbing specific data values from all 'products'... */ foreach( $xml->fetchElements('product') as $products ) { print $xml->fetchItem( $products, 'title' ) . "\"; } /* Prints: */ Baked Beans Green Beans $xml->loadXML( $xmlData ); I'm sure you can appreciate how simple it is to use these new XML features! XMLArchive I created our XMLArchive format for IP.Board as a way of combining several files into one without using tar or zip which can be troublesome on some servers. XMLArchive is very simply file data in a basic XML format. I rewrote the class to make it easier to use and to add more functionality. Here's how to read an archive: $archive->read( "/path/to/archive.xml" ); print $archive['someDir/file.html']; $archive = new classXMLArchive(); As you can see, we use PHPs ArrayAccess to transparently allow access to the contents of the fileset from the main class handle. Here's how to create an archive: $archive->add( "someDir" ); $archive->add( "anotherDir/anotherFile.html" ); $archive->add( "Create a new file from scratch!", "anotherDir/brandNewFile.html" ); # Save gzipped $archive->saveGZIP( "/path/to/archive.xml.gzip" ); # Save normally $archive->save( "/path/to/archive.xml" ); # Just return the data $archive->getArchiveContents(); $archive = new classXMLArchive(); For simplicity's sake, there is now just one interface to add files: "add()". Personally, the fewer function names to remember the better! Naturally, this blog entry does not cover all the changes and improvements within the kernel classes but it does go some way to showing just how much energy we've invested in ensuring that IP.Board is as robust and efficient as it can be.
-
Brandon blogged a while back about IP.Board's integration points. I wanted to spend a moment discussing the features within IP.Board 3 that all you integrate the board with your website and to create your own network. Member Management Since IP.Board 2, we've had, what we call, "Log In Modules". This is basically a mini-framework to allow custom code to be used easily when authenticating and registering members. For example, if you had a database full of members and you wanted for them to be able to use their existing usernames and passwords, you could add a log in module to query your database or other system (via SOAP, XML-RPC, etc) for authentication. This system has been enhanced based on user feedback and IP.Board now ships with modules for LDAP and OpenID which will make it much easier to use existing authentication. Networking The log in modules also tie into our 'IP.Converge' product which allows you to share authentication details across multiple IP.Board installations. The IP.Boards don't even need to be on the same server! In fact, we use Converge and the log in modules ourselves so that our customers can use the same log in details on the forum as they do in our ticket center. You could use this functionality to share members across many forums, creating a true network of members. Using the IP.Board Engine We've made no secret that IP.Board 3 is a complete rewrite. The new framework makes full use of PHP 5 and incorporates many timesaving features that you can instantly make use of. It's common for our customers to ask how they can use certain parts of IP.Board within their own website. For example, they'd like to show a list of recent topics or posts. That's no problem as we've had an API class interface since IP.Board 2. However, if you wanted to send data to IP.Board, such as a new post or new personal message; things got tricky. Even using a template bit required a lot of code copying. For example, if you wanted to make use of our database engine or templating engine, you would need to copy a lot of 'index.php' so that it set up ipsclass correctly. With IP.Board 3, that is no longer required. You can use our engine in your own scripts as simply as this: require_once( IPS_ROOT_PATH . 'sources/base/ipsRegistry.php' ); $registry = ipsRegistry::instance(); $registry->init(); require_once( './initdata.php' ); Those few lines of code give you access to: Caches, settings, member management, database management and more. Writing your own code Quite often, you want to add some IP.Board functionality to your own website. With IP.Board 3.0.0 it's very simple. You want to add a new post? No problem, simply use that code above and add: $postClass = new classPostForms( $registry ); $postClass->setForumID( $forumID ); $postClass->setForumData( $this->registry->class_forums->forum_by_id[ $forumID ] ); $postClass->setTopicTitle( "My Topic" ) $postClass->setPostContent( "Hello, I am post content!" ); $postClass->setAuthor( $memberID ); try { $postClass->addTopic(); } catch( Exception $error ) { print $error->getMessage(); } require_once( IPSLib::getAppDir( 'forums' ) . '/sources/classes/post/classPost.php' ); It's really as simple as that! Note the try -> catch block? That's consistent with all the new API-like functions. We take advantage of the PHP 5 exception handler to return information on what went wrong. We also list all the possible exceptions that are returnable in the phpDoc for that function. Of course, seasoned modification authors will already be familiar with functions similar to those present in IP.Board 2's API system. The good news here is that this functionality doesn't require an API bridge anymore, it's the exact same code that is used in the normal posting routines. Do you want to send a new personal conversation to someone in your own code? Simple! $messengerFunctions = new messengerFunctions( $registry ); $messengerFunctions->sendNewPersonalTopic( $toMemberID, $fromMemberID, $invitedUserIDArray, $msgTitle, $msgContent ); require_once( IPSLib::getAppDir( 'members' ) . '/sources/classes/messaging/messengerFunctions.php' ); Want to get a list of PMs in your own code? $messengerFunctions = new messengerFunctions( $registry ); $messengerFunctions->getPersonalTopicsList( $memberID, 'in' ); require_once( IPSLib::getAppDir( 'members' ) . '/sources/classes/messaging/messengerFunctions.php' ); Another common request is to use IP.Board's templates in your own projects, again this is as simple as: require_once( IPS_ROOT_PATH . 'sources/base/ipsRegistry.php' ); $registry = ipsRegistry::instance(); $registry->init(); print $registry->output->getTemplate( $templateGroup )->templateName( $templateArguments); require_once( './initdata.php' ); There really is so much that you can do. Listing them all would make for a very large blog entry indeed! We will of course be providing a lot of documentation on these new features. Combine this functionality with the new hooks and plug-ins system and you've got a very quick way to build new code using our core. We're very excited to see what you do with it!
-
Around ten years ago, I was hard at work on one of the first 'Private Message' modifications for a board that has long ceased to exist. At the time it was an exciting novelty to be able to message another board member. These days it's an expected feature for any seriously considered forum software. Not much has really changed with messaging's core features. Sure, the interface has become a little smarter and there have been a few little bells and whistles added such as message tracking but ultimately it's still sort-of email based in functionality. This was a limitation I noted when developing IP.Dynamic. In fact, almost two years ago, I blogged about Personal Topics, the next logical step in private messaging. Enter Personal Conversations As, with most of IP.Board 3.0.0's new features, personal conversations - as we like to call them - have been a popular request. A request that we have now fulfilled! Why bother CC-ing and forwarding messages to other members when you can invite them right into the conversation? How it Works Where you would normally 'compose' a new message, you now 'start a new conversation'. The form looks largely the same. However, instead of there being a space to "CC" other members, there are boxes for you to invite other members. How many members you can invite is up to the administrator as it's limited to your group settings. In fact, the admin could remove your ability to invite other members completely if they desired to return to a more traditional messaging system. The recipient and each invited member will be notified they have a new message (in terms of notification, a message is either a new conversation or a reply to any existing conversation). The new conversation will appear in both their "Inbox" and their "New" folders. It will remain in their "new" folder until its read and then it will only be available in the inbox. Why? Well, because of the magic folders. But more of that in a moment. The conversation looks largely the same as a normal topic does; linearly sorted with the newest replies last. There is even a "fast reply" form much like you use when replying to topics. This instantly makes it easier to view previous replies and keep the 'thread' of conversation without having to delving into your 'sent' folder to see what you replied with. The topic starter (and any participating super moderator) can choose to 'block' any participant that is blockable. Immunity from being blocked is granted by the "Not Ignorable" setting. This means you can ensure that you (the admin) can never be blocked from a conversation you are participating in. When a participant is blocked, the topic vanishes from their folders until they are unblocked. Each participant apart from the topic starter can choose to leave a conversation at any time. Once the participant has left the conversation, it is only available in the magic "Finished" folder and the replies no longer count towards your space allowance. The topic starter cannot re-invite you but you can choose to rejoin the conversation at any time. The topic starter can choose to invite more members (up to their invite allowance) at any time from the conversation display screen. Each participant can also view all participants and see the last time they read the conversation. Magic Folders This is the term we give to certain folders that are not editable or removable. They are: "New", "Finished", "All" and "My Conversations". Topics in "New" and "My Conversations" can also be in other folders. "My Conversations" is the default location for any conversation you start. You can then move into another folder -- but it will always be accessible from "My Conversations". "New" simply lists any new personal conversations or any personal conversations with new replies. So, you could have a conversation in "My Folder". When it receives a new reply, it will also be listed in "New" until you read it. "Finished" simply lists all the conversations you've left, allowing you to rejoin if desired. "All", as the name implies, lists all the conversations you are participating in as either a starter, recipient or invitee. We think this is a very exciting step forward for personal communication and opens up new ways to communicate via the forum. Ten years is a long time to wait for change. We hope you find it worth the wait!
-
Possibly the most often requested feature we've had since the very first version of IP.Board is 'friendly URLs'. Although this sounds like you'd expect your URLs to greet you with a self-empowerment phrase first thing in the morning, it really relates to making the board generated URLs a little more attractive to both humans and search engines. I am being very careful to avoid the phrase "Search Engine Optimization" in this opening few paragraphs despite it being used often in the request for friendly URLs. What we've added will definitely help with SEO but it's not a complete solution and neither is it intended to be. So, what do you have? In a nutshell: friendly URLs! The process to create and manage them is far more interesting than the end result, but more on that in a moment. Lets first look at some examples of the new URLs. Here's a few sample URLs from IPB 2.3.x: To show a forum (My Test Forum): www.board.com/forums/index.php?showforum=10 To show a topic (My Test Topic): www.board.com/forums/index.php?showtopic=99 To show a user (Matt Mecham): www.board.com/forums/index.php?showuser=30 There's nothing wrong with those URLs. They are short and concise and they spider very well, but we can do a little better to make them more attractive. If you are on a Windows webserver, you can use the 'query' string method which presents URLs like this: www.board.com/forums/index.php?/forum/10/my-test-forum www.board.com/forums/index.php?/topic/99/my-test-topic www.board.com/forums/index.php?/user/30/matt-mecham If you are on an apache based web server you can make use of the 'path_info' method: www.board.com/forums/index.php/forum/10/my-test-forum www.board.com/forums/index.php/topic/99/my-test-topic www.board.com/forums/index.php/user/30/matt-mecham Even better, if you can manage your own .htaccess files, you can make use of the mod_rewrite functionality. For convenience, the mod_rewrite code is generated for you. The end result looks like this: www.board.com/forums/forum/10/my-test-forum www.board.com/forums/topic/99/my-test-topic www.board.com/forums/user/30/matt-mecham What would happen if you used accented characters like this: M
-
Topic markers have evolved quite a bit over the past few years. What started out as being an almost secondary concern has become quite an important part of the user experience. A Very Brief History Early versions of IP.Board relied on cookies to track read topics. This worked fairly well but it wasn't without problems. Anything to do with cookies is always a little flaky. There is a very finite amount of information you can store and browsers have a habit of eating them or not setting them correctly. The biggest complaint, however, is that topics that you have read on one computer do not stay read on another computer. IP.Board 2 introduced topic markers that are stored in a database table. This greatly increased stability and allowed the read topics to remain read regardless of computer used. However, this did lead to some performance issues on busier boards as the topic marker table is often in demand whcih can result in locking issues and processes queueing. Also, the code wasn't centralized and sprawled through different files making maintenance very difficult. Learning From Experience It was obvious we needed to centralize all the code and create a common, simple public interface as a matter of course. We also wanted to allow our other applications (such as blog and gallery) to use this system without having to copy code. Making it truly extensible also allows modification authors to use this centralized system without having to maintain their own code. Next up was the performance issue. How could we increase performance without losing functionality? The most obvious answer was to store the read topics in the user's session. This was fine in principle but there were obstacles to overcome. First of all, session handling in IP.Board 2 is not entirely centralized. There is a class but it is not used exclusively. Other files such as register.php and login.php are allowed to modify the session table without telling other classes what it did. When you're trying to preserve data, this cannot be allowed. Also, session handling in general isn't an exact science. IP addresses change meaning new sessions can be created several times during a single visit. The solution to these problems must be robust, centralized and extensible. So, what's new? First up was tightening up session handling. All session management is now performed through a single class (publicSessions). Other files that need to manage sessions are done so via publicSessions. This allows this one class to keep tight control over the session data. The biggest challenge was to allow sessions to handle the marker data and only allowing the markers to be written back to the database table when the session is deleted. The theory is sound and simple but the execution a little trickier. The gory details of which are explained a littler further down for the technically minded. In brief, the system loads the markers from the database table when a new member session is created. The markers are saved in with the rest of the session data when the session is updated at the end of a page view (to update the running time and location, etc). This continues until the session is due to be removed, for example, when the member is no longer active and the session is older than the allowed time. The sessions to be removed are captured and allowed to be examined by other classes. The item marking class examines these sessions and writes back the marker information to the marker database. The system also makes use of cookies to give some permanence for guests and those that are not logged in. The cookies are limited to the last 100 items read to prevent cookies being sent that are too large. The end result is a much leaner system which should be much more efficient and presents a very simple public interface. Here's some sample code to give you a taste: $itemMarking->markRead( array( 'forumID' => 2, 'topicID' => 10 ) ); # To fetch the read status of an item if ( $itemMarking->isRead( array( 'forumID' => 2, 'itemID' => 99, 'itemLastUpdate' => 1200098989 ) ) === TRUE ) { .... } # To fetch the last time an item was marked $lastMarked = $itemmarking->fetchTimeLastMarked( array( 'forumID' => 2, 'itemID' => 99 ) ); # To mark an item read (an item is a topic for the forums 'application') Modification authors can write their own plug-ins and make use of the system with minimal coding effort. Listen Up, Here Comes The Science Bit For the more technically minded, this section explains how the system works within the new IP.Board 3.0.0 framework. The first challenge was to make proper use of __construct and __destruct within PHP 5. This is very straightforward in the case of __construct; this is run when the class is initialized. There are no problems with that. The item marking class grabs and filters the cookie data and also grabs and filters the session item marking data in __construct. The real issue is with __destruct. Specifically the order in which they are called. In PHP 5.0.0 to PHP 5.2.4 they are called in the order they were created. This means that by the time __destruct() is called in item Marking the DB connection has been closed which isn't at all helpful. This is because the DB is set up before classItemMarking. Now, as of PHP 5.2.5 the __destruct order has been reversed. This means that the DB connection will still be available. However, that's not very convenient for code that has to be robust on different platforms and with unpredictable version of PHP. Another solution was needed. Thankfully register_shutdown_function() executes before any __destruct() calls, so we can reliably use this. There is a __myDestruct function in the ipsRegistry class which calls __myDestruct() in all the registry child classes (DB, Member, Request, Cache, Settings, etc). The member registry class makes use of this to fire a manual destruct function within itemMarking to allow it to save back any data for deleted sessions and to provide information for the session update. A __myDestruct fires within the public session class to actually update the sessions and delete the expired ones. Phew! As you can see, there's a lot going on behind the scenes in IP.Board 3.0.0 which makes full use of all PHP 5 has to offer.
-
Way, way back in the early days when we were planning IP.Board 3, a primary consideration was to completely overhaul the output engine to add several new features and to increase extensibility. Out with the old... The system in IP.Board 2.x is really just a perfunctory "engine" build around a few methods in a class. There was no real cohesive structure with many different files and functions accessing 'skin' methods. We decided that a virtual re-write was required. We didn't want to be tied to a single output format. Back when IP.Board 2 was first written, there were no iPhones and the idea of actually viewing a forum on a cell phone seemed more than a little crazy. Times have changed. ...and in with the new At first glance, the new system isn't that different from what we had in IP.Board 2. It's when you scratch the surface that its power becomes more obvious. The first change is that you can have an unlimited depth for child skins. In IP.Board 2 you could only have one level of child skins which was very limiting to some skinners. Also, in IP.Board 2 there was a single master skin from which each skin inherited from. While there is still a single "master" skin in IP.Board 3.0.0, it's completely transparent and instead you can set up several different "root" skins for which child skins inherit from. This instantly makes for more flexibility. In IP.Board 3, each skin can have multiple CSS files which can be hard-positioned within the ACP to ensure the correct load order for correct cascading. Also, each skin allows you to set group-based permissions so that you can determine exactly who can view and select skins. And finally, guests can select a skin (as long as you give them permission to do so!) Going Deeper: User Agents IP.Board has a completely new user-agent system where you can add new user-agents and group together several. This system is now used for the 'search engine spider' settings. This also means that you can tie a user agent to a specific skin. Do you want your iPod touch and iPhone users to use a specific skin? You can do that very easily within the ACP. Taking it further, you can even designate specific versions for each user agent. Do you want to take advantage of Firefox 3 or IE 8? That's very simple to do now. You can even set a range of versions very simply. Going Deeper Still: Output Formats The biggest change is that IP.Board can now handle multiple output formats. By this we mean that IP.Board has a plug-in architecture to allow completely different processing for HTML, XML or even WAP. This system is completely extensible so modification authors can easily write their own engine plug-ins which drop in and are available for use immediately. Each skin must choose an output format to use which means that you can have completely different skin sets for XML and HTML bringing in even more flexbility. You can also use a "gateway" file to set the output format. IP.Board 3 ships with two gateway files. "index.php" which is tied to the HTML output format and "xml.php" which is tied to the XML output format. This means that "index.php?showforum=1" and "xml.php?showforum=1" enable the correct output engine instantly. Putting it Together Take this scenario: You want to support WAP using XML with XLST templates specifically for Nokia cell phones. Done, done and done. All without having to make a single PHP modification. Now that's a powerful system!
-
A while ago, I posted about the new template tags system in IP.Board 3.0.0. After some initial feedback on the syntax and having developed the system further, I felt it was worth revisiting in our blog. The template tag system is still based on PHP classes (extends and implements, PHP5 fans) which act as plug-ins. These plug-ins are only run when the templates are cached which makes the system very fast and very efficient. The new tag syntax is very straightforward and easy to remember: {parse plugin="data" option="value" option2="value2"} The updated system also allows you to embed tags, which we'll see later. First off, here are the tags which are currently being used in IP.Board 3.0.0 Expression This tag is used to parse an in-line PHP expression which returns a value. This can be any inbuilt PHP function or any IP.Board function. {parse expression="intval($data['count'])"} {parse expression="IPSText::alphanumerical_clean( $data['value'] )"} This makes it very easy to format data on the fly and removes the need for obsessive intval'ing in your code to ensure that a correct value is displayed such as a zero rather than nothing at all! Template This tag is used to insert any other template bit from IP.Board. {parse template="pageLinks" group="skin_global" params="$data['start'], $data['end']"} The benefit of this tag is immediately obvious. You don't have to 'hack' the PHP sources to re-use template bits. Variable This tag is used to set and alter a template variable. To set up a variable with they key 'className': {parse variable="className" default="tdRow1" oncondition="$data['foo'] == 'bar'" value="tdRow2"} To alter the variable based on different data: {parse variable="className" oncondition="$data['foo'] == 'foo'" value="tdRow3"} To use the variable: {parse variable="className"} Lets look at a practical example: {parse variable="className" default="row1" oncondition="$data['banned'] === TRUE" value="rowBanned"} <foreach loop="$key => $data"> <div class="{parse variable="className"}"> </foreach> Striping While Rikki was working on the new IP.Board skin, he mentioned that it would be very handy to have some kind of tag that could handle the 'zebra' striping (the alternate row colours when viewing a forum list, etc). To set up a striping tag: {parse striping="someKey" classes="tdrow1, tdrow2, tdrow3"} To use a striping tag: {parse striping="someKey"} When parsed, this tag will cycle through the classes, repeating the cycle if required. Let's look at a practical example: {parse striping="rows" classes="row1, row2"} <foreach loop="$data as $key => $value"> <div class="{parse striping="rows"}">$key = $value</div> </foreach> AddToHead It can be difficult to adhere to strict standards when dealing with templates and piece-meal code. Often you compromise and have javascript and CSS scattered throughout the finished document. IP.Board allows you to add content to the document <head> very quickly and simply: {parse addtohead="/js/someScript.js" type="javascript"} {parse addtohead="/css/someCSS.css" type="css"} {parse addtohead="<script>alert('boo')</script>" type="raw"} Date In previous versions of IP.Board, all the date processing was performed inside the PHP code which made it very hard to customize the date formats beyond what was allowed in the Admin CP. Now, IP.Board allows you to process dates using the date tag in templates. Examples: {parse date="now"} {parse date="1765346750" relative="1" format="long"} {parse date="-1 hour" format="manual{d, m, Y}"} URL IP.Board 3 allows a very easy way to format URLs without having to reply on special PHP variables. Examples: {parse url="showtopic=1" base="public"} {parse url="showforum=5" base="publicWithApp"} {parse url="photo1.jpg" base="upload"} Format Number IP.Board 3 allows a quick and easy to to access the built in "format_number" function. Example: {parse format_number="123456"} Replacement To keep our tags uniform, we've removed the old 'macro' tag (<{MACRO_HERE}>) and replaced it with the new 'replacements' tag: {parse replacement="macroKey"} Of course, the real power in this system is that you can very quickly and simply create your own template tags and drop them in the template tags folder for them to be available instantly.
-
In a previous blog entry on IP.Board 3.0's new framework, I mentioned that at the core of the new framework is something called 'ipsRegistry'. This blog post will go into more detail and will be of interest to modification authors. Overview IP.Board, like most complex applications, has a need for 'core' data, like settings, session and input data ($_GET, $_POST, etc). There is also a need for a database connection and access to global objects like cached data. It would be incredibly wasteful to make each file and class set up a database connection, load any cached data, authorize the browser session and build up any settings. This could happen many time in any IP.Board view. Clearly there is a need for a way to initialize this data once and then pass it throughout the application. This is exactly what the ipsRegistry is for. Previous versions of IP.Board relied on the 'ipsclass' object as a registry; but this quickly became tarnished without clear boundries as more and more data was attached to it. It was more of a global variable than anything else. The new registry is composed of clearly defined objects. Also in previous versions initialization of the settings, caches, input data, sessions, etc was handled over a scattering of classes and files. The new registry is a single point for this initialization. This initialization occurs when init() is called. This makes it much easier to use IPS registry data in your own scripts and modifications. Previously you would have needed to copy out half of the default index.php. Now your scripts can be as simple as: require_once( 'classes/base/ipsRegistry.php' ); /* Call init which loads up input, caches, settings, creates the DB handle, authorizes and loads the member $registry = ipsRegistry::instance()->init(); print $registry->request()->getField('foo'); $registry->DB()->do_update( 'table', array( 'foo' => 'bar' ); ); print ( $registry->member()->getProperty('member_id') ) ? 'Hello ' . $registry->member()->getProperty('members_display_name') : 'You are a guest'; require_once( 'conf_data.php' ); Gathering Input from URLs and Forms One key part of IP.Board's initialization was the sanitization of $_GET and $_POST into a clean array. This was vital to the overall security of the board. IP.Board 3.0 as a 'request' registry object for this and all functions relating to this initialization (parse_key, parse_value, etc). This is available via either $this->request->getField('foo') or $this->request['foo']. This effectively replaces $this->ipsclass->input['foo']; Cached Data There are many improvements to the caching backend which we'll go into more detail in a later blog entry. The cache registry object simply returns the cache when requested via $this->registry->cache()->getCache('forums'); The cache is loaded and unpacked during initialization so you do not need to manually call it. The Database So much of the database code has been refactored that it's almost a separate blog on its own. Here, I'll concentrate on the registry object. The main function is getDB() which is interfaced via $this->DB (or ipsRegistry: :D B() when outside of class scope). This is a simple function to return the DB object which is set up during initialization via setDB(). This happens transparently when the file is included and init() called (which is done so by ipsController.php). You may set up more connections by specifying a key, for example: return $this->registry->DB( 'newConnection' )->build_and_exec_query( array( 'select' => '*', 'from' => 'table' ) ); $this->DB->setDB( 'mysql', 'newConnection', 'user@localhost.com', 'password', 'localhost' ); Settings The setting cache is loaded and unpacked during initialization. This registry object merely returns the setting when requested via $this->settings->getSetting('foo') or $this->settings['foo']. Member Information Previously member information was scattered through 'ipsclass'. IP.Board's member registry centralizes this information. This object sets up the incoming IP address, browser and operating system and attaches it to the member class. It also authorizes the current browser session (via cookies or an inline URL) and loads up the session member and builds up the permission arrays. All this is done during initialization meaning there is less to do on the front end. I hope this post has given you some insight into the IPS registry and the improvements it brings not only to IP.Board itself, but also to modification authors who will find their workload greatly reduced by automatic set up of all the required information. If you find that you do not need all of this information set up, then you can write a child class of ipsRegistry and alter the 'protected' functions to prevent them from initializing.
-
HTML logic has been a feature of Invision Power Board for quite some time now. Although we didn't make much use of the '<foreach>' tag so that skins could be backwards compatible, we did make good use of the <if> <else /> logic. Now that we have a clean slate with v3.0, we can really make some positive changes. Invision Power Board 3.0 makes full use of the existing HTML logic and adds some more functionality. This allows for some dramatic customization without touching any of the PHP code. Where possible, each 'view' (board index, topic listing, viewing a topic) has a single template. Previous versions 'stitched' together several templates (as many as 30!) to create a single page view. This meant that some items were fixed and unable to be moved. For example, on the board index, it was not possible to move the board stats above the list of forums. Likewise it was not possible to move the active users below the board statistics. Now you can. You can move any item to any place for that view without having to edit the PHP files themselves. This will really open up designer's creativity and allow some really unique looking templates. Another leap forward for Invision Power Board 3 is the ability to use display logic in the templates themselves. Naturally, we were always able to use <if> and <else /> but you can now use the following standard tags: The Date Tag: Examples: {%date="1210012321"|format="manual{d m Y}"%} {%date="-1 day"|format="long"%} {%date="now"|format="long"|relative="false"%} For the first time, you can now explicitly specify a date format on a per-use basis. The tag accepts either a unix 'timestamp' or a human string like 'now', '-1 day', 'tomorrow', etc. The format parameter can either be a standard IPB date format (long, short, joined, etc) or a manual PHP Date format. The Parse Tag: Examples: <parse expression="substr( $data['name'], 0, 10 )" /> <parse expression="sprintf( "14", "There are %s apples in the bag" )" /> This parse tag allows you to make on-the-spot parsing using PHP code. This tag is replaced with the value returned from PHP. The URL Tag: Examples: {%url="foo=1&bar=2"|base="public"%} {%url="foo=1&bar=2"|label="Click Me"|base="public"|id="myLink"|class="linkCSS"|onclick="this.function()"%} The first example will actually create the entire <a href='' ... >...</a> HTML chunk whereas the second example will only return a formatted URL. The main reason for this tag is to prevent hardcoded entire URLs or even fixing part of the URL to a setting. In IPB 2.3 it wasn't unusual to see this: <a href='{$this->ipsclass->base_url}&act=login'>Log In</a> The new method would be like so: <a href='{%url="act=login"|base="public"%}'>Log In</a> The 'base' value being 'public' tells the template engine to use the public URL and not the ACP url. The real power of this feature lies in the return value being automatically fed via formatURL() which can return a friendly URL if friendly URLs are enabled. The Variable Tag: Example: <variable key="tdColor" oncondition="$foo == "green"" value="green" /> <variable key="tdColor" oncondition="$foo == "black"" value="black" /> <span style='color:<variable="tdColor" />'>Hello World!</span> <variable key="tdColor" default="blue" /> In this example, depending on $foo having a value of green: <span style='color:green'>Hello World!</span> This tag allows you to decide in the template itself how part of the template should display without having to edit PHP code. This is a handy tag for use in foreach blocks to alternate between colours when showing posts, topics, etc. Custom Tags The tags URL and date tags shown above use the {%tag="foo"|param="bar"%} format. These are actually custom plug-ins. You can write your own custom plug ins and they are available immediately within the templates. You could even modify the default plug-ins to change their behaviour. We're looking forward to how these new tools are used in your own templates!
-
One of the biggest discussions we had during Invision Power Board 3.0's planning was whether or not to drop support for PHP 4 and require a minimum of PHP 5. The advantages of using only PHP 5 were numerous and we really felt like we could increase security and efficiency by taking advantage of the new PHP 5 features. This decision became much easier when we learned that PHP 4 was no longer being developed. To really see the benefit of using PHP 5, one must first consider how Invision Power Board's new framework is made possible by PHP 5. Although Invision Power Board 1 and 2 were loosely based on the 'front controller' design pattern, it had no real framework to hang the code on. The closest it had to one was the 'ipsclass' super-class. 'ipsclass' was a convenient method of transporting various classes and functions around Invision Power Board. Convenient, but not ideal. One had to pass this 'super-class' from class to class forcing PHP 4 to use a reference (and being severely punished when forgetting!). This super-class contained almost all the 'core' functionality of Invision Power Board. Member, input and database objects were attached along with numerous other classes and functions. None of which was ordered in any logical format. We have recoded Invision Power Board 3.0's framework from the ground up. We have done away with the 'ipsclass' super-class and employed the 'Controller -> Command -> View' pattern. This allows us to quickly add new code and to allow fast refactoring of our existing code. This pattern is built upon the 'IPS Registry'. This is a singleton class which maintains interfaces to several other registry objects (database, request, settings and member). Each of these objects maintains a clear place within the registry. This allows us to pass core data through the different levels of our pattern. Other functions from 'ipsclass' are moved into singtons: "IPSLib"; disparate functions that do not belong elsewhere, "IPSText"; functions for parsing and cleaning text, "IPSCookie"; functions to handle cookie management and "IPSMember"; functions that deal with loading, saving and parsing members. This offers a clear structure with clear boundries for each singleton class. Being singletons, you do not need to pass or reference the class in other files. Here's an example: IPB 2.3 Code $value = $this->ipsclass->settings['board_name']$id = $this->ipsclass->member['id'];$this->ipsclass->input['f'] = 2;print $this->ipsclass->get_cookie('foo');$text = $this->ipsclass->txt_alphanumerical_clean( $text );print $this->ipsclass->class_forums->build_info(); print $this->ipsclass->input['name']; IPB 3.0 Code $value = $this->settings->getSetting('board_name');$id = $this->member->getProperty('member_id');$this->request->setField( 'f', 2 );print IPSCookie::get('foo');$text = IPSText::alphanumerical_clean( $text );print $this->registry->getClass('class_forums')->build_info(); print $this->request->getField('name'); It's worth noting that we have also applied the ArrayAccess interface to the registry, so you may access them like so: $this->settings['board_name']; print $this->request['name']; Although the code examples use $this->request, $this->member, etc, these are set up in a constructor. You would pass the IPS Registry singleton into the class. Here's a typical constructor: { $this->registry = $registry; $this->member = $registry->member(); $this->request = $registry->request(); $this->settings = $registry->settings(); $this->DB = $registry->DB();} function __construct( ipsRegistry $registry ) You could also access the ipsRegistry class directly, although this is strongly discouraged: print ipsRegistry::instance()->request()->getField('name'); PHP 5 offers a much better OOP (object orientated programming) environment where references are assigned automatically. You can also chain along functions, which we make great use of. This allows us to do some neat trickery, like so: IPB 2.3 Code print $this->ipsclass->compiled_templates['skin_boards']->board_index( $data ); $this->ipsclass->load_template('skin_boards'); IPB 3.0 Code print $this->registry->getClass('output')->getTemplate('boards')->board_index($data); You'll note that you no longer have to implicitly load the template anymore. This is handled within the 'getTemplate' function if it's not already loaded. This object is then returned for use to chain onto 'board_index()'. This simple adjustment of code makes for less manual code and less room for error. We are also making great use of PHP 5 abstract classes and interfaces to define extensible classes. This will make it much easier and clearer for others writing their own additions to Invision Power Board. Having a clear interface to work with will reduce errors in development and formalize how you may access Invision Power Boards class structures. The 'controller -> command' structure is built so that you may add new modules and sections dynamically without the need to change a single line of code elsewhere in the script. Modification authors can just drop in new folders and Invision Power Board will run them when called correctly via a URL. The controller makes use of variables in a URL and safely loads a command file if a matching command file is located. For example: "appcomponent=core&module=global§ion=login" is mapped to "applications/core/modules_public/global/login.php". We make use of the Reflection class functions to ensure that any potential command file is a sub-class of the controller to prevent the risk of tampering. We've barely scratched the surface, but it's clear that Invision Power Board 3's framework is very powerful and code-efficient. This is only made possible by the advancements in PHP 5 that we've taken full advantage of.
-
A lot of the time a significant portion of development time is spent working on items that the end user will never get to see. A case in point is the attachments system. The attachments system was introduced back in the early days of IP.Board. During the development of IP.Board 2.2 the attachment system came under focus for two reasons. The first reason was that we were already testing the 'new' attachment system in IP.Dynamic (as shown in this movie) and we wanted to introduce that into IP.Board. The original system forced one to wait for an entire page reload before continuing with a post. The new version used a fancy concoction of javascript and iframes to upload data 'inline' - that is, without a page reload. Naturally, AJAX (XMLHttpRequest) would have been an obvious choice but it is impossible to upload data using current XMLHttpRequest methods. The second reason was that the code was due an overhaul because it was originally developed long before we had our components system in place. This made the original system inflexible and not very extensible. Infact, Remco had to maintain his own attachments system for the IP.Blog component which duplicated PHP code, HTML templates and SQL information. With this in mind, we abstracted the code into component friendly modules. This allowed one to simply create a new module to manage the component's settings and upload paths. The main attachment class handled the rest. This saved us a lot of code re-use and allowed us to store attachments from several components in one table. We think the new interface alone is a marked improvement over the old system. It's much quicker and allows one to upload several attachments quickly. We have plans to further abstract the attachments system in a later version of IP.Board to allow attachments to be saved and retrieved in different formats. This rather minor feature really highlights how new web technologies and ways of thinking can push even a secondary feature further along and how we're always looking to improve the efficiency of IP.Board and its components.
-
Laurel resting is an activity that is banned at IPS and for good reason. While Brandon was busy working on the bug reports for IPB 2.2.0 back in the release candidate stages I managed to tick off another bunch of items from the IP.Dynamic to-do list. Here's a round-up of the latest features to be added. Staff Calendar We use an online calendar here at IPS to organize ourselves and to schedule meetings and other events and I thought that most other organizations would likely do the same, so it made perfect sense to add one into IP.Dynamic so that you can keep everything within one application. 4.5mb Quicktime Movie Task Manager Maintaining a website is a large and complex responsibility especially when shared with several other people. Some kind of task manager was a must to help keep staff on track and on schedule. 4.6mb Quicktime Movie Personal Topics This is like private messages, but better! The private message implementation found at IPB emulaltes the email system. You can PM one member and CC in others. The problem with that, like email, is that there is no real way to have a discussion with more than one person without a lot of copy and pasting. Enter Personal Topics. They behave in a similar manner to IPB topics with the topic starter becoming the moderator for the topic giving them the power to remove and block members from the topic. 6.4mb Quicktime Movie Content Versioning Every good CMS must be able to store versions of content and even more importantly roll back to them even when they've been deleted from the content repository. Unfortunately a lot of so-called CMS software doesn't have this functionality. Fortunately, IP.Dynamic isn't like most other CMS software! IP.Dynamic versions pages, system templates, content templates and content blocks. It's a modular system so one can write ones own modules to add versioning to any area of IP.Dynamic. 9.6mb Quicktime Movie.