SIP Strings For Patron Accounts

Previously, we’ve looked at how to make our web server talk to our SIP server, and how to make PHP talk to our SIP server. Let’s start to retrieve some real data from SIP.

Sending some garbage text to SIP, and receiving a 96 code is great, but now let’s define some real codes. To do this, log into your SIP server. Navigate to where you have installed SIP. You should see a folder called ‘sirsidynix’. This folder will contain folders for each of the SIP connections you have a license for (so as an example, we have 8). Each one should be named in a fairly straightforward way. Go into the one that you will be using.

Now that you’re into the right folder for your connection, navigate into the bin folder. Here you should see a file called AcsTester.exe. Run that program and you should see something like this screen shot.

At the top of the program window, you’ll see a button labeled “Create Message”. Clicking that will give you a list of different messages that you can send to SIP. For this excercise, let’s use Patron Status Request (but also take note of the “Fee Paid” message – you should be connecting dots at this point).

As far as I know, what you will see at this point will vary slightly from connection to connection, based on the settings and setup for that connection. For example, requiring the patron’s pin may be turned off, requiring a transaction date may be turned off, and likely any other number of settings can affect this. I’m going to show a screen shot of what I entered, so that you can follow along when I generate my actual SIP message.

The end result is this message string:

2300120110501    102705|AO|AAmyBarcodeHere|AC|AD1234|AY0AZF001

Let’s feed this string into the code we created in the last article, and we should receive back a real patron’s status.

Basic Code

<?php
	$sip_server_address = 'MyServerName';
	$sip_server_port = 2023;

	$fp = fsockopen($sip_server_address,$sip_server_port,$errno,$errstr);
	stream_set_blocking($fp, 0);
	
	$check = '2300120110501    102705|AO|AAmyBarcodeHere|AC|AD1234|AY0AZF001';

	fputs($fp, "$check\r");
	sleep(2);

	$response = fread($fp, 256);

	echo $response;
?>

This should return a code for the user. Again, this code will look a little different from system to system, but should be fairly recognizable regardless. If the user has no fines, it will typically have this code in it “BLY|CQY”. We’ll set up our message to be a bit more dynamic below, so you can try a few barcodes, with a few results to see how a patron with a block appears, a patron that owes fines, a patron that is in good standing, etc.

Break It Down Further

So we should know have the basic code working. Let’s separate out that $check variable and make it a bit more dynamic so that it’s built off of the current date, and with whatever barcode you want. Then we can start to trap our output and return some real messages.

	$sip_server_address = 'MyServerName';
	$sip_server_port = 2023;
	$barcode = '999999999';
	$pin = '1234';

	$fp = fsockopen($sip_server_address,$sip_server_port,$errno,$errstr);
	stream_set_blocking($fp, 0);
	
	$check = '23001' . date('dmY') . '    102705|AO|AA' . $barcode . '|AC|AD' . $pin . '|AY0AZF001';

	fputs($fp, "$check\r");
	sleep(2);

	$response = fread($fp, 256);

	switch($response) {
		case (strpos($response,'BLY|CQY|AY')!==FALSE):
			echo '<p>You do not owe any fines!</p>';
			break;
		case (strpos($response,'BLY|CQY|BH')!==FALSE):
			echo '<p>You owe fines!</p>';
			break;
		case (strpos($response,'Incorrect password')!==FALSE):
			echo '<p>Your pin does not match!</p>';
			break;
		case (strpos($response,'Unknown borrower barcode')!==FALSE):
			echo '<p>Your barcode was not found!</p>';
			break;
		default:
			echo '<p>We need to make a definition for this message: ' . $response . '</p>';
			break;
	}

Note For Other ILS’s

I want to be very clear that your mileage may vary with this code. This is what works for me, with a Horizon setup. I had a friend look into III’s setup, and he came back from the manuals that a lot of their SIP stuff is integrated with the WebPAC and Express Lane products only. They do offer the Fine Payment Web Service for this type of integration, but that comes at a price (cost is unknown, he didn’t go to his sales rep yet).

Sum Up

I hope this crash course in SIP programming has been beneficial to you. As always, please leave a comment to let me know how this is (or isn’t) working out for you.

