Your Ad Here

Thursday, December 8, 2011

XML-RPC with the Incutio PHP Library


XMP-RPC stands for XML Remote Procedure Calling. It is a protocol for making and receiving procedure calls over the internet.
What this means is that different computers can use XML-RPC to "ask each other questions". Using XML-RPC is just like making a function call in PHP, only the computer that executes the function could be thousands of miles away.
With the Incutio XML-RPC Library, making and receiving XML-RPC requests is almost as simple as calling native PHP functions. Here's some sample code, which calls a function entitled "test.getTime" on our simple demonstration server:
$client = new IXR_Client('http://scripts.incutio.com/xmlrpc/simpleserver.php');
$client->query('test.getTime');
print $client->getResponse();
// Prints the current time, according to our web server
With error checking, the above code looks like this:
$client = new IXR_Client('http://scripts.incutio.com/xmlrpc/simpleserver.php');
if (!$client->query('test.getTime')) {
   die('An error occurred - '.$client->getErrorCode().":".$client->getErrorMessage());
}
print $client->getResponse();
You can also send arguments along with your queries:
$client = new IXR_Client('http://scripts.incutio.com/xmlrpc/simpleserver.php');
if (!$client->query('test.add', 4, 5)) {
   die('An error occurred - '.$client->getErrorCode().":".$client->getErrorMessage());
}
print $client->getResponse();
// Prints '9'
Arguments are not limited to simple values. You can send strings and arrays as well:
$client = new IXR_Client('http://scripts.incutio.com/xmlrpc/simpleserver.php');
if (!$client->query('test.addArray', array(3, 5, 7))) {
   die('An error occurred - '.$client->getErrorCode().":".$client->getErrorMessage());
}
print $client->getResponse();
// Prints '3 + 5 + 7 = 15'
Writing an XML-RPC server is simple as well. Here's the full code for simpleserver.php:
<?php

include('IXR_Library.inc.php');

/* Functions defining the behaviour of the server */

function getTime($args) {
    return date('H:i:s');
}

function add($args) {
    return $args[0] + $args[1];
}

function addArray($array) {
    $total = 0;
    foreach ($array as $number) {
        $total += $number;
    }
    return implode(' + ', $array).' = '.$total;
}

/* Create the server and map the XML-RPC method names to the relevant functions */

$server = new IXR_Server(array(
    'test.getTime' => 'getTime',
    'test.add' => 'add',
    'test.addArray' => 'addArray'
));

?>

Tuesday, December 6, 2011

Find URLs in Text and Make Links


<?php
// The Regular Expression filter
$reg_exUrl = "/(http|https|ftp|ftps)\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(\/\S*)?/";
// The Text you want to filter for urls
$text = "The text you want to filter goes here. http://google.com";
// Check if there is a url in the text
if(preg_match($reg_exUrl, $text, $url)) {

       // make the urls hyper links
       echo preg_replace($reg_exUrl, "<a href="{$url[0]}">{$url[0]}</a> ", $text);
} else {

       // if no urls in the text just return the text
       echo $text;
}
?>
The basic function of this is to find any URLs in the block of text and turn them into hyperlinks. It will only find URLs if they are properly formatted, meaning they have a http, https, ftp or ftps.
Check out the comments below for more solutions.

 

Monday, December 5, 2011

How to Update Magento Products Prices

Magento: Update Product Prices Globaly


There are many ways to mass update product attributes in Magento, each well suited to a different purpose. Magento's built-in mass product attribute updater is great if you want to modify a selection of products or the new attribute value is the same for all products you want to edit. Alternatively, if you wish to alter the attributes in more dynamic ways, updating them programmatic ally via PHP is probably a better way. The downside to both of these methods is speed, with each product update taking a few seconds to complete. While this time can be dramatically reduced by disabling indexing, the wait can still be too long for a store with a massive catalog. A more efficient way to update product attributes is to write direct SQL queries. As an example, I will show you how to mass update product pricing for all products, products from a certain store and products that use a certain attribute set.

Why would I want to mass update price?

When I was first asked to do this I asked myself the same question, however, the reason is quite simple. In Magento, shipping costs aren't usually displayed to the user until they enter their delivery address. While this makes sense, the customer usually enters their delivery address during the checkout process, meaning a lot of customers weren't aware of this extra cost. During a study of one site, I found that almost 30% of customers were leaving the store during checkout and that this bounce rate could almost definitely be attributes to the shipping cost. To remove this problem, it was decided I should add £6 (the shipping cost) on to every product price and offer free shipping instead. As soon as this was done a lot less people left the site during checkout!

How do I update product price globally?

In this first example, I will add £6 to every single product price.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
 
$priceToAdd = 6;
 
