The Importance of Version Numbers

As a developer, I pay attention to version numbers and version number set ups used by different companies. To an end user, version numbers are meaningless, useless numbers. To administrators, it can mean a great deal when deciding when to upgrade, what it will entail, things like that. To a developer, they should be supreme. Let’s first explain a few terms before I go into why I feel version numbering is all over the place at times and for some developers.

Alpha

An “Alpha” version of a software should be incomplete and barely useable. When you see Alpha, it should imply the possibilities for major malfunction and/or incomplete features. These are things you want to be weary of installing live.

Beta

Beta software is mostly “feature complete” and is usually accompanied by bugginess and issues. However, this usually implies that a complete set of features to call the software “useable” is there. You should be weary of using this live, but it may not be as troublesome as some would lead you to believe.

Release Candidate (RC)

When a software is tagged with “Release Candidate” it implies that the developer feels its stable enough to be used in a live environment and doesn’t include any known show stopping bugs or something huge. Feature complete usually comes with an RC release as any new features may include a plethora of new bugs that need ironed out.

Gold or “Stable” or nothing

After RC we go into the “gold” stage where you don’t have any type of defining word after the version number. At this point, a developer implies that it is live-site ready and should be stable and as bug free as possible.

The Numbers

The actual version number (1.0, 2.3, etc.) is what really matters. Anyone who has ever used anything has received a three numbered version number: x.y.z where x is a “branch”, y is the “major” version, and z is the “minor” version. I’m a firm believer in this type of numbering system and you can find large software projects utilizing this: Mac OS X, XenForo, vBulletin, IPB, phpBB (I believe), and many, many others. Now, what does Branch, Major, and Minor versions mean?

Branch

