MODx Revolution 2.0.0 Beta2 Released

My partner Greg will probably blog about this a lot more on his blog because he primarily handles all of our MODx projects as to where I handle most of our WordPress projects.  Not having worked with MODx very much I am still impressed with all that it brings to the table in terms of rapid deployment and content management.

If you are looking for a good enterprise level content management system there are several of them out there, Drupal, Joomla, and even WordPress has enterprise level applications these days, but for us MODx has been a great platform for us to build upon.  We recommend it highly whenever we talk with other developers and one of the things that we like about it the most is that it is supported by a highly active community of developers.  The activity in the support forum at MODx is unlike anything you have ever seen tied to open source software.

If you are already familiar with MODx, here’s a short list of the improvements / new functionality that makes up Revolution 2.0.0.

  • Manager upgraded to ExtJS 3.0 from 2.2; now faster and more responsive.
  • You now can drag/drop Elements and Resources directly into any content field and watch as it builds the tag for you.
  • We’ve added a Visual Tag Builder for Elements, where after drag/dropping them into fields, you can select a Property Set and customize values from a form, and watch it build the tag syntax for you after you’re done.
  • Now you can Quick Update or Create any type of Element or Resource. Don’t want to leave a page to update another Document? Quick Update it!
  • Toggle your Manager layout between tab-based and portal based with a setting.
  • Speed improvements to the backend manager.
  • Fixed issues with uninstalling packages; they properly revert and save zipped copies of older versions.
  • Package Download section now grays out packages you’ve already downloaded.
  • Manage all Plugins assigned to an Event now easily from an intuitive grid.
  • Added new methods for zip compression for installation and packaging that speeds up install time.
  • Many, many bug fixes across the framework

As soon as I get caught up on a few projects I have going on I plan on spending some time getting acquainted with it on one of our sandbox environments.

Update: This just goes to show the response time that all of the people surrounding the MODx project have regarding support, etc.  I just posted this blog post on my blog and walked to the kitchen to get a bottle of water.  When I returned I already had a comment waiting on me from Shaun with Collabpad.  Here’s what he had to say regarding the beta 3 release:

You might want to check out 2.0-beta-3. There was an important security fix in beta3 that we recommend users upgrade to.

Thanks for the article! We’re glad you like MODx, and hope you enjoy the future of MODx in Revolution. Feel free to leave us feedback and suggestions for Revolution on our forums (they are super active! it’s crazy!)

Work Faster, Drag to Done, Quick Create/Update Elements and Resources: MODx Revolution 2.0.0-beta-2 has Landed | MODx CMS / CMF

Locking Down Authentication Inside PHPRunner

One of the biggest challenges you face when building hosted applications is how to prevent brute force or guessed password authentications.  Especially given the number of warez type applications that are out there that allow unsavory users to do just that.  Well, I found a resource on Xlinesoft’s website that demonstrates how to do block a user after three unsuccessful attempts to login to your application.

This schema uses visitors IP address to store log attempts in the database and block access to to the login feature for 30 minutes after the third unsuccessful attempt. This schema involves Events function which is available in ASPRunnerpro 6.0/PHPRunner 5.0, I have reposted the processes involved for PHPRunner below, but you can find the ASPRunner notes here…

Step One:
In MySQL Server run the following script to create table in your database that logs login attempts. The box below demonstrates the MySQL command.

   1: CREATE TABLE `LoginAttempts`
   2: (
   3: `IP` VARCHAR(20) NOT NULL,
   4: `Attempts` INT NOT NULL,
   5: `LastLogin` DATETIME NOT NULL
   6: )

Step Two:

Open your PHPRunner project and go to the security tab and switch on the “Create Login Page” checklist.

Check the Username and password from database option and choose appropriate fields. If you have no table in which all of the login details are stored you have to create it.

Step Three:

Add three global events on the Events tab: BeforeLogin, AfterSuccessfulLogin, AfterUnsuccessfulLogin.  Below you will find the PHPRunner example for this:

   1: <?
   2: function BeforeLogin($username, $password)
   3: {
   4: //********** Custom code ************
   5: // check if this IP address is currently blocked
   6: global $conn;
   7: $sql = "select Attempts, LastLogin from LoginAttempts where ip = '" . $_SERVER["REMOTE_ADDR"] . "'";
   8: $rs = db_query($sql,$conn);
   9: $data = db_fetch_array($rs);
  10:  
  11: if (!$data || !strlen($data["LastLogin"]))
  12:   return true;
  13:  
  14: $atime = db2time($data["LastLogin"]);
  15: $time = mktime($atime[3],$atime[4],$atime[5],$atime[1],$atime[2],$atime[0]);
  16: $diff = (time()-$time)/60;
  17:  
  18: if ($data["Attempts"]>=3)
  19: {
  20:   if($diff<30)
  21:   {
  22:     echo "<p align=center><br><font color=red><b>Access denied for 30 minutes</b> <font></p>";
  23:     return false;
  24:   }
  25:   else
  26:   {
  27:     db_exec("update LoginAttempts set Attempts=0 where ip = '" . $_SERVER["REMOTE_ADDR"] . "'",$conn);
  28:     return true;
  29:   }
  30: }
  31: return true;
  32: }
  33:  
  34: function AfterSuccessfulLogin()
  35: {
  36: //********** Custom code ************
  37: // clear previous attempts
  38:  
  39: global $conn;
  40: db_exec("update LoginAttempts set Attempts=0 where ip = '" . $_SERVER["REMOTE_ADDR"] . "'",$conn);
  41:  
  42: }
  43:  
  44: function AfterUnsuccessfulLogin()
  45: //********** Custom code ************
  46: // increase number of attempts
  47: // set last login attempt timeif required
  48: {
  49: global $conn;
  50: $sql = "select * from LoginAttempts where ip = '" . $_SERVER["REMOTE_ADDR"] . "'";
  51: $rs = db_query($sql,$conn);
  52: $data = db_fetch_array($rs);
  53:  
  54: if($data)
  55: {
  56:   $attempts = $data["Attempts"]+1;
  57:  
  58:   if($attempts==3)
  59:     db_exec("update LoginAttempts set Attempts=" . $attempts . ", LastLogin=now() where ip = '" .$_SERVER["REMOTE_ADDR"] . "'",$conn);
  60:   else
  61:     db_exec("update LoginAttempts set Attempts=" . $attempts . " where ip = '" .$_SERVER["REMOTE_ADDR"] . "'",$conn);
  62: }
  63: else
  64:   db_exec("insert into LoginAttempts (Attempts,IP,LastLogin) values (1, '".$_SERVER["REMOTE_ADDR"] . "',NOW())",$conn);
  65: }
  66: ?> 

Step Four:

You should finish the code generation / compiling process and upload your application.  It’s important to remember that by doing this, your visitors have to enter their username and password to gain access to the site. After the third unsuccessful login attempt, their IP addresses access will be denied for 30 minutes. When the visitor tries to login when the account is blocked they will see message saying access is denied.

Find out how to do this for ASPRunner also…

——————————————————————

There are a lot of other useful resources outlined for PHPRunner users in the Articles section on Xlinesoft’s website, you can find them here…

Facebook | PHPRunner User Group

phplogo I recently started a PHPRunner user group on Facebook in hopes of connecting with other PHPR programmers.  So far we have 6 members, myself and of course Sergey, who is the CEO of the company that puts out PHPRunner, and four other guys.  My main goal for creating the group was to provide some awareness of PHPRunner as well as being a way for PHPR developers to reach out to one another for paid assistance on projects built with PHPR.  Honestly there have been times that I would have hired a developer to assist me on projects if he was familiar with PHPR. 

Of course the formation of the group was met with a little opposition by users on the support forums because they didn’t see the need for such a group because the forums on xlinesoft’s website are already pretty active. I pointed out that there are user groups on Facebook for other IDE software clients such as CodeSmith, CodeCharge, and Eclipse and having a group for PHPR would probable help bring exposure to the product. 