Connect To SIP With PHP

Last time, we took a look at how to make sure your web server can see your SIP server. This time, we’re going to carry on with actually making the two talk to each other.

Conceptually, all that needs to happen, is we open a socket connection to SIP. This basically acts as a Telnet session, so we can send a message to the server, and it will reply back with a message. For this reason, we need to use a sleep command, to wait for the server to reply. Depending on your network setup, you may want to experiment with different sleep times to see how short you can go.

Basic Code

<?php
	$sip_server_address = 'MyServerName';
	$sip_server_port = 2023;

	$fp = fsockopen($sip_server_address,$sip_server_port,$errno,$errstr);
	stream_set_blocking($fp, 0);
	
	$check = 'Our_SIP_String_Will_Go_Here';

	fputs($fp, "$check\r");
	sleep(2);

	$response = fread($fp, 256);

	echo $response;
?>

By taking the above code, and swapping in your server’s name (or IP address will work as well), and port number you should receive back a response of 96. As stated before code 96 is SIP’s way of saying it doesn’t understand what you’re asking for. If you get that, you are connected to SIP, and everything is working as it should.

Troubleshooting

If your page is timing out, you likely have a problem with your server name or port. Double check the port, and in a pinch try connecting via IP address. I actually have my server name defined in a hosts file on my server. This may be overkill, but it may also be just the tip you need to get things working. Another idea is to check that you still have a connection to SIP, to rule out network issues.

Next time, we’ll take a look at how to create a real message to SIP, so that we can receive account information back. If you have any success or failures with connection, please let me know by using the comments section.

How To Connect To SIP

This post is the start of a series. I’m going to be releasing all of the bits and pieces that make up the online payments system at my library. It should be a good model for most libraries to follow, you can use nearly any payment gateway, and all you need is SIP and PHP.

The first step is going to be to make sure our web server can actually connect to our SIP server. This will save you lot’s of time thinking your PHP is broken, when it’s really a port open/closed issue.

First, login to your web server, the one that will be connecting to SIP through PHP. Open up a command prompt and enter the command ‘telnet sip.mydomain.com 666’ where sip.mydomain.com is the address to your SIP server, and 666 is the port number you wish to connect into.

After you press enter, your screen should go totally blank. It will look really weird, but not to worry, this is normal. You’ve now started a telnet connection to your SIP server. You can type in anything you want (it won’t show up, this is something called localecho), and when you hit enter you should get a 96 as a response. The 96 response means that SIP didn’t understand the command. This does verify that you are connected into the server, however.

If you receive another message, like the one in this screen shot, then your webserver cannot see your SIP server. You’ll have to make the changes to the firewall to open the port.

Failed SIP Connection

Now we know that your webserver can actually connect to your SIP server. In the next post in this series, we’ll actually make the connection through SIP, and get some real data from SIP.

Aggregating ISBNs From Horizon

I’m working on creating a spreadsheet that will be imported into another application. The format for the spreadsheet is fairly picky, as it expects to match titles, authors, ISBNs, etc. The problem is that it wants each bib to be a new line.

Sounds simple, right? Well, when you look beneath the hood (this is a Horizon ILS using a SQL Server backend), each ISBN (and author, and UPC, etc) is a separate line in the database. This is great from a normalization standpoint, but despite my many years of writing SQL queries for SQL Server, this never ceases to amaze me how much of a pain in the butt it can be.

MySQL has an awesome GROUP_CONCAT function that handles this perfectly. MSSQL does not.

Here’s a bit of code I used to make this work. You can easily modify it to pull off of any MARC field. My actual implementation uses the 592 note field to look for a tag that flags the title as being an upcoming bestseller for the next season, but for demonstration purposes, I made a couple examples that should be easier to follow.

592 Note Field

SELECT bib, LEFT(isbn_list, LEN(isbn_list)-1) AS isbn_list
FROM (
	SELECT bib, (
		SELECT isbn.processed + ';' AS [text()]
		FROM isbn
		WHERE isbn.bib# = bibs_to_use.bib
		FOR XML PATH('')
	) as isbn_list
	FROM (
		SELECT b.bib# bib
		FROM bib b
		WHERE b.tag='592'
			AND b.text LIKE '%New and Forthcoming Fiction Winter 2011%'
	) as bibs_to_use
) as outter_query;