The branch of a version is basically the “software” iteration and shouldn’t be changed for minor things. For example, XenForo (http://xenforo.com) is currently on v1.0.4, and is working toward v1.1.0. Both these versions are built upon the same basic idea and framework, so they are apart of the same branch of the software. Branch number increases should imply major changes to the underlying architecture of the software, major updates to the front end (in terms of online software, let’s put this as a major rewrite to the front end style) or something that users should entirely be sure they are ready to upgrade to. Any add-ons, plugins, or modifications should be thoroughly tested and updated before upgrading for these types of versions.

Maj0r

The major version should be reserved for new features. Major new features should only be included in major versions for reasons that’ll be described in “Minor,” but another reason is to signify that upgrading from 1.0 to 1.1 of a software version may be very advantageous to an administrator because it will bring fresh new things to the site. For example, XenForo is working on v1.1, and one feature I know will be included is administrator statistics. As an administrator, if it was imperative that I keep track of statistics, I could upgrade to v1.1 knowing that new features will be there (including the one I need). Major versions, for me at least, should go through their own alpha, beta, and RC release cycles to ensure “gold” status is achieved and its not prematurely released.

Minor

The minor version should be updated with every release (except Patch Level releases, but I feel that they are usually unnecessary and should be included in a minor release). A minor version should not include new features as the basic idea of upping a minor version (v1.0.4 vs v1.0.3) is that the higher the minor version is, the more stable the software is. These are ‘bug fixes’ for the most part. However, that isn’t to say new features can’t be included in these updates, they shouldn’t be large. For example, XenForo shouldn’t include a Custom BBCode Manager in v1.0.5 as that is a major feature and should be reserved for v1.1 or later as it should be properly tested before release. Minor features (such as XenForo’s inclusion of Google’s +1 button in v1.0.3) should be OK because a minimal amount of developer testing is all that should be necessary. These versions imply to users that when they update the software should work the same, just “better” than it did before. These versions should also be as backward compatible as possible with previous versions of the same major release for add-ons and such.

So, now you may be asking me why I just wrote all this out and are wondering what the point is. Well, I constantly see versions such as v1.0 RC go directly to v2.0RC when in reality, it should be v1.0 RC1 to v1.0 RC2 because its still the same code as v1.0RC 1, just more stable. Or things go from v1.0.2 to v2.0.0 with the inclusion of one or two features. I wish there was a universal standard for version numbering (something as I had laid out above, or something similar) that means I could learn what a single version number implies and look at any of them. When I see a version going from v1.0 to v2.0, I expect a lot to be changed and thoroughly test everything I can so that I can be sure my site doesn’t break. But when I do a test and see that only feature X was, I see a developer trying to boast their image and ‘speed of development.’ This is a mere annoyance that I have with many developers….

Separating Data from Logic

This article is geared more towards a program that is compiled, rather than web languages. But it should still have its applications within any application you can think of.

When you develop an application, there are two main parts of anything: logic and data. Web languages, such as PHP and JavaScript, can intermix the two just as easily, but what happens when it comes to updating the data to a newer set? For a web application, it is usually easy as it usually just requires updating a single file, and everything that uses it now contains the most up to date system. However, let’s look at what happens when you want to update something within a compiled application.

Let’s use a practical example: Radio lists within iTunes. The iTunes application allows streaming of internet radio stations, and it has a large collection of these stations pre-added into the system. However, when I started listening to The Voice Internet Radio, I  tried to supplement this list of internally stored radio stations with a direct link to their radio station. I first went to the preferences, to find out they didn’t allow adding of them (nor did they in the Radio UI). Next, I pulled a trick I attempted with Coda (and succeeded to a point) by locating a database file within the application bundle itself to add a new station. To my surprise, they had none, meaning the entire database is created  within the compiled code. So, how do they update and add new stations? Here’s the process:

  1. Open filex.m within the iTunes code base.
  2. Edit an NSArray (or NSDictionary) and add the new station’s information.
  3. Verify application is ready for redistribution.
  4. Its not? Wait. Repeat steps 3 & 4 till it is.
  5. Recompile the entire application.
  6. Force users to download a large update to an application to gain a new radio station.

What happens if the sole reason for updating the application is to ADD a station. If the application is 162.2MB (iTunes is) and you only employ a download and replace system rather than incremental upgrades (I’m not entirely certain how to implement that without use of powerful libraries such as Andy Matuschak’s Sparkle Framework for Mac OS X ), you are forcing a huge update, for something minor. This process is what happens when you build your data into your application.

Now, let’s look at a process of including the data in a file, and building it dynamically via your software. When I went to look to add it, I was looking for a .plist file within the application bundle. A .plist file is more or less a form of XML for those of you unfamiliar with Mac OS X’s internals. If I created a .plist of all radio stations, it could be a few kilobytes to a few megabytes in size. When I want to add a new song, I can write an “update” script to retrieve and download an updated file, and overwrite the current version. Immediately, the application would be reading in the new stations when it loaded the Radio UI. Let me tell you, I will take a few kilobyte download in comparison to what that is over 150 MB.

In the world of web programming, computer programming, and other programming, this type of separation can and should be apart of a proper Model-View-Controller set up. Your models store the data (or retrieve it from a file), while your controller (compiled code) uses your models to fetch data. Its the easy, proper way to do it. Of course, if you wanted utterly complete control over the content of something (such as Apple does), compiling your data with your logic is your best way to go. I’m all about allowing freedom and simplicity for users down the road.

Edit: It appears that my example is out of whack, as iTunes fetches the radio list from the internet and populates it that way. However, the principle still remains.

The benefits of a native applications

When you discuss native applications for your site on an iPhone, Android, or blackberry, you normally wan to add a simple, easy to use application that allows easier access for your end users. But, I have recently been using some more applications on my iPhone, because I find it easier to get items than I did on my Droid, and have noticed reasons why you’d do native applications, and how an application should function.

1. Decrease in network usage.

Many cellular companies are starting to limit the amount of data that may be used by any given phone in a billing cycle without charging outrageous overages. If we take a look at what an application should be doing, it should help with this problem.

Smartphone users are data hungry, no doubt about that. I am actually writing this while on my iPhone. The goal of an application should be to make your website easier to navigate and feel like it belongs on the device. So, in theory, the only data passed between an application and the website should be small: information only, and allow the phone to properly display the data.

2. Provide tighter integration with the hardware

These phones can do a ton. They have a camera, APIs, and I can even print from my phone. By creating a native application, you can actively take advantage of these features, much better than any website can. Just looking at what I can put into my upcoming application (A native application for The Pitt News), really excites me. When I use the application, it should be feeling native and such, not a slow clunky web page.

3. Optimized UI

I’m most definitely a guy who is all about proper, easy to use, easy to understand UI. A website is designed for a browser, with a precise mouse and lots of room. Placing a website on a 480×360 (?) screen makes it a requirement to zoom and hope you click the right thing. You can really optimize the way things look and work on a native application, using things such as menus, tables, and tabs. Much more than you can do with a website.

4. Provide as much functionality as possible

When you market a native application, you want to make it a viable solution for replacing use of the site on a mobile phone. You shouldn’t provide a dogleg feature and market it as your “official” native application. When I look at the iPhone application I am developing users can read, share, and comment on stories. If I place an application on the market that solely reads articles, they are still dependent on the website for everything else, making the application partially useless to many. Take Unfuddle’s iPhone application (No offense should be taken). I downloaded it expecting to be able to create, manage, and respond to tickets, as well as view commits and source code and notebooks. However, it basically just provides simplistic functionality, and I still find myself powering up Safari to do all the tasks I wanted to do.

Now, many of you may be asking why I thought I needed to explain my feelings. The applications I have been using break every convention. Quite a few have just built wrappers for an iPhone website, that breaks norms in the iPhone world. I know I will be replacing media temple’s application with a sorta hidden part of the Pitt news application.

Programming Roadblock

So, I was writing my new add-on for XenForo a few days ago (finals week ended yesterday) and I had my plan set. The UI would be amazing, with fields being added to the form via jQuery as required based on the number of images being uploaded to the gallery. Kind of like XenForo’s very own attachment uploader, but with form fields too. However, I struck a some what challenging problem: I had to figure out how to SAVE all the information when an infinite number of images could be uploaded at once? Initially, I thought of adding a hidden variable with a list of IDs that were added, but this seemed trouble some (JavaScript can be iffy sometimes). I wanted a fool-proof method that just worked. And I’m rather proud of the solution… Let’s go into the problem (in terms of programming theory).

The issue is simple: Write a function that saves data to the database from the given fields (unique to an ID). This sounds simple but in reality can be very hard to accomplish. The problem lies in how you pass along the necessary IDs to the saving function.

The solution? Quite simple actually: A transaction system. When a user starts the upload process (by opening the index.php?gallery/upload page) it calls my gallery model and creates a transaction. Through a single line of code (Note: This is still in ‘experimental’ phase, so the method may change so it actually works. :P ):

	public static function createTransaction()
	{
		return uniqid(XenForo_Visitor::getInstance()->get('username'), true);
	}

And this Transaction ID is returned. And then passed on to the save and upload functions. When uploading, the “transaction_id” is saved to the database. Then, when the saving occurs (I didn’t write this yet… so not PHP code here) I use my model again and call the function “getImageIdsByTransaction($transaction)” to fetch all image IDs and then I run a loop in the save function and modify the filtering function to include necessary IDs.

This may sound dumb, but I am extremely proud of myself for coming up with this. Now, the difficult part? Getting the UI rewritten and to work!

Also, yes, my “secret” add-on is a gallery. I hope to have it in early beta (useable form) by the end of my break.

Sneak Peak: My New Add-On

So, I’ve started a new add-on. Its fairly major and I thought I’d give you a sneak peak… Without really giving you much information. Its the Splash screen for the add-on, isn’t as prominent as you want, but its there… Time to guess away in the comments!

Programming Theory and PHP

This is the first of many “learning PHP” articles that I plan to write for a few friends.They will start with the basics, and progressively advance to a point that I am at. Within them, I will have code examples, explanations, questions, challenges, and “quizzes” for readers. The intent is there to promote learning, and for me, I’ll be presenting the information in a way that I feel is logical, smart, and informative. I do not claim to be a master at PHP, much to the contrary, I feel I am intermediate and have a solid knowledge of the language. However, I feel like I am a “master” programmer in the fact that I feel that I can accomplish a task that is set in front of me, sometimes in a language I have no prior experience with.

This first article in the series will have a focus on the following topics:

  1. Programing theory
  2. Programming Logic
  3. Logic & Theory in PHP
  4. Coding Style, Syntax, and Consistency
  5. Basic PHP introductions
  6. And how to utilize the five above goals to accomplish a solid program in PHP.

First off, let’s discuss the theory behind programming and why it is important to think logically as you program. Programming languages are not intelligent beings. They cannot think, make decisions, read your mind, or speak to you in any way, shape or form. Herein lies the issue… As a programmer, to achieve a higher “rank” in the world of programming, you must have the ability to think logically and plan how your code is going to work, interact with itself, store data, present data, make calculations, and be a meaningful application. The more convoluted your code, the harder it becomes to maintain, and the sloppier you get as a programmer. In more traditional languages, you have strict type variables, syntax, and you must really think about how to store items. PHP provides you with a small relief in the fact that it does not require any specific type. A variable can hold absolutely anything you wish. Arrays will grow as needed, and store any information you need. There are some bonuses to arrays that will be discussed in a later article. But first, I would like to give you a short example comparing a traditional language (I will be using Java, as the syntax is similar to PHP).

Example: I want to have a program that will take numerical input (input is assumed in the following code excerpts and may be ignored. Input / Output will be discussed in a later article. A few Java specific dependencies are left out, so this is not completely functional Java code.) and do some calculations on it. The calculations will be simple and not very difficult. The differences will be pointed out after the code is presented.

Java:

public class Ex1
{
	public static void main(String[] args)
	{
		Scanner inScan = new Scanner(System.in);
		int firstInteger = inScan.nextInt();
 
		double firstDouble = firstInteger;
 
		firstInteger++;
		firstDouble--;
 
		int secondInteger = (int)firstDouble;
		double secondDouble = (double)secondInteger / (double)firstInteger;
		int thirdInteger = secondInteger / firstInteger;
	}
}

PHP:

$firstInteger = $_GET['integer'];
$firstDouble = (double)$firstInteger;
 
$firstInteger++;
$firstDouble--;
 
$secondInteger = (int)$firstDouble;
$secondDouble = $secondInteger / $firstInteger;
$thirdInteger = $secondInteger / $firstInteger;

Now, let’s look at the Java example. The first few lines define Java-specific syntax (everything must be in a class in Java). The next line takes input and places into an integer value. Note, it is key to know that is placed into an integer value, as that can only store values such as -1, 0, 1, 2, 3, etc. The next line defines a double variable and inserts the integer variable into the double. Now, this will turn any number to have a .0 at the end. So when I insert the integer 1 into the double variable it will become 1.0. Next, we increase the integer variable and decrease the double variable. The ++ and — operators are equivalent to adding and subtracting 1, respectively. Our next line casts the double  variable into integer to store in a new integer variable. This is required as Java is type specific and a double can’t be placed into an integer due to loss of precision. This is important into the comparison to PHP. Next we want to preform a double division, meaning we want our answer to be in decimal form. However, if you divide integers, the program will place a floor() and return the largest full number contained within the answer (basically, you just drop off the decimal points). So we must again cast it back into a double to add decimal precision to the code. If we didn’t, the cast is done automatically, but only on the answer to the integer division, so it is necessary to cast each variable. Lastly, we do integer division.

Now, to the fun part, PHP. The code does the exact same thing as the Java code, so I will spare you the time in rereading an explanation and only explain the differences. First of all, the syntax is clearly different. Code in PHP does not have to be contained within a class, function, or anything other than the opening <?php tag. Also, if you hadn’t noticed, variables in PHP are noted by a dollar sign ($). A variable without the dollar sign is considered a constant and will be discussed later. Also, since PHP isn’t type specific, casting doesn’t need to be done, unless you want to turn 1 into 1.0 (as done above). Integer division doesn’t exist, and a double is always returned. Casting can be done in PHP, but is hardly necessary unless you want to guarantee input (which doesn’t always work).

Now, lets discuss how logic works in PHP and how programming theory applies. Since PHP doesn’t require its code to be encapsulated, it can be completely procedural (execute code from top to bottom), event driven (code added in functions and called as needed. This also allows for greater reusability), or completely Object Oriented (Referred to as OO or OOP, and is very similar with inheritance, and data encapsulation). The challenge in programming in PHP is to maximize reusability while creating a proper application. The idea comes into play when you plan on how you want to store, interact, and display data. More on this type of programming will be discussed later, while everything will be strictly procedural.

Moving on to the actually programming aspect, we want to talk about programming style and consistency. A programmer’s programming style can be as unique as a finger print (especially when working with a small group) where those who read your code will be able to tell you who wrote what lines of code. Your style can’t be taught to you, preached to you, or even fixed by someone. Below, I have outlined my programming style which is how all the code presented in these articles will be formatted:

  • All if() statement blocks are surrounded by the curly braces to signify what is meant to be doing that.
  • Curly braces have their own dedicated line, tabbed to the same indent as the line it corresponds to.
  • Each block of code (surrounded by curly braces) is indented by one (as can be seen in the Java example)
  • Variables have camelCase naming conventions.

The above simplifies my coding style, but they are what I live by, and will be expanded on as we move throughout the articles. It is also a good idea to name your variables, functions, and classes in a logical manner that will allow you to remember what their users are for. Programs that consist of horribly named programs. Variables are case sensitive, so $VAR is different from $var. Here’s a quick example of what I mean:

Here’s some horrible PHP naming.

$x = "Hello";
$xx = $x . " World";
$X = $xx . "!";
 
// Lots of random code here..
 
echo $X;
// Oh wait, what was $X again?

Imagine if  ”// Lots of random code here…” is replaced by 3,000 lines of logic. Would you remember what $X was? Now, on the contrary, here’s some good PHP naming:

$hello = "Hello";
$helloWorld = $hello . " World";
$finishedHelloWorld = $helloWorld . "!";
 
// Lots of random code here...
 
echo $finishedHelloWorld;
// Oh yeah, that's what that means!

Not only is the second example easier to understand the logic (the . operator appends the strings together), but it allows for easy remembrance of a specific function. But, it is also important to remain consistent in your naming and syntax. If you start naming variables in camelCase, then randomly switch to_using_underscores, it will become twice as difficult to remember what variable names were and you’ll spend a good portion of time looking up these names. However, this is the one part I tend to be inconsistent with when I’m writing code, as I mix up what I’m doing, but in these articles they will all be consistent (if not, give yourself a bonus point and point it out to me!).

Lastly, let’s now discuss some basic PHP syntax that you’ll need to learn like the back of your hand to program in PHP. If you don’t learn these (or have no desire to learn these), congratulations on making it thus far in the article, but I would ask you to stop reading. You want to learn? Good, let’s continue.

  1. Whitespace is an ignorable item in code. White space only matters within strings (encapsulated by double or single quotes)
  2. All statements are terminated via a semi-colon ( ; )
  3. Variable names always start with a dollar symbol ($)
  4. Variable names may only include a-z, A-Z, underscores, and numbers. They may only start with an underscore or letter and may not start with a number! Variables must also be all 1 word (or separated by underscores only).
  5. The variable “$this” has a special meaning within OOP and may not be redefined by any of your programs. Usage of $this will be described in a later article focusing on the basics of OOP.
  6. Variables exist in a specific scope. Variables defined within a specific code block are only available within the defined block (and any sub-blocks, excluding function calls). Modifying the variable scope to get required variables into a required scope is possible, and will be discussed later.
  7. Functions and class names may never begin with a dollar sign ($) or a number, as the dollar sign indicates a variable, and beginning with a number is invalid via the PHP parser. However, calling functions dynamically through a variable storing the name is possible, and will be discussed at a later date (as this is a more advanced topic)
  8. Functions may or may not return a value.

Utilizing the above described theories, knowledge, syntax, and coding style, you as a programmer will be able to implement a PHP program. If you mess the syntax up, PHP will fail to be able to parse it and errors will be thrown to you and your users. The coding style is more for code maintainability and readability, but trust me, it will help you in the long run. Finally, thinking logically will allow you to implement a program that just makes sense, rather than one that may work, but just confuses anyone who attempts to look at your code.

Now, its Quiz Time!

This quiz is not required, but recommended to test your progress in learning. I will post an answer key within the next week (most likely as a linked PDF as to not expose the answers to anyone except those who actively seek them). Also, it is best if you attempt to avoid looking back at the material above to answer these. If you have any questions, please leave a comment and I will attempt to answer them to the best of my ability. If you would like your answers checked, please email me (remove spaces): “j dentel 91 @ gmail . com”

  1. When programming in PHP, is $1_integer a valid variable? Why or why not?
  2. Is PHP a type specific programming language? Why or why not?
  3. Explain what uses casting a variable has in PHP.
  4. Where does whitespace matter, where can it be ignored?
  5. Can variables, function names, or class names include spaces in them?
  6. What does ++ and — accomplish while programming?
  7. Is $variable = $variable + 1; equivalent to $variable++;
  8. When dividing integers that are not evenly divisible, what type of answer would you expect to be returned?
  9. Can you place a float variable’s value into an integer variable? Why or why not?
  10. What are the three types of ways your code can be organized? Provide a brief description of each.

And, just a bonus question or two… These will be discussed in later articles, obviously, but just challenge yourself to think ahead and see if you can use your logical thinking to figure these out.

  1. Are
    $variable++;

    and

    ++$variable;

    equivalent?

  2. Are
    $variable2 = $variable--;

    and

    $variable2 = --$variable;

    equivalent?

Answers: http://kingkovifor.com/wp-content/uploads/2010/12/ProgrammingTheoryQuiz.pdf

Creating a Custom BBCode in XenForo: A Comprehensive Guide

This is meant to guide developers (novice to advanced) in adding a custom BBCode to your XenForo installation. That is until Kier & Mike implement a backend manager, such I as I have done with BBCode Manager. Personally, I feel that is the easiest way to accomplish this… but if your curious, keep reading! I encourage that too!

In XenForo, BB Codes are handled via PHP classes (as in most other bulletin boards), and to add more, you must extend these classes, adding more tags to the list. Below, I will take you step by step in adding three tags: a spoiler tag (one that may or may not have options), a simple header tag (h2), and another simple float tag (with an option).

Note: This article does not cover how to utilize a template for your display, and everything is currently handled via PHP. I may revisit this in the future to add a “template based” PHP BBCode.

The Basic Setup and Classes

In this section, I will describe the basic setup required to setup the BB Code system to utilize your newly created tags / classes.

Step 1: Place a test/development board into debug mode.

I do not suggest you place a live, functioning XenForo forum into debug mode. Always, always utilize a test / development board. To accomplish this, add the following PHP code to the beginning of your config.php file after creating a back up of the file:

$config['debug'] = 1;

Reloading your Administrator Control Panel (ACP) should now show a ‘Development’ tab next to the ‘Tools’ tab. If it does not, please make sure you uploaded and overwrote your existing configuration file.

Step 2: Create your add on.

Since XenForo wants everything related (and rightly so), our next step is to setup a new “add on” for use on your site (you will export this at the end of this article to import to your live board). For simplicity, I shall name mine “XenBBCode”. To add a new add on you want to go into the Development tab within the ACP and click on “Create Add-On.” In here, you want to place your ID in the ID slot. This ID will be used later in the article in naming of classes, and should not include any spaces.

Step 3: Create your event listener class.

The next required step, is to create an event listener class to allow extension of the BB Code system. To accomplish this, create a file located in /library/XenBBCode/EventListener/ and called BbCode.php. You will need to create the XenBBCode directory and its sub-directories yourself. These are unique to your add-on. (Note: XenBBCode should be the ID of your add-on for the rest of this article.) Now, add the following contents to your new PHP file…

class XenBBCode_EventListener_BbCode
{
    public static function listen($class, array &$extend)
    {
        if ($class == 'XenForo_BbCode_Formatter_Base')
        {
            $extend[] = 'XenBBCode_BbCode_Formatter_Base';
        }
    }
}

Within this file, as you may have noticed is a class. The title of the class should always follow your directory structure. Every time you have a slash ( / ), replace it with an underscore ( _ ), and just chop off the .php extension. So, let’s look at that transformation:

XenBBCode/EventListener/BbCode.php -> XenBBCode_EventListener_BbCode

This allows XenForo’s autoloading of classes to recognize and know the location of the file your new class resides in.

You may have also noticed the contents of the class is a single function, with two arguments. These arguments are gathered from XenForo, and will be explained in the next step. The code within the function is something we only want run when it attempts to load the BbCode formatting class that comes with XenForo, so, we add the condition. And lastly, the code within the condition tells XenForo which class should be used to extend the default class (and it is $extend[] so that XenForo can automatically resolve hierarchy and such for you). Within the function definition, it is also important to have the word “static” in there as XenForo calls this function statically, and never creates a new object.

Step 4: Add the Event Listener

Now that we have created our class, we want to tell XenForo to utilize the class when it attempts to load the specified class. We do this through the extremely powerful Event Listener system. This system basically opens up the entire forum to extension through just a few extremely well placed “events.” So, let’s add ours to the mix.

Within the development tab, lets click on “Code Event Listeners” and then again on “Create New Code Event Lisener.” Within this screen, we want to set a few options. Firstly, we want to choose “load_class_bb_code” as the event we want to listen in on. Once you choose this option, you will notice a a description pop up below the select. Within this description, you will see a list of arguments, that correspond to the arguments we added into our listen() function in the last step. These must be absolutely exactly the same for the system to work correctly.

Now, let’s set the callback method. The first option is the class name you created (XenBBCode_EventListener_BbCode, for my example) and the second is the method. The method will be listen as that’s the way I told you to name it in the last one, but it should be the name of the function you created to extend the BbCode parsing class.

The last two options you want to set are the description and add-on. The add-on should be the one you created in the second step. The description can be anything you want, but should be used to describe why the event listener is added. For this example, I placed “Adds three custom BBCodes to XenForo.”

Adding the Custom BB Code Parser Class

Now that we have the basics of our add-on / custom BB Code system in place, we can start actually adding BB Codes to our system. To do this, we will create a class that will be used to house all three BB Codes that will be added. By creating a simple class, you ensure all are properly added and created for use on your board. I’m sure you can already figure out the class name we will be using, but in case it didn’t immediately jump into your head (don’t worry, there’s nothing wrong with that) the class name will be: XenBBCode_BbCode_Formatter_Base.

Step 5: Create the Extending Class

Next, we want to create the class that we defined as the extension to the default BB Code formatting class. Create a new file in /library/XenBBCode/BbCode/Formatter/ and name it Base.php. Again, refer to Step 3 as to why the file is to be located in this directory and named Base.php. Add the following code to your class (Note: If this class was left as is, your forum’s BBCode would be unchanged, as it is identical to the included BB Code formatter):

class XenBBCode_BbCode_Formatter_Base extends XFCP_XenBBCode_BbCode_Formatter_Base
{
	protected $_tags;
	public function getTags()
	{
		$this->_tags = parent::getTags();
		return $this->_tags;
	}
}

You will (or should) notice that our class is extending a class that isn’t defined. This will be resolved by XenForo automatically, but you always want to place an extends statement in your definition. Our classes extend a class with the same name as ours, just with the prefix of XFCP_. Make sure this line is there, or else, our add-on will not work as desired, if at all.

Within the class, we define a protected variable _tags, which will hold an array off all the tags and their information. This variable name was taken from its parent class, but isn’t strictly required to be _tags, but I recommend you do this.

Now, we overwrite a function called getTags() inside of our class so that we can add tags. When the parser starts to parse BB Code, it’ll call our getTags(), rather than the included one. Which means, if we just define our tags, everything as simple as [b][/b] would not be displayed as bolded text. So, the first line in our getTags() class should always be:

$this->_tags = parent::getTags();

This statement will return an array of all default XenForo BB Codes and add them to our array, which we will build upon in later sections of this guide. And the last line should always be returning our array, so the parser can build and display posts correctly (with custom tags!).

Creating a simple, no option required BB Code (

)

Next, we are going to look at how to add our BB Codes to the system. To do this (and for the rest of this article), we will be working in the class we defined in Step 5, and not moving outside of that class. So, when I ask you to add code, modify a function, etc., it should be done within this class.

Step 6: Add the definition for an [h2][/h2] tag.

Within the getTags() function, we want to add a new entry into our _tags[] array, so that it’ll recognize [h2][/h2] as a valid tag. To do this, we want to modify our getTags() function to look like such:

	public function getTags()
	{
		$this->_tags = parent::getTags();
		$this->_tags['h2'] = array(
			'hasOption' => false,
			'replace' => array('
<h2>', '</h2>
')
		);
		return $this->_tags;
	}

What we do, is we add a new index to the array (‘h2′), and define an array of options as its value. For this simple tag, we want to say that it does not have an option (‘hasOption’ => false) and that it should be replaced with

for [h2] and

for [/h2]. This is defined in the option ‘replace’, which itself houses an array of two things: the replacement for the opening tag (first index) and the replacement for the second tag (second index).

If you save and upload this file, you should now be able to utilize [h2] within your posts. If you comment out these lines, [h2] will not be replaced when BB Code formatting occurs. Test this, just to make sure there weren’t any errors (there shouldn’t be, tho).

Building a simple, option required BB Code (float)

Now that you’ve seen how to add a simple, no option required BB Code for your forum via PHP and Event listeners, let’s now add one that adds a small bit of complexity, but is still relatively simple. We want to create a [float][/float] tag that tags the direction (left, right) as the option. in theory, if a user knows his way around this system and CSS, you can style you’re float any way you want (as it’s just a ) in different posts. So, let’s dive right in.

Step 7: Add the definition for a [float][/float] tag.

Now, we want to add another entry into our index, so let’s do that by modifying getTags() once more to give us the following PHP code:

1
2
3
4
5
6
7
8
9
10
11
12
13
	public function getTags()
	{
		$this->_tags = parent::getTags();
		$this->_tags['h2'] = array(
			'hasOption' => false,
			'replace' => array('<h2>', '</h2>')
		);
		$this->_tags['float'] = array(
			'hasOption' => true,
			'replace' => array('<span style="float: %s;">', '</span>')
		);
		return $this->_tags;
	}

Now, you will notice that the form is very similar between float and h2, but the key difference is that ‘hasOption’ => is now set to true. Meaning, when XenForo starts to parse the thread, if you put [float][/float], it will leave it as such, as you haven’t specified an option. You can get around this via a more complicated method that’ll be explained with the addition of a spoiler BB Code in the next section.

Now, you must provide XenForo with data on where you want the option to be displayed and you’ll want to do this by placing “%s” where you want the option to display. Note: You may only use this method once, meaning you cannot put the option in more than one place. If you’d like to do this, please look at adding the spoiler BBCode, else, it won’t work correctly and will spit out PHP errors and warnings.

Now, save you’re class, and over write the existing class. Adding [float="right"]Hi![/float] should create a new div that floats to the right. Something such as [float="right; right-margin: 5px"]margin![/float], should still work just fine.

Building a complex BB Code (spoiler)

You have seen just how easy it is to create a few simple BB Codes via the PHP classes described above. In the next few steps, we will take this a few steps further and create a “complex” BB Code tag. In our example, the tag just needs to use the option in multiple places in the out put, so this’ll show you how to accomplish that. However, this type of method can be used to create advanced BB Codes with multiple options, a option/no option set up (as in this case also), and other cool features you may want to incorporate. As long as you can do it via PHP, it can be done via this method.

Step 8: Add the definition for the [spoiler][/spoiler] tags.

Again, we want to update getTags() to add another entry to our list of BB Codes. Your new getTags() function should look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
	public function getTags()
	{
		$this->_tags = parent::getTags();
		$this->_tags['h2'] = array(
				'hasOption' => false,
				'replace' => array('<h2>', '</h2')
			);
		$this->_tags['float'] = array(
				'hasOption' => true,
				'replace' => array('<span style="float: %s;">', '</span>')
			);
		$this->_tags['spoiler'] = array(
				'parseCallback' => array($this, 'parseValidatePlainIfNoOption'),
				'callback' => array($this, 'renderTagSpoiler')
			);
		return $this->_tags;
	}

If you’ll notice in the tag definition for ‘spoiler’ we only define the option ‘callback’ rather than ‘hasOption’ or ‘replace’, because both of those functions will be handled by the function that’ll be added in the next step. Honestly, I’m not sure what ‘parseCallback’ does, but I modeled my tag definitions for PHP callback functions off of the shipped class and they all had this.

Step 9: Create the function renderTagSpoiler()

This new function will have 2 arguments, and the PHP code will utilize an array of data passed to it by the parser. This data is in the form of this (no option):

array(4) 
{
	["tag"]=>
		string(4) "tagDefinition"
	["option"]=>
		NULL
	["original"]=>
		array(2)
		{
			[0]=>
				string(6) "[tagDefinition]"
			[1]=>
				string(7) "[/tagDefintion]"
		}
	["children"]=>
		array(1)
		{
			[0]=>
				string(6) "insideTags"
		}
}

or (with option)

array(4)
{
	["tag"]=>
		string(4) "tagDefinition"
	["option"]=>
		string(1) "option"
	["original"]=>
  		array(2)
  		{
			[0]=>
				string(10) "[tagDefinition="option"]"
			[1]=>
				string(7) "[/tagDefinition]"
		}
	["children"]=>
		array(1)
		{
    		[0]=>
				string(6) "inisdeTag"
		}
}

Our PHP function (renderTagSpoiler()) must be able to decipher the array and build an output as desired. So, let’s create the function with the following PHP code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
	public function renderTagSpoiler(array $tag, array $rendererStates)
	{
		if($tag['option'] != NULL)
		{
			$buttonText = $tag['option'];
		}
		else
		{
			$buttonText = 'Show Spoiler';
		}
 
		$output = '<div class="spoiler">
						<div class="quotetitle">
							<input type="button" value="' . $buttonText . '" style="font-size:11px;margin:0px;padding:0px;" onclick="
								if (this.parentNode.parentNode.getElementsByTagName(\'div\')[1].getElementsByTagName(\'div\')[0].style.display != \'\')
								{
									this.parentNode.parentNode.getElementsByTagName(\'div\')[1].getElementsByTagName(\'div\')[0].style.display = \'\';
									this.innerText = \'\'; this.value = \'Hide\';
								}
								else
								{
									this.parentNode.parentNode.getElementsByTagName(\'div\')[1].getElementsByTagName(\'div\')[0].style.display = \'none\';
									this.innerText = \'\'; this.value = \'' . $buttonText . '\';
								}" />
						</div>
						<div class="quotecontent">
							<div style="display: none;">' . $tag['children'][0] . '</div>
						</div>
					</div>';
		return $output;
	}

Now, let’s look at this function in closer detail. First, we are checking to see if the $tag['option'] is NULL, and if it is, set some default text. Now, if you need to see where $tag option is set, please refer to the previous code blocks for the structure. Now, once done, we want to build the output (of which I did in a “pretty” form, but it can be all on one line if need be). And once we build the output as desired, we want to return that output to the parser, who will then display it inside of the post.

Installing

After finishing building and testing your new BB Codes, you want to export the add on (click on Manage Add-ons in the home screen of the ACP and Controls -> Export on the one you created for this). Download the entire /library/XenBBCode/ folder and upload it to your live sites library directory (keeping the structure under XenBBCode folder there too). Install the add-on in your live site’s Add-On manager.

Complete Files

Now that we have built our classes, let’s look at the two files we need for this (note, I have not listed the back end code event listeners / add on set up, as that entirety is listed in steps 1-4).

File: /library/XenBBCode/EventListener/BbCode.php

1
2
3
4
5
6
7
8
9
10
class XenBBCode_EventListener_BbCode
{
    public static function listen($class, array &$extend)
    {
        if ($class == 'XenForo_BbCode_Formatter_Base')
        {
            $extend[] = 'XenBBCode_BbCode_Formatter_Base';
        }
    }
}

File: /library/XenBBCode/BbCode/Formatter/Base.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class XenBBCode_BbCode_Formatter_Base extends XFCP_XenBBCode_BbCode_Formatter_Base
{
	protected $_tags;
	public function getTags()
	{
		$this->_tags = parent::getTags();
		$this->_tags['h2'] = array(
				'hasOption' => false,
				'replace' => array('<h2>', '</h2')
			);
		$this->_tags['float'] = array(
				'hasOption' => true,
				'replace' => array('<div style="float: %s;">', '</div>')
			);
		$this->_tags['spoiler'] = array(
				'callback' => array($this, 'renderTagSpoiler')
			);
		return $this->_tags;
	}
	public function renderTagSpoiler(array $tag, array $rendererStates)
	{
		if($tag['option'] != NULL)
		{
			$buttonText = $tag['option'];
		}
		else
		{
			$buttonText = 'Show Spoiler';
		}
		$output = '<div class="spoiler">
						<div class="quotetitle">
							<input type="button" value="' . $buttonText . '" style="font-size:11px;margin:0px;padding:0px;" onclick="
								if (this.parentNode.parentNode.getElementsByTagName(\'div\')[1].getElementsByTagName(\'div\')[0].style.display != \'\')
								{
									this.parentNode.parentNode.getElementsByTagName(\'div\')[1].getElementsByTagName(\'div\')[0].style.display = \'\';
									this.innerText = \'\'; this.value = \'Hide\';
								}
								else
								{
									this.parentNode.parentNode.getElementsByTagName(\'div\')[1].getElementsByTagName(\'div\')[0].style.display = \'none\';
									this.innerText = \'\'; this.value = \'' . $buttonText . '\';
								}" />
						</div>
						<div class="quotecontent">
							<div style="display: none;">' . $tag['children'][0] . '</div>
						</div>
					</div>';
		return $output;
	}
}

Closing Comments

Now that we have accomplished building our classes, we can now happily use our new custom BBCodes on our board. However, there is a lot that I may have not covered in this guide, as I was not 100% sure of all the options that can be used. Maybe Kier or Mike can comment, but if you’d like to see more options available to BB Codes, check out this file:

/library/XenForo/BbCode/Formatter/Base.php

Also, the power of this system is that you have access to everything. Options, phrases, visitors, etc. are all accessible.

Also, if you’d like to install these BB Codes, please install my add-on, as it now includes all three of these, plus, a few extra ones.

Welcome

Well, you have stumbled upon my lovely home on the internet. Well, its relatively new, so there’s not much to see here… (yet). Stay tuned for some pure awesomeness articles and posts and helpful resources. ;)