Site_Plugins

Jason Pell edited this page Jun 21, 2013 · 1 revision

Table of Contents

Introduction

A site plugin consists of at most 3 files:

  1. The site plugin class - site/$classname.class.php
  2. The site plugin icon - site/images/$site_type.[gif|jpg|png]
  3. The site plugin installation script - admin/s_site_plugin/sql/$site_type.sql
The $classname is the s_site_plugin table classname column value, and allows more than one site plugin to share the same plugin class file.

The $site_type is the s_site_plugin table site_type column value, and is unique for a single plugin only.

Components

Site plugin class

The site plugin class consists of a class which extends SitePlugin (functions/SitePlugin.class.inc) and has 3 methods, two of which require modification by the plugin developer.

A template for site plugin classes is the best place to start, and looks like this:

 class plugin extends SitePlugin
 {
     function plugin($site_type)
     {
         parent::SitePlugin($site_type);
     }
 
     function queryListing($page_no, $items_per_page, $offset, $s_item_type, $search_vars_r)
     {
         // standard block of code to cater for refresh option, where item already has
         // reference to site item unique ID.
         if(strlen($search_vars_r['plugin_id'])>0)
         {
             $this->addListingRow(NULL, NULL, NULL, array('plugin_id'=>$search_vars_r['plugin_id']));
             return TRUE;
         }
 
         //else - if no ListingRows then 'No Records Found' is displayed, while returning FALSE
         // results in a Undefined Exception
         return TRUE;
     }
 
     function queryItem($search_attributes_r, $s_item_type)
     {
         return FALSE;
     }
 }

For your individual plugin, you should replace the references to 'plugin' both for the php file name, and in the source itself, to whatever classname you have chosen; for example 'amazon'.

function plugin($site_type)

The constructor function looks like this, and except for changing the 'plugin' reference you should leave it exactly as is - changing this function will render the plugin useless!

 function plugin($site_type)
 {
     parent::SitePlugin($site_type);
 }

function queryListing($page_no, $items_per_page, $offset, $s_item_type, $search_vars_r)

@returns - Returns TRUE if no HTTP access issues were encountered, even if no records returned. Otherwise return false.

This function is where you build up a list of all matching titles for a specified set of $search_vars_r. The variables in the $search_vars_r correspond to the s_site_plugin_input_field records. The 'field' name will be the index.

The other parameters have been derived for you, if possible. Where an $items_per_page value of zero is configured, the $page_no, $items_per_page and $offset values should be ignored.

The $s_item_type is the item type of the item the user is adding / refreshing. This can be useful to modify the queries sent to the site plugin website or webservice, but can be ignored if you don't need it. For instance the IMDB plugin only handles generic video data, so the $s_item_type is ignored in this plugin.

This function is not responsible for building the user interface for the listing, merely to provide the raw data (in the form of arrays of arrays) for the architecture to generate the UI. Thus the data does need to be provided in a particular format.