082 Call Number

SELECT bib, LEFT(isbn_list, LEN(isbn_list)-1) AS isbn_list
FROM (
	SELECT bib, (
		SELECT isbn.processed + ';' AS [text()]
		FROM isbn
		WHERE isbn.bib# = bibs_to_use.bib
		FOR XML PATH('')
	) as isbn_list
	FROM (
		SELECT b.bib# bib
		FROM bib b
		WHERE b.tag='082'
			AND b.text LIKE '%005%'
	) as bibs_to_use
) as outter_query;

260 Publisher

SELECT bib, LEFT(isbn_list, LEN(isbn_list)-1) AS isbn_list
FROM (
	SELECT bib, (
		SELECT isbn.processed + ';' AS [text()]
		FROM isbn
		WHERE isbn.bib# = bibs_to_use.bib
		FOR XML PATH('')
	) as isbn_list
	FROM (
		SELECT b.bib# bib
		FROM bib b
		WHERE b.tag='260'
			AND b.text LIKE '%Little, Brown and Co%'
	) as bibs_to_use
) as outter_query;

To be fair, I used an old example from a blog called Rational Relational and then converted that over to our needs in Horizon.

Update – 21 Jan 11

Here’s a little update that adds in a similar method for authors. It is very common to need to list authors in a comma seperated list, and it was a little tricky for me, so I’d assume it could be very tough for some readers.

SELECT bib, LEFT(isbn_list, LEN(isbn_list)-1) AS isbn_list,LEFT(author_list, LEN(author_list)-1) AS author_list
FROM (
	SELECT bib, (
		SELECT isbn.processed + ';' AS [text()]
		FROM isbn
		WHERE isbn.bib# = bibs_to_use.bib
		FOR XML PATH('')
	) as isbn_list, (
		SELECT a.processed + ';' AS [text()]
		FROM bib_auth ba, author a
		WHERE ba.bib# = bibs_to_use.bib
			AND a.auth# = ba.auth#
		FOR XML PATH('')
	) as author_list
	FROM (
		SELECT b.bib# bib
		FROM bib b
		WHERE b.tag='260'
			AND b.text LIKE '%Little, Brown and Co%'
	) as bibs_to_use
) as outter_query;

Get Jacket Covers From BC API

Here is a little function for getting jacket covers from the BiblioCommons API.

I keep a folder with functions like this, so that I’m never re-writing code to retreive data from the API. It’s also handy in the event that something changes with the API, be it the data returned, or the format of the call.

The Code

function getJacket($bib_id, $min_width = 150) {
	$timeout = 3;
	$bib_id = trim(mysql_escape_string($bib_id));

	$ch = curl_init();
	curl_setopt($ch, CURLOPT_URL, 'http://my_library.bibliocommons.com/api/ContentService/imageurl/' . urlencode($bib_id) .'/'. urlencode($min_width) .'/150');
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
	curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
	$result = curl_exec($ch);
	curl_close($ch);
	 
	$xml = simplexml_load_string($result);
	if($xml) {
		return $xml;
	} else {
		return 'images/book.jpg';
	}
}

Things To Remember

Remember to change my_library to your libraries sub-domain on BiblioCommons. If you are using this as an app developer, or a plugin/widget/add-on/module developer, you can swap out that entire line for this (making sure to set, or pass in, a $bc_key variable):

curl_setopt($ch, CURLOPT_URL, 'http://api.bibliocommons.com/api/ContentService/imageurl/' . urlencode($bib_id) .'/'. urlencode($min_width) .'/150?api_key=' . $bc_key);

This function requires that you have cURL installed and working correctly on your server. I use cURL to fetch the XML because then I am able to set a timeout.

You can change the $timeout variable to be whatever you want. I find 3 seconds is reasonable for my uses (including the front page of my library’s website).

The $bib_id variable is expecting a BiblioCommons item ID. This item ID can be made by appending your BiblioCommons library ID to the end of a Horizon item ID. So, my library ID is 001. I can make an item ID for Horizon item ID X as X001. Mileage may vary for non Horizon libraries.

