--- /dev/null
+package OpenILS::WWW::Social;
+
+# Copyright (C) 2010 Laurentian University
+# Dan Scott <dscott@laurentian.ca>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+use strict; use warnings;
+
+use Apache2::Log;
+use Apache2::Const -compile => qw(OK REDIRECT DECLINED NOT_FOUND :log);
+use APR::Const -compile => qw(:error SUCCESS);
+use Apache2::RequestRec ();
+use Apache2::RequestIO ();
+use Apache2::RequestUtil;
+use CGI;
+use Template;
+
+use OpenSRF::EX qw(:try);
+use OpenSRF::Utils qw/:datetime/;
+use OpenSRF::Utils::Cache;
+use OpenSRF::System;
+use OpenSRF::AppSession;
+
+use OpenILS::Utils::Fieldmapper;
+use OpenSRF::Utils::Logger qw/$logger/;
+use OpenILS::Application::AppUtils;
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+use OpenILS::Utils::ModsParser;
+use XML::LibXML;
+use DateTime;
+
+my $log = 'OpenSRF::Utils::Logger';
+my $U = 'OpenILS::Application::AppUtils';
+
+my ($bootstrap, $editor, $actor, $templates, $LocalTZ);
+my $i18n = {};
+my $init_done = 0; # has child_init been called?
+
+# helper functions inserted into the TT environment
+my $_TT_helpers = {
+
+ # turns a date into something TT can understand
+ format_date => sub {
+ my $date = shift;
+ $date = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($date));
+ return sprintf(
+ "%0.2d:%0.2d:%0.2d %0.2d-%0.2d-%0.4d",
+ $date->hour,
+ $date->minute,
+ $date->second,
+ $date->day,
+ $date->month,
+ $date->year
+ );
+ },
+
+ # escapes a string for inclusion in an XML document. escapes &, <, and > characters
+ escape_xml => sub {
+ my $str = shift;
+ $str =~ s/&/&/sog;
+ $str =~ s/</</sog;
+ $str =~ s/>/>/sog;
+ return $str;
+ },
+
+};
+
+sub import {
+ my $self = shift;
+ $bootstrap = shift;
+}
+
+sub child_init {
+ OpenSRF::System->bootstrap_client( config_file => $bootstrap );
+
+ my $conf = OpenSRF::Utils::SettingsClient->new();
+ my $idl = $conf->config_value("IDL");
+ Fieldmapper->import(IDL => $idl);
+ $templates = $conf->config_value("dirs", "templates");
+ OpenILS::Utils::CStoreEditor::init();
+ $editor = new_editor();
+ load_i18n();
+
+ # Getting the timezone is slow; do it once
+ $LocalTZ = DateTime::TimeZone->new( name => 'local' );
+
+ $init_done = 1;
+}
+
+sub handler {
+ my $apache = shift;
+
+ child_init() unless $init_done;
+
+ return Apache2::Const::DECLINED if (-e $apache->filename);
+
+ my $ctx = {};
+
+ $ctx->{'helpers'} = $_TT_helpers;
+
+ $ctx->{'uri'} = $apache->uri;
+
+ # Get our locale from the URL
+ (my $locale = $apache->path_info) =~ s{^.*?/([a-z]{2}-[A-Z]{2})/.*?$}{$1};
+ if (!$locale) {
+ $locale = 'en-US';
+ }
+
+ # If locale exists, use it; otherwise fall back to en-US
+ if (exists $i18n->{$locale}) {
+ $ctx->{'i18n'} = $i18n->{$locale};
+ } else {
+ $ctx->{'i18n'} = $i18n->{'en-US'};
+ }
+
+ my $tt = Template->new({
+ INCLUDE_PATH => $templates
+ }) || die "$Template::ERROR\n";
+
+ # So what object are we dealing with here?
+ if ($apache->uri =~ m{/social(/[a-z]{2}-[A-Z]{2})?/user/}) {
+ if ($apache->uri =~ m{.atom$}) {
+ return user_activity_stream($apache);
+ } else {
+ return display_user($apache, $tt, $ctx);
+ }
+ } elsif ($apache->uri =~ m{/social(/[a-z]{2}-[A-Z]{2})?/thing/}) {
+ # A "thing" is represented by a biblio.record_entry
+ # It is not necessarily a book, thus "thing"
+ if ($apache->uri =~ m{.atom$}) {
+ return thing_activity_stream($apache);
+ } else {
+ return display_thing($apache, $tt, $ctx);
+ }
+ }
+}
+
+# XXX Need to build an HTML-friendly template for displaying a thing
+# and all of the social activity around it
+sub display_thing {
+ return;
+}
+
+# The user URIs identify the user by ID because usernames and email can change
+# Grab the user object from the database while we're at it
+sub _get_base_user {
+ my $apache = shift;
+
+ (my $uid = $apache->uri) =~ s{.*/social(/[a-z]{2}-[A-Z]{2})?/user/(\d+).*?$}{$2};
+
+ my $user = $editor->retrieve_actor_user($uid);
+
+ return $user;
+}
+
+# Things are identified strictly by their record ID
+sub thing_activity_stream {
+ my ($apache) = @_;
+
+ (my $thing = $apache->uri) =~ s{.*/social(/[a-z]{2}-[A-Z]{2})?/thing/(\d+).*?$}{$2};
+
+ if (!$thing) {
+ return Apache2::Const::OK;
+ }
+
+ $apache->content_type('application/atom+xml');
+
+ my $stream = create_thing_feed($apache, $thing);
+
+ print $stream->toString(1);
+
+ return Apache2::Const::OK;
+}
+
+# Generate the Activity Stream for a given user
+sub user_activity_stream {
+ my ($apache) = @_;
+
+ my $user = _get_base_user($apache);
+ if (!$user) {
+ return Apache2::Const::OK;
+ }
+
+ $apache->content_type('application/atom+xml');
+
+ my $stream = create_user_feed($apache, $user);
+ print $stream->toString(1);
+
+ return Apache2::Const::OK;
+}
+
+# Generate the Activity Stream for a given thing
+sub create_thing_feed {
+ my ($apache, $thing) = @_;
+
+ my ($doc, $feed) = base_feed($apache);
+
+ my $title = $doc->createElementNS(undef, "title");
+ $title->appendTextNode('Activity stream for the object "' . get_bib_and_mods($thing)->title . '"');
+ $feed->addChild($title);
+
+ create_thing_entries($apache, $doc, $thing);
+
+ return $doc;
+}
+
+# All Activity Stream feeds have a common base
+# XXX We're assuming HTTP, not HTTPS here...
+sub base_feed {
+ my ($apache) = @_;
+
+ my $now = DateTime->now(time_zone => $LocalTZ);
+
+ my $host = $apache->hostname;
+ my $uri = $apache->uri;
+ my $html_uri = $uri;
+ $html_uri =~ s/.atom$//;
+
+ my $doc = XML::LibXML->createDocument();
+ my $feed = $doc->createElementNS("http://www.w3.org/2005/Atom", "feed");
+ $doc->setDocumentElement($feed);
+
+ $feed->setNamespace("http://www.w3.org/2005/Atom");
+ $feed->setNamespace("http://activitystrea.ms/spec/1.0/", "activity", 0);
+ $feed->setNamespace("http://portablecontacts.net/spec/1.0", "poco", 0);
+
+ my $feed_id = $doc->createElementNS(undef, "id");
+ $feed_id->appendTextNode(generate_tag($host, $uri, $now));
+ $feed->addChild($feed_id);
+
+ my $updated = $doc->createElementNS(undef, "updated");
+ $updated->appendTextNode($now->strftime('%FT%TZ'));
+ $feed->addChild($updated);
+
+ # Link to our HTML representation
+ my $l_html = $doc->createElementNS(undef, "link");
+ $l_html->setAttributeNS(undef, "rel", "alternate");
+ $l_html->setAttributeNS(undef, "type", "text/html");
+ $l_html->setAttributeNS(undef, "href", "http://$host$html_uri");
+ $feed->addChild($l_html);
+
+ # Link to our self
+ my $l_self = $doc->createElementNS(undef, "link");
+ $l_self->setAttributeNS(undef, "rel", "self");
+ $l_self->setAttributeNS(undef, "type", "application/atom+xml");
+ $l_self->setAttributeNS(undef, "href", "http://$host$uri");
+ $feed->addChild($l_self);
+
+ return ($doc, $feed);
+}
+
+# Standardized form of representing the user's full name
+sub generate_user_name {
+ my ($user) = @_;
+
+ my $display_name = $user->first_given_name . ' ';
+ $display_name .= ($user->second_given_name || '') . ' ';
+ $display_name .= $user->family_name || '';
+ $display_name =~ s/ +/ /g;
+
+ return $display_name;
+}
+
+sub create_user_feed {
+ my ($apache, $user) = @_;
+
+ my ($doc, $feed) = base_feed($apache);
+ my $display_name = generate_user_name($user);
+
+ my $title = $doc->createElementNS(undef, "title");
+ $title->appendTextNode("Personal activity stream for " . ($display_name || ("person " . $user->id)));
+ $feed->addChild($title);
+
+ add_author($doc, $user, $feed);
+
+ create_user_entries($apache, $doc, $user, $display_name || $user->usrname);
+
+ return $doc;
+}
+
+sub add_author {
+ my ($doc, $user, $anchor) = @_;
+
+ my $display_name = generate_user_name($user);
+
+ # Introduce the actor (atom:author)
+ my $author = $doc->createElementNS(undef, "author");
+
+ # atom:name link, required in atom:author elements
+ my $aname = $doc->createElementNS(undef, "name");
+ my $aname_done = 0;
+
+ # Get the best name we can; flesh out Portable Contacts while we're at it
+ if ($display_name) {
+ my $dname = $doc->createElementNS(undef, "poco:displayName");
+ $dname->appendTextNode($display_name);
+ $anchor->addChild($dname);
+
+ $aname->appendTextNode($display_name);
+ $author->addChild($aname);
+ $aname_done = 1;
+ }
+
+ my $uname = $doc->createElementNS(undef, "poco:preferredUsername");
+ if ($user->usrname) {
+ $uname->appendTextNode($user->usrname);
+ if (!$aname_done) {
+ $aname->appendTextNode($user->usrname);
+ $author->addChild($aname);
+ $aname_done = 1;
+ }
+ } else {
+ # This is a poor excuse for an author name; oh well
+ $uname->appendTextNode($user->id);
+ if (!$aname_done) {
+ $aname->appendTextNode($user->id);
+ }
+ $author->addChild($aname);
+ }
+ $anchor->addChild($author);
+
+ if ($user->email) {
+ my $email = $doc->createElementNS(undef, "email");
+ $email->appendTextNode($user->email);
+ $author->addChild($email);
+ }
+ $anchor->addChild($uname);
+}
+
+sub create_user_entries {
+ my ($apache, $doc, $user, $user_name) = @_;
+
+ my $activities = $editor->search_social_activity_stream(
+ { actor => $user->id },
+ { order_by => { socas => "stamped DESC" }, limit => 10 }
+ );
+
+ foreach my $activity (@$activities) {
+ generate_activity_entries($activity, $apache, $doc, $user, $user_name);
+ }
+}
+
+sub create_thing_entries {
+ my ($apache, $doc, $thing) = @_;
+
+ my $activities = $editor->search_social_activity_stream(
+ { object => $thing },
+ { order_by => { socas => "stamped DESC" }, limit => 10 }
+ );
+
+ foreach my $activity (@$activities) {
+ my $user = $editor->retrieve_actor_user($activity->actor);
+ my $display_name = generate_user_name($user);
+ generate_activity_entries($activity, $apache, $doc, $user, $display_name || $user->usrname);
+ }
+}
+
+
+sub generate_activity_entries {
+ my ($activity, $apache, $doc, $user, $user_name) = @_;
+
+ my $entry = $doc->createElementNS(undef, "entry");
+
+ if ($activity->activity eq "add_bookbag") {
+ _add_bookbag($activity, $user_name, $apache, $doc, $entry);
+ } elsif ($activity->activity eq "add_bookbag_item") {
+ _add_bookbag_item($activity, $user_name, $apache, $doc, $entry);
+ } elsif ($activity->activity eq "add_review") {
+ _add_review($activity, $user_name, $apache, $doc, $entry);
+ } elsif ($activity->activity eq "circ") {
+ _circ($activity, $user_name, $apache, $doc, $entry);
+ }
+
+ # Required by Atom Activity spec
+ my $published = $doc->createElementNS(undef, "published");
+ my $date = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($activity->stamped));
+ $published->appendTextNode($date->strftime('%FT%TZ'));
+ $entry->addChild($published);
+
+ # Required by Atom spec
+ my $updated = $doc->createElementNS(undef, "updated");
+ $updated->appendTextNode($date->strftime('%FT%TZ'));
+ $entry->addChild($updated);
+
+ add_author($doc, $user, $entry);
+
+ $doc->documentElement()->addChild($entry);
+}
+
+sub _add_review {
+ my ($activity, $user_name, $apache, $doc, $entry) = @_;
+ # verb = post (http://activitystrea.ms/schema/1.0/post)
+ # object = list (http://activitystrea.ms/schema/1.0/review)
+
+ my $review = $editor->retrieve_social_user_review($activity->object);
+ my $mods = get_bib_and_mods($activity->target);
+
+ my $title = $doc->createElementNS(undef, "title");
+ $title->appendTextNode("$user_name reviewed " . $mods->title);
+ $entry->addChild($title);
+
+ my $summary = $doc->createElementNS(undef, "summary");
+ $summary->appendTextNode("$user_name reviewed " . $mods->title);
+ $entry->addChild($summary);
+
+ my $content = $doc->createElementNS(undef, "content");
+ $content->setAttributeNS(undef, "type", "text/html");
+ $content->appendTextNode($review->value);
+ $entry->addChild($content);
+
+ my $id = $doc->createElementNS(undef, "id");
+ $id->appendTextNode(generate_tag($apache->hostname, '/opac/social/create/review/' . $activity->object));
+ $entry->addChild($id);
+
+ my $verb = $doc->createElementNS(undef, "activity:verb");
+ $verb->appendTextNode("http://activitystrea.ms/schema/1.0/post");
+ $entry->addChild($verb);
+
+ my $object = $doc->createElementNS(undef, "activity:object");
+ my $object_type = $doc->createElementNS(undef, "activity:object-type");
+ $object_type->appendTextNode("http://activitystrea.ms/schema/1.0/review");
+ $object->addChild($object_type);
+
+ my $object_id = $doc->createElementNS(undef, "id");
+ $object_id->appendTextNode(generate_tag($apache->hostname, '/opac/social/review/' . $activity->object));
+ $object->addChild($object_id);
+
+ my $object_content = $doc->createElementNS(undef, "content");
+ $object_content->setAttributeNS(undef, "type", "text/html");
+ $object_content->appendTextNode($review->value);
+ $object->addChild($object_content);
+
+ my $object_link = $doc->createElementNS(undef, "link");
+ $object_link->setAttributeNS(undef, "rel", "alternate");
+ $object_link->setAttributeNS(undef, "type", "text/html");
+ $object_link->setAttributeNS(undef, "href", 'http://' . $apache->hostname . '/opac/social/review/' . $activity->object);
+ $object->addChild($object_link);
+
+ # Must be empty if the review does not have a user-generated title
+ my $object_title = $doc->createElementNS(undef, "title");
+ $object->addChild($object_title);
+ $entry->addChild($object);
+
+ my $target = $doc->createElementNS(undef, "activity:target");
+ my $t_object_type = $doc->createElementNS(undef, "activity:object-type");
+ $t_object_type->appendTextNode("http://activitystrea.ms/schema/1.0/list");
+ $target->addChild($t_object_type);
+
+ my $t_object_id = $doc->createElementNS(undef, "id");
+ $t_object_id->appendTextNode(generate_tag($apache->hostname, '/opac/social/thing/' . $activity->target));
+ $target->addChild($t_object_id);
+
+ my $t_object_title = $doc->createElementNS(undef, "title");
+ $t_object_title->appendTextNode($mods->title);
+ $target->addChild($t_object_title);
+
+ $entry->addChild($target);
+
+}
+
+
+sub _circ {
+ my ($activity, $user_name, $apache, $doc, $entry) = @_;
+ # verb = post (http://activitystrea.ms/schema/1.0/post)
+ # object = list (http://activitystrea.ms/schema/1.0/list)
+
+ my $circ = $editor->retrieve_action_circulation($activity->object);
+ my $mods = get_bib_and_mods($activity->target);
+
+ my $title = $doc->createElementNS(undef, "title");
+ $title->appendTextNode("$user_name returned " . $mods->title);
+ $entry->addChild($title);
+
+ my $summary = $doc->createElementNS(undef, "summary");
+ $summary->appendTextNode("$user_name returned " . $mods->title);
+ $entry->addChild($summary);
+
+ my $content = $doc->createElementNS(undef, "content");
+ $content->appendTextNode("$user_name returned " . $mods->title);
+ $entry->addChild($content);
+
+ my $id = $doc->createElementNS(undef, "id");
+ $id->appendTextNode(generate_tag($apache->hostname, '/opac/social/create/circ/' . $activity->object));
+ $entry->addChild($id);
+
+ my $verb = $doc->createElementNS(undef, "activity:verb");
+ $verb->appendTextNode("http://activitystrea.ms/schema/1.0/post");
+ $entry->addChild($verb);
+
+ my $object = $doc->createElementNS(undef, "activity:object");
+ my $object_type = $doc->createElementNS(undef, "activity:object-type");
+ $object_type->appendTextNode("http://activitystrea.ms/schema/1.0/list");
+ $object->addChild($object_type);
+
+ my $object_id = $doc->createElementNS(undef, "id");
+ $object_id->appendTextNode(generate_tag($apache->hostname, '/opac/social/circ/' . $activity->object));
+ $object->addChild($object_id);
+
+ my $object_title = $doc->createElementNS(undef, "title");
+ $object_title->appendTextNode("Circulation");
+ $object->addChild($object_title);
+ $entry->addChild($object);
+}
+
+
+sub _add_bookbag {
+ my ($activity, $user_name, $apache, $doc, $entry) = @_;
+ # verb = post (http://activitystrea.ms/schema/1.0/post)
+ # object = list (http://activitystrea.ms/schema/1.0/list)
+
+ my $bb = $editor->retrieve_container_biblio_record_entry_bucket($activity->object);
+
+ my $title = $doc->createElementNS(undef, "title");
+ $title->appendTextNode("$user_name created the bookbag " . $bb->name);
+ $entry->addChild($title);
+
+ my $summary = $doc->createElementNS(undef, "summary");
+ $summary->appendTextNode("$user_name created the bookbag " . $bb->name);
+ $entry->addChild($summary);
+
+ my $content = $doc->createElementNS(undef, "content");
+ $content->appendTextNode("$user_name created the bookbag " . $bb->name);
+ $entry->addChild($content);
+
+ my $id = $doc->createElementNS(undef, "id");
+ $id->appendTextNode(generate_tag($apache->hostname, '/opac/social/create/bookbag/' . $activity->object));
+ $entry->addChild($id);
+
+ my $verb = $doc->createElementNS(undef, "activity:verb");
+ $verb->appendTextNode("http://activitystrea.ms/schema/1.0/post");
+ $entry->addChild($verb);
+
+ my $object = $doc->createElementNS(undef, "activity:object");
+ my $object_type = $doc->createElementNS(undef, "activity:object-type");
+ $object_type->appendTextNode("http://activitystrea.ms/schema/1.0/list");
+ $object->addChild($object_type);
+
+ my $object_id = $doc->createElementNS(undef, "id");
+ $object_id->appendTextNode(generate_tag($apache->hostname, '/opac/social/bookbag/' . $activity->object));
+ $object->addChild($object_id);
+
+ my $object_title = $doc->createElementNS(undef, "title");
+ $object_title->appendTextNode($bb->name);
+ $object->addChild($object_title);
+ $entry->addChild($object);
+}
+
+sub _add_bookbag_item {
+ my ($activity, $user_name, $apache, $doc, $entry) = @_;
+ # verb = save (http://activitystrea.ms/schema/1.0/save)
+ # object = article (ugh) (http://activitystrea.ms/schema/1.0/article)
+ # target = list (http://activitystrea.ms/schema/1.0/list)
+
+ my $bb = $editor->retrieve_container_biblio_record_entry_bucket($activity->target);
+ my $mods = get_bib_and_mods($activity->object);
+
+ my $id = $doc->createElementNS(undef, "id");
+ $id->appendTextNode(generate_tag($apache->hostname, '/opac/social/save/bookbag_item/' . $activity->target . '/' . $activity->object));
+ $entry->addChild($id);
+
+ my $title = $doc->createElementNS(undef, "title");
+ $title->appendTextNode("$user_name added " . $mods->title . " to bookbag " . $bb->name);
+ $entry->addChild($title);
+
+ my $summary = $doc->createElementNS(undef, "summary");
+ $summary->appendTextNode("$user_name added " . $mods->title . " to bookbag " . $bb->name);
+ $entry->addChild($summary);
+
+ my $content = $doc->createElementNS(undef, "content");
+ $content->appendTextNode("$user_name added " . $mods->title . " to bookbag " . $bb->name);
+ $entry->addChild($content);
+
+ my $verb = $doc->createElementNS(undef, "activity:verb");
+ $verb->appendTextNode("http://activitystrea.ms/schema/1.0/save");
+ $entry->addChild($verb);
+
+ my $object = $doc->createElementNS(undef, "activity:object");
+ my $object_type = $doc->createElementNS(undef, "activity:object-type");
+ $object_type->appendTextNode("http://activitystrea.ms/schema/1.0/article");
+ $object->addChild($object_type);
+
+ my $object_id = $doc->createElementNS(undef, "id");
+ $object_id->appendTextNode(generate_tag($apache->hostname, '/opac/social/bookbag/' . $activity->object));
+ $object->addChild($object_id);
+
+ my $object_title = $doc->createElementNS(undef, "title");
+ $object_title->appendTextNode($mods->title);
+ $object->addChild($object_title);
+
+ $entry->addChild($object);
+
+ my $target = $doc->createElementNS(undef, "activity:target");
+ my $t_object_type = $doc->createElementNS(undef, "activity:object-type");
+ $t_object_type->appendTextNode("http://activitystrea.ms/schema/1.0/list");
+ $target->addChild($t_object_type);
+
+ my $t_object_id = $doc->createElementNS(undef, "id");
+ $t_object_id->appendTextNode(generate_tag($apache->hostname, '/opac/social/thing/' . $activity->target));
+ $target->addChild($t_object_id);
+
+ my $t_object_title = $doc->createElementNS(undef, "title");
+ $t_object_title->appendTextNode($bb->name);
+ $target->addChild($t_object_title);
+
+ $entry->addChild($target);
+}
+
+
+# Generate a unique tag for this feed or feed entry
+sub generate_tag {
+ my ($hostname, $uri, $now) = @_;
+
+ if (!$now) {
+ $now = DateTime->now(time_zone => $LocalTZ);
+ }
+
+ $hostname =~ s{^.*?([^\.]+\.[^\.]{2,4})/?$}{$1};
+ $uri =~ s{#}{/}g;
+ my $date = $now->strftime('%F');
+ return "tag:$hostname,$date:$uri";
+}
+
+=pod
+
+Need to build a $ctx structure for the user info like so:
+ $ctx->{'user'}->{'avatar'}
+ $ctx->{'user'}->{'email'}
+ $ctx->{'user'}->{'name'}
+ $ctx->{'user'}->{'usrname'}
+ $ctx->{'home_library'}->{'name'}
+ $ctx->{'circ_history'}->[ {'isbn', 'record_id', 'title', 'author', 'xact_finish' }, ]
+=cut
+sub display_user {
+ my ($apache, $tt, $ctx) = @_;
+
+ my $user = _get_base_user($apache);
+ if (!$user) {
+ $tt->process('social/user/about.tt2', $ctx) || die $tt->error();
+ return Apache2::Const::OK;
+ }
+
+ # Check user settings here to prevent disclosing information they
+ # do not want to disclose
+
+ $ctx->{'user'}{'usrname'} = $user->usrname;
+ $ctx->{'user'}{'email'} = $user->email || '';
+ $ctx->{'user'}{'name'} = generate_user_name($user);
+
+ my $home_ou = $editor->retrieve_actor_org_unit($user->home_ou);
+ $ctx->{'home_library'}{'name'} = $home_ou->name;
+
+ # Now get circs - expose most recent 10 circs by default?
+ my $circs = $editor->search_action_circulation([
+ { usr => $user->id, xact_finish => { '!=' => undef } },
+ { order_by => { circ => 'xact_start DESC' }, limit => 10 }
+ ]);
+
+ my @circ_history;
+ foreach my $circ (@$circs) {
+ my $title = $U->simple_scalar_request(
+ "open-ils.storage",
+ "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
+ $circ->target_copy
+ );
+
+ next unless $title;
+
+ my $mods = get_mods($title);
+
+ push @circ_history, {
+ record_id => $title->id,
+ isbn => $mods->isbn,
+ title => $mods->title,
+ author => $mods->author,
+ xact_finish => $circ->xact_finish
+ };
+ }
+
+ if (@circ_history) {
+ $ctx->{'circ_history'} = \@circ_history;
+ }
+
+ # Now expose public bookbags; set a reasonable limit
+ my @bookbags;
+ my $bbs = $editor->search_container_biblio_record_entry_bucket([
+ { owner => $user->id, pub => 't', btype => 'bookbag' },
+ { order_by => { cbreb => 'name ASC' }, limit => 100 }
+ ]);
+
+ foreach my $bb (@$bbs) {
+ my $bbitems = $editor->search_container_biblio_record_entry_bucket_item([
+ { bucket => $bb->id }
+ ]);
+
+ push @bookbags, { id => $bb->id, name => $bb->name, count => (scalar @$bbitems) };
+
+ };
+
+ if (@bookbags) {
+ $ctx->{'bookbags'} = \@bookbags;
+ }
+# my $bbitems = $editor->search_container_biblio_record_entry_bucket_item([
+# { bucket => $bb->id },
+# { order_by => { cbrebi => 'id ASC' }, limit => 100 }
+# ]);
+#
+# foreach my $bbitem (@$bbitems) {
+# my $title = $editor->retrieve_biblio_record_entry($bbitem->target_biblio_record_entry);
+# get_mini_records($title, $bookbags);
+# }
+
+ # Now expose published reviews; set a reasonable limit
+ my @reviews;
+ my $revs = $editor->search_social_user_review([
+ { creator => $user->id, approved => 't' },
+ { order_by => { socr => 'create_date DESC' }, limit => 100 }
+ ]);
+
+ foreach my $review (@$revs) {
+ my $mods = get_bib_and_mods($review->record);
+
+ push @reviews, {
+ record_id => $review->record,
+ isbn => $mods->isbn,
+ title => $mods->title,
+ author => $mods->author,
+ review => $review->value
+ };
+ };
+
+ if (@reviews) {
+ $ctx->{'reviews'} = \@reviews;
+ }
+
+ $apache->content_type('text/html');
+
+ $tt->process('social/user/about.tt2', $ctx) || die $tt->error();
+ return Apache2::Const::OK;
+}
+
+sub get_bib_and_mods {
+ my ($bib_id) = @_;
+
+ my $bre = $editor->retrieve_biblio_record_entry($bib_id);
+
+ my $mods = get_mods($bre);
+}
+
+sub get_mods {
+ my ($bre) = @_;
+
+ my $u = OpenILS::Utils::ModsParser->new();
+ $u->start_mods_batch($bre->marc());
+ my $mods = $u->finish_mods_batch();
+ $mods->doc_id($bre->id) if $mods;
+ my $isbn = $mods->isbn || '';
+ $isbn =~ s/-//g;
+ $isbn =~ s/(\d+).*?$/$1/g;
+ $mods->isbn($isbn);
+
+ return $mods;
+}
+
+# Load our localized strings - lame, need to convert to Locale::Maketext
+sub load_i18n {
+ foreach my $string_bundle (glob("$templates/password-reset/strings.*")) {
+ open(I18NFH, '<', $string_bundle);
+ (my $locale = $string_bundle) =~ s/^.*\.([a-z]{2}-[A-Z]{2})$/$1/;
+ $logger->debug("Loaded locale [$locale] from file: [$string_bundle]");
+ while(<I18NFH>) {
+ my ($string_id, $string) = ($_ =~ m/^(.+?)=(.*?)$/);
+ $i18n->{$locale}{$string_id} = $string;
+ }
+ close(I18NFH);
+ }
+}
+
+1;
+
+# vim: et:ts=4:sw=4