I also have always felt funny about soliciting developers from message boards, not to mention it’s forbidden on a lot of support forums, but having a Facebook group is perfect for something like this.  I have made it very clear that the user group is not a support forum and is only intended for developers to network with one another, showcase their work, and to post projects for bid.  I am hopeful that the group will grow in size and we can develop a pretty good community but I am also a realist and I know that PHPR users make up a pretty small piece of the pie.  As I am sure a lot of you that have talked with me about PHPR already know, I am pretty hung up on this code generation thing and out of all the products I have seen on the market, PHPRunner is the best one out there.

If you are looking to learn more about PHPRunner, be sure to checkout their website.

phprunner-screenshot

Facebook | PHPRunner User Group

Critch on VMware, Apache, PHP/MySQL

I am happy (bordering on giddy) that our server engineer / administrator Matt Critcher is now blogging, dude is probably one of the sharpest guys I have ever met and he is an all around cool guy to hang with too, but beware of the fancy cheese he brings to dinner parties because you could find yourself in the emergency room on New Years Eve thanks to a long-standing penicillin allergy.

As some of you might know we made the transition to Virtualization a while back and have been extremely happy with the versatility it has brought us with our managed hosting and vps products that it has allowed us to bring to our clients, but with growth there can also be growing pains, it is for this reason that I am so glad we have Matt in our corner, dude knows his stuff and he can get to the bottom of an issue better than anyone I have ever worked with.

Lately we have been transitioning to VMware and have had some issues w/ websites that are slow to respond via browsers, but yet they still ping out okay.  It’s been a weird week or so, here’s a post that Matt put together the other night about the issues, I thought maybe someone else could benefit from his findings down the road:

I posted a few weeks back that Pleth had transitioned some of their equipment over to VMware Server and for the most part it’s been a very smooth process. But, as of late we’ve ran into some slowdowns, especially on the VPS with Plesk (which happens to host several of our websites). After doing a bunch of research and spending many a late hour digging through tons of mpstat and other sysutils data I think I found the culprit(s).