Depending on the circumstance, an easier way to get an item ID is to pull it right from its URL in the catalog. Here is the URL to a title: http://opl.bibliocommons.com/item/show/612436001_harry_potter_and_the_deathly_hallows. The BC item ID is 612436001.

How It Works

This function calls the API imageurl function, passing in a few variables from our getJacket function call. We pass in the bib ID, and the width that we want the jackets set to.

The API call is fetched through cURL. If we don’t receive the results before our timeout, we will return a generic cover called book.jpg (feel free to change this line to match your path). The API may be slow, it may be down, your web server may be having issues with connection, DNS, or any other number of problems. For this reason, we set a timeout.

When I originally created this function, I had no timeout (and no cURL dependency). Every time we had an issue, this function would crash. This would cause the entire right column of our homepage, and countless other pages site wide, to not load. It would also cause the footer to not show up, as anything after this call would not load.

If the XML is returned, and loaded properly, we will return the URL that we receive. This may be for Amazon or Syndetics depending on your library’s setup. In the future, who knows where else these could come from (IMDB? LibraryThing?). We let the API handle that, and just interface with the API.

Calling the Function

Here’s two examples of how to call this function, both yielding the same results.

include('bc-api/get-item-jacket-function.php');

echo '<img alt="Books Title Here" src="';
echo getJacket($bib_id);
echo '">';
include('bc-api/get-item-jacket-function.php');
$jacket_url = getJacket($bib_id);

echo '<img alt="Books Title Here" src="' . $jacket_url . '">';

Edit/Update

After posting this, I received an email from one of the main developers at BiblioCommons (maybe the main developer, not sure your title Marty!). It turns out that this portion of the API won’t likely be available for your library. It will not be available in the “new” API, for select developers. For backwards compatibility, a few libraries will still have access to it, but it may not be available forever. Forward looking, any new libraries coming online with the service will not have access to this call.

I want this to be very clear for everyone that they won’t likely have access for it, I don’t want to mislead anyone. I will leave this post up, however, for those of us who do have it.

Adding BiblioCommons Widgets To Your Site

Example Carousel

I’ve had several libraries ask me about the carousel of items that we feature on our website at Oakville Public Library. Many seem to have difficulty setting this up.

Documentation

I think the problem really goes further back then that. Many people don’t seem to know where the documentation is for these widgets. Take this address: http://opl.bibliocommons.com/info/integration/ and swap out the “opl” sub-domain for your library’s sub-domain (seatle, vpl, hpl, etc. are all good examples). This page is behind a login, and will only let you view it if the barcode you are logging in with is setup as an administrator.

Am I An Admin?

When you login to any page on BiblioCommons, look at the very top bar of the page. Every user will have “Logged in as X”, messages, account settings, etc. If you are an admin, you will have an added option called “Lib Admin”. This is where you can change library wide settings (turn on/off different reminders, which user generated content appears on the dashboard, turn on/off community credits, etc).

So Now What?

Once you are on the http://my-library.bibliocommons.com/info/integration/ page you will find a set of 6 simple steps to get your widgets up and running:

  1. Download the Javascript Package and The Stylesheet package from this page.
  2. Place these files with the rest of your css and javascript files.
  3. Place a link to these files in the header of your site.
  4. Drop this code into a script block in your header as well:
    var baseurl = “http://apl.bibliocommons.com”
  5. Replace “http://apl.bibliocommons.com” with the domain for your library’s bibliocommons site. (e.g. http://opl.bibliocommons.com)
  6. That’s it, you are ready to start adding widgets.

This JS Is Huge!

You’ll notice that the JavaScript file is over 7500 lines of code. Pretty big, huh? Well the cool thing about it is that all of the JavaScript functionality for BiblioCommons is built off of the MooTools framework.

If you’re not familiar with JavaScript frameworks, it basically packages together really common functionality, and extends the language to simplify it somewhat. It also handles all of your cross-browser compatibility stuff, and makes AJAX a lot easier.

Here’s a more thorough explanation of JavaScript frameworks: http://en.wikipedia.org/wiki/JavaScript_library. You can read further on MooTools at www.mootools.net.

