<?xml version="1.0" encoding="UTF-8"?> <rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:creativeCommons="http://backend.userland.com/creativeCommonsRssModule"><channel><title>Disruptive Library Technology Jester &#187; Drupal</title> <atom:link href="http://dltj.org/tag/drupal/feed/" rel="self" type="application/rss+xml" /><link>http://dltj.org</link> <description>We&#039;re Disrupted, We&#039;re Librarians, and We&#039;re Not Going to Take It Anymore</description> <lastBuildDate>Fri, 18 May 2012 15:43:10 +0000</lastBuildDate> <language>en</language> <sy:updatePeriod>hourly</sy:updatePeriod> <sy:updateFrequency>1</sy:updateFrequency> <cloud domain='dltj.org' port='80' path='/?rsscloud=notify' registerProcedure='' protocol='http-post' /> <creativeCommons:license>http://creativecommons.org/licenses/by-nc-sa/3.0/us/</creativeCommons:license> <item><title>Drupal as the Foundation of Ohio Textbook Portal</title><link>http://dltj.org/article/ohio-textbook-portal-design/</link> <comments>http://dltj.org/article/ohio-textbook-portal-design/#comments</comments> <pubDate>Fri, 12 Sep 2008 20:34:03 +0000</pubDate> <dc:creator>Peter Murray</dc:creator> <category><![CDATA[Textbooks]]></category> <category><![CDATA[Drupal]]></category> <category><![CDATA[textbook]]></category> <category><![CDATA[University System of Ohio]]></category><guid isPermaLink="false">http://dltj.org/?p=487</guid> <description><![CDATA[At the end of last month, the Ohio Board of Regents announced the University System of Ohio Textbook Portal. The service has been talked about in the media, in trade publications, and in numerous blog postings. Enough time has passed &#8230; <a href="http://dltj.org/article/ohio-textbook-portal-design/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description> <content:encoded><![CDATA[<abbr class="unapi-id ignore noPrint" title="http://dltj.org/?p=487"></abbr><p>At the end of last month, the Ohio Board of Regents <a href="http://uso.edu/newsUpdates/media/releases/2008/08/MediaRel_27Aug08.php" title="University System of Ohio announcement of Textbook Portal" class="broken_link" rel="nofollow">announced</a> the <a href="http://textbooks.uso.edu/" title="Ohio Textbook Portal homepage" class="broken_link" rel="nofollow">University System of Ohio Textbook Portal</a>.  The service has been talked about in the <a href="http://www.columbusdispatch.com/live/content/local_news/stories/2008/08/27/e-books.ART_ART_08-27-08_A1_U5B58IR.html?sid=101" title="The Columbus Dispatch : Half-priced college books: Students to get cheaper, digital option">media</a>, in <a href="http://www.insidehighered.com/news/2008/08/26/etextbooks" title="Next Steps for E-Texts :: Inside Higher Ed :: Higher Education&#039;s Source for News, Views and Jobs">trade publications</a>, and in <a href="http://thecite.blogspot.com/2008/08/university-system-of-ohio-and.html" title="The CITE: University System of Ohio and CourseSmart">numerous</a> <a href="http://www.ljndawson.com/permalink/2008/09/03/USO_and_CourseSmart.html" title="USO and CourseSmart in LJNDawson&#039;s blog" class="broken_link" rel="nofollow">blog</a> <a href="http://library.duke.edu/blogs/scholcomm/2008/08/29/state-of-play/" title="Scholarly Communications @ Duke &amp;raquo; E-textbooks: the state of play">postings</a>.  Enough time has passed now that word has gotten out, and I won&#8217;t be taking any of the chancellor&#8217;s thunder about the project.  I did the back-end development work for the portal and wrote this document as an introduction to the project for our development team and anyone else interested about the project.</p><p>The textbook portal is based on the <a href="http://www.drupal.org/" title="Drupal homepage">Drupal</a> (<a href="http://api.drupal.org/api/6" title="Drupal API reference for version 6">version 6</a>) content management system.  In particular, the portal makes heavy use of the <a href="http://api.drupal.org/api/file/modules/search/search.module/6" title="search.module | Drupal version 6API">search module</a> to execute and format search results.  If you are familiar with Drupal, it is going to be different enough, however, that you&#8217;re going to want to read this to see why some decisions were made.  If you are not familiar with Drupal, this document will give you a head start into understanding the <a href="http://drupal.org/node/326" title="Drupal&#039;s APIs">Drupal way of the world</a>.</p><p>A couple of points before we start.  First, before starting this project I had only a passing familiarity with PHP as a programming language and no experience with code development for Drupal. <sup><a href="http://dltj.org/article/ohio-textbook-portal-design/#footnote_0_487" id="identifier_0_487" class="footnote-link footnote-identifier-link" title="These made seem like odd choices to make for a project that had a short conception-to-production timeline, but a) there were already some helpful pieces written in PHP that sped development of some aspects of the portal, and b) I thought drinking the cool-aid of Drupal would be a good way to see what it was all about.">1</a></sup> Read the code with that frame of mind; if you have more experience in either of these areas and know of a better way to do something, please let me know and I will gratefully incorporate your suggestions into the code.  Second, you can find the <a href="https://drc-dev.ohiolink.edu/svn/eTextbookPortal/">code in OhioLINK&#8217;s public Subversion repository</a> and reference to it in <span class="removed_link" title="http://drc-dev.ohiolink.edu/browser/eTextbookPortal/">OhioLINK&#8217;s public Trac project server</span>, should you want to take a look at it yourself.</p><p>This code in the Subversion repository corresponds to everything under the <code>/sites</code> directory of a Drupal installation.  In the basic Drupal installation, there are two subdirectories in this directory: <code>all</code> and <code>default</code>.  In a multi-site Drupal installation, the &#8220;all&#8221; directory is supposed to correspond to modules/themes that are made available to all sites within an installation while the &#8220;default&#8221; directory is intended for modules/themes for the &#8220;<a href="http://drupal.org/node/53705" title="Setup of Drupal /sites directory for multi-site">default site</a>&#8220;.  I&#8217;m using the distinction somewhat differently.  Everything in the &#8220;all&#8221; directory is third-party modules and everything in the &#8220;default&#8221; directory is stuff I&#8217;ve created.  It is an arbitrary, unnecessary distinction, but I think it will help with maintenance.</p><p>At a very high level, you can look at the <span class="removed_link" title="http://drc-dev.ohiolink.edu/browser/eTextbookPortal/etextbook-installation.html?rev=1042">Installation Documentation for the ETextbook Portal</span>.  This document was written from the perspective of a bare metal restore of the service.  (Well, not quite &#8212; it assumes Ubuntu is installed on the server.)  It has the various applications and modules that need to be installed to get the site up and running.  This should make a good checklist should you wish to reproduce the portal.  Knowing how to <a href="http://drupal.org/getting-started" title="Installation Guide for Drupal 6">install Drupal</a> comes in handy, but the installation process itself it pretty easy.</p><p>If you follow the documentation up to the point of restoring the database, you&#8217;ll have a good foundation.  But doing so will mean that there are several configuration options you&#8217;ll need to set that would otherwise be in the database backup.  You&#8217;ll need to activate these modules:</p><dl><dt><span class="removed_link" title="http://drc-dev.ohiolink.edu/browser/eTextbookPortal/default/modules/all?rev=1042">eTextbook Metasearch</span></dt><dd>Integrates the results from the various textbook search modules.  This corresponds to the Drupal node type &#8220;all&#8221;.</dd><dt><span class="removed_link" title="http://drc-dev.ohiolink.edu/browser/eTextbookPortal/default/modules/csmart?rev=1042">CourseSmart Search</span></dt><dd>Searches the CourseSmart eTextbook Database.  This corresponds to the Drupal node type &#8220;csmart&#8221;.</dd><dt><span class="removed_link" title="http://drc-dev.ohiolink.edu/browser/eTextbookPortal/default/modules/ebooks?rev=1042">OhioLINK E-Books Search</span></dt><dd>Searches the OhioLINK E-book Center.   This corresponds to the Drupal node type &#8220;ebooks&#8221;.</dd><dt><span class="removed_link" title="http://drc-dev.ohiolink.edu/browser/eTextbookPortal/default/modules/libcat?rev=1042">OhioLINK Library Catalog Search</span></dt><dd>Searches the OhioLINK Central Catalog.   This corresponds to the Drupal node type &#8220;libcat&#8221;.</dd><dt><span class="removed_link" title="http://drc-dev.ohiolink.edu/browser/eTextbookPortal/default/modules/safari?rev=1042">Safari Search</span></dt><dd>Searches Safari Books Online.   This corresponds to the Drupal node type &#8220;safari&#8221;.</dd></dl><p>Each of them has minor, but important, configuration parameters that you&#8217;ll need to set up in the Drupal installation&#8217;s <code>/admin/settings</code> directory.  In particular, the CourseSmart Search module will have parameters for the discount coupon code plus the username/password for the private API (the private API is discussed in the module-specific section below).</p><p><h2>Structure of the Search Modules</h2><br />Each of the search modules &#8212; CourseSmart, OhioLINK EBC, OhioLINK Library Catalog, and Safari &#8212; follow the same basic structure.  (The &#8220;all&#8221; metasearch module is a little different and is covered below.)  The outline, hooks followed by supporting functions, is:</p><div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">function</span> module_menu<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
  <span style="color: #339933;">...</span>