VMware Server, unlike the ESX/ESXi products, does not run in a Type 1 Hypervisor. This means that the underlying OS (in our case Red Hat Enterprise Linuxwas tuned out of the box for a general all-purpose server. This configuration isn’t always optimal for a Type 2 Hypervisor. It works just fine as long as things are "normal," but as the new VMware server got a larger load (in terms of I/O and CPU) performance went downhill.

One of the major problems has to do with how VMware Server uses disk-backed memory files (*.vmem). There is great debate on the web whether or not you should disable them, but one thing that is clear — when a site is busy, the file will be updated with memory information to reflect the changing memory of the VPS in question. This is where the problem lies — servers with unga-bunga hardware RAID solutions with 15K RPM disks and tons of spindles have a less of a problem with it but moderate quad-core Xeon and SAS disks in a RAID1 configuration like we and most other webhosts our size have it is a bigger issue. All those writes causes a wait-state in the CPU and therefore a backlog of transactions to be processed causing said server slowdown.

One way to deal with this is to modify the /etc/sysctl.conf to add (or modify) the following parameters:

vm.dirty_background_ratio
vm.dirty_ratio

I set my vm.dirty_background_ratio = 2 and vm.dirty_ratio = 85

Basically what these 2 parameters do is dictate the percentage of memory that can be "dirty" before it begins to flush (background_ratio) and the percentage of memory that can be "dirty" before a forced flush begins. When these files are updated, we can either have them done in the background (hence the low number for background ratio) with pdflush which allows other processes to continue to run, or we can have them queued up and wait for a synchronous (forced) write causing the iowait states (hence the large number for dirty_ratio). The big gap between background writes and synchronous is to try to keep the background writes coming consistently and avoid the synchronous writes as much as possible. You’ll have to play around with these figures to see what works best for you. See this page about half-way down for a little more in-depth explanation of these two parameters.

I also made some configuration changes to PHP and Apache to try to get a tad bit more performance out of each of them. I had written out a whole list of stuff that I’d modified to post here, and as I was looking for websites to help explain the modifications, I stumbled upon this website from IBM that lists pretty much every change that I made to Apache and PHP.

If you want to tune your MySQL database, this website is invaluable. It explains almost every parameter that you can possibly adjust and how to adjust them. One that it doesn’t really get into though is

innodb_flush_log_at_trx_commit

Setting this to "2" will force the system to write out any changes to the transaction log when the commit occurs but will only cause a flush of this data from memory to disk once every second (which gets stuck in the scheduler and is handled in the background by pdflush). The default setting of "1" will write out to file and flush this data from memory every time a commit happens. On really busy servers with InnoDB tables, this can cause slowdowns if your server really isn’t designed to handle a heavy DB load (most webservers aren’t). The drawback to this is that if the system crashes, you could lose 1 second of writes. Depending on what you are doing, this might be acceptable. Setting this to 0 will cause the write every second, but if the server crashes you might lose a ton of data because nothing is done at transaction commit. Scary, but fast (to me, scary outweighs speed in this case).

None of these changes should be taken without first thinking about what might happen. We have a test box in our office that basically mirrors our production server that I could test on beforehand. The Apache and PHP config changes are easy — no server reboots required, and you’ll know almost immediately if you mess them up. If you modify sysctl.conf incorrectly, the server might not boot. Better test a few things out (a VMware VM is a perfect testbed for these settings) BEFORE you have downtime.

VMware, Apache, MySQL, and PHP Performance Tuning | www.mcritch.com

Central Arkansas Refresh Meeting

Just a heads up, the Central Arkansas Refresh Group is going to have our second meeting this next week (Tuesday, March 31) at the Starbucks located at 9401 N. Rodney Parham Rd. in Little Rock.  The start time is 6:00pm and we expect the meetup to last about 2 hours but with all of the networking that went on last week it’s hard to say what time we all need to tell our families that we will be home.  Also, if you haven’t already, please RSVP on the Facebook Event page so we can kind of get an idea for headcount…

Our Facebook group has grown to over 55 members now and continues to grow each week.  The group is open to everyone interested, our goal is to have a diverse crowd, here’s a little bit more from our website about the group: (compliments of David Kinkade, who is also promoting the group on his blog)

The Central Arkansas Refresh Group consists of technically minded professionals who make their living on the web — bloggers, software developers, graphic artists, social networking enthusiasts, photographers, videographers, marketing and media gurus, and website owners.

Centered in Little Rock, the Central Arkansas Refresh group launched in February 2009 to serve as a hub for education, networking and the sharing of ideas among like-minded web professionals.

The group is organized around the principles of the Refresh movement:

The Refresh Manifesto

  • Let’s Gather Great Minds
  • Let’s Share All Of Our Knowledge
  • Let’s All Grow And Learn
  • Let’s Promote Local Talent
  • Let’s Be More Than We Think Can Be
  • Let’s Make Our Cities Better

Membership is open to all interested web professionals, programmers and new media enthusiasts. Visit our Facebook page now to get involved.

Interested in being a corporate sponsor for the Central Arkansas Refresh Group? Visit our sponsors page for more information.

Facebook | Refresh Group Meeting

WordPress RSS Widget – target=’blank’ fix

If you have ever used RSS Feed Widgets on a website before you will know that one of the biggest aggravations is that the links always open in the same window therefore leaving your website. 

I have posted numerous threads about this in the wordpress forums and haven’t really gotten a lot of response back from the community as to how I could workaround this.  Here are some links to my posts:

Well, long story short, there is a workaround on this but you are going to have to get your hands dirty and mess with some code and editing the wp-includes\widgets.php file.

You will first want to go to about line #1525 in the code and insert target=’_blank’ in two locations on that file.  Here’s what it will look like once you are done:

   1: $title = "<a class='rsswidget' target='_blank' href='$url' title='" . attribute_escape(__('Syndicate this content')) ."'><img style='background:orange;color:white;border:none;' width='14' height='14' src='$icon' alt='RSS' /></a> <a class='rsswidget' target='_blank' href='$link' title='$desc'>$title</a>";

Next, you will go to approximately line #1621 and do the same thing here, adding the target=’_blank’ into the code.  It will look something like this:

   1: echo "<li><a class="rsswidget" title="$desc" href="$link" target="_blank">$title</a>{$date}{$summary}$author}</li>";

Now, as with anything you do to the core wordpress software, you are going to have to make sure you add this back should you ever overwrite the software doing an upgrade, etc.  Why this is not an option on the wordpress cms core functions yet is beyond me…

Create a Custom WordPress Plugin

One of my favorite websites these days is NETTUTS, they always seem to have fresh, interesting content everytime I visit their site.  Today Cristian Lupu posted a tutorial that walks you through the process of building your own WordPress Plugin from scratch that I thought was very well written. 

Basically the tutorial walks you through the process of connecting to an OSCommerce database and displaying products from that database on your WordPress Blog.  They also show you how to build your own configuration page for the WordPress Admin panel, which was very helpful to me.

Introduction

WordPress is gaining more and more popularity each day, not just as a blogging platform but also as a basic CMS, thus improving and extending its basic functionality becoming a day-to-day necessity for a lot of developers. Fortunately, the WordPress developers have foreseen these needs and added the possibility of customizing the basic functionality by adding plugins. Basicaly, a WordPress plugin is a (more or less) stand-alone piece of code that can be executed in different sections and stages within a page or site.

In today’s tutorial we’ll be talking about creating a WordPress plugin that extracts and displays products from an external OSCommerce shop database. We will start by describing the file structure of a plugin and where it must be included in the WordPress structure, then we’ll be having a closer look at how to make our plugin visible for WordPress and integrating it with actions run by its frame. Next, we’ll be creating a configuration panel for our plugin to allow the site administrator to customize it to his/her needs. Once done, we’ll be implementing the front-end functions themselves that will interact with the OSCommerce database and extract the required data. Finally, we’ll be modifying the default template to display the extracted data in the sidebar. Excited? Let’s get started!

Getting Started

While it would be possible to follow this tutorial by simply reading through it, I would recommend installing WordPress on your computer and follow the tutorial implementing all the steps. For this, you’ll need a local server running on your machine, like XAMPP for instance. Once you have it running, download and install WordPress. You will find extensive information about the installation process and troubleshooting on the WordPress site. For this tutorial we will be using release 2.7

Further on, you will need to set up an OSCommerce shop on your machine. You can download the latest release here: http://www.oscommerce.com/solutions/downloads

Files & Folders

First, we’ll need to create our basic files and folder structure. WordPress stores its plugins in the wp-content/plugins/ folder. This is the place where we’ll be adding our files as well. Normally, if your plugin is going to be very simple, you will include all the code inside one single PHP file. In this case, you will simply store the file in the folder mentioned above. However, in our case, we are going to use two files (one for the main plugin file and one for implementing the administration page) therefore we’ll be putting all our files in a specific folder that we’ll name oscommerce_importer. Go ahead and create this folder.

Creating the Plugin File

Next, we must create our main plugin file. We’ll name it oscommerce_importer.php. You can really name it whatever you want, it doesn’t make any difference.

If you now open your WordPress administration panel and navigate to the Plugins sections, your screen will look something like this.

As you can see, there is not the slightest sign of our new plugin. It’s time to change that and tell WordPress that our file is going to implement a plugin. The process to do so is very simple. All we need to do is add a plugin specific information header to our newly created file. This standard header will look like this:

   1: 1. <?php   
   2: 2.     /* 
   3: 3.     Plugin Name: OSCommerce Product Display 
   4: 4.     Plugin URI: http://www.orangecreative.net 
   5: 5.     Description: Plugin for displaying products from an OSCommerce shopping cart database 
   6: 6.     Author: C. Lupu 
   7: 7.     Version: 1.0 
   8: 8.     Author URI: http://www.orangecreative.net 
   9: 9.     */  
  10: 0. ?>  
  11:  

Simple enough, don’t you think? You can, of course, change the content of this header to your liking but make sure you keep all the lines, otherwise WordPress won’t correctly recognize your plugin.

If you refresh your administration panel’s plugin page, you’ll now see our plugin listed along with the other ones, click here for screenshot.

Working with Action Hooks

Our plugin is now shown in the administration panel so WordPress is aware of it. However, it doesn’t do anything as it contains nothing except of the information header. We are going to change this now.

WordPress offers a great way to include your plugin code in different places all over the template, be it physical positions within a page or logical positions within the process of building up a page that is going to be displayed. First, we are going to have a closer look at the second category, the logical positions, better known as action hooks.

Action Hooks

You can view action hooks as callback function. Whenever WordPress is executing a certain operation, like, for instance, displaying the page footer, it will allow your plugins to execute their own code that must be run at that exact moment.

For a better understanding, let’s consider a generic plugin called my_plugin that implements a function called mp_footer() that has to be run whenever the page footer is displayed. We will tell WordPress to call this function, at the moment of displaying the footer by using a special function called add_action():

   1: 1. <php add_action('wp_footer', 'mp_footer'); ?>  

The add_action() function takes the action hook name as its first parameter and the name of the function that must be executed, as a second parameter. This function call will be added to your main plugin file (the one containing the information header), usually, right under the function code that needs to be executed (mp_footer() in our example). You will find the full list of available action hooks in the WordPress Codex.

We’ll be using action hooks in the next chapter, where we are going to build the administration page for our plugin.

Creating the Plugins Administration Page

We’ll start the implementation of the module by defining its configurable parameters and make these accessible to the site administrator. Let’s see what these configuration bits would be:

  • Database settings
    • database host
    • database name
    • database user
    • database password
  • Store settings
    • store URL
    • folder for the product images

First, we need the database host, name, user and password in order to be able to connect to it and extract the needed data. Second, we need some general data about the store like its URL and the folder where the product images are stored. We need this information in order to be able to build the links because the paths contained in the database are all relative the previously mentioned product image folder.

Now that we know what we want to include in the configuration panel, it’s time to implement it. We’ll start by creating a new menu item to access the page and we’ll place it inside the Settings menu. Remember our chat about the action hooks in the previous chapter? It’s time to use this feature.

If you’ll scroll over the list of action hooks, you’ll see that WordPress also provides one that gets called when the basic menu structure has been generated (admin_menu) so, this would be the optimal place to chime in and create our own menu item.

Now that we identified the action we are going to use, all we need is to define our own function that will be called when this action hook runs. We’ll call our function oscimp_admin_actions() where oscimp_ stands for oscommerce importer and is used to create a possibly unique function name that will not get mismatched with any other function within WordPress or any of its plugins. Let’s see how the code will look like:

   1: 1. function oscimp_admin_actions() {  
   2: 2.       
   3: 3. }  
   4: 4.   
   5: 5. add_action('admin_menu', 'oscimp_admin_actions');  

As you can see, we are creating our function oscimp_admin_actions() then associate it with the admin_menu action hook using the add_action() function. The next step would then be to add some code to our oscimp_admin_actions() function to actually create the new menu item.

As with most WordPress things, adding a new menu item is also very easy. It all boils down to calling a single function. We would like to add our new menu item to the Settings menu so, in this case the function we need is add_options_page(). We’ll add the code inside the oscimp_admin_actions() function.

   1: 1. function oscimp_admin_actions() {  
   2: 2.     add_options_page("OSCommerce Product Display", "OSCommerce Product Display", 1, "OSCommerce Product Display", "oscimp_admin");  
   3: 3. }  
   4: 4.   
   5: 5. add_action('admin_menu', 'oscimp_admin_actions');  

If you refresh your admin page, you’ll see the new menu item appear under Settings. Click here for Screenshot.

Each existing menu has its own function to be used to add sub-menu items. For instance, if we would like to add our sub-menu item to the Tools menu instead of Settings, we would use the add_management_page() function instead of add_options_page(). You can find more details about the available options in the Adding Administration Menus section of the WordPress Codex.

If we get back to the newly added code line, you’ll probably notice the last parameter. This is actually a function name that will be called when the newly added menu item is clicked on and will be used to build the administration page of our plugin. Next, we’ll be adding this new function. However, before proceeding we should stop for a moment and think about what will be implemented on this page.

We already defined the parameters we want to make configurable (database host, name, user, etc) so these will have to be included in a form in order to allow the user to send the data to the database. Once the form is defined, we’ll need a bit of code that extracts the sent data from the form and saves it to the database. Last but not least, we need some code to extract the existing data from the database (if any) and pre-populate the form with these values. As you can see, there are quite a few things to do so, it might be a good idea to separate this functionality to its own file. We’ll name the file oscommerce_import_admin.php. Now, go and create an empty file with the given name.

As already mentioned, we’ll have to create the function that will display our plugin configuration page (we named this function oscimp_admin()). The code inside this function will be included from our newly created PHP file, oscommerce_import_admin.php

   1: 1. function oscimp_admin() {  
   2: 2.     include('oscommerce_import_admin.php');  
   3: 3. }  
   4: 4.   
   5: 5. function oscimp_admin_actions() {  
   6: 6.     add_options_page("OSCommerce Product Display", "OSCommerce Product Display", 1, "OSCommerce Product Display", "oscimp_admin");  
   7: 7. }  
   8: 8.   
   9: 9. add_action('admin_menu', 'oscimp_admin_actions');  

If you now click on the link under the Settings menu, you will be directed to an empty page. This is because our oscommerce_import_admin.phpfile is still empty. Click here for screenshot.

Next, we are going to create our form. For this we’ll use the following code:

   1: 1. <div class="wrap">  
   2: 2.     <?php    echo "<h2>" . __( 'OSCommerce Product Display Options', 'oscimp_trdom' ) . "</h2>"; ?>  
   3: 3.       
   4: 4.     <form name="oscimp_form" method="post" action="<?php echo str_replace( '%7E', '~', $_SERVER['REQUEST_URI']); ?>">  
   5: 5.         <input type="hidden" name="oscimp_hidden" value="Y">  
   6: 6.         <?php    echo "<h4>" . __( 'OSCommerce Database Settings', 'oscimp_trdom' ) . "</h4>"; ?>  
   7: 7.         <p><?php _e("Database host: " ); ?><input type="text" name="oscimp_dbhost" value="<?php echo $dbhost; ?>" size="20"><?php _e(" ex: localhost" ); ?></p>  
   8: 8.         <p><?php _e("Database name: " ); ?><input type="text" name="oscimp_dbname" value="<?php echo $dbname; ?>" size="20"><?php _e(" ex: oscommerce_shop" ); ?></p>  
   9: 9.         <p><?php _e("Database user: " ); ?><input type="text" name="oscimp_dbuser" value="<?php echo $dbuser; ?>" size="20"><?php _e(" ex: root" ); ?></p>  
  10: 0.         <p><?php _e("Database password: " ); ?><input type="text" name="oscimp_dbpwd" value="<?php echo $dbpwd; ?>" size="20"><?php _e(" ex: secretpassword" ); ?></p>  
  11: 1.         <hr />  
  12: 2.         <?php    echo "<h4>" . __( 'OSCommerce Store Settings', 'oscimp_trdom' ) . "</h4>"; ?>  
  13: 3.         <p><?php _e("Store URL: " ); ?><input type="text" name="oscimp_store_url" value="<?php echo $store_url; ?>" size="20"><?php _e(" ex: http://www.yourstore.com/" ); ?></p>  
  14: 4.         <p><?php _e("Product image folder: " ); ?><input type="text" name="oscimp_prod_img_folder" value="<?php echo $prod_img_folder; ?>" size="20"><?php _e(" ex: http://www.yourstore.com/images/" ); ?></p>  
  15: 5.           
  16: 6.       
  17: 7.         <p class="submit">  
  18: 8.         <input type="submit" name="Submit" value="<?php _e('Update Options', 'oscimp_trdom' ) ?>" />  
  19: 9.         </p>  
  20: 0.     </form>  
  21: 1. </div>  

Explaining the Code

If you are familiar with HTML and PHP, the code above will make some sense but, still, let us shortly walk through the lines.

  • We start by creating a div with the class wrap. This is a standard WordPress class that will make our page look like any other page in the administration area.
  • The form will be using the POST method to send data back to itself. This means that the form data will be received by the same page so, we can add the database update code to the same file.
  • Next, there is a hidden field that will be used to determine whether the current page is displayed after the user has pressed the Update Options button or not. When the page receives the form data, the value of this field will be set to Y.
  • The next lines will create the form input fields for the database and store settings. As you can easily see, the value parameters are be set by the content of PHP variables. We’ll talk about these soon.
  • Now if you refresh the admin page, you’ll see our newly created form. However, pressing the Update Options button will have no effect other than refreshing the page and the form fields are empty. Click here for screenshot.

Handling the Data

Once the form is ready to go, we’ll take care of the form data handling itself, updating the database and retrieving existing option values from the database. For this, we’ll first have to decide whether the current page is displayed after the user has pressed the Update Options button or not. We’ll do this by analyzing the value of the form’s hidden field, oscimp_hidden. The following code will be added at the very beginning of our oscommerce_import_admin.php file, before the code for displaying the form:

   1: 1. <?php   
   2: 2.     if($_POST['oscimp_hidden'] == 'Y') {  
   3: 3.         //Form data sent  
   4: 4.     } else {  
   5: 5.         //Normal page display  
   6: 6.     }  
   7: 7. ?>  

Next, we’ll be handling the form data and update the plugin options in the database accordingly. For this we’ll be using the update_option() function. The first parameter of this function is the option name which will be sued later to uniquely identify this option and its value. The second parameter is the value to be assigned.

   1: 1. <?php   
   2: 2.     if($_POST['oscimp_hidden'] == 'Y') {  
   3: 3.         //Form data sent  
   4: 4.         $dbhost = $_POST['oscimp_dbhost'];  
   5: 5.         update_option('oscimp_dbhost', $dbhost);  
   6: 6.           
   7: 7.         $dbname = $_POST['oscimp_dbname'];  
   8: 8.         update_option('oscimp_dbname', $dbname);  
   9: 9.           
  10: 0.         $dbuser = $_POST['oscimp_dbuser'];  
  11: 1.         update_option('oscimp_dbuser', $dbuser);  
  12: 2.           
  13: 3.         $dbpwd = $_POST['oscimp_dbpwd'];  
  14: 4.         update_option('oscimp_dbpwd', $dbpwd);  
  15: 5.   
  16: 6.         $prod_img_folder = $_POST['oscimp_prod_img_folder'];  
  17: 7.         update_option('oscimp_prod_img_folder', $prod_img_folder);  
  18: 8.   
  19: 9.         $store_url = $_POST['oscimp_store_url'];  
  20: 0.         update_option('oscimp_store_url', $store_url);  
  21: 1.         ?>  
  22: 2.         <div class="updated"><p><strong><?php _e('Options saved.' ); ?></strong></p></div>  
  23: 3.         <?php  
  24: 4.     } else {  
  25: 5.         //Normal page display  
  26: 6.     }  
  27: 7. ?>  

The code above if pretty much self-explanatory but please note that here we are using the PHP variables we have previously mentioned while building the form. These variables will be updated with the current form data values and will be displayed in the form itself. Go, check it out! Refresh the configuration page and enter your OSCommerce database settings as well as your store parameters then press Update Options.

If everything was implemented like described above, you’ll see an Options saved success message and the form fields will contain the data you have just entered.  Click here for a Screenshot.

Last but not least, we’ll need to pre-populate the form with the database data when the user opens the configuration page. For this, we’ll be using the get_option() function which retrieves the specified option from the database.

   1: 1. <?php   
   2: 2.     if($_POST['oscimp_hidden'] == 'Y') {  
   3: 3.         //Form data sent  
   4: 4.         $dbhost = $_POST['oscimp_dbhost'];  
   5: 5.         update_option('oscimp_dbhost', $dbhost);  
   6: 6.           
   7: 7.         $dbname = $_POST['oscimp_dbname'];  
   8: 8.         update_option('oscimp_dbname', $dbname);  
   9: 9.           
  10: 0.         $dbuser = $_POST['oscimp_dbuser'];  
  11: 1.         update_option('oscimp_dbuser', $dbuser);  
  12: 2.           
  13: 3.         $dbpwd = $_POST['oscimp_dbpwd'];  
  14: 4.         update_option('oscimp_dbpwd', $dbpwd);  
  15: 5.   
  16: 6.         $prod_img_folder = $_POST['oscimp_prod_img_folder'];  
  17: 7.         update_option('oscimp_prod_img_folder', $prod_img_folder);  
  18: 8.   
  19: 9.         $store_url = $_POST['oscimp_store_url'];  
  20: 0.         update_option('oscimp_store_url', $store_url);  
  21: 1.         ?>  
  22: 2.         <div class="updated"><p><strong><?php _e('Options saved.' ); ?></strong></p></div>  
  23: 3.         <?php  
  24: 4.     } else {  
  25: 5.         //Normal page display  
  26: 6.         $dbhost = get_option('oscimp_dbhost');  
  27: 7.         $dbname = get_option('oscimp_dbname');  
  28: 8.         $dbuser = get_option('oscimp_dbuser');  
  29: 9.         $dbpwd = get_option('oscimp_dbpwd');  
  30: 0.         $prod_img_folder = get_option('oscimp_prod_img_folder');  
  31: 1.         $store_url = get_option('oscimp_store_url');  
  32: 2.     }  
  33: 3. ?>  

You can test the code above by simply navigating to another page within the admin area and then retuning to this page by clicking the OSCommerce Product Display sub-menu item in the Setting menu. If everything goes well, you will see the form with all the fields pre-populated with the data you have entered.  Click here for a screenshot.

With this last piece of code, we have finished implementing the plugin’s configuration page so, let’s review what has been done in this chapter:

  • we defined what parameters need to be configured by the site administrator
  • we added an action hook for when the menu is displayed in the administration panel to help us add a new sub-menu item for our plugin
  • we have added a new sub-menu item to the Settings menu that will link to our plugin’s configuration page
  • we have defined a function that will build the plugin’s configuration page and separated its code in a second PHP file
  • we have built the form containing the user inputs for each of the configurable data bits
  • we have built the database update function
  • we have built a function that will pre-populate the form with the option values stored in the database

Creating the User Function

Well, everything went quite fine so far but our plugin is yet unusable because we haven’t implemented the part that will actually allow us to display the products in the front-end.

In order to allow our users to display the products in the front-end, we’ll need to declare a function that can be called from the template’s PHP code and which will return the HTML code to be inserted in the template. We are going to name this function oscimp_getproducts() and accept the number of products to be displayed as a function parameter. The function itself will be implemented in our plugin’s main file, oscommerce_import.php

   1: 1. function oscimp_getproducts($product_cnt=1) {  
   2: 2.   
   3: 3. }  

As you can see, we are assigning a default value to our function parameter thus allowing our users to call the function both with and without a parameter. If the function is called with a parameter, like oscimp_getproducts(3), it will display three products. If the function is called without a parameter, like oscimp_getproducts(), it will only display one product.

First thing in our function would be to establish a connection to the OSCommerce database. Thanks to our plugin configuration page, we now have all the information we need: database host, name, user and password. We’ll be using the built-in wpdb class to create a new database object.

   1: 1. function oscimp_getproducts($product_cnt=1) {  
   2: 2.     //Connect to the OSCommerce database  
   3: 3.     $oscommercedb = new wpdb(get_option('oscimp_dbuser'),get_option('oscimp_dbpwd'), get_option('oscimp_dbname'), get_option('oscimp_dbhost'));  
   4: 4. }  

Once this is done, we declare a variable that will contain the HTML code and start quering the OSCommerce database for each of the specified number of products. The code below merely implements this query loop and can be further-on improved by checking for duplicates, for instance, but this is not the subject of this tutorial so, we’ll keep it simple for the sake of readability.

   1: 1. function oscimp_getproducts($product_cnt=1) {  
   2: 2.     //Connect to the OSCommerce database  
   3: 3.     $oscommercedb = new wpdb(get_option('oscimp_dbuser'),get_option('oscimp_dbpwd'), get_option('oscimp_dbname'), get_option('oscimp_dbhost'));  
   4: 4.   
   5: 5.     $retval = '';  
   6: 6.     for ($i=0; $i<$product_cnt; $i++) {  
   7: 7.         //Get a random product  
   8: 8.         $product_count = 0;  
   9: 9.         while ($product_count == 0) {  
  10: 0.             $product_id = rand(0,30);  
  11: 1.             $product_count = $oscommercedb->get_var("SELECT COUNT(*) FROM products WHERE products_id=$product_id AND products_status=1");  
  12: 2.         }  
  13: 3.           
  14: 4.         //Get product image, name and URL  
  15: 5.         $product_image = $oscommercedb->get_var("SELECT products_image FROM products WHERE products_id=$product_id");  
  16: 6.         $product_name = $oscommercedb->get_var("SELECT products_name FROM products_description WHERE products_id=$product_id");  
  17: 7.         $store_url = get_option('oscimp_store_url');  
  18: 8.         $image_folder = get_option('oscimp_prod_img_folder');  
  19: 9.   
  20: 0.         //Build the HTML code  
  21: 1.         $retval .= '<div class="oscimp_product">';  
  22: 2.         $retval .= '<a href="'. $store_url . 'product_info.php?products_id=' . $product_id . '"><img src="' . $image_folder . $product_image . '" /></a><br />';  
  23: 3.         $retval .= '<a href="'. $store_url . 'product_info.php?products_id=' . $product_id . '">' . $product_name . '</a>';  
  24: 4.         $retval .= '</div>';  
  25: 5.   
  26: 6.     }  
  27: 7.     return $retval;  
  28: 8. }  

Once this is done, all we have to do is insert the oscimp_getproducts() function call to the template. We’ll be displaying three products at the bottom of the sidebar so, we are going to modify the sidebar.php file of our template, inserting the following code right below the list item containing the meta links:

   1: 1. <li><?php echo oscimp_getproducts(3); ?></li>  

If you refresh your front-end page now, you’ll see the three random products displayed at the bottom of the sidebar.  Click here for a screenshot.

We have now implemented a WordPress plugin from scratch. Let’s summarize what has been done:

  • we defined the way we store our plugin files
  • we defined the information header in order to make our plugin visible for WordPress
  • we talked about the action hooks and the way these are used
  • we defined what parameters need to be configured by the site administrator
  • we added an action hook for when the menu is displayed in the administration panel to help us add a new sub-menu item for our plugin
  • we have added a new sub-menu item to the Settings menu that will link to our plugin’s configuration page
  • we have defined a function that will build the plugin’s configuration page and separated its code in a second PHP file
  • we have built the form containing the user inputs for each of the configurable data bits
  • we have built the database update function
  • we have built a function that will pre-populate the form with the option values stored in the database
  • we have built our user function for use in the template
  • we connected to the OSCommerce database
  • we queried the OSCommerce database extracting the product ID, image and name
  • we have built the HTML code for displaying the extracted data
  • we have included the user function to the template sidebar

I hope this tutorial gave you all the information you need to build a WordPress plugin from the beginning. Please feel free to post your comments below.

 

Create a Custom WordPress Plugin From Scratch – NETTUTS

AJAX for updating other fields

Here is a cool trick for utilizing AJAX to update other fields on pages inside of PHPR code.  I have to thank Giles for pointing out calculation.js to me, see his notes below in this post.

The problem I wanted to solve was updating many price fields on a page as the user entered data in other fields using pricing information in a table not displayed at the time. The problem could be solved with a beforerecordupdated event but that would mean the user would have to save the record each time they wanted to see the price with subsequent impact on performance and usability.

The approach was to use AJAX responseXML. A javascript "calculation.js" gets the pricing data from "getdataxml.php", then performs the calc and displays the data on the user’s current page. The price data is only retrieved on the first execution of the script as it persists with the page. The script is executed whenever the fields involved in the calc are changed by the user.

calculation.js

   1: var xmlHttp
   2: var gotdata //used as flag so data only retreived once for each page call up
   3:  
   4: // declare a var for each data element being read from the mysql table by the getdataxml.php page
   5: // declared here so they persist for the life of the page
   6: var id
   7: var date_created
   8: var date_modified
   9: var pricefield
  10:  
  11: //main function
  12: //on first activation it sets up to get the data from mysql
  13: //on subsequent activations it does the calculation...
  14:  
  15: function docalcofsomesort(str)
  16: {
  17: xmlHttp=GetXmlHttpObject()
  18: if (xmlHttp==null)
  19: {
  20: alert ("Browser does not support HTTP Request")
  21: return
  22: }
  23: if (gotdata==null) //go get the data if not already done so...
  24: {
  25: alert ("Refreshing data") //just a footprint for debugging purposes
  26: var url="getdataxml.php"
  27: url=url+"?q="+str
  28: url=url+"&sid="+Math.random() //the random element is attached to stop page caching on the server
  29: xmlHttp.onreadystatechange=stateChanged
  30: xmlHttp.open("GET",url,true)
  31: xmlHttp.send(null) // ok, let's send the request and then wait for an answer...
  32: }
  33: else //if data already there do the calc...
  34: {
  35: alert ("Calculating") //just a footprint for debugging purposes
  36: var newvalue = updatevalue()
  37: document.getElementById("value_fieldwhereyouwantresult").value= newvalue; //store the result on the page...
  38: }
  39: }
  40:  
  41: function stateChanged() //answer is back, let's grab the data
  42: {
  43:  
  44: if (xmlHttp.readyState==4 || xmlHttp.readyState=="complete")
  45: {
  46: xmlDoc=xmlHttp.responseXML;
  47:  
  48: alert ("Storing data to variables") //just a footprint for debugging purposes
  49: try
  50: {
  51: id = xmlDoc.getElementsByTagName("id")[0].childNodes[0].nodeValue;
  52: date_created = xmlDoc.getElementsByTagName("date_created")[0].childNodes[0].nodeValue;
  53: date_modified = xmlDoc.getElementsByTagName("date_modified")[0].childNodes[0].nodeValue;
  54: anotherfield = xmlDoc.getElementsByTagName("pricefield")[0].childNodes[0].nodeValue;
  55:  
  56: gotdata = 1;
  57: alert("Stored data") //just a footprint for debugging purposes
  58: docalcofsomesort("1") //recall the function to actually do the calc after getting the data
  59: }
  60: catch(err)
  61: {
  62: // with xml used in this way it's hard to know exactly what's going on as you can't directly
  63: // see the returned data...the try-catch is an attempt to help trap missing data.
  64: // could do with better error handling as it only helps a tiny bit. can get this alert for other
  65: // reasons
  66:  
  67: alert ("A value in the data has not been set up. Correct this and try again."+err.description)
  68: }
  69: }
  70: }
  71:  
  72: function GetXmlHttpObject() // this was just cut and paste from example. If you need
  73: // explanation here you're on your own...
  74: {
  75: var objXMLHttp=null
  76: if (window.XMLHttpRequest)
  77: {
  78: objXMLHttp=new XMLHttpRequest()
  79: }
  80: else if (window.ActiveXObject)
  81: {
  82: objXMLHttp=new ActiveXObject("Microsoft.XMLHTTP")
  83: }
  84: return objXMLHttp
  85: }
  86:  
  87: function updatevalue() // here's the function that actually does the calculation
  88: // insert your own here...simple example below
  89:  
  90: {
  91: var initvalue = 0
  92: initvalue += document.getElementById("value_some_field_qty").value*anotherfield
  93: ...and whatever else needs to be calculated....
  94:  
  95: return initvalue
  96: }
  97:  
  98: function setupthepageforcalc() //fields where results will be displayed are editable text fields in phprunner
  99: // but are disabled and colour highlighted so user cannot change them.
 100: // fields used in the calcs have their onchange event set so they trigger the calc
 101: {
 102: color = "#F1F1F1"; // used later down for distinguishing these fields
 103:  
 104: // make the following fields shaded and disabled so user cannot edit them
 105: document.getElementById("value_fieldwhereyouwantresult").disabled=1;
 106: document.getElementById("value_fieldwhereyouwantresult").style.background = color;
 107:  
 108: // make the following fields execute the calc onchange...
 109: document.getElementById("value_some_field_qty").onchange = function() {docalcofsomesort("13");};
 110: }
 111:  
 112: function submitform() //in order to save the calculated resluts in the data base must enable fields
 113: {
 114: alert("Saving"); // another footprint
 115: document.getElementById("value_some_field_qty").disabled=false;
 116: document.editform.submit();
 117: }

getdataxml.php:

   1: <?php
   2: header('Content-Type: text/xml');
   3: header("Cache-Control: no-cache, must-revalidate");
   4: //A date in the past
   5: header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
   6:  
   7: $q=$_GET["q"];
   8:  
   9: $con = mysql_connect('localhost', 'user', 'yourpassword');
  10: if (!$con)
  11: {
  12: die('Could not connect: ' . mysql_error());
  13: }
  14:  
  15: mysql_select_db("mysql database", $con);
  16:  
  17: $sql="SELECT * FROM yourtable WHERE ID = ".$q."";
  18:  
  19: $result = mysql_query($sql);
  20:  
  21: echo '<?xml version="1.0" encoding="ISO-8859-1"?>
  22: <dataset>';
  23: while($row = mysql_fetch_array($result))
  24: {
  25: echo "<id>" . $row['ID'] . "</id>";
  26: echo "<date_created>" . $row['Date_Created'] . "</date_created>";
  27: echo "<date_modified>" . $row['Date_Modified'] . "</date_modified>";
  28: echo "<anotherfield>" . $row['anotherfield'] . "</anotherfield>";
  29: }
  30: echo "</dataset>";
  31:  
  32: mysql_close($con);
  33: ?>

Additional Points by Giles:

1. Each field involved in the calculation or the results must have an id attribute. I used text fields only and modified the include/commonfunction.php to generate the id for editable text fields by inserting id="’.$cfield.’" in the second echo statement as shown below. (Admin, could we have this in the standard product??)

   1: if($format==EDIT_FORMAT_TEXT_FIELD)
   2: {
   3: if(IsDateFieldType($type))
   4: echo '<input type="hidden" name="'.$ctype.'" value="date'.EDIT_DATE_SIMPLE.'">'.GetDateEdit($field,$value,0,$secondfield,$edit);
   5: else
   6: {
   7: if($edit==MODE_SEARCH)
   8: echo '<input type="text" autocomplete="off" name="'.$cfield.'" '.GetEditParams($field).' value="'.htmlspecialchars($value).'">';
   9: else
  10: echo '<input type="text" name="'.$cfield.'" id="'.$cfield.'" '.GetEditParams($field).' value="'.htmlspecialchars($value).'">';

2. The javascript was added in the header section of the _edit page via the Visual Editor in html mode

   1: <script> src="calculation.js" </script>

3. Activation of setupthepageforcalc() was done by adding the following line just above the Save/Back To List buttons

   1: <script type="text/javascript"> setupthepageforcalc() </script>

This function needs to be executed each time the page is displayed to disable the results fields

4. The submitform() function needs to be activated when the page is saved so the onclick event of the save button was modified:

   1: onclick=submitform()

 

For simplicity sake I’ve simplified the amount of data being retrieved and the number of calculations and results fields. also inserted comments and changed a few field/field names to protect the guilty. So be on the lookout for any errors.

Also I created a php page to help create some repetitive parts of the code (e.g. var decarations in the javascript) where multiple fields are involved. Just run the following php and copy/paste as needed. Note the GoodFieldName() function is from phprunner but also forces the goodfieldname to lower case. I thought javascript would not like upper case…

   1: <?php
   2:  
   3: //Create the field data section of the getdataxml.php file
   4:  
   5: $con = mysql_connect("localhost", "user", "your password");
   6: if (!$con)
   7: {
   8: die('Could not connect: ' . mysql_error());
   9: }
  10:  
  11: $db_selected = mysql_select_db("mysql database",$con);
  12:  
  13: $sql = "SELECT * from your table";
  14: $result = mysql_query($sql,$con);
  15:  
  16: $donetablename=0;
  17: while ($property = mysql_fetch_field($result))
  18: {
  19: If ($donetablename==0)
  20: {
  21: echo "Table name: " . $property->table . ". Entries for xml.php file<br />";
  22: $donetablename = 1;
  23: }
  24: $fieldname = GoodFieldName($property->name);
  25: $str = "echo \"<".$fieldname.">\" . row['".$property->name."'] . \"</".$fieldname.">\";";
  26: echo htmlspecialchars($str)."<br>";
  27:  
  28: }
  29:  
  30: echo "<br>";
  31: echo "<br>";
  32: echo "<br>";
  33:  
  34: $result = mysql_query($sql,$con);
  35:  
  36: $donetablename=0;
  37: while ($property = mysql_fetch_field($result))
  38: {
  39: If ($donetablename==0)
  40: {
  41: echo "Table name: " . $property->table . ". var entries for js file<br />";
  42: $donetablename = 1;
  43: }
  44: $fieldname = GoodFieldName($property->name);
  45: $str = "var ".$fieldname;
  46: echo htmlspecialchars($str)."<br>";
  47:  
  48: }
  49:  
  50: echo "<br>";
  51: echo "<br>";
  52: echo "<br>";
  53:  
  54: $result = mysql_query($sql,$con);
  55:  
  56: $donetablename=0;
  57: while ($property = mysql_fetch_field($result))
  58: {
  59: If ($donetablename==0)
  60: {
  61: echo "Table name: " . $property->table . ". getElementsByTagName entries for js file<br />";
  62: $donetablename = 1;
  63: }
  64: $fieldname = GoodFieldName($property->name);
  65: $str = $fieldname." = xmlDoc.getElementsByTagName(\"".$fieldname."\")[0].childNodes[0].nodeValue;";
  66: echo htmlspecialchars($str)."<br>";
  67:  
  68: }
  69:  
  70: mysql_close($con);
  71:  
  72:  
  73: // construct "good" field name
  74: function GoodFieldName($field)
  75: {
  76: $field=(string)$field;
  77: for($i=0;$i<strlen($field);$i++)
  78: {
  79: $t=ord($field[$i]);
  80: if(($t<ord('a') || $t>ord('z')) && ($t<ord('A') || $t>ord('Z')) && ($t<ord('0') || $t>ord('9')))
  81: $field[$i]='_';
  82: }
  83: $field = strtolower($field);
  84: return $field;
  85: }
  86:  
  87: ?>

If someone knows of a better way to approach this or suggestions how to improve or make this simpler, please feel free to comment on the forums: Using AJAX for updating other fields on pages – Forums

Pulling URL from _table_search.php

If you are using PHPR (phprunner) as your IDE to develop your PHP applications in here’s a trick that you are likely going to appreciate at some point in your coding career. 

If you utilize the advanced search feature in your applications (not the standard inline search, but the advanced search) and you would like to be able to find the URL for your advanced search results page (with multiple fields included) here’s a way to do it in both PHPR 4.2 and PHPR 5.0.  Both of these are provided by Jane at Xlinesoft on the Tips and Tricks forum:

You will first want to pull up your advanced search page {_table_search.php} inside the visual editor regardless of which version you are using.

PHPR 4.2

Switch out this line of code:

   1: <form method="POST" action="TableName_list.php" name="editform">

and replace it with this line of code:

   1: <form method="GET" action="TableName_list.php" name="editform">

PHPR 5.0

Switch out this line of code:

   1: $contents_block["begin"]="<form method=\"POST\" ";

and replace it with this line of code:

   1: $contents_block["begin"]="<form method=\"GET\" ";

 

How to get the URL of advanced search results page – Forums

Beau Lebens Facebook Connect

I have going back and forth w/ Beau Lebens today after reading his How To blog post on Dented Reality discussing his variation of the Facebook Connect application and after testing it today, it’s a very clean plug-in and it seems to work very smoothly.

In his blog post he also outlines all of the requirements to get everything working correctly and provides some source code examples as well.  He basically rolled his own solution and has it running on Resume Donkey, check out his blog post below:

2008-12-23: There were a number of problems with the code samples in this post previously due to some WordPress formatting problems. They are all corrected now, and you should be able to follow through this post and get this working on your own blog quite easily.

2008-12-26: Fixed a bug that caused the JS to overwrite details on a non-FB Connect comment as well. Also changed the fake email address that’s stored to include the user’s FB user ID.

In case you’ve been living under a no-technology-news rock for the last few weeks, you’ll know that Facebook Connect was released recently. I had been seeing/hearing a lot about it, including this video at Mashable, showing how to implement FB Connect in 8 minutes. So when my friend Morgan from BlownMortgage asked me if I’d be able to help him implement it on his new resume-editing site ResumeDonkey.com, I figured “how hard could it be” and said yes. Although it definitely didn’t take 8 minutes, I got it done, so I thought I’d post some details on the specific approach I used for ResumeDonkey.com.

Before I rolled my own solution, I took a good look at a few of the existing WordPress options including:

None of these worked quite how Morgan and I had discussed, so I decided to make my own, lightweight solution. Before editing any actual theme files, there’s some prep-work to be done, so:

  1. Log into Facebook and then go and add the Facebook Developers Application
  2. Click the big button at the top right to Set Up a New Application
  3. Enter a name and agree to the terms (you read them all, right?)
  4. On the next page, enter the base URL of your website in the “Callback URL” field. MAKE SURE you use the correct preference for your website as far as www. or no www. is concerned, and preferably enforce that on your website using a plugin or something. If you enter http://www.domain.com here, and someone accesses your site as http://domain.com, then your FB Connect integration will break and throw a warning about being on the wrong URL.
  5. You can also set some sexy icons/logos to appear in the News Feed of people who comment on your blog, but I’ll let you handle that.
  6. Get a copy of the “API Key” at the top of this page, you’ll need that later.

OK, now we need to register a “template bundle”, which will be used to post updates to the News Feed of people who comment on your blog.

  1. Go to the list of your Facebook Apps and click on the app we just created on the left
  2. Click “Create Feed Template” in the list of links on the right
  3. Make sure your correct App is selected in the box, then click Next
  4. In the “One Line Template” box, paste this exact text
       1: {*actor*} commented on the {*blog*} post {*post*}.
  5. In the “Sample Template Data” box, paste this (make sure quote marks are still  quotes and not fancy curly-quotes)
       1: {"blog":"<a href='http://test.domain.com'>My Blog Name</a>", "post":"<a href='http://test.domain.com/post-url/'>Test Post Title</a>"}
  6. Click Update Preview and make sure that you’re happy with the News Feed format (if not, change the One Line Template string)
  7. Click Next
  8. Now click Skip (and ignore/Okay any errors) until you get to the final page and then click “Register Template Bundle”
  9. It will give you a Template Bundle ID, and you’ll want to get a copy of that, because we’ll need it later as well.

OK. Now you’ve got a registered and configured (roughly) App on Facebook, time to get dirty on your own blog. Create a file in the root of your domain and call it “xd_receiver.htm”, then copy the following code into it:

   1: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
   2: <html xmlns="http://www.w3.org/1999/xhtml">
   3: <head>
   4: <title>Cross-Domain Receiver Page</title>
   5: </head>
   6: <body>
   7: <script src="http://static.ak.facebook.com/js/api_lib/v0.4/XdCommReceiver.js" type="text/javascript"></script>
   8: </body>
   9: </html>

Add the “fb” XML namespace to the header.php file in your theme. Mine ended up looking like this (in PHP):

   1: <html xmlns="http://www.w3.org/1999/xhtml" xmlns:fb="http://www.facebook.com/2008/fbml" <?php language_attributes(); ?>>

And also drop in a reference to jQuery if you don’t already use it in your theme. It’s bundled with WordPress so you can reference it like this (anywhere before the call to “wp_head()” in your header.php):

   1: <?php wp_enqueue_script('jquery'); ?>

Then you’ll want to edit comments.php (assuming you’re using a relatively normal theme), and make some changes to add the FB Connect button. Find the part where a user would normally enter their name/email/URL and change it to look something like this:

   1: <div id="comment-user-details">
   2: <fb:login-button length="long" onlogin="update_user_details();"></fb:login-button>
   3:
   4: <p style="clear:left;"><strong>Or enter your details below:</strong></p>
   5:
   6: <p><label for="name">Name <?php if ($req) echo "(required)"; ?></label><br />
   7: <input type="text" name="author" id="name" value="<?php echo $comment_author; ?>" size="50" tabindex="1" /></p>
   8:
   9: <p><label for="email">Email Address <?php if ($req) echo "(required)"; ?></label><br />
  10: <input type="text" name="email" id="email" value="<?php echo $comment_author_email; ?>" size="50" tabindex="2" /></p>
  11:
  12: <p><label for="url">Website</label><br />
  13: <input type="text" name="url" id="url" value="<?php echo $comment_author_url; ?>" size="50" tabindex="3" /></p>
  14: </div>

Just above this block, you should also find the start of the <form> tag for posting a comment, you want to add the “onsubmit” attribute to it so that it looks something like this:

   1: <form action="<?php echo get_option('siteurl'); ?>/wp-comments-post.php" method="post" id="commentform" onsubmit="update_form_values();">

The important parts there are that it’s all wrapped in a DIV or SPAN with id=”comment-user-details” and then obviously the <fb:login-button> stuff. Now further down (I went right down to the bottom of the comments.php file actually), add this code:

   1: <script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php" type="text/javascript"></script>
   2: <style type="text/css">
   3: #fb-user { border: 1px dotted #C0C0C0; padding: 5px; display: block; height: 48px; }
   4: #fb-msg { float:left; }
   5: .fb_profile_pic_rendered { margin-right: 5px; }
   6: a.FB_Link img { float: left; }
   7: </style>
   8:
   9: <script type="text/javascript">
  10: var fb_connect_user = false;
  11: function update_user_details() {
  12: fb_connect_user = true;
  13: // Show their FB details
  14: if (!jQuery('#fb-user').length) {
  15: jQuery('#comment-user-details').hide().after("<span id='fb-user'>" +
  16: "<fb:profile-pic uid='loggedinuser' facebook-logo='true'></fb:profile-pic>" +
  17: "<span id='fb-msg'><strong>Hi <fb:name uid='loggedinuser' useyou='false'></fb:name>!</strong><br />You are logged in with your Facebook account. " +
  18: "<a href='#' onclick='FB.Connect.logoutAndRedirect(\"<?php the_permalink() ?>\"); return false;'>Logout</a>" +
  19: "</span></span>");
  20: }
  21:
  22: // Refresh the DOM
  23: FB.XFBML.Host.parseDomTree();
  24: }
  25:
  26: function update_form_values() {
  27: if (fb_connect_user) {
  28: profile = jQuery('#fb-user').find('.FB_ElementReady .FB_Link')[1]['href'];
  29: user_id = profile.substring(profile.indexOf('?id=')+4);
  30: jQuery('#url').val(profile); // FB profile URL
  31: jQuery('#email').val(user_id+'@facebook.com'); // Can't get a real one from FB unfortunately. This saves their user id @facebook.com
  32: jQuery('#fb-user').find('.FB_ElementReady .FB_Link').each(function(i){ if (i==1) { jQuery('#name').val(jQuery(this).text()); } }); // Gets their name from the DOM
  33: setCookie('fb_connect', 'yes');
  34: }
  35: }
  36:
  37: function setCookie(c_name,value,expiredays) {
  38: var exdate=new Date();
  39: exdate.setDate(exdate.getDate()+expiredays);
  40: document.cookie=c_name+ "=" +escape(value)+((expiredays==null) ? "" : ";expires="+exdate.toGMTString());
  41: }
  42:
  43: function getCookie(c_name) {
  44: if (document.cookie.length>0) {
  45: c_start=document.cookie.indexOf(c_name + "=");
  46: if (c_start!=-1) {
  47: c_start=c_start + c_name.length+1;
  48: c_end=document.cookie.indexOf(";",c_start);
  49: if (c_end==-1) c_end=document.cookie.length;
  50: return unescape(document.cookie.substring(c_start,c_end));
  51: }
  52: }
  53: return "";
  54: }
  55:
  56: FB.init("YOUR-FACEBOOK-API-KEY", "/xd_receiver.htm");
  57: FB.Connect.ifUserConnected(update_user_details);
  58: if (getCookie('fb_connect') == 'yes') {
  59: setCookie('fb_connect', null);
  60: FB.Connect.showFeedDialog(YOUR-TEMPLATE-BUNDLE-ID, {'blog':'<a href="<?php bloginfo('home') ?>"><?php addslashes(bloginfo('name')) ?></a>', 'post':'<a href="<?php the_permalink() ?>"><?php addslashes(the_title()) ?></a>'}, null, null, null, FB.RequireConnect.promptConnect);
  61: }
  62: </script>

For those of you paying any attention to what you’re copy-pasting, you would have noticed that there are 2 important things you need to replace in that last block of code. Go back now and replace “YOUR-FACEBOOK-API-KEY” and “YOUR-TEMPLATE-BUNDLE-ID” with the appropriate values from the beginning of this process. YOUR-FACEBOOK-API-KEY should be replaced with the 32-character string from the Facebook App config, and should include double-quotes around it in the code above. The YOUR-TEMPLATE-BUNDLE-ID should not have quotes around it.

Save everything and upload it (if you were working offline). If all has gone well, you should now get a FB Connect button on your comments (you need to log out of WordPress to see it), and when you click it, you should connect to FB, then be able to post a comment.

When a Facebook user comments on your blog now, their name will be loaded from Facebook, their profile URL will be used as their URL, and the email address will be recorded as “user@facebook.com” (their API doesn’t allow you to actually get it, to avoid spam I assume).

Enjoy.

HOWTO: Implement Facebook Connect on WordPress (in reality) « Dented Reality