$write = Mage::getSingleton('core/resource')->getConnection('core_write');
$write->query("
  UPDATE catalog_product_entity_decimal val
  SET  val.value = (val.value + $priceToAdd)
  WHERE  val.attribute_id = (
     SELECT attribute_id FROM eav_attribute eav
     WHERE eav.entity_type_id = 4
       AND eav.attribute_code = 'price'
    )
");
If you have a development site, add the code to a template file or run Magento's code in an external PHP file and all of your products should now cost £6 more.

How do I update all prices from a certain store?

This technique is useful when working in a multi-store Magento environment. The SQL query used is very similar, except you will need to add a clause in the WHERE section to limit the records updated by store ID.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
 
$priceToAdd = 6;
$storeId = 4;
 
$write = Mage::getSingleton('core/resource')->getConnection('core_write');
$write->query("
  UPDATE catalog_product_entity_decimal val
  SET  val.value = (val.value + $priceToAdd)
  WHERE  val.attribute_id = (
     SELECT attribute_id FROM eav_attribute eav
     WHERE eav.entity_type_id = 4
       AND eav.attribute_code = 'price'
    )
    AND val.store_id = $storeId
");

How do I update all product prices with a certain attribute set?

The concept behind this is the same, however you will need to join an extra table so that you can filter using attribute_set_id.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
 
$priceToAdd = 6;
$attributeSetId = 4;
 
$write = Mage::getSingleton('core/resource')->getConnection('core_write');
$write->query("
  UPDATE catalog_product_entity_decimal val
  SET  val.value = (val.value + $priceToAdd)
  WHERE  val.attribute_id = (
     SELECT attribute_id FROM eav_attribute eav
     WHERE eav.entity_type_id = 4
       AND eav.attribute_code = 'price'
    )
AND entity_id = (
   SELECT p.entity_id FROM catalog_product_entity p
   WHERE p.attribute_set_id = $attributeSetId
)
");

How do I update the Special Price?

This one is also extremely easy! If you take any of the above examples and swap 'price' for 'special_price' they will all work! See below for an example of how to update the special price for every product.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
 
$priceToAdd = 6;
 
$write = Mage::getSingleton('core/resource')->getConnection('core_write');
$write->query("
  UPDATE catalog_product_entity_decimal val
  SET  val.value = (val.value + $priceToAdd)
  WHERE  val.attribute_id = (
     SELECT attribute_id FROM eav_attribute eav
     WHERE eav.entity_type_id = 4
       AND eav.attribute_code = 'special_price'
    )
");
These features only scratch the surface of the Magento database but should hopefully give you an insight into the possibility of modifying data directly in the database. This method is much quicker than the alternatives, however can go drastically wrong extremely easily. I would make sure you test ALL queries on a development server and always back up your live server before running a query!

How do you add a new module position in joomla 1.6

Create a module position in joomla 1.6

Inserting the code

  • Open the index.php file of the Template you wish to edit
  • Locate the place in the Template where you wish to put the new position.
  • Insert
<jdoc:include type="modules" name="newposition"   />
  • The variable can be used (between existing tags) to replace an image by replacing the <img src="xxx" border="0" alt="">. Or By creating a new tag with its own class/id.
  • Open the Template's TemplateDetails.xml file and locate the
<positions></positions> Start and end tags 
  • Then add
<position>newposition</position>
  • Example
<positions>
  <position>debug</position>
  <position>position-0</position>
  <position>position-1</position>
  <position>position-2</position>
  <position>position-3</position>
  <position>position-4</position>
  <position>position-5</position>
  <position>position-6</position>
  <position>position-7</position>
  <position>position-8</position>
  <position>position-9</position>
  <position>position-10</position>
  <position>position-11</position>
  <position>position-12</position>
  <position>position-13</position>
  <position>position-14</position>
  <position>position-15</position>
  <position>newposition</position>
</positions>

Viewing the changes

With Joomla 1.6 to see all of the existing template locations in your browser edit the Template manager options.
Template manager options






Then append "?tp=1",to the end of your normal URL (for example, "http://www.yoursite.com/?tp=1").

Joomla! 1.5 Joomla 1.5

In this example, we will add a new position to the default rhuk_milkyway template. Here are the steps.
  • Open the file "<your Joomla! home>/templates/rhuk_milkyway/index.php" for editing and determine where your new position will be on the page. Note that you can see all of the existing template locations in your browser by adding "?tp=1" to the end of your normal URL (for example, "http://www.yoursite.com/?tp=1").
  • In our example, we will add a new location after the "breadcrumb" position called "mynewposition". To do this, find the line in the file <div id="whitebox"> and insert a new line <jdoc:include type="modules" name="mynewposition" /> as shown below:
<div id="pathway">
   <jdoc:include type="modules" name="breadcrumb" />
</div>
   <div class="clr"></div>
   <div id="whitebox">
      <jdoc:include type="modules" name="mynewposition" />
   <div id="whitebox_t">
  • Open the file "<your Joomla! home>/templates/rhuk_milkyway/templateDetails.xml" for editing and find the "<positions>" section of the file. Add the new entry for "mynewposition" as shown below:
<positions>
   <position>breadcrumb</position>
   <position>left</position>
   <position>right</position>
   <position>top</position>
   <position>user1</position>
   <position>user2</position>
   <position>user3</position>
   <position>user4</position>
   <position>footer</position>
   <position>debug</position>
   <position>syndicate</position>
   <position>mynewposition</position>
</positions>
Now, when you look at your site with the "?tp=1" URL, you should see the new position as shown below:
New template position2.png
In the Module:[Edit] screen, the new position should display in the drop-down list box of available positions, as shown below.
New template position1.png

Joomla! 1.0 Joomla 1.0

To create a "new" position, choose one of the names from the list of positions shown in Site > Template Manager > Module Positions.

Sunday, December 4, 2011

Create a iFrame Application to your Facebook Fan Page

What is an iFrame application?

An iFrame application you allows you to embed an external Web page in your custom Facebook Page tab. Your "index" or "canvas" page is actually hosted on a non-Facebook server and is surrounded by Facebook "chrome" (the Facebook elements on the page).
Because this iframed page isn't hosted on Facebook, it can use standard HTML, CSS, and JavaScript like any other Web page does. Interactions with Facebook content are done using the Facebook Software Development Kits (SDKs) and XFBML tags. (For this tutorial, the Facebook SDK is not required.)
The downside of this approach is that you need to be familiar with those technologies and you will need a Web-accessible server where you upload the files for your application page. Or you can add an iFrame-creation application to your page, such as TabPress, freeing you from having to create a Facebook application but more restrictive in that you can't control the icon that appears next to the tab name in the left navigation.

Setting up your server

Facebook's HTTPS / Secure Hosting Requirement
The first thing to know is that wherever you host the index page of your Facebook iFrame application, the server will have to be secure, i.e., have an SSL Security Certificate for the domain under which it's hosted.
If your index page is not hosted on an SSL secure URL, or you don't specify a Secure URL in your app settings, your tab will not display for those using Facebook under Secure Browsing. Instead, the user will see:
Non-secure page Facebook security error HTTPS
Read our article on Facebook Page Tabs and HTTPS.
The other assets called into your page (images, JavaScript, CSS, video, etc.) will also have to be hosted under HTTPS. We recommend Amazon S3 hosting for this, and this is also addressed in our HTTPS article.

Create your iFrame application

On your secure Web server, create a directory for your iFrame application. In this example, we are going to create a new directory on the server called "facebook" and then a subdirectory called "mytestapp". The file path will look something like this in your FTP program:

You will want to put all of your files (HTML, CSS, Javascript, PHP, etc) in this folder or its subdirectories. If you don't know how to do this, read this FTP tutorial.)

Your HTML file

Remember, in your HTML file you can utilize CSS — and inlining styles with the <style> ... </style> tags works fine with iFramed HTML files — and JavaScript (Do not use FBML or FBJS!).
You'll want to set the main container DIV for your content to 520 pixels wide. Here's a very stripped-down example of your HTML file:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<link rel="stylesheet" type="text/css" href="style.css" />
<style type="text/css">
body {
width:520px;
margin:0; padding:0; border:0;
}
</style>
<meta http-equiv="Content-Type" content="text/html;
charset=iso-8859-1" />
</head>
<body>
<div id="container">
<p>This is some HTML content</p>
</div>
</body>
</html>
In the above example, I include both the code for an external stylesheet called with the <link /> tag, as well as inlined styles called with the <style> ... </style> tags, in case you want to do it that way. Either should work fine.

Installing the Facebook Developer Application

The first step in creating an application in Facebok is to install the Facebook Developer application.
To do that, log in to Facebook and then visit the URL http://facebook.com/developers.
If this is the first time you've installed the Developer Application, you will see the Request for Permission dialog show below:

Click the Allow button to proceed.

Creating your iFrame application

Now that you have the Developer App installed, click on the Create New App button.
Facebook Create New App
Give you application an "App Display Name" (the name displayed to users) and a "Namepace" (for use with Open Graph — 8-character minimum; alpha, dashes and underscores only — keep trying until you get a Namespace that hasn't been used). Then tick the "I agree to Facebook Platform Policies" box; then click the Continue button.
Create new Facebook iFrame App
On the next screen, enter the security phrase and then click Submit.

There are a lot of options you can tweak related to your application. In this post, we are going to focus on the basics needed to get your iFrame tab application up and running.

The Settings Tab

This is where you do the basic set up for your app.
Facebook app settings
First, at the top, you'll see the App ID and App Secret values. Most frequently you'll be using your App ID to integrate with Facebook.
Second, notice the "edit icon" below the App Secret. This is the icon that will appear to the left of your tab's name in your page navigation, so make it eye catching and make the dimensions 16 x 16 pixels. If you don't create your own icon, your tab will have a generic Facebook-tab icon: Generic Facebook Tab Icon
Basic info:
  • App Display Name: Make this the same as the original value you provided;
  • App Namespace: Make this the same as the original value you provided;
  • Contact Email: Where you want Facebook to send emails regarding your app;
  • App Domain: just put "mydomain.com" where "mydomain.com" is your secure hosting server;
  • Category: Select a category from the pulldown list.
Cloud Services
Since Facebook instituted their HTTPS requirement for all applications, they started offering cloud hosting solutions for those who find setting up a hosting account and secure server too much bother, expense or both. But click the Learn More if you're interested.

Select how your app integrates with Facebook

This is where you select the type of application you're creating and how it integrates with Facebook.
How does your App integrate with Facebook?
An explaination of the Facebook-integration values
For the purposes of this tutorial, you will select "Page Tab" from the various integration options. It's the last one listed but once you've saved your changes it will be listed first (as in the above example).
  • Page Tab Name: The displayed name of the tab in the Page navigation;
  • Page Tab URL: The unsecure URL (HTTP) of your index page;
  • Secure Page Tab URL: Same as the "Page Tab URL" but with HTTPS instead of HTTP;
  • Page Tab Edit URL: You can create any URL at your domain here and then set up that URL to redirect to the Edit Page for the Facebook tab. This is commonly done using a 301 or 302 Redirect. I'm not covering 301/302 redirects in this tutorial;

If you intend to use calls to the Facebook JavaScript SDK on your tab - Add "App on Facebook" values

If you want to use the Facebook JavaScript SDK in your Page Tab — for example, our Share Button for Page Tabs — you will also need to select the "App on Facebook" integration, and add the same URL values as for "Page Tab":
Facebook application - App on Facebook
The "Canvas Page" value is autofilled by Facebook, with your Namespace value.
Click "Save Changes" and you're done!

Installing your iFrame application on your Fan Page

Once your Facebook application has been created, you will need to add it to your Fan Page. To do that, click on the View App Profile Page link in the left column of your application page:
View Facebook App Profile Page
Now click the Add to My Page link on the left.
Add to my page
In the popup dialog, find the page to which you want to add the tab, then click the "Add to Page" button. The button will then change to "Added to Page."
Your new iFrame app should now appear on your Fan Page. If you don't see it there right away, you may need to adjust your Page settings. From your Fan Page, click on the "Edit Page" button in the top-right corner of your page. Then click on "Apps" and find the application that you just added. Click on the "Edit Settings" and you'll get this popup dialog:
Edit Facebook App Settings
  • Tab: If it says "Available (add)", click "add" to add it to your navigation; if it says "Added (remove)", you're set;
  • Custom Tab Name: You can override the default tab name by entering a new name in this field, up to 32 characters, and then clicking "Save".
Click "Okay" to save your changes.

Troubleshooting

Based on feedback to this post, we are starting to compile some iFrame App Troubleshooting Tips. We will update this section as new questions some up.

Check your URLs!

Make sure that the URL you set for your iFrame is correct. Try accessing it directly, via your browser, instead of via your Page tab. Bad URL addresses are the most common problem. If the URL to the Web page or image you want in your iFrame Page tab is incorrect, obviously the tab won't work.
You can also test the validity of your URL by right-clicking the area where your iFramed content should be and then select "This Frame: Open Frame in new window" or something similar (each browser presents this option a little differently).
Make sure you have specified an HTTPS / Secure URL for your Page Tab application. If you don't, your tab won't load for people using Facebook with Secure Browsing activated.

If you can't add your Facebook Page Tab application to a Page

People often report this problem, and the cause will likely be one of the following:
  • You've already added the App to your Page: Click the "Edit page" button at the top-right of your Page; then click "Apps" in the left column of the admin area; look for your Page Tab app; click "Edit settings" and make sure that in the popup dialog it says "Added (remove" and NOT "Available (add)";
  • You're not an admin of the Page to which you want to add the App;
  • Under the App's "Settings > Advanced" area, you have set "Authentication > Sandbox Mode" to "enabled"; this restricts the ability to add the App to a Page to only the App's developers;
  • It's a Facebook bug/glitch: Yes, this could be the cause.

Error messages from your server (error 405 - HTTP verb or similar)

If your server returns an error when Facebook tries to load the HTML page into the iFrame, you may need to change the file extension from .html to .php or .aspx (depending on the server platform you are using). When Facebook loads the iFrame, they do a POST to the index page in order to pass some data to your application and it looks like some servers are set up to not allow a POST to a file with the .html extension. We will be taking a look at how to access the data that Facebook passes in the next tutorial, but I wanted to mention this now since it caused issues for some people.

API Error Code: 191 Popup Dialog

If you get the "API Error Code: 191" popup dialog error:
Facebook API Error Code 191 - Dialog Popup
when using an embedded Share Button or other feature that requires the Facebook JavaScript SDK, it's may be because you haven't specified the "App on Facebook" URLs. See above for details.

Scroll Bars - Getting rid of them!

If your iFrame content causes a horizontal scroll bar to appear, something is causing the width to exceed 520 pixels, which is the maximum that Facebook allows. Read our tutorial on troubleshooting and eliminating the iFrame scrollbars.
We recommend adding some CSS (either inline as shown below or in your separate CSS file) to remove margin, padding, and border from elements by default. Many browsers add spacing around certain elements by default which can cause the scrollbars to appear unexpectedly.
<style type="text/css">
body {
width:520px;
margin:0; padding:0; border:0;
}
</style>

Next Steps

Up next: Creating a Reveal Tab on an Facebook iFrame application using the PHP SDK
We would love to hear what you would like to see in this series -- If you would like to know how to do something specific using iFrame applications, just note it in the comments and we will see what we can do.

Saturday, December 3, 2011

Joomla 1.5 - Add a new module position inside an article

To do this, you need to define a new position first, so go to /templates/your_template. Open the templateDetails.xml file and search for
Add a new position, something like
adsense_in_content
.
Save the file, and upload it back to it's place.
Ok, now go to /components/com_content/views/article/tmpl and open the default.php file.
Somewhere near line 120 you will see this code :

<?php if (isset ($this->article->toc)) : ?>
    <?php echo $this->article->toc; ?>
<?php endif; ?>
After this code, do something like this:












<?php
$article = $this->article->text;
 
$countchars = strlen($article);
 
$divide = $countchars / 2;
 
$firstpart = substr($article, 0, $divide);
 
$secondpart = substr($article, $divide);
?>
So, we took the article and count the characters inside it, divide that amount to 2 (We need 2 parts. Between them we will add the new position code.) and then add the first part in the variable named $firstpart and the second part in a variable $secondpart.
Now, echo the first part of the article:


<?php echo $firstpart; ?>
Now, we will insert the new module position:









<div style="float:left; padding-top: 5px; padding-right: 5px; padding-bottom: 5px;">
<?php
$myblurb_modules = &JModuleHelper::getModules( 'your_module_position' );
foreach ($myblurb_modules as $myblurb) {
$_options = array( 'style' => 'xhtml' );
echo JModuleHelper::renderModule( $myblurb, $_options );
}
?>
</div>
The module will float to the left part of the page, as shown in the picture. Float it to right if you want, or change it as you wish. Don't forgot to change the 'your_module_position' with your new module position name.
Now, we only have to echo the second part of the article:

<?php echo $secondpart; ?>
That's it. Now go to your "module manager" in joomla admin and add what module you want to this new position.

Tuesday, November 29, 2011

Automatic Post Creation with Wordpress, PHP, and XML-RPC


Automatic Post Creation with Wordpress, PHP, and XML-RPC

So, for those of you who read my last blog post, you might notice that I was having issues with a script I wrote to create a new post in Wordpress when it came time to upgrade the Wordpress code.  The SQL internals were modified, and I was inserting directly in to the database (yes, I know, I broke a cardinal rule).  I needed an alternative way to insert information in to the database that would be much more future-proof.  I remembered about XML-RPC.
It took me some time to find answers to questions I had about XML-RPC and the Wordpress’ API.  Wordpress comes with the ability to use XML-RPC, and AtomPub.  With regards to XML-RPC, it supports a few protocols:
  1. MetaWeblog
  2. Movable Type
  3. Blogger
  4. Wordpress’ own methods
Since there was little written documentation as to how to do this, I thought I’d share my findings.  Also, although the Blogger API was very easy to figure out and use, I’m not going to cover it here mainly because it doesn’t support the creation of a title field – something for my purposes were required.  (To access the blogger API, I used this class.)  I’m also only going to cover what’s necessary to create a new post for my own means, nothing more – hopefully it’ll get you on your way to how you’d like to use it.

One thing to note: In order for this to work, your version of Wordpress will have to have “Remote Publishing” enabled; depending on which protocol(s) you’ll be using, you’ll want to enable either “Atom Publishing Protocol” or “XML-RPC”, or both. This can be found (as of Wordpress version 2.7) under “Settings” and then “Writing”.
Let’s get to some nitty-gritty. The following XML-RPC methods are allowable with Wordpress (again, version 2.7):
Wordpress API:
  1. wp.getUsersBlogs
  2. wp.getPage
  3. wp.getPages
  4. wp.newPage
  5. wp.deletePage
  6. wp.editPage
  7. wp.getPageList
  8. wp.getAuthors
  9. wp.getCategories
  10. wp.getTags
  11. wp.newCategory
  12. wp.deleteCategory
  13. wp.suggestCategories
  14. wp.uploadFile
  15. wp.getCommentCount
  16. wp.getPostStatusList
  17. wp.getPageStatusList
  18. wp.getPageTemplates
  19. wp.getOptions
  20. wp.setOptions
  21. wp.getComment
  22. wp.getComments
  23. wp.deleteComment
  24. wp.editComment
  25. wp.newComment
  26. wp.getCommentStatusList
Blogger API:
  1. blogger.getUsersBlogs
  2. blogger.getUserInfo
  3. blogger.getPost
  4. blogger.getRecentPost
  5. blogger.getTemplate
  6. blogger.setTemplate
  7. blogger.newPost
  8. blogger.editPost
  9. blogger.deletePost
MetaWeblog API:
  1. metaWeblog.newPost
  2. metaWeblog.editPost
  3. metaWeblog.getPost
  4. metaWeblog.getRecentPosts
  5. metaWeblog.getCategories
  6. metaWeblog.newMediaObject
  7. metaWeblog.deletePost
  8. metaWeblog.getTemplate
  9. metaWeblog.setTemplate
  10. metaWeblog.getUsersBlogs
MovableType API:
  1. mt.getCategoryList
  2. mt.getRecentPostTitles
  3. mt.getPostCategories
  4. mt.setPostCategories
  5. mt.supportedMethods
  6. mt.supportedTextFilters
  7. mt.getTrackbackPings
  8. mt.publishPost
Wordpress also supports pingback, supposedly as an API. As for the AtomPub API, I don’t really understand that one yet, so if you want to find out what’s supported, you’ll want to look through the wp-app.php file found in your main Wordpress folder. The XML-RPC methods can be found in the xmlrpc.php file, also found in the main Wordpress folder.
As you can see, the Wordpress API is jam-packed with features – I believe most desktop applications that allow you to moderate your comments use the Wordpress API. Unfortunately, no “New Post” option for our needs. Dang. Because of this, and also because the Blogger API doesn’t support the creation of a title field in a new post, I decided to go with the MetaWeblog API. Technically speaking, MovableType and AtomPub are superior options to both Blogger and MetaWeblog from what I’ve been reading; regardless of that (possible) fact, I found it easy to work with the MetaWeblog API.
Let’s get to the code…
Umm…can PHP handle the transformation from data to the XML-RPC protocol of our choice automatically?
Unfortunately, no. However, the creators of Wordpress’ API features thought ahead. They packaged Wordpress with certain features and functionality (and classes) that can be utilized by us mere mortals to achieve our goals. Many of these libraries (such as the one we’ll use) were written by those not affiliated with Wordpress – so they took the legwork of finding good, well-written libraries for us.
We’ll be using the Incutio XML-RPC Library for PHP created by Simon Willison. You could download this yourself and include it in your script (note: commenter believes the official download to be corrupted), or you could just use what Wordpress already has in its folder structure, it’s the same thing with a few (pertinent) modifications and a different file name. (Wordpress uses it internally for some features since it works as a client and a server.)
The file is class-IXR.php, found in your wp-includes folder. So, to start out, we’ll create a PHP file that includes that script. We’ll also need to instantiate a new client object (to talk to the XML-RPC server – aka Wordpress). To find out what the address of your Wordpress XML-RPC server is (in order to instantiate the client), after you’ve enabled the XML-RPC remote publishing in your Wordpress settings, go to your Wordpress blog (any non-admin page) and view the source.
<link rel=”EditURI” type=”application/rsd+xml” title=”RSD” href=”http://www.example.com/xmlrpc.php?rsd />
You’ll want to take the URL from the HREF property of that line of code and place it in your browser. The rendered XML of the page gives us information as to the target URI for the protocol(s) we’d be using. If you’ll be using multiple protocols that use multiple targets, you’d have to target them each individually when making client calls. So, on to the fun stuff…
1include('/wp-includes/class-IXR.php');
2$client = new IXR_Client('http://www.example.com/xmlrpc.php');
This includes our XML-RPC library, allowing us to create a client connection. It then uses (defines) the target server access point for our client to access the APIs we aim to use. xmlrpc.php supports Wordpress, MovableType, MetaWeblog, and Blogger APIs all in one access point which makes it very easy if you want to use any or all four of them with the same IXR_Client object instantiation. Let’s build on that some more.
view sourceprint?
1include("../wp-includes/class-IXR.php");
3
4if (!$client->query('wp.getCategories','', 'admin','password')) {
5    die('An error occurred - '.$client->getErrorCode().":".$client->getErrorMessage());
6}
7$response = $client->getResponse();
This extra code now tells our client to query our server for the Wordpress API’s “getCategories” method using the username of “admin” with a password of “password”. If any errors occur, the script will stop execution after printing the error codes and messages supplied by Wordpress’ API server. If it’s successful, the server’s response is stored in the $response variable.
Taking a step back, the getCategories method takes on 3 parameters. I left one blank simply because Wordpress (as of version 2.7) does not require the BlogID field (WPMU might, however). The 3 parameters are BlogID, Username, and Password. It will authenticate the user before allowing information to be returned.
The Wordpress API is quite easy (or at least for the getCategories method anyway), it simply returns a PHP structured array. An example print_r output from the $response variable would look like the following:
01Array
02(
03    [0] => Array
04        (
05            [categoryId] => 1
06            [parentId] => 0
07            [description] =>
08            [categoryName] => Uncategorized
09            [htmlUrl] => http://127.0.0.1/wordpress/category/uncategorized/
10            [rssUrl] => http://127.0.0.1/wordpress/category/uncategorized/feed/
11        )
12)
That’s pretty darn easy to break down to values we might need! Now, moving on, let’s add some code to create a new post.
1$content['title'] = 'Test Draft Entry using MetaWeblog API';
2$content['categories'] = array($response[1]['categoryName']);
3$content['description'] = '<p>Hello World!</p>';
4if (!$client->query('metaWeblog.newPost','', 'admin','password', $content, false)) {
5    die('An error occurred - '.$client->getErrorCode().":".$client->getErrorMessage());
6}
7echo $client->getResponse();    //with Wordpress, will report the ID of the new post
This code sets up our desired blog title, the category we wish to use (in string format, do not use the ID value), the body of the blog post, and then queries the XML-RPC server. It calls the MetaWeblog API’s “New Post” method, and passes it some values. The parameters for this method are as follows:
Parameter Description
BlogID not used (yet)
Username the authenticated username that has permission to create posts via XML-RPC
Password the username’s associated password
Content the ARRAY of values that will be converted to XML by our XML-RPC library, and sent to Wordpress. There are a plethora of array key values that Wordpress supports for this data structure, but many of them are Wordpress specific. If you wish to remain more true to the specification, the ones I’ve shown are about all you’ve got.
Publish a boolean value to tell whether or not to immediately publish the post; in this example, it will create a draft entry, requiring someone to check it before publishing.

So we query the Wordpress XML-RPC server to create our new blog post, and if it’s successful, it returns the newly created blog posts’ ID field. The neat thing about this, is that (again, as of Wordpress version 2.7), if you wish to preview a blog post, you can use your Wordpress installation’s URL and adding a short query string to the end with this blog ID. For example, let’s assume this returned the ID of 3, the preview URL would then be:

http://www.example.com/?p=3
Pretty easy, huh? Here’s the full sample code without any breaks:
view sourceprint?
01<?php
02    include("../wp-includes/class-IXR.php");
03    $client = new IXR_Client('http://www.example.com/xmlrpc.php');
04
05    if (!$client->query('wp.getCategories','', 'admin',’password’)) {
06        die('An error occurred - '.$client->getErrorCode().":".$client->getErrorMessage());
07    }
08    $response = $client->getResponse();
09
10    $content['title'] = 'Test Draft Entry using MetaWeblog API';
11    $content['categories'] = array($response[1]['categoryName']);
12    $content['description'] = '<p>Hello World!</p>';
13    if (!$client->query('metaWeblog.newPost','', 'admin',’password’, $content, false)) {
14        die('An error occurred - '.$client->getErrorCode().":".$client->getErrorMessage());
15    }
16    echo $client->getResponse();    //with Wordpress, will report the ID of the new post
17?>
Hopefully having this documented somewhere will help someone out. I’ve used it at the work to allow librarians to upload exported XML New Item Record reports to the server, let it be processed, and then post the processed output to a Wordpress Draft where it can be reviewed for any irregularities or errors, and then posted for our public to see.

 

Thursday, November 24, 2011

Natural Language Full-Text Searches

By default, the MATCH() function performs a natural language search for a string against a text collection. A collection is a set of one or more columns included in a FULLTEXT index. The search string is given as the argument to AGAINST(). For each row in the table, MATCH() returns a relevance value; that is, a similarity measure between the search string and the text in that row in the columns named in the MATCH() list.
mysql> CREATE TABLE articles (
    ->   id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
    ->   title VARCHAR(200),
    ->   body TEXT,
    ->   FULLTEXT (title,body)
    -> ) ENGINE=MyISAM;
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO articles (title,body) VALUES
    -> ('MySQL Tutorial','DBMS stands for DataBase ...'),
    -> ('How To Use MySQL Well','After you went through a ...'),
    -> ('Optimizing MySQL','In this tutorial we will show ...'),
    -> ('1001 MySQL Tricks','1. Never run mysqld as root. 2. ...'),
    -> ('MySQL vs. YourSQL','In the following database comparison ...'),
    -> ('MySQL Security','When configured properly, MySQL ...');
Query OK, 6 rows affected (0.00 sec)
Records: 6  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM articles
    -> WHERE MATCH (title,body) AGAINST ('database');
+----+-------------------+------------------------------------------+
| id | title             | body                                     |
+----+-------------------+------------------------------------------+
|  5 | MySQL vs. YourSQL | In the following database comparison ... |
|  1 | MySQL Tutorial    | DBMS stands for DataBase ...             |
+----+-------------------+------------------------------------------+
2 rows in set (0.00 sec)
By default, the search is performed in case-insensitive fashion. However, you can perform a case-sensitive full-text search by using a binary collation for the indexed columns. For example, a column that uses the latin1 character set of can be assigned a collation of latin1_bin to make it case sensitive for full-text searches.
When http://odesk-test-solutions.blogspot.com/2011/05/odesk-php-5-test-questions-and-answers.html href="http://odesk-test-solutions.blogspot.com/2011/05/odesk-php-5-test-questions-and-answers.html">MATCH() is used in a WHERE clause, as in the example shown earlier, the rows returned are automatically sorted with the highest relevance first. Relevance values are nonnegative floating-point numbers. Zero relevance means no similarity. Relevance is computed based on the number of words in the row, the number of unique words in that row, the total number of words in the collection, and the number of documents (rows) that contain a particular word.
To simply count matches, you could use a query like this:
mysql> SELECT COUNT(*) FROM articles
    -> WHERE MATCH (title,body)
    -> AGAINST ('database');
+----------+
| COUNT(*) |
+----------+
|        2 |
+----------+
1 row in set (0.00 sec)
However, you might find it quicker to rewrite the query as follows:
mysql> SELECT
    -> COUNT(IF(MATCH (title,body) AGAINST ('database'), 1, NULL))
    -> AS count
    -> FROM articles;
+-------+
| count |
+-------+
|     2 |
+-------+
1 row in set (0.00 sec)
The first query sorts the results by relevance whereas the second does not. However, the second query performs a full table scan and the first does not. The first may be faster if the search matches few rows; otherwise, the second may be faster because it would read many rows anyway.
For natural-language full-text searches, it is a requirement that the columns named in the MATCH() function be the same columns included in some FULLTEXT index in your table. For the preceding query, note that the columns named in the MATCH() function (title and body) are the same as those named in the definition of the article table's FULLTEXT index. If you wanted to search the title or body separately, you would need to create separate FULLTEXT indexes for each column.
It is also possible to perform a boolean search or a search with query expansion. These search types are described in Section 11.9.2, “Boolean Full-Text Searches”, and Section 11.9.3, “Full-Text Searches with Query Expansion”.
A full-text search that uses an index can name columns only from a single table in the MATCH() clause because an index cannot span multiple tables. A boolean search can be done in the absence of an index (albeit more slowly), in which case it is possible to name columns from multiple tables.
The preceding example is a basic illustration that shows how to use the MATCH() function where rows are returned in order of decreasing relevance. The next example shows how to retrieve the relevance values explicitly. Returned rows are not ordered because the SELECT statement includes neither WHERE nor ORDER BY clauses:
mysql> SELECT id, MATCH (title,body) AGAINST ('Tutorial')
    -> FROM articles;
+----+-----------------------------------------+
| id | MATCH (title,body) AGAINST ('Tutorial') |
+----+-----------------------------------------+
|  1 |                        0.65545833110809 |
|  2 |                                       0 |
|  3 |                        0.66266459226608 |
|  4 |                                       0 |
|  5 |                                       0 |
|  6 |                                       0 |
+----+-----------------------------------------+
6 rows in set (0.00 sec)
The following example is more complex. The query returns the relevance values and it also sorts the rows in order of decreasing relevance. To achieve this result, you should specify MATCH() twice: once in the SELECT list and once in the WHERE clause. This causes no additional overhead, because the MySQL optimizer notices that the two MATCH() calls are identical and invokes the full-text search code only once.
mysql> SELECT id, body, MATCH (title,body) AGAINST
    -> ('Security implications of running MySQL as root') AS score
    -> FROM articles WHERE MATCH (title,body) AGAINST
    -> ('Security implications of running MySQL as root');
+----+-------------------------------------+-----------------+
| id | body                                | score           |
+----+-------------------------------------+-----------------+
|  4 | 1. Never run mysqld as root. 2. ... | 1.5219271183014 |
|  6 | When configured properly, MySQL ... | 1.3114095926285 |
+----+-------------------------------------+-----------------+
2 rows in set (0.00 sec)
The MySQL FULLTEXT implementation regards any sequence of true word characters (letters, digits, and underscores) as a word. That sequence may also contain apostrophes (“'”), but not more than one in a row. This means that aaa'bbb is regarded as one word, but aaa''bbb is regarded as two words. Apostrophes at the beginning or the end of a word are stripped by the FULLTEXT parser; 'aaa'bbb' would be parsed as aaa'bbb.
The FULLTEXT parser determines where words start and end by looking for certain delimiter characters; for example, “ ” (space), “,” (comma), and “.” (period). If words are not separated by delimiters (as in, for example, Chinese), the FULLTEXT parser cannot determine where a word begins or ends. To be able to add words or other indexed terms in such languages to a FULLTEXT index, you must preprocess them so that they are separated by some arbitrary delimiter such as “"”.
Some words are ignored in full-text searches:
  • Any word that is too short is ignored. The default minimum length of words that are found by full-text searches is four characters.
  • Words in the stopword list are ignored. A stopword is a word such as “the” or “some” that is so common that it is considered to have zero semantic value. There is a built-in stopword list, but it can be overwritten by a user-defined list.
The default stopword list is given in Section 11.9.4, “Full-Text Stopwords”. The default minimum word length and stopword list can be changed as described in Section 11.9.6, “Fine-Tuning MySQL Full-Text Search”.
Every correct word in the collection and in the query is weighted according to its significance in the collection or query. Consequently, a word that is present in many documents has a lower weight (and may even have a zero weight), because it has lower semantic value in this particular collection. Conversely, if the word is rare, it receives a higher weight. The weights of the words are combined to compute the relevance of the row.
Such a technique works best with large collections (in fact, it was carefully tuned this way). For very small tables, word distribution does not adequately reflect their semantic value, and this model may sometimes produce bizarre results. For example, although the word “MySQL” is present in every row of the articles table shown earlier, a search for the word produces no results:
mysql> SELECT * FROM articles
    -> WHERE MATCH (title,body) AGAINST ('MySQL');
Empty set (0.00 sec)
The search result is empty because the word “MySQL” is present in at least 50% of the rows. As such, it is effectively treated as a stopword. For large data sets, this is the most desirable behavior: A natural language query should not return every second row from a 1GB table. For small data sets, it may be less desirable.
A word that matches half of the rows in a table is less likely to locate relevant documents. In fact, it most likely finds plenty of irrelevant documents. We all know this happens far too often when we are trying to find something on the Internet with a search engine. It is with this reasoning that rows containing the word are assigned a low semantic value for the particular data set in which they occur. A given word may reach the 50% threshold in one data set but not another.