<span style="color: #009900;">&#125;</span>
&nbsp;
&nbsp;
<span style="color: #000000; font-weight: bold;">function</span> module_perm<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
  <span style="color: #339933;">...</span>
<span style="color: #009900;">&#125;</span>
&nbsp;
&nbsp;
<span style="color: #000000; font-weight: bold;">function</span> module_search<span style="color: #009900;">&#40;</span><span style="color: #000088;">$op</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">'search'</span><span style="color: #339933;">,</span> <span style="color: #000088;">$keys</span> <span style="color: #339933;">=</span> <span style="color: #009900; font-weight: bold;">NULL</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
  <span style="color: #339933;">...</span>
<span style="color: #009900;">&#125;</span>
&nbsp;
&nbsp;
<span style="color: #000000; font-weight: bold;">function</span> module_form_alter<span style="color: #009900;">&#40;</span><span style="color: #339933;">&amp;</span><span style="color: #000088;">$form</span><span style="color: #339933;">,</span> <span style="color: #000088;">$form_state</span><span style="color: #339933;">,</span> <span style="color: #000088;">$form_id</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
  <span style="color: #339933;">...</span>
<span style="color: #009900;">&#125;</span>
&nbsp;
&nbsp;
<span style="color: #000000; font-weight: bold;">function</span> module_search_process<span style="color: #009900;">&#40;</span><span style="color: #000088;">$keys</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
  <span style="color: #339933;">...</span>
<span style="color: #009900;">&#125;</span>
&nbsp;
&nbsp;
<span style="color: #000000; font-weight: bold;">function</span> module_format_result<span style="color: #009900;">&#40;</span><span style="color: #000088;">$item</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
  <span style="color: #339933;">...</span>
