I was recently working with the YouTube API, and was having some frustrations getting a particular piece of data I was looking for. After a bit of Googling, I quickly discovered that other people were having a similar issue. I finally was able to dig up the answer, buried somewhere in an IBM document.
People were struggling with the way the XML was being interpreted by the standard PHP simplexml object, and couldn’t seem to accurately target the media or stats portions of the code. This means it’s difficult to get the video length and view counts.
What I ended up doing, was creating a function that would accept a YouTube username as a parameter, and return an array of video information for all videos that user has uploaded. Here’s that function:
function get_youtube_videos($max_number, $user_name) {
$xml = simplexml_load_file('http://gdata.youtube.com/feeds/api/users/' . $user_name . '/uploads?max-results=' . $max_number);
$server_time = $xml->updated;
$return = array();
foreach ($xml->entry as $video) {
$vid = array();
$vid['id'] = substr($video->id,42);
$vid['title'] = $video->title;
$vid['date'] = $video->published;
//$vid['desc'] = $video->content;
// get nodes in media: namespace for media information
$media = $video->children('http://search.yahoo.com/mrss/');
// get the video length
$yt = $media->children('http://gdata.youtube.com/schemas/2007');
$attrs = $yt->duration->attributes();
$vid['length'] = $attrs['seconds'];
// get video thumbnail
$attrs = $media->group->thumbnail[0]->attributes();
$vid['thumb'] = $attrs['url'];
// get <yt:stats> node for viewer statistics
$yt = $video->children('http://gdata.youtube.com/schemas/2007');
if (isset($yt->statistics)) {
$attrs = $yt->statistics->attributes();
$vid['views'] = $attrs['viewCount'];
} else {
$vid['views'] = "0";
}
array_push($return, $vid);
}
return $return;
}
Fun fact: Just before this went to a client (IE one of the largest companies in Canada), we discovered it was broken. Turns out that videos with zero views were returning a null value somewhere, causing the function to explode. Since most of their videos where at least a month old, this made it through testing, until they happened to upload a new video just before launch. Close call. I have now updated the post to include the latest, greatest version of this function.
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.
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.
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.
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.
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.
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.
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;
Something that I do quite commonly is link to a search result. This might be linking to a collection, linking to a subject heading, linking to multiple subject headings, or linking to something that is heavily faceted.
This can often leave a less then desirable title on the page. Take this example. I started with an Advanced search for language of Italian. I’ve then faceted by format DVD, and by Italian as a primary language (as opposed to English materials with Italian subtitles).
This is a pretty cool link to have on a page for newcomers, or a page for ESL patrons. You’ll notice though, that the page’s title ends up being the initial advanced search string: language:”ita”. Not very useful.
To make this more user friendly, all you need to do as append a title parameter to the URL. Your browser will likely replace spaces with %20 characters automatically, so you’ll want to visit the page before using it for a link. Here is the end result of the example search, with a nice title.
For me, it’s always been the small details that make or break a website’s design, but for some, this bit of accessibility will make a world of difference for their experience. This quick one will help with both of those for your site!