So, by including the BiblioCommons JavaScript file in your sites header, you can use MooTools on every page of your site, and build some pretty cool stuff. This made choosing a JS framework a really simple choice for me. No need to duplicate things.

And The Code?

BiblioCommons has two types of carousels. The one you’re probably thinking of first is the recently returned/recently reviewed carousel. The second one is displaying any user list as a carousel. The code for both is fairly similar, but different enough to throw a kink at you.

Reports

We feature recently reviewed right on our homepage. This type of carousel is called a “report”. Once you have your JS and CSS file included on the page, simply add the below few lines.

<div id="recently_reviewed_bibs_container"></div>

<script charset="utf-8">
       new Widget({
                widgetType: 'browser',
                element: 'recently_reviewed_bibs_container',
                widgetOpts: {
                          report: 'recently_reviewed_bibs'
                },
                local: false
       });
</script>

User Lists

The user lists carousels are awesome. Let’s say your Business Specialist wants to feature some items on their section of the website. No problem. “You make a list, provide me with its ID, and then you can maintain it as often as you like.” I use these all over the OPL website.

One small note, items without jackets will not display in the carousel, so be careful with local history resources.

<div id="holder_name_here"></div>

<script charset="utf-8">
    new Widget({
          widgetType: 'list',
          element: 'holder_name_here',
          widgetOpts: {
               list_id: 22343432
          },
          local: false
    });
</script>

To display any list, simply change the list_id to the ID of the list you wish to display. The ID is displayed as part of the URL of a list. Let’s use an example from a librarian at OPL: http://opl.bibliocommons.com/list/show/68369006_oplreads/68373900_growing_up_and_coming_of_age. The ID of this list is 68373900. Not to be confused with 68369006, which is the ID of this user.

How I Use It

I hate to write the same code twice. I also hate to be at the mercy of a third party vendor, like BiblioCommons. I have almost thirty different lists featured on my site using the carousel widget. If BC decides to change the syntax of the call to the widget, I’d then have to track down all thirty uses and change them. To be fair, since early beta in late 2007, this syntax has never changed; knock on wood.

So here’s what I do. This is written in PHP, but you should easily be able to port this to another language. I have a folder with all of the carousels in it. In that folder, I have a file called base.inc.php. The base code looks like this:

<div id="holder_<?php echo $bc_list_id;?>" class="clearfix"></div>

<script charset="utf-8">
    new Widget({
         widgetType: 'list',
         element: 'holder_<?php echo $bc_list_id;?>',
         widgetOpts: {
               list_id: <?php echo $bc_list_id;?>,
               local: false
         }
    });
</script>

This simply creates a unique div ID (you may have more then one list on a page) and also fills in the list ID to call. Then, I have a file for each individual list I’m displaying. Here’s the current one for our kids homepage.

<?php
    $bc_list_id = 75941581;
    include('base.inc.php');
?>

Pretty straight forward. Set the list ID, then include the base code. Now, if the base code changes, we only have to change it in one spot and it updates site wide. Again, imagine you have 30 (or 50 or 100) lists setup like this example. Each one just has the list ID in it. No code duplication.

To use this list on a page, now all I have to do is (in PHP):

include(‘path_to_widgets/bc/carousel/kids_homepage.php’);

One Step Further

Having multiple lists on a page is okay, they will work fine. At some point, speed will be an issue, but I doubt you’re going to want ten carousels on a single page anyway. I do, however, often receive requests from staff who want to feature more then one list. This may be because the topic is very broad, so they need lists to cover different sub-topics, or it may be to make the page seem a little more fresh.

Using the above example, I can now change my kids_homepage.php file to the following:

<?php
      $possible_lists = array(
             71818961,71124019,69036536,
             69081450,69089522,69043692,
             69043018,69040840,71124019,71124019
      );
      $bc_list_id = $possible_lists[array_rand($possible_lists)];
      include('base.inc.php');
?>

This will set the list ID to be a random one from the array. Now you can define as many lists as you want, and every page load will display a different one.

How Do You Use Them?

I’d love to hear how you’re using carousels. Let me know in the comments below!