<span style="color: #009900;">&#125;</span>
&nbsp;
&nbsp;
<span style="color: #000000; font-weight: bold;">function</span> module_search_box_form_submit<span style="color: #009900;">&#40;</span><span style="color: #000088;">$form</span><span style="color: #339933;">,</span> <span style="color: #339933;">&amp;</span><span style="color: #000088;">$form_state</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
  <span style="color: #339933;">...</span>
<span style="color: #009900;">&#125;</span>
&nbsp;
&nbsp;
<span style="color: #000000; font-weight: bold;">function</span> module_search_query<span style="color: #009900;">&#40;</span><span style="color: #000088;">$keys</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">''</span><span style="color: #339933;">,</span> <span style="color: #000088;">$query</span> <span style="color: #339933;">=</span> <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span> <span style="color: #000088;">$search</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">'web'</span><span style="color: #339933;">,</span> <span style="color: #000088;">$version</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">'v1'</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
  <span style="color: #339933;">...</span>
<span style="color: #009900;">&#125;</span></pre></div></div><p>Some explanation for each of these:</p><ul type="disc"><li><code><i>module</i>_menu()</code> is a <a href="http://api.drupal.org/api/function/hook_menu/6" title="hook_menu | Drupal 6 API">Drupal hook that defines the menu options</a> for the setting screen.  The code to generate the menus themselves will be in a file in the module called &#8220;<i>module</i>.admin.inc&#8221;.</li><li><code><i>module</i>_perm()</code> is a <a href="http://api.drupal.org/api/function/hook_perm/6" title="hook_perm | Drupal 6 API">Drupal hook for defining the user permissions</a> appropriate for this module.  It isn&#8217;t really used in the portal.  (The settings screens look for the &#8220;administer site configuration&#8221; user permission value.)</li><li><code><i>module</i>_search()</code> is a <a href="http://api.drupal.org/api/function/hook_search/6" title="hook_search | Drupal 6 API">Drupal hook that defines a custom search routine</a> for nodes of this type.  The code pattern in other Drupal modules seems to be to use this as a level of indirection to a non-hook function, such as <code><i>module</i>_search_process()</code>.</li><li><code><i>module</i>_form_alter()</code> is a <a href="http://api.drupal.org/api/function/hook_form_alter/6" title="hook_form_alter | Drupal 6 API">Drupal hook for changing the behavior of a form</a> before it is rendered in the HTML back to the user.  In conjunction with <code><i>module</i>_search_box_form_submit()</code>, the code in this hook will turn FORM POST requests into pretty URLs.</li><li><code><i>module</i>_search_process()</code> is the function called by the <code><i>module</i>_search()</code> function.  This function prepares the query, including the pagination-of-results calculation, and calls another function &#8212; <code><i>module</i>_search_query()</code> &#8212; to do the actual searching.  We&#8217;re adding this level of indirection because the &#8220;metasearch&#8221; module will also call <code><i>module</i>_search_query()</code> to get results, but the code in that module does do all of the things <code><i>module</i>_search_process()</code> does.</li><li><code><i>module</i>_format_result()</code> is called with information about the search hit, and formats it in a way that can be fed back into the Drupal search.module output engine.  The issue here is that we&#8217;ve got fielded data (author, copyright year, publisher, and ISBN) that we want to display as fielded, but Drupal doesn&#8217;t give us a way to do that.  Rather, Drupal&#8217;s standard search module is looking for an array with keys for &#8216;title&#8217; of the hit, &#8216;link&#8217; of the hit, and a &#8216;snippet&#8217; to display to give the user context for the result.  (See the &#8220;Return Value&#8221; heading of the <a href="http://api.drupal.org/api/function/hook_search/6" title="hook_search | Drupal A6 PI">hook_search API documentation</a>.)  So this module will create a snippet of HTML that builds a nice display of the fielded data.</li><li><code><i>module</i>_search_box_form_submit()</code>, in conjunction with <code><i>module</i>_form_alter()</code>, forms the callback to turn FORM POST requests into pretty URLs.</li><li><code><i>module</i>_search_query()</code> performs whatever functions are required to get hits from the remote service.  This, of course, is the real heart of what we&#8217;re doing.  Rather than searching text of nodes internal to Drupal, this function will return an array of results that comes from a query of a remote service.  The array returned has two elements:  &#8216;total&#8217; &#8212; an integer representing the total number of hits for the query, and &#8216;items&#8217; &#8212; an array of individual hits from this search.</li></ul><p><h2>Module-specific details</h2><br />Although each of the search modules follows this general code pattern, they each have their idiosyncrasies.</p><p><strong>CourseSmart</strong> is probably the simplest module of the bunch and a good place to start when looking at the code.  Note that we are using the private API (appending <code>md=1</code> to the end of the URL) in order to get the ISBNs as listed on the CourseSmart website.  Calls to the private API is restricted to particular IP addresses, so in order to use it you&#8217;ll need to contact CourseSmart.  CourseSmart is also a little funky in that they will return items in their inventory that they won&#8217;t sell.  This is designated with an esubscription price of $0, and <span class="removed_link" title="http://drc-dev.ohiolink.edu/browser/eTextbookPortal/default/modules/csmart/csmart.module?rev=1042#L101">are filtered out in the <code><i>module</i>_search_process()</code> function</span>.</p><p><strong>OhioLINK EBook Center</strong> uses the <a href="http://xtf.wiki.sourceforge.net/experimental_SRU_Servlet" title="XTF SRU servlet documentation">SRU interface to the underlying XTF installation</a> in order to get search results out.  The search results come back in an XML document returned with multiple namespaces, which complicates somewhat the DOM parsing of that document.  Basically, it means one has to <span class="removed_link" title="http://drc-dev.ohiolink.edu/browser/eTextbookPortal/default/modules/ebooks/ebooks.module?rev=1042#L130">register the namespaces with the XPath processor</span> and take them into account when <span class="removed_link" title="http://drc-dev.ohiolink.edu/browser/eTextbookPortal/default/modules/ebooks/ebooks.module?rev=1042#L137">using XPath to pull out elements</span> for formatting the result record.</p><p><strong>OhioLINK Library Catalog</strong> uses the <a href="http://code.google.com/p/shrew/" title="shrew - Google Code">Shrew PHP class</a> created by David Walker at California State University.  Shrew hacks through the MARC display of records for an Innovative Interfaces WebPAC and returns a <a href="http://www.loc.gov/standards/marcxml/" title="MARC 21 XML Schema">MARCXML</a> document.  Without this, I&#8217;d really be stuck as to how to efficiently get the library catalog search results into the portal.  I&#8217;m grateful to him for releasing the code at exactly the right time and to Rob Casson at Miami who pointed me in David&#8217;s direction when I was considering having to write the Shrew-equivalent myself.</p><p><strong>Safari Books Online</strong> is using the same underlying engine as CourseSmart to deliver materials, so the search module is very similar.</p><p><h2>Structure of the Metasearch Module</h2><br />The eTextbook Metasearch module (a.k.a. &#8220;all&#8221;) is structured very similar to the other search modules, but deviates in several important ways.</p><ul type="disc"><li>When the Drupal <code>all_search</code> hook is called with the &#8216;search&#8217; operation parameter, a results array with explicitly 1 &#8220;result&#8221; and the search keys as the item returned.  What we&#8217;re really doing is faking out the Drupal Search module into thinking that there are actually results so we can get to the <code>all_search_page()</code> hook.  If we didn&#8217;t set the number of results to a value greater than zero, Drupal would display the &#8220;no hits found&#8221; message for us (which we don&#8217;t want it to do).</li><li>The <a href="http://drupal.org/node/185469" title="Open ticket for hook_search_page() documentation | drupal.org">undocumented</a> <code>hook_search_page()</code>, when defined for a module, is called by Drupal rather than using the built-in internal search results page.  (The other modules use the built-in results page rendering.)  We override the hook using <span class="removed_link" title="https://drc-dev.ohiolink.edu/browser/eTextbookPortal/default/modules/all/all.module?rev=1042#L61"><code>all_search_page()</code></span>, and that function calls each of the <code><i>module</i>_search_query()</code> functions for the four remote sources in sequence.  The results are then put into output block and the block is returned to the calling core code.</li><li>&#8220;all&#8221; also contains several utility functions used by the other modules. <span class="removed_link" title="https://drc-dev.ohiolink.edu/browser/eTextbookPortal/default/modules/all/all.module?rev=1042#L173"><code>all_parse_keys()</code></span> will look at the user&#8217;s search string for ISBN values and return the user&#8217;s search string as an array of an ISBN and everything else. <span class="removed_link" title="https://drc-dev.ohiolink.edu/browser/eTextbookPortal/default/modules/all/all.module?rev=1042#L206"><code>all_proxyify_url()</code></span> will determine whether a user is outside of a campus network and prepend the OhioLINK proxy server string to the URL.</li></ul><p><h2>Plans for Enhancements</h2><br />Some ideas and plans for making this better.</p><ul type="disc"><li>We want to include bookstores in the search results.  In particular, where possible, we&#8217;d like to search the bookstore&#8217;s inventory control system and display results right in the metasearch results.</li><li>For the metasearch results, each of the target remote services are called in sequence.  Ideally, the four services would be called in parallel.  Even better, perhaps, would be to render the base page, then inject search results from the remote services via AJAX as they become available.</li></ul><p style="padding:0;margin:0;font-style:italic;" class="removed_link">The text was modified to remove a link to http://drc-dev.ohiolink.edu/browser/eTextbookPortal/etextbook-installation.html?rev=1042 on January 13th, 2011.</p><p style="padding:0;margin:0;font-style:italic;" class="removed_link">The text was modified to remove a link to http://drc-dev.ohiolink.edu/browser/eTextbookPortal/ on January 13th, 2011.</p><p style="padding:0;margin:0;font-style:italic;" class="removed_link">The text was modified to remove a link to https://drc-dev.ohiolink.edu/browser/eTextbookPortal/default/modules/all/all.module?rev=1042#L173 on January 13th, 2011.</p><p style="padding:0;margin:0;font-style:italic;" class="removed_link">The text was modified to remove a link to https://drc-dev.ohiolink.edu/browser/eTextbookPortal/default/modules/all/all.module?rev=1042#L61 on January 13th, 2011.</p><p style="padding:0;margin:0;font-style:italic;" class="removed_link">The text was modified to remove a link to http://drc-dev.ohiolink.edu/browser/eTextbookPortal/default/modules/ebooks/ebooks.module?rev=1042#L137 on January 13th, 2011.</p><p style="padding:0;margin:0;font-style:italic;" class="removed_link">The text was modified to remove a link to http://drc-dev.ohiolink.edu/browser/eTextbookPortal/default/modules/ebooks/ebooks.module?rev=1042#L130 on January 13th, 2011.</p><p style="padding:0;margin:0;font-style:italic;" class="removed_link">The text was modified to remove a link to http://drc-dev.ohiolink.edu/browser/eTextbookPortal/default/modules/csmart/csmart.module?rev=1042#L101 on January 13th, 2011.</p><p style="padding:0;margin:0;font-style:italic;" class="removed_link">The text was modified to remove a link to http://drc-dev.ohiolink.edu/browser/eTextbookPortal/default/modules/libcat?rev=1042 on January 13th, 2011.</p><p style="padding:0;margin:0;font-style:italic;" class="removed_link">The text was modified to remove a link to http://drc-dev.ohiolink.edu/browser/eTextbookPortal/default/modules/ebooks?rev=1042 on January 13th, 2011.</p><p style="padding:0;margin:0;font-style:italic;" class="removed_link">The text was modified to remove a link to http://drc-dev.ohiolink.edu/browser/eTextbookPortal/default/modules/csmart?rev=1042 on January 13th, 2011.</p><p style="padding:0;margin:0;font-style:italic;" class="removed_link">The text was modified to remove a link to http://drc-dev.ohiolink.edu/browser/eTextbookPortal/default/modules/all?rev=1042 on January 13th, 2011.</p><p style="padding:0;margin:0;font-style:italic;" class="removed_link">The text was modified to remove a link to https://drc-dev.ohiolink.edu/browser/eTextbookPortal/default/modules/all/all.module?rev=1042#L206 on January 13th, 2011.</p><p style="padding:0;margin:0;font-style:italic;" class="removed_link">The text was modified to remove a link to http://drc-dev.ohiolink.edu/browser/eTextbookPortal/default/modules/safari?rev=1042 on January 13th, 2011.</p><p style="padding:0;margin:0;font-style:italic;">The text was modified to update a link from http://drupal.org/getting-started/6 to http://drupal.org/getting-started on January 28th, 2011.</p><h2>Footnotes</h2><ol class="footnotes"><li id="footnote_0_487" class="footnote">These made seem like odd choices to make for a project that had a short conception-to-production timeline, but a) there were already some helpful pieces written in PHP that sped development of some aspects of the portal, and b) I thought <a href="http://www.urbandictionary.com/define.php?term=drink+the+kool-aid" title="Urban Dictionary: drink the kool-aid">drinking the cool-aid</a> of Drupal would be a good way to see what it was all about.</li></ol>]]></content:encoded> <wfw:commentRss>http://dltj.org/article/ohio-textbook-portal-design/feed/</wfw:commentRss> <slash:comments>10</slash:comments> </item> <item><title>Schemes to Add Functionality to the Web OPAC</title><link>http://dltj.org/article/web-opac-schemes/</link> <comments>http://dltj.org/article/web-opac-schemes/#comments</comments> <pubDate>Mon, 15 Oct 2007 15:55:46 +0000</pubDate> <dc:creator>Peter Murray</dc:creator> <category><![CDATA[Raw Technology]]></category> <category><![CDATA[Drupal]]></category> <category><![CDATA[Koha]]></category> <category><![CDATA[libraries]]></category> <category><![CDATA[ngc4lib]]></category> <category><![CDATA[opac]]></category> <category><![CDATA[openils]]></category> <category><![CDATA[tagging]]></category> <category><![CDATA[WordPress]]></category><guid isPermaLink="false">http://dltj.org/2007/10/web-opac-schemes/</guid> <description><![CDATA[Schemes to add functionality to the web OPAC fall into four categories: web OPAC enhancements, web OPAC wrappers, web OPAC replacements, and integrated library system replacements. I&#8217;m outlining these four techniques in a report I&#8217;m editing for an OhioLINK strategic &#8230; <a href="http://dltj.org/article/web-opac-schemes/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description> <content:encoded><![CDATA[<abbr class="unapi-id ignore noPrint" title="http://dltj.org/2007/10/web-opac-schemes/"></abbr><p>Schemes to add functionality to the web OPAC fall into four categories:  web OPAC enhancements, web OPAC wrappers, web OPAC replacements, and integrated library system replacements.  I&#8217;m outlining these four techniques in a report I&#8217;m editing for an OhioLINK strategic task force and a bit of a reality check on this categorization is desired, so if I&#8217;m missing anything big (conceptually or announcements of projects/products that fall into these categories), please let me know in the comments.  Generally speaking, this list is ordered by cost/complexity to implement &#8212; from lowest to highest &#8212; as well as the ability to offer the described enhanced services from least likely to most likely.</p><p><strong>Web OPAC enhancements</strong> are functions that are added to the existing web OPAC system. This most often entails additional product purchases from the automation vendor, such as the optional enhancements in <a href="http://web.archive.org/web/20071015145500/http://iii.com/mill/webopac.shtml" title="WebPAC Pro product description">WebPAC Pro</a> for Millennium OPACs or <a href="http://web.archive.org/web/20071109170053/http://www.sirsidynix.com/Solutions/Products/portalsearch.php#content" title="SirsiDynix : Solutions : Portal &amp; Search Solutions">content solutions</a> in SirsiDynix.  Enhancement can also be added through creative use of an existing web OPAC&#8217;s template functions, such as the method by which <a href="http://www.librarything.com/forlibraries/" title="Library Think for Libraries homepage">LibraryThing for Libraries</a> can be added to OPAC displays.</p><p><strong>Web OPAC wrappers</strong> use the existing web OPAC provided by the integrated library system as a source of information, but hide that information behind a completely new interface.  The intervening system get that information from the integrated library system through a variety of mechanism.  In some cases, it may be possible to use established protocols (such as Z39.50) or programming interfaces (such as an XML content server).  In cases where such functionality is not available from the underlying integrated library system, a &#8220;<a href="http://www.google.com/search?q=screen-scraping+HTML" title="Google search results for &#039;screen scraping HTML&#039;">screen-scraping HTML</a>&#8221; technique may be required. <sup><a href="http://dltj.org/article/web-opac-schemes/#footnote_0_284" id="identifier_0_284" class="footnote-link footnote-identifier-link" title="Such a technique gets the information from the ILS using the existing web OPAC.  Such schemes are generally fragile because changes to the underlying web OPAC can have detrimental affects on the content scraping process.">1</a></sup></p><p>One example of such a wrapper is the work at Ann Arbor Public Library on SOPAC.  Short for &#8220;Social OPAC,&#8221; SOPAC is &#8220;a set of social networking tools integrated into the AADL catalog [that] gives users the ability to rate, review, comment-on, and tag items.&#8221;<sup><a href="http://dltj.org/article/web-opac-schemes/#footnote_1_284" id="identifier_1_284" class="footnote-link footnote-identifier-link" title="Blyberg, J. (2007). AADL.org Goes Social. blyberg.net. Retrieved October 12, 2007, from http://www.blyberg.net/2007/01/21/aadlorg-goes-social/">2</a></sup> It uses an open source content management system called Drupal as a structure through which the added functionality is provided.  For example, when a user seeks the bibliographic information page for a catalog record, that request is made from the user&#8217;s browser to the Drupal software.  The Drupal software in turn makes a request to the integrated library system for the bibliographic information it holds.  The response from the ILS is parsed by the Drupal software for key information such as title, author, subjects, holdings, etc.  This information is mixed with information stored in the Drupal database (ratings, tags, reviews, cover images, etc.) and a new web page is created and returned to the user&#8217;s browser.</p><p>Another example of a web OPAC wrapper is <a href="http://about.scriblio.net/" title="Scriblio about page" class="broken_link" rel="nofollow">Scriblio</a> (formerly called WPopac).  Using the underlying framework of WordPress, Scriblio offers faceted browsing, tagging, and syndication feeds for the underlying Millennium WebOPAC.  Scriblio is a project of Plymouth State University, supported in part by the Andrew W. Mellon Foundation.  Both SOPAC and Scriblio are available under open source licenses.</p><p><strong>Web OPAC replacements</strong> are new systems that completely replace the existing web OPAC.  Unlike wrappers (which get their bibliographic data in real-time from the underlying web OPAC), these replacements operate on sets of records that are extracted from the ILS or come from another source.  (In some cases, these replacements still rely on the underlying web OPAC as a source of item status information such as checked out status and due date.) The first notable OPAC replacement was at <a href="http://www.lib.ncsu.edu/endeca/" title="About Endeca at NCSU Libraries">North Carolina State University when its library installed and configured</a> the <a href="http://endeca.com/" title="Endeca corporate homepage">Endeca software</a> to provide <a href="http://www.lib.ncsu.edu/catalog/" title="NCSU Libraries Online Catalog">a faceted browse to the library catalog</a>.  By itself, an Endeca OPAC display does not enable tagging, annotation, or user aggregation services such as recommendation engines.&#160; Other similar web OPAC replacements are <a href="http://web.archive.org/web/20080328163000/http://www.iii.com/encore/main_index2.html" title="Encore product information page">Encore from Innovative Interfaces</a><sup><a href="http://dltj.org/article/web-opac-schemes/#footnote_2_284" id="identifier_2_284" class="footnote-link footnote-identifier-link" title="As Betsy Graham, Vice President of Product Management at Innovative Interfaces, notes in the comments, the Encore will perform real-time queries to a Millennium ILS for bibliographic data, and in such cases the data extract is not needed.">3</a></sup>, <a href="http://www.exlibrisgroup.com/primo.htm" title="Primo product information page">Primo from Ex Libris</a> and <a href="http://www.medialab.nl/" title="Aquabrowser product information page">Aquabrowser from Medialab Solutions</a>.  Miami University&#8217;s experiments with the open source Apache SOLR and the exported records from their Millennium system also fall into this category.  Worldcat Local is also a form of web OPAC replacement noting that the source of bibliographic records is the OCLC Worldcat database rather than the local ILS.</p><p><strong>ILS replacements</strong> offer the biggest opportunity for enhanced user services, particularly by adopting one of the open source solutions now available. At this time, neither of the open source solutions (<a href="http://open-ils.org/" title="Evergreen homepage">Evergreen</a> and <a href="http://www.koha.org/" title="Koha homepage">Koha</a>) offers more than faceted search and browsing. Unlike the commercial systems, however, the source code of the system can be modified to add these functions, and the modifications shared with other users of the same system.</p><p>[Update 20071015T1624 : Corrections made -- and the text improved! -- based on Betsy Graham's comment.  Thanks, Betsy!]<p style="padding:0;margin:0;font-style:italic;">The text was modified to update a link from http://www.iii.com/mill/webopac.shtml to http://web.archive.org/web/20071015145500/http://iii.com/mill/webopac.shtml on January 20th, 2011.</p><p style="padding:0;margin:0;font-style:italic;">The text was modified to update a link from http://www.iii.com/encore/main_index2.html to http://web.archive.org/web/20080328163000/http://www.iii.com/encore/main_index2.html on January 20th, 2011.</p><p style="padding:0;margin:0;font-style:italic;">The text was modified to update a link from http://www.sirsidynix.com/Solutions/Products/portalsearch.php#content to http://web.archive.org/web/20071109170053/http://www.sirsidynix.com/Solutions/Products/portalsearch.php#content on January 28th, 2011.</p><h2>Footnotes</h2><ol class="footnotes"><li id="footnote_0_284" class="footnote">Such a technique gets the information from the ILS using the existing web OPAC.  Such schemes are generally fragile because changes to the underlying web OPAC can have detrimental affects on the content scraping process.</li><li id="footnote_1_284" class="footnote">Blyberg, J. (2007). AADL.org Goes Social. blyberg.net. Retrieved October 12, 2007, from <a href="http://www.blyberg.net/2007/01/21/aadlorg-goes-social/" title="Blog posting announcing SOPAC">http://www.blyberg.net/2007/01/21/aadlorg-goes-social/</a></li><li id="footnote_2_284" class="footnote">As Betsy Graham, Vice President of Product Management at Innovative Interfaces, notes in the comments, the Encore will perform real-time queries to a Millennium ILS for bibliographic data, and in such cases the data extract is not needed.</li></ol>]]></content:encoded> <wfw:commentRss>http://dltj.org/article/web-opac-schemes/feed/</wfw:commentRss> <slash:comments>26</slash:comments> </item> <item><title>Getting Around Drupal&#8217;s Prohibition of @ Characters in User Ids</title><link>http://dltj.org/article/drupal-at-sign/</link> <comments>http://dltj.org/article/drupal-at-sign/#comments</comments> <pubDate>Fri, 19 Jan 2007 16:46:06 +0000</pubDate> <dc:creator>Peter Murray</dc:creator> <category><![CDATA[Raw Technology]]></category> <category><![CDATA[Drupal]]></category> <category><![CDATA[networking]]></category> <category><![CDATA[programming]]></category> <category><![CDATA[system administration]]></category><guid isPermaLink="false">http://dltj.org/2007/01/drupal-at-sign/</guid> <description><![CDATA[A while back we created an LDAP directory to consolidate account information for various back-room services, and when we created it we decided to use the individual&#8217;s e-mail address as the account identifier (uid in LDAP-speak). It seemed like the &#8230; <a href="http://dltj.org/article/drupal-at-sign/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description> <content:encoded><![CDATA[<abbr class="unapi-id ignore noPrint" title="http://dltj.org/2007/01/drupal-at-sign/"></abbr><p>A while back we created an LDAP directory to consolidate account information for various back-room services, and when we created it we decided to use the individual&#8217;s e-mail address as the account identifier (<tt>uid</tt> in LDAP-speak).  It seemed like the logical thing to do &#8212; it is something that the user knows and it is a cheap and easy way to assume that the account identifiers will be unique.  This is not uncommon for many internet services, of course.</p><p>Now we&#8217;re bring up a <a href="http://drupal.org/" title="drupal.org | Community plumbing">Drupal</a> content management system and of course want to tie the authentication into the existing LDAP directory.  The initial configuration appeared to work, but there were odd, unexplained failures &#8212; most notably, Drupal would not consider it a &#8216;real&#8217; account because it didn&#8217;t have an e-mail field.  Even weirder was the fact that we configured Drupal to know exactly which LDAP attribute to use as the e-mail address (<tt>mail</tt>, in LDAP-speak).  It wasn&#8217;t until one of our system engineers wondered out loud if the at-sign (&#8216;@&#8217;) in the user id wasn&#8217;t causing problems that we started making progress towards a solution.</p><p>As it turns out, he was right.  Without spending so much time in the guts of the Drupal code to know exactly if this is true, it seems like Drupal wants to reserve the &#8216;<tt>@something</tt>&#8216; construct for inter-Drupal authentication.  In other words, if you have an account on one Drupal server (let&#8217;s call it <em>DrupalA</em>) and want to access a second (let&#8217;s call it <em>DrupalB</em>) &mdash; and if the two servers agree to share user accounts &mdash; the account from <em>DrupalA</em> would be recorded in the database of <em>DrupalB</em> as &#8220;<tt>UserId@DrupalA</tt>&#8220;.</p><p>The &#8216;at&#8217; symbol for us, though, is just a normal part of an e-mail address.  We really didn&#8217;t want to reconstruct our LDAP account scheme, so the best choice seemed to be to find a way to trick Drupal into accepting these account identifiers.  This, unfortunately, was no easy task.  I couldn&#8217;t find the root cause of the problem, but did diagnose enough of the symptoms to force a patch into the system.  The patch, in the form of a new module (code included below) forces the account to have two necessary attributes that seem to go missing whenever a &#8216;@&#8217; character appears in the user id.  If you have similar problems, I can&#8217;t claim that this will work for you, nor can I guarantee this approach will be supportable in the future.  All&#8217;s I know is that it seems to work for us in our situation right now.</p><div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"> <span style="color: #339933;">&amp;</span>lt<span style="color: #339933;">;</span>?php
&nbsp;
<span style="color: #000000; font-weight: bold;">function</span> olinkldap_help<span style="color: #009900;">&#40;</span><span style="color: #000088;">$section</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
  <span style="color: #000088;">$output</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">''</span><span style="color: #339933;">;</span>
  <span style="color: #b1b100;">switch</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$section</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
    <span style="color: #b1b100;">case</span> <span style="color: #0000ff;">'admin/modules#olinkldap'</span><span style="color: #339933;">:</span>
      <span style="color: #000088;">$output</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">'olinkldap;
      break;
    case '</span>admin<span style="color: #339933;">/</span>modules<span style="color: #666666; font-style: italic;">#description':