You need to use the $this->addListingRow($title, $cover_image_url, $comments, $attributes_r) function call for each item you want to list as an option.

  • $title - is the complete title of the item (for users to recognise), which might include the year or other info, but not any images!
  • cover_image_url - should be a complete url (including http:// prefix) for a cover image url. If you do not provide one, the architecture will generate a default theme specific one, so don't do it yourself.
  • $comments - any additional comments about the title, for instance a release date, or the fact that its a special edition, vs. normal, etc.
  • $attributes_r - a set of attribute values that can be used to uniquely identify the item, such as the 'imdb_id' or 'amazonasin' for example. You would format the call thus:
 $this->addListingRow($regs[1], $regs[2], array('amazonasin'=>$regs[3])

where $regs is a regular expression result, which matched title, cover_img_url and amazonasin (in that order!)

Notes

In order to cater for an exact match on the $search_vars_r parameters, which should happen in every case for refresh option (where the appropriate site attribute is saved), is done by saving a single listing row.

The standard block of code that always should be at the beginning of your queryListing function:

 if(strlen($search_vars_r['plugin_id'])>0)
 {
     $this->addListingRow(NULL, NULL, NULL, array('plugin_id'=>$search_vars_r['plugin_id']));
     return TRUE;
 }

... where 'plugin_id' is the unique ID for a site plugin. For instance IMDB uses 'imdb_id'.

Returns
  • TRUE - Indicate that the site was queried successfully, this is not to say any listing titles will actually be found however.
  • FALSE - If the fetchURI(...) call fails. The fetchURL will set the appropriate HTTP error as a result, so you never should.

function queryItem($search_attributes_r, $s_item_type)

The $search_attributes_r array should contain enough information to uniquely identify ONE site title. If it does not, you should return FALSE, as the queryListing function has obviously been coded incorrectly.

For that unique item, you need to save all the attributes of the item, applicable for populating a $s_item_type item and attributes. You use the function:

 $this->addItemAttribute($name, $value);

Do NOT space delimit lookup values (such as AUDIO_LANG) as you would have done previously in the old plugins architecture, instead you should call the $this->addItemAttribute($name, $value); for each individual value, the process will handle it correctly.

For multi-value attributes that are not for lookup values (such as MOVIEPLOT), you would still call $this->addItemAttribute($name, $value); for each one, and the interface will present the user with all of them, from which they have to pick one.

You can use the Attribute Map functionality to map your $name's to specific s_attribute_type's in the OpenDb. For instance, you may map 'genre' to MOVIEGENRE as an example.

Notes

You do not need to trim the $value before passing it to $this->addItemAttribute($name, $value); as thats done for you.

You can pass an array for $value, and each $value will be separately added, so that it can be dealt with by the input process in a standard way.

Returns
  • TRUE - Indicate that the item was found and processed successfully.
  • FALSE - Indicate item was not found, should never occur in practice, but may if the plugin is badly coded.

SQL Script

 #
 # Cleanup
 #
 DELETE FROM s_site_plugin WHERE site_type = 'plugin';
 DELETE FROM s_site_plugin_conf WHERE site_type = 'plugin';
 DELETE FROM s_site_plugin_input_field WHERE site_type = 'plugin';
 DELETE FROM s_site_plugin_link WHERE site_type = 'plugin';
 DELETE FROM s_site_plugin_s_attribute_type_map WHERE site_type = 'plugin';
 DELETE FROM s_site_plugin_s_attribute_type_lookup_map WHERE site_type = 'plugin';
 
 INSERT INTO s_site_plugin (site_type, classname, order_no, title, image, description, external_url, items_per_page, more_info_url)
 VALUES('plugin', 'plugin', 1, 'Plugin.com', 'plugin.gif', 'A site plugin description', 'http://www.plugin.com', 25, '');
 
 #
 # Input Fields
 #	
 INSERT INTO s_site_plugin_input_field (site_type, field, order_no, description, prompt, field_type, default_value, refresh_mask)
 VALUES('plugin', 'title', 1, '', 'Title Search', 'text', '', '{title}');
 
 INSERT INTO s_site_plugin_input_field (site_type, field, order_no, description, prompt, field_type, default_value, refresh_mask)
 VALUES('plugin', 'pluginid', 2, '', 'ID Number', 'text', '', '{pluginid}');
 
 #
 # Links
 #
 INSERT INTO s_site_plugin_link(site_type, s_item_type_group, s_item_type, order_no, description, url, title_url)
 VALUES('plugin', '*', '*', 1, 'More Info', 'http://www.plugin.com/{pluginid}', 'http://www.plugin.com/search.php?title={title}');
 
 #
 # site variable to s_attribute_type mapping
 #
 INSERT INTO s_site_plugin_s_attribute_type_map(site_type, variable, s_item_type_group, s_item_type, s_attribute_type)
 VALUES ('plugin', 'title', '*', '*', 'ALT_TITLE');
 
 INSERT INTO s_site_plugin_s_attribute_type_map(site_type, variable, s_item_type_group, s_item_type, s_attribute_type)
 VALUES ('plugin', 'title', '*', '*', 'S_TITLE');
 
 ####################################################################################################
 # Item Type / Attribute Type relationships
 ####################################################################################################
 

Debugging Site Plugins

While testing your plugins its often useful to see what raw data elements are being returned (prior to mapping to attributes occuring). You can enable a debug setting, with the following SQL statement:

INSERT INTO s_config_group_item ( group_id, id, order_no, prompt, description, type ) VALUES ('item_input.site', 'debug', 1, 'Debug Site Plugins', , 'boolean');

This will add a new checkbox to the Item Input tab in the Site Plugins section called 'Debug Site Plugins', check this checkbox and save the configuration, and you will get a Debug div displaying all elements returned from a site plugin.

Notes For the PHP-Challenged - Common php functions to use and how to use them

Introduction

Making plugins isn't hard if you've done any basic programming. The syntax is actually quite similar to C++ in a lot of respects. This isn't a section on how to program, but it will hopefully help you to migrate your skills from one language to the other. I'd recommend searching for a good beginners guide for php that will help you learn some of the specific syntax. With that said, there are a few functions that are near essential to use for any plugin.

strpos()
substr()
preg_match() & preg_match_all()

strpos

strpos($bufferToSearchThrough, $string, $optionOnlyToStartAtPosition);
this will search through $bufferToSearchThrough and return the position of the first instance of $string. If you specify $optionOnlyToStartAtPosition, it will search from that position.

Example you'll see in the plugins:

 $start = strpos($pageBuffer, "<b>Description<\b>");

searches the webpage($pageBuffer) for the first instance of <b>Description<\b> and returns the position in the webpage that it starts at.

Further Example:

 $end = strpos($pageBuffer, "&lt;/table&gt;", $start);

searches the webpage for the first instance of </table> from the $start position. It makes it easy to find a block of text you want to parse through. Which brings us to...

substr

substr($bufferToExtractTextFrom, $startPosition, $howManyCharactersToStopExtracting)

Returns a block of $bufferToExtractFrom, starting at $startPosition and ending $howManyCharactersToStopExtracting characters later.

Example you'll see($pageBuffer is the webpage we're parsing through):

 //First, look in $pageBuffer for the first instance of &lt;b&gt;Description&lt;\b&gt;
 $start = strpos($pageBuffer, "&lt;b&gt;Description&lt;\b&gt;");
 //Make sure it exists
 if($start)
 {
 	//See if we can find the end piece of the block we want to search for
 	$end = strpos($pageBuffer, "&lt;/table&gt;", $start);
 	//Make sure it was found
 	if($end)
 	{
 		/*
 		  see if we can get a block of text
 		  Note: We use $end-$start because it's not $end we're putting
 		  in here, but the total number of characters from $start to
 		  $end.
 		*/
 		$descriptionBlock = substr($pageBuffer, $start, $end-$start);
 		/*
  		   $desciptionBlock should now contain everything in the webpage
 		   from the first instance of &lt;b&gt;Description&lt;/b&gt; to the first
 		   instance of &lt;/table&gt; following.
 		*/
 	}
 }

preg_match & preg_match_all

preg_match($patternToSearchFor, $blockToSearchThrough, $arrayOfMatches)
and
preg_match_all($patternToSearchFor, $blockToSearchThrough, $arrayOfMatches)

These functions search $blockToSearchThrough for $patternToSearchFor and put the data in $arrayOfMatches. There are two main differences between the two.

First, the return values. Where preg_match returns a 0 or 1 depending on whether it found a match(0 being no matches, 1 being a match), preg_match_all returns how many matches it found(0 being no matches, 1+ being how many).

Second, the $arrayOfMatches value is returned a little differently.

In preg_match, $arrayOfMatches returns as such:
$arrayOfMatches[0] is the whole pattern found.
$arrayOfMatches[1(and] is just the part of the pattern we're searching for.
We'll get to patterns in a moment.

In preg_match_all, $arrayOfMatches returns these ways:
$arrayOfMatches[0][0] returns the first instance of the whole pattern found.
$arrayOfMatches[0][2] returns the third instance of the whole pattern found.
$arrayOfMatches[1][0] returns the first instance of the first variable in the pattern found.
$arrayOfMatches[4][2] returns the third instance of the fourth variable in the pattern found.

Okay, that's probably a little confusing. Let's explain patterns.

Patterns are essential to getting info from webpages to your plugin. They are basically strings that are far from basic. Let's say we were searching for a spot in the webpage that looks like this: <b>Description:</b>This is a little bit about a movie. What we're planning on doing is finding a line that looks like that and extracting the "This is a little bit about a movie." text to put in our Item Attribute.

This is how the function would look(assuming there's only one instance of <b>Description</b> on the web page($pageBuffer) we're searching through):

 preg_match("!&lt;b&gt;Description:&lt;/b&gt;([^<]+)</table>!", $pageBuffer, $match);
 echo $match[0];
 echo $match[1];

Our $match[0] would be equal to "<b>Description:</b>This is a little bit about a movie.</table>" While our $match[1] would be equal to "This is a little bit about a movie."

Let's break the pattern down a bit. The first part, !, says that the pattern will end with an !. So, everything between ! and ! is the pattern.

The second part, <b>Description:</b>, is the first part of the pattern searched for.

The third part, ([^

It could be read like this to preg_match. Search for any number of characters after "<b>Description</b>" that is not a < character and put it in $match[1]. Or, everything between </b> and </table>.

The last part, </table> is similar to <b>Description:</b>, it looks for a string that ends in </table>.

What's returned by preg_match() can be summed up with 0 for FALSE or 1 for TRUE, making if statements useful here.

Now, $match can possibly equal many things. $match[0] will always be the whole pattern(as a string) including <b>Description:</b>, </table>, and everything in-between. $match[1] will be what's included in the first set of ()s. If we had another set of ()s, $match[2] would be that set, and so on.

preg_match_all() is very similar to preg_match(). There are two differences. The return value(which is how many matches there were) and that $match returns an array of an array. Instead of stopping at the first instance of the pattern like preg_match(), preg_match_all() will put all instances of the pattern into the $match string. You can access it like this:(imagine the pattern is the same as before)

$match[0][0] is the whole pattern of the first instance from <b>Description:</b> to </table>. $match[1][0] is the whole pattern of the second instance. $match[2][0] is the whole pattern of the third instance, and so on.

$match[0][1] is the bit between the ()s for the first match. $match[1][1] is the bit between the ()s for the second match. $match[2][1] is the bit between the ()s for the third match and so on.

Expressions

These are some things I picked up while learning how to do this from different websites.

^ - Matches any string that starts with what follows("^The" matches any string that starts with The

$ - Matches any string that ends with statement("of despair$" matches any string that ends with 'of despair'

"whatever" - Matches whatever

* - Matches a string that is followed by one or more characters("ab*" matches a string that has an a followed by zero or more bs("a", "ab", "abbbb", etc.))

+ - Matches a string that has one or more characters("ab+" matches "ab", "abbb", etc.)

? - Matches, well...("ab?" there might be a b or not)
example "a?b+$" a possible a followed by one or more bs ending a string

(n) - matches a string that has exactly n characters("ab(2)" matches "abb")

(n,) - matches at least n characters("ab(2,)" matches "abb", "abbbb", etc.)

(n,x) - matches a range of characters from n to x("ab(3,5)" matches "abbb", "abbbb", or "abbbbb")
example "a(bc)*" matches a string that has an a followed by zero or more copies of the sequence "bc"
example "a(bc)(1,5)" matches one to five copies of "bc"

| - works as an OR operator("hi|hello" matches a string that has either "hi" or "hello" in it)
example "(b|cd)ef" matches string that has "bef" or "cdef" in it
example "(a|b)*c matches string that has sequence of alternating "a"s and "b"s ending in a c

. - stands for any single character("a.[0-9]" matches a string that has an "a" followed by one character and a digit)
example "^.(3)$" a string with exactly 3 characters

[or] - Brackets specify which characters are allowed in a single position of a string("[ab]" matches a string that has either an "a" or a "b")
example "[a-d]" matches a string that has lowercase letters a through d
example "^[a-zA-Z]" matches a string that starts with a letter
example "[0-9]%" matches a string that has a single digit before a percent sign
example ",[a-zA-Z0-9]$" matches a string that ends in a comma followed by an alphanumeric character

^ - Used to list which characters you don't want in a bracket expression("%[^a-zA-Z]%" matches a string with a character that is not a letter between two percent signs)

\ - Used to make literal connections for "^.[$()|*+?{\" garbage]$()|*+?{\"

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.