</span>    <span style="color: #b1b100;">case</span> <span style="color: #0000ff;">'admin/help#olinkldap'</span><span style="color: #339933;">:</span>
      <span style="color: #000088;">$output</span> <span style="color: #339933;">=</span> t<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'Sets up OhioLINK-specific LDAP parameters.'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
      <span style="color: #b1b100;">break</span><span style="color: #339933;">;</span>
  <span style="color: #009900;">&#125;</span>
  <span style="color: #b1b100;">return</span> <span style="color: #000088;">$output</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span>
&nbsp;
<span style="color: #000000; font-weight: bold;">function</span> olinkldap_settings<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span> <span style="color: #009900;">&#125;</span>
&nbsp;
<span style="color: #000000; font-weight: bold;">function</span> olinkldap_user<span style="color: #009900;">&#40;</span><span style="color: #000088;">$op</span><span style="color: #339933;">,</span> <span style="color: #339933;">&amp;</span><span style="color: #000088;">$edit</span><span style="color: #339933;">,</span> <span style="color: #339933;">&amp;</span><span style="color: #000088;">$user</span><span style="color: #339933;">,</span> <span style="color: #000088;">$category</span> <span style="color: #339933;">=</span> <span style="color: #009900; font-weight: bold;">NULL</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
  <span style="color: #b1b100;">switch</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$op</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
    <span style="color: #b1b100;">case</span> <span style="color: #0000ff;">'load'</span><span style="color: #339933;">:</span>
      olinkldap_user_load<span style="color: #009900;">&#40;</span><span style="color: #000088;">$user</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
      <span style="color: #b1b100;">break</span><span style="color: #339933;">;</span>
  <span style="color: #009900;">&#125;</span>
<span style="color: #009900;">&#125;</span>
&nbsp;
<span style="color: #000000; font-weight: bold;">function</span> olinkldap_user_load<span style="color: #009900;">&#40;</span><span style="color: #339933;">&amp;</span><span style="color: #000088;">$user</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
  <span style="color: #666666; font-style: italic;">// Calculate the DN for the user -- you'll need to adjust this to match your LDAP base DN</span>
  <span style="color: #000088;">$ldap_dn</span><span style="color: #339933;">=</span><span style="color: #990000;">sprintf</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;uid=<span style="color: #009933; font-weight: bold;">%s</span>,ou=People,dc=somewhere,dc=outthere&quot;</span><span style="color: #339933;">,</span> <span style="color: #000088;">$user</span><span style="color: #339933;">-&amp;</span>gt<span style="color: #339933;">;</span>name<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
  <span style="color: #666666; font-style: italic;">// Create a new array with the two LDAP-specific values that seem to be missing.</span>
  <span style="color: #000088;">$forced_data</span><span style="color: #339933;">=</span><span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'ldap_authentified'</span> <span style="color: #339933;">=&amp;</span>gt<span style="color: #339933;">;</span> <span style="color: #cc66cc;">1</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'ldap_dn'</span> <span style="color: #339933;">=&amp;</span>gt<span style="color: #339933;">;</span> <span style="color: #000088;">$ldap_dn</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
    <span style="color: #666666; font-style: italic;">// It seems like this should work, but it doesn't (it throws a segmentation fault)</span>
    <span style="color: #666666; font-style: italic;">//  user_save($user_edit,array($forced_data);</span>
    <span style="color: #666666; font-style: italic;">// so we're going to interact directly with the database</span>
  <span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$user</span><span style="color: #339933;">-&amp;</span>gt<span style="color: #339933;">;</span>uid<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
    <span style="color: #666666; font-style: italic;">// Get the 'data' field for the user and put it in the $data array</span>
    <span style="color: #000088;">$data</span> <span style="color: #339933;">=</span> <span style="color: #990000;">unserialize</span><span style="color: #009900;">&#40;</span>db_result<span style="color: #009900;">&#40;</span>db_query<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'SELECT data FROM {users} WHERE uid = %d'</span><span style="color: #339933;">,</span> <span style="color: #000088;">$user</span><span style="color: #339933;">-&amp;</span>gt<span style="color: #339933;">;</span>uid<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #666666; font-style: italic;">// Put all of the attributes from $forced_data into $data</span>
    <span style="color: #b1b100;">foreach</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$forced_data</span> <span style="color: #b1b100;">as</span> <span style="color: #000088;">$key</span> <span style="color: #339933;">=&amp;</span>gt<span style="color: #339933;">;</span> <span style="color: #000088;">$value</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
      <span style="color: #000088;">$data</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$key</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$value</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span>
    <span style="color: #666666; font-style: italic;">// Reserialize the $data array and update it in the database</span>
    <span style="color: #000088;">$v</span><span style="color: #009900;">&#91;</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #990000;">serialize</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$data</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    db_query<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;UPDATE {users} SET data='<span style="color: #009933; font-weight: bold;">%s</span>' WHERE uid=<span style="color: #009933; font-weight: bold;">%d</span>&quot;</span><span style="color: #339933;">,</span><span style="color: #990000;">array_merge</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$v</span><span style="color: #339933;">,</span><span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$user</span><span style="color: #339933;">-&amp;</span>gt<span style="color: #339933;">;</span>uid<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
  <span style="color: #009900;">&#125;</span>
<span style="color: #009900;">&#125;</span>
?<span style="color: #339933;">&amp;</span>gt<span style="color: #339933;">;</span></pre></div></div><p>Save this as &#8216;olinkldap.module&#8217;, update the DN to reflect your LDAP server&#8217;s base DN (see comment in code), copy it into your Drupal modules directory, and activate it.  Your &#8216;@&#8217;-impaired userids should start working again.  If you are using the inter-Drupal account sharing (we&#8217;re not) this might break something for you.  That&#8217;s not interesting for us, so I&#8217;m not testing it against that condition.  If you use this and find that it works or doesn&#8217;t work, or you have a better way of solving the problem, please leave a comment or traceback&#8230;</p>]]></content:encoded> <wfw:commentRss>http://dltj.org/article/drupal-at-sign/feed/</wfw:commentRss> <slash:comments>0</slash:comments> </item> </channel> </rss>
<!-- Served from: dltj.org @ 2012-05-24 08:33:24 by W3 Total Cache -->
