The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
CHANGES 5862
MANIFEST 55
META.yml 1311
Makefile.PL 94
README 5733
SIGNATURE 058
TODO 10
html/Callbacks/RTx-Calendar/Elements/Tabs/Privileged 80
html/Callbacks/RTx-Calendar/Ticket/Elements/Tabs/Default 190
html/Callbacks/RTx-Calendar/User/Elements/Tabs/Default 90
html/NoAuth/Calendar/dhandler 1600
html/Prefs/Calendar.html 1250
html/Prefs/Elements/CalendarFeed 680
html/Search/Calendar.html 838
inc/Module/Install/Base.pm 11
inc/Module/Install/Can.pm 11
inc/Module/Install/Fetch.pm 11
inc/Module/Install/Include.pm 034
inc/Module/Install/Makefile.pm 22
inc/Module/Install/Metadata.pm 33
inc/Module/Install/RTx/Runtime.pm 079
inc/Module/Install/RTx.pm 87118
inc/Module/Install/Win32.pm 11
inc/Module/Install/WriteAll.pm 11
inc/Module/Install.pm 99
inc/YAML/Tiny.pm 0873
inc/unicore/Name.pm 0417
lib/RTx/Calendar.pm 8243
28 files changed (This is a version diff) 7281794
@@ -1,78 +1,82 @@
-Changes for 0.20
-  * Packaging fixes only (0.19 failed to update CHANGES)
+1.00
+ - Packaging and documentation updates
+ - Drop ical support, as it is in core RT
+ - Add documentation to Calendar.html
 
-Changes for 0.19
-  * Fix double-encoding of previous/next month chevrons
+0.20
+ - Packaging fixes only (0.19 failed to update CHANGES)
 
-Changes for 0.18
-  * RT 4.2 compatibility
-  * UI updates from Ivan Kohler
+0.19
+ - Fix double-encoding of previous/next month chevrons
 
-Changes for 0.17
-  * Re-released after resolving PAUSE permissions; no changes since 0.16
+0.18
+ - RT 4.2 compatibility
+ - UI updates from Ivan Kohler
 
-Changes for 0.16 — Never indexed by PAUSE
-  * Remove dependency on Date::ICal and use RT::Date's support instead
+0.17
+ - Re-released after resolving PAUSE permissions; no changes since 0.16
 
-Changes for 0.15
-  * Switch to Digest::SHA from Digest::SHA1 to appease Debian
+0.16 Never indexed by PAUSE
+ - Remove dependency on Date::ICal and use RT::Date's support instead
 
-Changes for 0.14
-  * Guard the modern stylesheets call on older RTs
+0.15
+ - Switch to Digest::SHA from Digest::SHA1 to appease Debian
 
-Changes for 0.13
-  * Serve CSS the modern way on recent RT's
-  * Namespace our CSS to avoid styling the RT core UI
+0.14
+ - Guard the modern stylesheets call on older RTs
 
-Changes for 0.12
-  * Copyright updates
+0.13
+ - Serve CSS the modern way on recent RT's
+ - Namespace our CSS to avoid styling the RT core UI
 
-Changes for 0.11
-  * fixes for characters that were breaking 3.8
+0.12
+ - Copyright updates
 
-Changes for 0.10
-  * packaging tweak
+0.11
+ - fixes for characters that were breaking 3.8
 
-Changes for 0.09
-  * RT 4 compatibility
+0.10
+ - packaging tweak
 
-Changes for 0.08
+0.09
+ - RT 4 compatibility
 
-  * make sure the last date of a search is displayed (Shawn M Moore) [rt.cpan.org #63469]
+0.08
+ - make sure the last date of a search is displayed (Shawn M Moore)
+   [rt.cpan.org #63469]
 
-Changes for 0.07
+0.07
+ - add documentation on how to enable plugin in rt 3.8 and later
+ - add configuration to show owner in calendar (Shawn M Moore)
+ - make the popup display fields configurable (Shawn M Moore)
+ - tickets and reminders types (todo/event) are configurable in ical feed
 
-  * add documentation on how to enable plugin in rt 3.8 and later
-  * add configuration to show owner in calendar (Shawn M Moore)
-  * make the popup display fields configurable (Shawn M Moore)
-  * tickets and reminders types (todo/event) are configurable in ical feed
+0.06
+ - rt 3.8 compatibility
 
-Changes for 0.06
+0.05
+ - fix a bug in ics feeds when showing reminders
+ - use Module::Install::RTx 0.21
 
-  * rt 3.8 compatibility
+0.04
+ - should work with rt 3.6.0 and seems to work with 3.7.2
+ - display Starts date by default
+ - you can use Format to display dates you want (Created, Started, Due,
+   Starts, ...)
+ - if a saved search is named "calendar", use it for the default
+   Calendar.html and for the portlet
+ - fix a bug in Prefs
 
-Changes for 0.05
-  * fix a bug in ics feeds when showing reminders
-  * use Module::Install::RTx 0.21
+0.03
+ - now uses Query Builder to find tickets
+ - ics (ICal) feeds available for each personal search with magic number
+   authentication method
+ - default calendars and feeds show reminders
+ - Calendar.html move in Search
+ - MyCalendar links to your own tickets and Nobody's tickets for the
+   current month
+ - fix a problem with localtime and gmtime
 
-Changes for 0.04
+0.02
+ - First release
 
-  * should work with rt 3.6.0 and seems to work with 3.7.2
-  * display Starts date by default
-  * you can use Format to display dates you want (Created, Started, Due, Starts, ...)
-  * if a saved search is named "calendar", use it for the default Calendar.html and for the portlet
-  * fix a bug in Prefs
-
-Changes for 0.03
-
-  * now uses Query Builder to find tickets
-  * ics (ICal) feeds available for each personal search with magic number authentication method
-  * default calendars and feeds show reminders
-  * Calendar.html move in Search
-  * MyCalendar links to your own tickets and Nobody's tickets for the
-    current month
-  * fix a problem with localtime and gmtime
-
-Changes for 0.02
-
-  * First release
@@ -1,23 +1,22 @@
 CHANGES
 html/Callbacks/RTx-Calendar/Elements/Tabs/Privileged
-html/Callbacks/RTx-Calendar/Ticket/Elements/Tabs/Default
-html/Callbacks/RTx-Calendar/User/Elements/Tabs/Default
 html/Elements/CalendarEvent
 html/Elements/MyCalendar
-html/NoAuth/Calendar/dhandler
-html/Prefs/Calendar.html
-html/Prefs/Elements/CalendarFeed
 html/Search/Calendar.html
 inc/Module/Install.pm
 inc/Module/Install/Base.pm
 inc/Module/Install/Can.pm
 inc/Module/Install/Fetch.pm
+inc/Module/Install/Include.pm
 inc/Module/Install/Makefile.pm
 inc/Module/Install/Metadata.pm
 inc/Module/Install/ReadmeFromPod.pm
 inc/Module/Install/RTx.pm
+inc/Module/Install/RTx/Runtime.pm
 inc/Module/Install/Win32.pm
 inc/Module/Install/WriteAll.pm
+inc/unicore/Name.pm
+inc/YAML/Tiny.pm
 lib/RTx/Calendar.pm
 Makefile.PL
 MANIFEST			This list of files
@@ -34,3 +33,4 @@ static/images/starts.png
 static/images/starts_due.png
 static/images/updated.png
 TODO
+SIGNATURE                                Public-key signature (added by MakeMaker)
@@ -1,16 +1,15 @@
 ---
-abstract: 'Calendar view'
+abstract: 'Calendar for RT due dates'
 author:
-  - 'Best Practical Solutions'
-  - 'Nicolas Chuche <nchuche@barna.be>'
+  - 'Best Practical Solutions, LLC <modules@bestpractical.com>'
 build_requires:
-  ExtUtils::MakeMaker: 6.36
+  ExtUtils::MakeMaker: 6.59
 configure_requires:
-  ExtUtils::MakeMaker: 6.36
+  ExtUtils::MakeMaker: 6.59
 distribution_type: module
 dynamic_config: 1
-generated_by: 'Module::Install version 1.08'
-license: perl
+generated_by: 'Module::Install version 1.12'
+license: gpl
 meta-spec:
   url: http://module-build.sourceforge.net/META-spec-v1.4.html
   version: 1.4
@@ -20,13 +19,12 @@ no_index:
     - html
     - inc
     - static
-  package:
-    - RT::Interface::Web::Menu
 requires:
-  Data::ICal: 0
   DateTime: 0
   DateTime::Set: 0
-  Digest::SHA: 0
+  perl: 5.8.3
 resources:
-  license: http://dev.perl.org/licenses/
-version: '0.20'
+  license: http://opensource.org/licenses/gpl-license.php
+version: '1.00'
+x_module_install_rtx_version: '0.36'
+x_requires_rt: 4.0.9
@@ -1,16 +1,11 @@
 use inc::Module::Install;
 
 RTx('RTx-Calendar');
-author('Nicolas Chuche <nchuche@barna.be>');
-abstract('Calendar view');
-license('perl');
-readme_from('lib/RTx/Calendar.pm');
 
-requires 'DateTime'      => 0;
-requires 'DateTime::Set' => 0;
-requires 'Data::ICal'    => 0;
-requires 'Digest::SHA';
+requires 'DateTime';
+requires 'DateTime::Set';
 
-no_index(package => 'RT::Interface::Web::Menu');
+requires_rt '4.0.9';
 
+sign();
 WriteAll();
@@ -1,5 +1,5 @@
 NAME
-    RTx::Calendar - Calendar for RT due tasks
+    RTx::Calendar - Calendar for RT due dates
 
 DESCRIPTION
     This RT extension provides a calendar view for your tickets and your
@@ -8,94 +8,70 @@ DESCRIPTION
 
     There's a portlet to put on your home page (see Prefs/MyRT.html)
 
-    You can also enable ics (ICal) feeds for your default calendar and all
-    your private searches in Prefs/Calendar.html. Authentication is magic
-    number based so that you can give those feeds to other people.
-
 INSTALLATION
-    If you upgrade from 0.02, see next part before.
+    perl Makefile.PL
+    make
+    make install
+        May need root permissions
 
-    You need to install those two modules :
+    Edit your /opt/rt4/etc/RT_SiteConfig.pm
+        If you are using RT 4.2 or greater, add this line:
 
-      * Data::ICal
-      * DateTime::Set
+            Plugin('RTx::Calendar');
 
-    Install it like a standard perl module
+        For RT 4.0, add this line:
 
-     perl Makefile.PL
-     make
-     make install
+            Set(@Plugins, qw(RTx::Calendar));
 
-    If your RT is not in the default path (/opt/rt3) you must set RTHOME
-    before doing the Makefile.PL
+        or add RTx::Calendar to your existing @Plugins line.
 
-CONFIGURATION
-  Base configuration
-    In RT 3.8 and later, to enable calendar plugin, you must add something
-    like that in your etc/RT_SiteConfig.pm :
+    Clear your mason cache
+            rm -rf /opt/rt4/var/mason_data/obj
 
-      Set(@Plugins,(qw(RTx::Calendar)));
+    Restart your webserver
 
-    To use MyCalendar portlet you must add MyCalendar to $HomepageComponents
-    in etc/RT_SiteConfig.pm like that :
+CONFIGURATION
+  Base configuration
+    To use the MyCalendar portlet, you must add MyCalendar to
+    $HomepageComponents in etc/RT_SiteConfig.pm:
 
       Set($HomepageComponents, [qw(QuickCreate Quicksearch MyCalendar
          MyAdminQueues MySupportQueues MyReminders RefreshHomepage)]);
 
-    To enable private searches ICal feeds, you need to give
-    CreateSavedSearch and LoadSavedSearch rights to your users.
-
   Display configuration
     You can show the owner in each day box by adding this line to your
-    etc/RT_SiteConfig.pm :
+    etc/RT_SiteConfig.pm:
 
         Set($CalendarDisplayOwner, 1);
 
     You can change which fields show up in the popup display when you mouse
-    over a date in etc/RT_SiteConfig.pm :
-
-        @CalendarPopupFields = ('Status', 'OwnerObj->Name', 'DueObj->ISO');
+    over a date in etc/RT_SiteConfig.pm:
 
-  ICAL feed configuration
-    By default, tickets are todo and reminders event. You can change this by
-    setting $RT::ICalTicketType and $RT::ICalReminderType in
-    etc/RT_SiteConfig.pm :
-
-      Set($ICalTicketType,   "Data::ICal::Entry::Event");
-      Set($ICalReminderType ,"Data::ICal::Entry::Todo");
+        Set(@CalendarPopupFields, ('Status', 'OwnerObj->Name', 'DueObj->ISO'));
 
 USAGE
     A small help section is available in /Prefs/Calendar.html
 
-UPGRADE FROM 0.02
-    As I've change directory structure, if you upgrade from 0.02 you need to
-    delete old files manually. Go in RTHOME/share/html (by default
-    /opt/rt3/share/html) and delete those files :
-
-      rm -rf Callbacks/RTx-Calendar
-      rm Tools/Calendar.html
+AUTHOR
+    Best Practical Solutions, LLC <modules@bestpractical.com>
 
-    RTx-Calendar may work without this but it's not very clean.
+    Originally written by Nicolas Chuche <nchuche@barna.be>
 
 BUGS
-    All bugs should be reported via
-    <http://rt.cpan.org/Public/Dist/Display.html?Name=RTx-Calendar> or
-    bug-RTx-Calendar@rt.cpan.org.
+    All bugs should be reported via email to
 
-AUTHORS
-    Best Practical Solutions
+        L<bug-RTx-Calendar@rt.cpan.org|mailto:bug-RTx-Calendar@rt.cpan.org>
 
-    Nicolas Chuche <nchuche@barna.be>
+    or via the web at
 
-    Idea borrowed from redmine's calendar (Thanks Jean-Philippe).
+        L<rt.cpan.org|http://rt.cpan.org/Public/Dist/Display.html?Name=RTx-Calendar>.
 
-COPYRIGHT
-    Copyright 2007-2009 by Nicolas Chuche <nchuche@barna.be>
+LICENSE AND COPYRIGHT
+    This software is Copyright (c) 2010-2014 by Best Practical Solutions
 
-    Copyright 2010-2014 by Best Practical Solutions.
+    Copyright 2007-2009 by Nicolas Chuche
 
-    This program is free software; you can redistribute it and/or modify it
-    under the same terms as Perl itself.
+    This is free software, licensed under:
 
-    See <http://www.perl.com/perl/misc/Artistic.html>
+      The GNU General Public License, Version 2, June 1991
 
@@ -0,0 +1,58 @@
+This file contains message digests of all files listed in MANIFEST,
+signed via the Module::Signature module, version 0.73.
+
+To verify the content in this distribution, first make sure you have
+Module::Signature installed, then type:
+
+    % cpansign -v
+
+It will check each file's integrity, as well as the signature's
+validity.  If "==> Signature verified OK! <==" is not displayed,
+the distribution may already have been compromised, and you should
+not run its Makefile.PL or Build.PL.
+
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA1
+
+SHA1 93b5edfdbc96aaee55700e78655e4bdb28f4a08d CHANGES
+SHA1 069fb1d0efaa32ea73dde062044517d70f6b329f MANIFEST
+SHA1 a1193dbfbe36ed50f262c026646ab88e91b6b62e META.yml
+SHA1 ec583a4b786f31a5339836bd5cd89b948d5bfd8c Makefile.PL
+SHA1 e591584e85f8ca5539df93b048c6a3b74ec9dd68 README
+SHA1 4259a8cc84588a6c02d448172e281f74c29cfca9 TODO
+SHA1 c4c2273ca8a65f5f23689f5425d2cf00fcb7d718 html/Callbacks/RTx-Calendar/Elements/Tabs/Privileged
+SHA1 550731466ec0dfafb7da6aa92381acda57f353ae html/Elements/CalendarEvent
+SHA1 cba245b1e12d22745770f9c020f4cb0923915f2a html/Elements/MyCalendar
+SHA1 3f2989981d24accd86c199540ef27fc0df0eefcd html/Search/Calendar.html
+SHA1 9b5001bfa9cf8607b3b3935284d9253e0391c9f1 inc/Module/Install.pm
+SHA1 cab0e564f9bdf658535f683aa197157e06d0dcea inc/Module/Install/Base.pm
+SHA1 a1559b5b3b40f68efbbd256f4fef85970891b3ae inc/Module/Install/Can.pm
+SHA1 f15c1ba85f6d52e70c48c64bf0752c90a4ad66f9 inc/Module/Install/Fetch.pm
+SHA1 d44d96acd20793306dd201030c688e2a7d3083ee inc/Module/Install/Include.pm
+SHA1 eb48df8bafd07c6a862126d9b274df42b4395742 inc/Module/Install/Makefile.pm
+SHA1 95c73873c6c3cb7024614c225c53863e1e90c134 inc/Module/Install/Metadata.pm
+SHA1 6e010ba20a9d0ae23d8d0ff516868c1e571c2d44 inc/Module/Install/RTx.pm
+SHA1 3fdf4c0cffdb1a2e23e5cd26bf95be553f1f9590 inc/Module/Install/RTx/Runtime.pm
+SHA1 79f5b4199f622e8b05aac266b0c39f6a85bb303f inc/Module/Install/ReadmeFromPod.pm
+SHA1 f8b2ae3386f6ba26c33408968a953d450842eade inc/Module/Install/Win32.pm
+SHA1 f302bc703d76299cff243e5b44cecd61aac27b76 inc/Module/Install/WriteAll.pm
+SHA1 4e09b598c2626e08cec2bed5e981492fa9e90967 inc/YAML/Tiny.pm
+SHA1 034d0f3a7401dae4be3eee279258181f51a4ad81 inc/unicore/Name.pm
+SHA1 6e4ad3f34fd64e02ad62d51719b6860204c63476 lib/RTx/Calendar.pm
+SHA1 b2d9bba7a2668c1406eae6ab2c10d6aecf4f6e30 static/css/calendar.css
+SHA1 bd37038a23410e1b33f52bc47c26760ead317962 static/images/created.png
+SHA1 0e33b8c5c7b5f141301edbec9983fd0d727d650d static/images/created_due.png
+SHA1 7b320f37a1253b7e0fd90abf84f5740150f3c8e9 static/images/due.png
+SHA1 e34a63899ecdf6c49fc3fdb59a9769dc7ef04427 static/images/reminder.png
+SHA1 c11078b7f675e1af5dd9237840bcf5e3fa05df9d static/images/resolved.png
+SHA1 de6e66e9dfe0f780b8a28790264835727217cca5 static/images/started.png
+SHA1 0ad059771e0d96eaaa96e90883f4a375d67bcabd static/images/starts.png
+SHA1 b23cfcce3427f706f0dc6f5ff5909ee8a1c41658 static/images/starts_due.png
+SHA1 d123dfa126e3a8535175c273419aaa38398c9b7b static/images/updated.png
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1
+
+iEYEARECAAYFAlSPjDQACgkQMflWJZZAbqAilACdG6njy+zYe/+L2VvYtOdiFUCV
+0cQAnRPtIeUbgIAZJN2Sf5zVSXZYsjY8
+=i6tC
+-----END PGP SIGNATURE-----
@@ -1,6 +1,5 @@
 TODO
 
-  * add Format choice in ICal feed if asked
   * add test with Test::WWW::Mechanize
   * add links from the calendar page
 
@@ -62,12 +62,4 @@ if ( $request_path =~ qr{^/Search/} && $m->request_args->{Query} ) {
                        path => '/Search/Calendar.html?' . $query_string->( %{$m->request_args} ) );
 }
 
-my $about_me = Menu->child( 'preferences' );
-my $settings = $about_me ? $about_me->child('settings') : undef;
-
-if ( $settings ) {
-    $settings->child( 'calendar' => title => loc('Calendar'),
-                    path => '/Prefs/Calendar.html' );
-}
-
 </%INIT>
@@ -1,19 +0,0 @@
-<%init>
-my $args;
-$args= "?" . $m->comp(
-    '/Elements/QueryString',
-    Query   => $ARGS{'Query'}   || $session{'CurrentSearchHash'}->{'Query'},
-    Format  => $ARGS{'Format'}  || $session{'CurrentSearchHash'}->{'Format'},
-    OrderBy => $ARGS{'OrderBy'} || $session{'CurrentSearchHash'}->{'OrderBy'},
-    Order   => $ARGS{'Order'}   || $session{'CurrentSearchHash'}->{'Order'},
-    Page   => $ARGS{'Page'}   || $session{'CurrentSearchHash'}->{'Page'},
-    Rows    => $ARGS{'Rows'},
-  ) if ($ARGS{'Query'} or $session{'CurrentSearchHash'}->{'Query'});
-$args ||= '';
-
-$tabs->{'zz'} = { title =>loc("Calendar"),
-                  path  => "Search/Calendar.html$args" };
-</%init>
-<%args>
-$tabs
-</%args>
@@ -1,9 +0,0 @@
-<%init>
-    $tabs->{'z'} = { title =>loc("Calendar"),
-                          path  => "Prefs/Calendar.html" };
-</%init>
-<%args>
-$tabs
-$current_subtab => undef
-$Searches => undef
-</%args>
@@ -1,160 +0,0 @@
-<%init>
-
-use Data::ICal;
-use Data::ICal::Entry::Todo;
-use Data::ICal::Entry::Event;
-
-$RT::ICalTicketType   ||= "Data::ICal::Entry::Todo";
-$RT::ICalReminderType ||= "Data::ICal::Entry::Event";
-
-my ($UserId, $SearchId, $MagicNumber);
-my $arg = $m->dhandler_arg;
-
-if ($arg =~ m{^(\d+)@(\d+)/(.*)$}) {
-    $UserId = $1;
-    $SearchId = $2;
-    $MagicNumber = $3;
-} elsif ($arg =~ m{^(\d+)/(.*)}) {
-    $UserId = $1;
-    $MagicNumber = $2;
-} else {
-    Abort("Corrupted URL.");
-}
-
-my $CurrentUser = new RT::CurrentUser();
-$CurrentUser->LoadById($UserId);
-my $user = $CurrentUser->Name;
-
-# if no user, abort
-unless ($CurrentUser->Id) {
-    $RT::Logger->error("No such user id $UserId from $ENV{'REMOTE_ADDR'}");
-    $m->out("RT/".$RT::VERSION ." ".404 ."\n\nno such file\n");
-    $m->abort;
-}
-
-# verify user has LoadSavedSearch right
-if ($SearchId and not $CurrentUser->HasRight( Right => 'LoadSavedSearch',
-                                              Object=> $RT::System )) {
-    $RT::Logger->error("not enough rights for user $user from $ENV{'REMOTE_ADDR'}");
-    $m->out("RT/".$RT::VERSION ." ".404 ."\n\nno such file\n");
-    $m->abort;
-}
-
-
-# if MagicNumber doesn't match the one stored in database, abort
-my $Search;
-my $ICalAttribute;
-if ($SearchId) {
-    $Search = $CurrentUser->Attributes->WithId($SearchId);
-    $ICalAttribute = $Search->FirstAttribute('ICalURL');
-} else {
-    $ICalAttribute = $CurrentUser->UserObj->FirstAttribute('ICalURL');
-}
-
-unless ($ICalAttribute) {
-    $RT::Logger->error("No such ICal feed for $user from $ENV{'REMOTE_ADDR'}");
-    $m->out("RT/".$RT::VERSION ." ".404 ."\n\nno such file\n");
-    $m->abort;
-}
-
-
-if ($MagicNumber ne $ICalAttribute->Content) {
-    $RT::Logger->error("FAILED LOGIN for $user from $ENV{'REMOTE_ADDR'}");
-    $m->out("RT/".$RT::VERSION ." ".404 ."\n\nno such file\n");
-    $m->abort;
-}
-
-my $Tickets   = RT::Tickets->new($CurrentUser);
-
-my $Query = "( Status = 'new' OR Status = 'open' OR Status = 'stalled')
- AND ( Owner = '" . $CurrentUser->Id ."' OR Owner = 'Nobody'  )
- AND ( Type = 'reminder' OR 'Type' = 'ticket' )";
-
-$Query = $Search->SubValue('Query')
-    if $Search;
-
-$Query .= " AND ( Due > '1970-01-01' OR Starts > '1970-01-01' )";
-
-$Tickets->FromSQL($Query);
-
-$Tickets->OrderBy(FIELD => 'Due', ORDER => 'ASC');
-
-my $calendar = Data::ICal->new();
-
-my ($uid) = $RT::WebURL =~ m{https?://([^:]+)};
-
-while (my $Ticket = $Tickets->Next ) {
-
-    my $event;
-    if ($Ticket->Type eq 'ticket') {
-	$event = add_todo($Ticket, $uid);
-    } else {
-	$event = add_event($Ticket, $uid);
-    }
-    next unless $event;
-    $calendar->add_entry($event);
-}
-
-my $cal = $calendar->as_string;
-
-$r->content_type('text/calendar;charset=utf-8');
-$m->clear_buffer();
-$m->out($cal);
-$m->abort;
-
-sub add_event {
-    my ($Reminder, $uid) = @_;
-
-    return unless defined $Reminder->RefersTo->First;
-    my $Ticket  = $Reminder->RefersTo->First->TargetObj;
-
-    my $now = RT::Date->new( $session{'CurrentUser'} ); $now->SetToNow;
-    my %event = (
-	summary => $Reminder->Subject ? $Reminder->Subject : '',
-	url        => "${RT::WebURL}/Ticket/Display.html?id=" . $Ticket->id,
-	uid        => $now->iCal . "-" . $Reminder->Id . "@" . $uid,
-	categories => $Ticket->QueueObj->Name,
-	dtstart     => $Reminder->DueObj->iCal,
-    );
-
-    my $event = $RT::ICalReminderType->new();
-    $event->add_properties(%event);
-
-    return $event;
-}
-
-sub add_todo {
-    my ($Ticket, $uid) = @_;
-
-    my $now = RT::Date->new( $session{'CurrentUser'} ); $now->SetToNow;
-    my %vtodo = (
-	summary    => $Ticket->Subject ? $Ticket->Subject : '',
-	dtstart    => $Ticket->CreatedObj->iCal,
-	url        => "${RT::WebURL}/Ticket/Display.html?id=" . $Ticket->id,
-	uid        => $now->iCal . "-" . $Ticket->Id . "@" . $uid,
-	categories => $Ticket->QueueObj->Name,
-    );
-
-    $vtodo{due} = $Ticket->DueObj->iCal,
-        if $Ticket->DueObj;
-
-    if ($Ticket->OwnerObj->Id != $RT::Nobody->Id and $Ticket->OwnerObj->EmailAddress) {
-	$vtodo{organizer} = "MAILTO:" . $Ticket->OwnerObj->EmailAddress;
-	$vtodo{attendee} = "MAILTO:" . $Ticket->OwnerObj->EmailAddress;
-    } elsif ($Ticket->QueueObj->CommentAddress) {
-	$vtodo{organizer} = "MAILTO:" . $Ticket->QueueObj->CommentAddress;
-	$vtodo{attendee} = "MAILTO:" . $Ticket->QueueObj->CommentAddress;
-    }
-
-    $vtodo{priority} = $Ticket->Priority
-	if $Ticket->Priority;
-
-    my $vtodo = $RT::ICalTicketType->new();
-    $vtodo->add_properties(%vtodo);
-
-    return $vtodo;
-}
-
-
-
-</%init>
@@ -1,125 +0,0 @@
-<%args>
-$ChangeURL   => undef
-$ResetURL    => undef
-$SearchType  => 'Ticket'
-$HiddenField => undef
-</%args>
-
-<& /Elements/Header, Title => $title &>
-% if ( $m->comp_exists( '/User/Elements/Tabs' ) ) {
-<& /User/Elements/Tabs,
-    current_tab => 'Prefs/Calendar.html',
-    Title => $title
-&>
-% } else {
-<& /Elements/Tabs &>
-% }
-
-<&| /Widgets/TitleBox, title => loc('ICal Feeds (ics)') &>
-
-<&| /Widgets/TitleBox, title => 'Help' &>
-
-<h3>displaying reminders :</h3>
-<p>If you want to have reminders in a search you need to go in the <a
-href="<%$RT::WebPath%>/Search/Edit.html"><%loc("Edit Query")%></a> tab
-of the <%loc("query builder")%> and add something like that :
-
- <pre>
-   AND ( Type = 'ticket' OR Type = 'reminder' )
-</pre>
-</p>
-
-<h3>displaying other kind of dates :</h3>
-<p>By default RTx::Calendar display Due and Starts dates. You can
-select other kind of events you want with the <%loc("Display
-Columns")%> section in the <a
-href="<%$RT::WebPath%>/Search/Build.html"><%loc("Query
-Builder")%></a>. The following one will display the two latter and
-LastUpdated dates :
-
-<pre>
-  '&lt;small&gt;__Due__&lt;/small&gt;',
-  '&lt;small&gt;__Starts__&lt;/small&gt;',
-  '&lt;small&gt;__LastUpdated__&lt;/small&gt;'
-</pre>
-</p>
-
-<h3>changing the default query :</h3>
-<p>You can change the default Query of Calendar.html and MyCalendar
-portlet by saving a query with the name <code>calendar</code> in the
-<a href="<%$RT::WebPath%>/Search/Build.html"><%loc("Query
-Builder")%></a>.</p>
-
-</&>
-
-<& /Prefs/Elements/CalendarFeed &>
-
-% # only allow this part if
-% if ($AllowSearch) {
-
-% my $search_count;
-
-%   # I'm quite sure the loop isn't usefull but...
-%   my @Objects = $session{CurrentUser}->UserObj;
-%   for my $object (@Objects) {
-%     next unless ref($object) eq 'RT::User' && $object->id == $session{'CurrentUser'}->Id;
-%     my @searches = $object->Attributes->Named('SavedSearch');
-%     for my $search (@searches) {
-%       next if ($search->SubValue('SearchType')
-%              && $search->SubValue('SearchType') ne $SearchType);
-%       $search_count++;
-<& /Prefs/Elements/CalendarFeed, Object => $object, Search => $search &>
-
-%     }
-%   }
-%   unless ($search_count) {
-
-<&| /Widgets/TitleBox, title => loc('Private Search ICal feeds') &>
-
-You can add private ICal feeds by saving new queries in <a
-href="<%$RT::WebPath . '/Search/Build.html'%>">the Query Builder</a>
-
-</&>
-
-%   }
-% } else {
-%#<&| /Widgets/TitleBox, title => loc('Private Search ICal feeds')
-%#                     , title_class=> 'inverse'
-%#                     , color => "#993333" &>
-%#
-%#<%loc('Private search ICal feeds disabled. To enable them, ask your admin for "[_1]" and "[_2]" rights',
-%#       loc('CreateSavedSearch'),
-%#       loc('LoadSavedSearch') )%>
-%#
-%#</&>
-% }
-
-</&>
-
-<%INIT>
-use Digest::SHA;
-use RT::SavedSearches;
-
-my $title = loc("Calendar Prefs");
-my $AllowSearch;
-
-$AllowSearch = 1
- if $session{'CurrentUser'}->HasRight( Right => 'LoadSavedSearch',
-                                       Object=> $RT::System );
-
-my $object;
-
-if ($HiddenField && $HiddenField eq 'Private') {
-   $object = $session{CurrentUser}->UserObj;
-} elsif ($AllowSearch and $HiddenField and my ($SearchId) = $HiddenField =~ m/SavedSearch\-(\d+)/) {
-    $object = $session{CurrentUser}->Attributes->WithId($SearchId);
-}
-
-if (defined $ChangeURL) {
-  my @args = $object->SetAttribute(Name => 'ICalURL', Content => Digest::SHA::sha1_base64(time));
-} elsif (defined $ResetURL) {
-  my @args = $object->DeleteAttribute('ICalURL');
-}
-
-
-</%INIT>
@@ -1,68 +0,0 @@
-<%args>
-$Search => undef
-$Object => undef
-$HiddenField => undef
-</%args>
-
-<&| /Widgets/TitleBox, title => $title &>
-
-% if ($FeedText) {
-<p><%$FeedText%></p>
-% } else {
-This feed will show tickets with due date find with query:<br />
-"<%$Search->SubValue('Query')%>".
-% }
-
-% if ($ICalURL) {
-<p>Your can paste this url in your calendar  : <b><a href="<%$link%>"><%$link%></a></b><p>
-<table>
-<tr>
-<td>
-<form action="<%$RT::WebPath%>/Prefs/Calendar.html" method="post">
-<input type="hidden" name="HiddenField" value="<%$HiddenField%>" />
-<input type="submit" class="button" name="ResetURL" value="<%loc('Disable Feed')%>" />
-</form>
-</td>
-<td>
-<form action="<%$RT::WebPath%>/Prefs/Calendar.html" method="post">
-<input type="hidden" name="HiddenField" value="<%$HiddenField%>" />
-<input type="submit" class="button" name="ChangeURL" value="<%loc('Change Feed URL')%>" />
-</form>
-</td>
-</tr>
-</table>
-% } else {
-
-<form action="<%$RT::WebPath%>/Prefs/Calendar.html" method="post">
-<input type="hidden" name="HiddenField" value="<%$HiddenField%>" />
-<input type="submit" class="button" name="ChangeURL" value="<%loc('Enable Feed')%>" />
-</form>
-% }
-
-</&>
-
-<%init>
-my $title;
-my $ICalURL;
-my $Id;
-my $FeedText;
-my $link;
-
-if ($Object) {
-  $title = loc('Feed for "') . ($Search->Description || loc('Unnamed search')) . '" search';
-  $HiddenField = "SavedSearch-" . $Search->Id;
-  $ICalURL = $Search->FirstAttribute('ICalURL');
-  $Id = $session{CurrentUser}->Id . "@" . $Search->Id;
-  $title .= " (disabled)" unless $ICalURL;
-} else {
-  $title = loc('Feed for default calendar');
-  $HiddenField = "Private";
-  $ICalURL = $session{CurrentUser}->UserObj->FirstAttribute('ICalURL');
-  $Id = $session{CurrentUser}->Id;
-  $FeedText = "This feed will show yours and Nobody's tasks with due date.";
-}
-
-$link = $RT::WebURL . "NoAuth/Calendar/" . $Id . "/" . $ICalURL->Content
-  if $ICalURL;
-
-</%init>
\ No newline at end of file
@@ -117,14 +117,7 @@ $NewQuery => 0
 </table>
 
 <table width="100%">
-<tr>
-
-<td valign="top" rowspan=9>
-  <BR>
-  <a href="<%$RT::WebPath%>/Prefs/Calendar.html">Calendar Preferences and Help</a>
-</td>
-
-% foreach my $legend (keys %legend) {
+% foreach my $legend (sort keys %legend) {
     <tr>
       <td align="right">
         <img src="<%$RT::WebImagesURL%>/<%$legend%>.png" />
@@ -143,6 +136,43 @@ $NewQuery => 0
 
 </&>
 
+<&| /Widgets/TitleBox, title => loc('Help') &>
+
+<h3><&|/l&>displaying reminders</&>:</h3>
+<p>
+<&|/l_unsafe, qq{<a href="$RT::WebPath/Search/Edit.html">} . loc("Advanced") . '</a>' &>
+If you want to have reminders in a search you need to go to [_1] tab
+and add something to the Query like that:
+</&>
+ <pre>
+   AND ( Type = 'ticket' OR Type = 'reminder' )
+</pre>
+</p>
+
+<h3><&|/l&>displaying other kind of dates</&>:</h3>
+<p>
+<&|/l_unsafe, qq{<a href="$RT::WebPath/Search/Build.html">} . loc("Query Builder") . '</a>'&>
+By default RTx::Calendar display Due and Starts dates. You can select other
+kind of events you want with the Display Columns section in the [_1].
+The following one will display the two latter and LastUpdated dates:
+</&>
+<pre>
+  '&lt;small&gt;__Due__&lt;/small&gt;',
+  '&lt;small&gt;__Starts__&lt;/small&gt;',
+  '&lt;small&gt;__LastUpdated__&lt;/small&gt;'
+</pre>
+</p>
+
+<h3><&|/l&>changing the default query</&>:</h3>
+<p>
+<&|/l_unsafe, qq{<a href="$RT::WebPath/Search/Build.html">} . loc("Query Builder") . '</a>'&>
+You can change the default Query of Calendar.html and MyCalendar
+portlet by saving a query with the name <code>calendar</code> in the [_1].
+</&>
+</p>
+
+</&>
+
 <%ONCE>
 
 my %legend = (
@@ -4,7 +4,7 @@ package Module::Install::Base;
 use strict 'vars';
 use vars qw{$VERSION};
 BEGIN {
-	$VERSION = '1.08';
+	$VERSION = '1.12';
 }
 
 # Suspend handler for "redefined" warnings
@@ -8,7 +8,7 @@ use Module::Install::Base ();
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '1.08';
+	$VERSION = '1.12';
 	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
 }
@@ -6,7 +6,7 @@ use Module::Install::Base ();
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '1.08';
+	$VERSION = '1.12';
 	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
 }
@@ -0,0 +1,34 @@
+#line 1
+package Module::Install::Include;
+
+use strict;
+use Module::Install::Base ();
+
+use vars qw{$VERSION @ISA $ISCORE};
+BEGIN {
+	$VERSION = '1.12';
+	@ISA     = 'Module::Install::Base';
+	$ISCORE  = 1;
+}
+
+sub include {
+	shift()->admin->include(@_);
+}
+
+sub include_deps {
+	shift()->admin->include_deps(@_);
+}
+
+sub auto_include {
+	shift()->admin->auto_include(@_);
+}
+
+sub auto_include_deps {
+	shift()->admin->auto_include_deps(@_);
+}
+
+sub auto_include_dependent_dists {
+	shift()->admin->auto_include_dependent_dists(@_);
+}
+
+1;
@@ -8,7 +8,7 @@ use Fcntl qw/:flock :seek/;
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '1.08';
+	$VERSION = '1.12';
 	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
 }
@@ -133,7 +133,7 @@ sub makemaker_args {
 	return $args;
 }
 
-# For mm args that take multiple space-seperated args,
+# For mm args that take multiple space-separated args,
 # append an argument to the current list.
 sub makemaker_append {
 	my $self = shift;
@@ -6,7 +6,7 @@ use Module::Install::Base ();
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '1.08';
+	$VERSION = '1.12';
 	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
 }
@@ -347,7 +347,7 @@ sub name_from {
 		^ \s*
 		package \s*
 		([\w:]+)
-		\s* ;
+		[\s|;]*
 		/ixms
 	) {
 		my ($name, $module_name) = ($1, $1);
@@ -705,7 +705,7 @@ sub _write_mymeta_data {
 	my @yaml = Parse::CPAN::Meta::LoadFile('META.yml');
 	my $meta = $yaml[0];
 
-	# Overwrite the non-configure dependency hashs
+	# Overwrite the non-configure dependency hashes
 	delete $meta->{requires};
 	delete $meta->{build_requires};
 	delete $meta->{recommends};
@@ -0,0 +1,79 @@
+#line 1
+package Module::Install::RTx::Runtime;
+
+use base 'Exporter';
+our @EXPORT = qw/RTxDatabase RTxPlugin/;
+
+use strict;
+use File::Basename ();
+
+sub _rt_runtime_load {
+    require RT;
+
+    eval { RT::LoadConfig(); };
+    if (my $err = $@) {
+        die $err unless $err =~ /^RT couldn't load RT config file/m;
+        my $warn = <<EOT;
+This usually means that your current user cannot read the file.  You
+will likely need to run this installation step as root, or some user
+with more permissions.
+EOT
+        $err =~ s/This usually means.*/$warn/s;
+        die $err;
+    }
+}
+
+sub RTxDatabase {
+    my ($action, $name, $version) = @_;
+
+    _rt_runtime_load();
+
+    require RT::System;
+    my $has_upgrade = RT::System->can('AddUpgradeHistory');
+
+    my $lib_path = File::Basename::dirname($INC{'RT.pm'});
+    my @args = (
+        "-Ilib",
+        "-I$RT::LocalLibPath",
+        "-I$lib_path",
+        "$RT::SbinPath/rt-setup-database",
+        "--action"      => $action,
+        ($action eq 'upgrade' ? () : ("--datadir"     => "etc")),
+        (($action eq 'insert') ? ("--datafile"    => "etc/initialdata") : ()),
+        "--dba"         => $RT::DatabaseAdmin || $RT::DatabaseUser,
+        "--prompt-for-dba-password" => '',
+        ($has_upgrade ? ("--package" => $name, "--ext-version" => $version) : ()),
+    );
+    # If we're upgrading against an RT which isn't at least 4.2 (has
+    # AddUpgradeHistory) then pass --package.  Upgrades against later RT
+    # releases will pick up --package from AddUpgradeHistory.
+    if ($action eq 'upgrade' and not $has_upgrade) {
+        push @args, "--package" => $name;
+    }
+
+    print "$^X @args\n";
+    (system($^X, @args) == 0) or die "...returned with error: $?\n";
+}
+
+sub RTxPlugin {
+    my ($name) = @_;
+
+    _rt_runtime_load();
+    require YAML::Tiny;
+    my $data = YAML::Tiny::LoadFile('META.yml');
+    my $name = $data->{name};
+
+    my @enabled = RT->Config->Get('Plugins');
+    for my $required (@{$data->{x_requires_rt_plugins} || []}) {
+        next if grep {$required eq $_} @enabled;
+
+        warn <<"EOT";
+
+**** Warning: $name requires that the $required plugin be installed and
+              enabled; it is not currently in \@Plugins.
+
+EOT
+    }
+}
+
+1;
@@ -8,7 +8,7 @@ no warnings 'once';
 
 use Module::Install::Base;
 use base 'Module::Install::Base';
-our $VERSION = '0.34';
+our $VERSION = '0.36';
 
 use FindBin;
 use File::Glob     ();
@@ -18,100 +18,79 @@ my @DIRS = qw(etc lib html static bin sbin po var);
 my @INDEX_DIRS = qw(lib bin sbin);
 
 sub RTx {
-    my ( $self, $name ) = @_;
+    my ( $self, $name, $extra_args ) = @_;
+    $extra_args ||= {};
 
-    my $original_name = $name;
-    my $RTx = 'RTx';
-    $RTx = $1 if $name =~ s/^(\w+)-//;
+    # Set up names
     my $fname = $name;
     $fname =~ s!-!/!g;
 
-    $self->name("$RTx-$name")
+    $self->name( $name )
         unless $self->name;
-    $self->all_from( -e "$name.pm" ? "$name.pm" : "lib/$RTx/$fname.pm" )
+    $self->all_from( "lib/$fname.pm" )
         unless $self->version;
-    $self->abstract("RT $name Extension")
+    $self->abstract("$name Extension")
         unless $self->abstract;
-
-    my @prefixes = (qw(/opt /usr/local /home /usr /sw ));
-    my $prefix   = $ENV{PREFIX};
-    @ARGV = grep { /PREFIX=(.*)/ ? ( ( $prefix = $1 ), 0 ) : 1 } @ARGV;
-
-    if ($prefix) {
-        $RT::LocalPath = $prefix;
-        $INC{'RT.pm'} = "$RT::LocalPath/lib/RT.pm";
-    } else {
-        local @INC = (
-            $ENV{RTHOME} ? ( $ENV{RTHOME}, "$ENV{RTHOME}/lib" ) : (),
-            @INC,
-            map { ( "$_/rt4/lib", "$_/lib/rt4", "$_/rt3/lib", "$_/lib/rt3", "$_/lib" )
-                } grep $_, @prefixes
-        );
-        until ( eval { require RT; $RT::LocalPath } ) {
-            warn
-                "Cannot find the location of RT.pm that defines \$RT::LocalPath in: @INC\n";
-            $_ = $self->prompt("Path to directory containing your RT.pm:") or exit;
-            $_ =~ s/\/RT\.pm$//;
-            push @INC, $_, "$_/rt3/lib", "$_/lib/rt3", "$_/lib";
-        }
+    unless ( $extra_args->{no_readme_generation} ) {
+        $self->readme_from( "lib/$fname.pm",
+                            { options => [ quotes => "none" ] } );
+    }
+    $self->add_metadata("x_module_install_rtx_version", $VERSION );
+
+    # Try to find RT.pm
+    my @prefixes = qw( /opt /usr/local /home /usr /sw /usr/share/request-tracker4);
+    $ENV{RTHOME} =~ s{/RT\.pm$}{} if defined $ENV{RTHOME};
+    $ENV{RTHOME} =~ s{/lib/?$}{}  if defined $ENV{RTHOME};
+    my @try = $ENV{RTHOME} ? ($ENV{RTHOME}, "$ENV{RTHOME}/lib") : ();
+    while (1) {
+        my @look = @INC;
+        unshift @look, grep {defined and -d $_} @try;
+        push @look, grep {defined and -d $_}
+            map { ( "$_/rt4/lib", "$_/lib/rt4", "$_/lib" ) } @prefixes;
+        last if eval {local @INC = @look; require RT; $RT::LocalLibPath};
+
+        warn
+            "Cannot find the location of RT.pm that defines \$RT::LocalPath in: @look\n";
+        my $given = $self->prompt("Path to directory containing your RT.pm:") or exit;
+        $given =~ s{/RT\.pm$}{};
+        $given =~ s{/lib/?$}{};
+        @try = ($given, "$given/lib");
     }
 
-    my $lib_path = File::Basename::dirname( $INC{'RT.pm'} );
-    my $local_lib_path = "$RT::LocalPath/lib";
     print "Using RT configuration from $INC{'RT.pm'}:\n";
-    unshift @INC, "$RT::LocalPath/lib" if $RT::LocalPath;
-    unshift @INC, $lib_path;
-
-    $RT::LocalVarPath    ||= $RT::VarPath;
-    $RT::LocalPoPath     ||= $RT::LocalLexiconPath;
-    $RT::LocalHtmlPath   ||= $RT::MasonComponentRoot;
-    $RT::LocalStaticPath ||= $RT::StaticPath;
-    $RT::LocalLibPath    ||= "$RT::LocalPath/lib";
 
-    my $with_subdirs = $ENV{WITH_SUBDIRS};
-    @ARGV = grep { /WITH_SUBDIRS=(.*)/ ? ( ( $with_subdirs = $1 ), 0 ) : 1 }
-        @ARGV;
+    my $local_lib_path = $RT::LocalLibPath;
+    unshift @INC, $local_lib_path;
+    my $lib_path = File::Basename::dirname( $INC{'RT.pm'} );
+    unshift @INC, $lib_path;
 
-    my %subdirs;
-    %subdirs = map { $_ => 1 } split( /\s*,\s*/, $with_subdirs )
-        if defined $with_subdirs;
-    unless ( keys %subdirs ) {
-        $subdirs{$_} = 1 foreach grep -d "$FindBin::Bin/$_", @DIRS;
+    # Set a baseline minimum version
+    unless ( $extra_args->{deprecated_rt} ) {
+        $self->requires_rt('4.0.0');
     }
 
-    # If we're running on RT 3.8 with plugin support, we really wany
-    # to install libs, mason templates and po files into plugin specific
-    # directories
+    # Installation locations
     my %path;
-    if ( $RT::LocalPluginPath ) {
-        die "Because of bugs in RT 3.8.0 this extension can not be installed.\n"
-            ."Upgrade to RT 3.8.1 or newer.\n" if $RT::VERSION =~ /^3\.8\.0/;
-        $path{$_} = $RT::LocalPluginPath . "/$original_name/$_"
-            foreach @DIRS;
-
-        # Copy RT 4.2.0 static files into NoAuth; insufficient for
-        # images, but good enough for css and js.
-        $path{static} = "$path{html}/NoAuth/"
-            unless $RT::StaticPath;
-    } else {
-        foreach ( @DIRS ) {
-            no strict 'refs';
-            my $varname = "RT::Local" . ucfirst($_) . "Path";
-            $path{$_} = ${$varname} || "$RT::LocalPath/$_";
-        }
+    $path{$_} = $RT::LocalPluginPath . "/$name/$_"
+        foreach @DIRS;
 
-        $path{$_} .= "/$name" for grep $path{$_}, qw(etc po var);
-    }
+    # Copy RT 4.2.0 static files into NoAuth; insufficient for
+    # images, but good enough for css and js.
+    $path{static} = "$path{html}/NoAuth/"
+        unless $RT::StaticPath;
+
+    # Delete the ones we don't need
+    delete $path{$_} for grep {not -d "$FindBin::Bin/$_"} keys %path;
 
     my %index = map { $_ => 1 } @INDEX_DIRS;
     $self->no_index( directory => $_ ) foreach grep !$index{$_}, @DIRS;
 
     my $args = join ', ', map "q($_)", map { ($_, $path{$_}) }
-        grep $subdirs{$_}, keys %path;
+        sort keys %path;
 
-    print "./$_\t=> $path{$_}\n" for sort keys %subdirs;
+    printf "%-10s => %s\n", $_, $path{$_} for sort keys %path;
 
-    if ( my @dirs = map { ( -D => $_ ) } grep $subdirs{$_}, qw(bin html sbin) ) {
+    if ( my @dirs = map { ( -D => $_ ) } grep $path{$_}, qw(bin html sbin etc) ) {
         my @po = map { ( -o => $_ ) }
             grep -f,
             File::Glob::bsd_glob("po/*.po");
@@ -121,12 +100,15 @@ lexicons ::
 .
     }
 
+    $self->include('Module::Install::RTx::Runtime') if $self->admin;
+    $self->include_deps( 'YAML::Tiny', 0 ) if $self->admin;
     my $postamble = << ".";
 install ::
+\t\$(NOECHO) \$(PERL) -Ilib -I"$local_lib_path" -I"$lib_path" -Iinc -MModule::Install::RTx::Runtime -e"RTxPlugin()"
 \t\$(NOECHO) \$(PERL) -MExtUtils::Install -e \"install({$args})\"
 .
 
-    if ( $subdirs{var} and -d $RT::MasonDataDir ) {
+    if ( $path{var} and -d $RT::MasonDataDir ) {
         my ( $uid, $gid ) = ( stat($RT::MasonDataDir) )[ 4, 5 ];
         $postamble .= << ".";
 \t\$(NOECHO) chown -R $uid:$gid $path{var}
@@ -141,16 +123,16 @@ install ::
         $has_etc{acl}++;
     }
     if ( -e 'etc/initialdata' ) { $has_etc{initialdata}++; }
-    if ( grep { /\d+\.\d+\.\d+.*$/ } glob('etc/upgrade/*.*.*') ) {
+    if ( grep { /\d+\.\d+(\.\d+)?.*$/ } glob('etc/upgrade/*.*') ) {
         $has_etc{upgrade}++;
     }
 
     $self->postamble("$postamble\n");
-    unless ( $subdirs{'lib'} ) {
-        $self->makemaker_args( PM => { "" => "" }, );
-    } else {
+    if ( $path{lib} ) {
         $self->makemaker_args( INSTALLSITELIB => $path{'lib'} );
         $self->makemaker_args( INSTALLARCHLIB => $path{'lib'} );
+    } else {
+        $self->makemaker_args( PM => { "" => "" }, );
     }
 
     $self->makemaker_args( INSTALLSITEMAN1DIR => "$RT::LocalPath/man/man1" );
@@ -158,47 +140,96 @@ install ::
     $self->makemaker_args( INSTALLSITEARCH => "$RT::LocalPath/man" );
 
     if (%has_etc) {
-        $self->load('RTxInitDB');
         print "For first-time installation, type 'make initdb'.\n";
         my $initdb = '';
         $initdb .= <<"." if $has_etc{schema};
-\t\$(NOECHO) \$(PERL) -Ilib -I"$local_lib_path" -I"$lib_path" -Minc::Module::Install -e"RTxInitDB(qw(schema \$(NAME) \$(VERSION)))"
+\t\$(NOECHO) \$(PERL) -Ilib -I"$local_lib_path" -I"$lib_path" -Iinc -MModule::Install::RTx::Runtime -e"RTxDatabase(qw(schema \$(NAME) \$(VERSION)))"
 .
         $initdb .= <<"." if $has_etc{acl};
-\t\$(NOECHO) \$(PERL) -Ilib -I"$local_lib_path" -I"$lib_path" -Minc::Module::Install -e"RTxInitDB(qw(acl \$(NAME) \$(VERSION)))"
+\t\$(NOECHO) \$(PERL) -Ilib -I"$local_lib_path" -I"$lib_path" -Iinc -MModule::Install::RTx::Runtime -e"RTxDatabase(qw(acl \$(NAME) \$(VERSION)))"
 .
         $initdb .= <<"." if $has_etc{initialdata};
-\t\$(NOECHO) \$(PERL) -Ilib -I"$local_lib_path" -I"$lib_path" -Minc::Module::Install -e"RTxInitDB(qw(insert \$(NAME) \$(VERSION)))"
+\t\$(NOECHO) \$(PERL) -Ilib -I"$local_lib_path" -I"$lib_path" -Iinc -MModule::Install::RTx::Runtime -e"RTxDatabase(qw(insert \$(NAME) \$(VERSION)))"
 .
         $self->postamble("initdb ::\n$initdb\n");
         $self->postamble("initialize-database ::\n$initdb\n");
         if ($has_etc{upgrade}) {
             print "To upgrade from a previous version of this extension, use 'make upgrade-database'\n";
-            my $upgradedb = qq|\t\$(NOECHO) \$(PERL) -Ilib -I"$local_lib_path" -I"$lib_path" -Minc::Module::Install -e"RTxInitDB(qw(upgrade \$(NAME) \$(VERSION)))"\n|;
+            my $upgradedb = qq|\t\$(NOECHO) \$(PERL) -Ilib -I"$local_lib_path" -I"$lib_path" -Iinc -MModule::Install::RTx::Runtime -e"RTxDatabase(qw(upgrade \$(NAME) \$(VERSION)))"\n|;
             $self->postamble("upgrade-database ::\n$upgradedb\n");
             $self->postamble("upgradedb ::\n$upgradedb\n");
         }
     }
+
 }
 
 sub requires_rt {
     my ($self,$version) = @_;
 
+    _load_rt_handle();
+
+    if ($self->is_admin) {
+        $self->add_metadata("x_requires_rt", $version);
+        my @sorted = sort RT::Handle::cmp_version $version,'4.0.0';
+        $self->perl_version('5.008003') if $sorted[0] eq '4.0.0'
+            and (not $self->perl_version or '5.008003' > $self->perl_version);
+        @sorted = sort RT::Handle::cmp_version $version,'4.2.0';
+        $self->perl_version('5.010001') if $sorted[0] eq '4.2.0'
+            and (not $self->perl_version or '5.010001' > $self->perl_version);
+    }
+
     # if we're exactly the same version as what we want, silently return
     return if ($version eq $RT::VERSION);
 
-    _load_rt_handle();
     my @sorted = sort RT::Handle::cmp_version $version,$RT::VERSION;
 
     if ($sorted[-1] eq $version) {
-        # should we die?
-        die "\nWarning: prerequisite RT $version not found. Your installed version of RT ($RT::VERSION) is too old.\n\n";
+        die <<"EOT";
+
+**** Error: This extension requires RT $version. Your installed version
+            of RT ($RT::VERSION) is too old.
+
+EOT
+    }
+}
+
+sub requires_rt_plugin {
+    my $self = shift;
+    my ( $plugin ) = @_;
+
+    if ($self->is_admin) {
+        my $plugins = $self->Meta->{values}{"x_requires_rt_plugins"} || [];
+        push @{$plugins}, $plugin;
+        $self->add_metadata("x_requires_rt_plugins", $plugins);
     }
+
+    my $path = $plugin;
+    $path =~ s{\:\:}{-}g;
+    $path = "$RT::LocalPluginPath/$path/lib";
+    if ( -e $path ) {
+        unshift @INC, $path;
+    } else {
+        my $name = $self->name;
+        warn <<"EOT";
+
+**** Warning: $name requires that the $plugin plugin be installed and
+              enabled; it does not appear to be installed.
+
+EOT
+    }
+    $self->requires(@_);
 }
 
 sub rt_too_new {
     my ($self,$version,$msg) = @_;
-    $msg ||= "Your version %s is too new, this extension requires a release of RT older than %s\n";
+    my $name = $self->name;
+    $msg ||= <<EOT;
+
+**** Error: Your installed version of RT (%s) is too new; this extension
+            only works with versions older than %s.
+
+EOT
+    $self->add_metadata("x_rt_too_new", $version) if $self->is_admin;
 
     _load_rt_handle();
     my @sorted = sort RT::Handle::cmp_version $version,$RT::VERSION;
@@ -227,4 +258,4 @@ sub _load_rt_handle {
 
 __END__
 
-#line 367
+#line 390
@@ -6,7 +6,7 @@ use Module::Install::Base ();
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '1.08';
+	$VERSION = '1.12';
 	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
 }
@@ -6,7 +6,7 @@ use Module::Install::Base ();
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '1.08';
+	$VERSION = '1.12';
 	@ISA     = qw{Module::Install::Base};
 	$ISCORE  = 1;
 }
@@ -17,7 +17,7 @@ package Module::Install;
 #     3. The ./inc/ version of Module::Install loads
 # }
 
-use 5.005;
+use 5.006;
 use strict 'vars';
 use Cwd        ();
 use File::Find ();
@@ -31,7 +31,7 @@ BEGIN {
 	# This is not enforced yet, but will be some time in the next few
 	# releases once we can make sure it won't clash with custom
 	# Module::Install extensions.
-	$VERSION = '1.08';
+	$VERSION = '1.12';
 
 	# Storage for the pseudo-singleton
 	$MAIN    = undef;
@@ -156,10 +156,10 @@ END_DIE
 sub autoload {
 	my $self = shift;
 	my $who  = $self->_caller;
-	my $cwd  = Cwd::cwd();
+	my $cwd  = Cwd::getcwd();
 	my $sym  = "${who}::AUTOLOAD";
 	$sym->{$cwd} = sub {
-		my $pwd = Cwd::cwd();
+		my $pwd = Cwd::getcwd();
 		if ( my $code = $sym->{$pwd} ) {
 			# Delegate back to parent dirs
 			goto &$code unless $cwd eq $pwd;
@@ -239,7 +239,7 @@ sub new {
 
 	# ignore the prefix on extension modules built from top level.
 	my $base_path = Cwd::abs_path($FindBin::Bin);
-	unless ( Cwd::abs_path(Cwd::cwd()) eq $base_path ) {
+	unless ( Cwd::abs_path(Cwd::getcwd()) eq $base_path ) {
 		delete $args{prefix};
 	}
 	return $args{_self} if $args{_self};
@@ -338,7 +338,7 @@ sub find_extensions {
 		if ( $subpath eq lc($subpath) || $subpath eq uc($subpath) ) {
 			my $content = Module::Install::_read($subpath . '.pm');
 			my $in_pod  = 0;
-			foreach ( split //, $content ) {
+			foreach ( split /\n/, $content ) {
 				$in_pod = 1 if /^=\w/;
 				$in_pod = 0 if /^=cut/;
 				next if ($in_pod || /^=cut/);  # skip pod text
@@ -434,7 +434,7 @@ END_OLD
 
 # _version is for processing module versions (eg, 1.03_05) not
 # Perl versions (eg, 5.8.1).
-sub _version ($) {
+sub _version {
 	my $s = shift || 0;
 	my $d =()= $s =~ /(\.)/g;
 	if ( $d >= 2 ) {
@@ -450,12 +450,12 @@ sub _version ($) {
 	return $l + 0;
 }
 
-sub _cmp ($$) {
+sub _cmp {
 	_version($_[1]) <=> _version($_[2]);
 }
 
 # Cloned from Params::Util::_CLASS
-sub _CLASS ($) {
+sub _CLASS {
 	(
 		defined $_[0]
 		and
@@ -0,0 +1,873 @@
+#line 1
+use 5.008001; # sane UTF-8 support
+use strict;
+use warnings;
+package YAML::Tiny;
+BEGIN {
+  $YAML::Tiny::AUTHORITY = 'cpan:ADAMK';
+}
+# git description: v1.61-3-g0a82466
+$YAML::Tiny::VERSION = '1.62';
+# XXX-INGY is 5.8.1 too old/broken for utf8?
+# XXX-XDG Lancaster consensus was that it was sufficient until
+# proven otherwise
+
+
+#####################################################################
+# The YAML::Tiny API.
+#
+# These are the currently documented API functions/methods and
+# exports:
+
+use Exporter;
+our @ISA       = qw{ Exporter  };
+our @EXPORT    = qw{ Load Dump };
+our @EXPORT_OK = qw{ LoadFile DumpFile freeze thaw };
+
+###
+# Functional/Export API:
+
+sub Dump {
+    return YAML::Tiny->new(@_)->_dump_string;
+}
+
+# XXX-INGY Returning last document seems a bad behavior.
+# XXX-XDG I think first would seem more natural, but I don't know
+# that it's worth changing now
+sub Load {
+    my $self = YAML::Tiny->_load_string(@_);
+    if ( wantarray ) {
+        return @$self;
+    } else {
+        # To match YAML.pm, return the last document
+        return $self->[-1];
+    }
+}
+
+# XXX-INGY Do we really need freeze and thaw?
+# XXX-XDG I don't think so.  I'd support deprecating them.
+BEGIN {
+    *freeze = \&Dump;
+    *thaw   = \&Load;
+}
+
+sub DumpFile {
+    my $file = shift;
+    return YAML::Tiny->new(@_)->_dump_file($file);
+}
+
+sub LoadFile {
+    my $file = shift;
+    my $self = YAML::Tiny->_load_file($file);
+    if ( wantarray ) {
+        return @$self;
+    } else {
+        # Return only the last document to match YAML.pm,
+        return $self->[-1];
+    }
+}
+
+
+###
+# Object Oriented API:
+
+# Create an empty YAML::Tiny object
+# XXX-INGY Why do we use ARRAY object?
+# NOTE: I get it now, but I think it's confusing and not needed.
+# Will change it on a branch later, for review.
+#
+# XXX-XDG I don't support changing it yet.  It's a very well-documented
+# "API" of YAML::Tiny.  I'd support deprecating it, but Adam suggested
+# we not change it until YAML.pm's own OO API is established so that
+# users only have one API change to digest, not two
+sub new {
+    my $class = shift;
+    bless [ @_ ], $class;
+}
+
+# XXX-INGY It probably doesn't matter, and it's probably too late to
+# change, but 'read/write' are the wrong names. Read and Write
+# are actions that take data from storage to memory
+# characters/strings. These take the data to/from storage to native
+# Perl objects, which the terms dump and load are meant. As long as
+# this is a legacy quirk to YAML::Tiny it's ok, but I'd prefer not
+# to add new {read,write}_* methods to this API.
+
+sub read_string {
+    my $self = shift;
+    $self->_load_string(@_);
+}
+
+sub write_string {
+    my $self = shift;
+    $self->_dump_string(@_);
+}
+
+sub read {
+    my $self = shift;
+    $self->_load_file(@_);
+}
+
+sub write {
+    my $self = shift;
+    $self->_dump_file(@_);
+}
+
+
+
+
+#####################################################################
+# Constants
+
+# Printed form of the unprintable characters in the lowest range
+# of ASCII characters, listed by ASCII ordinal position.
+my @UNPRINTABLE = qw(
+    0    x01  x02  x03  x04  x05  x06  a
+    b    t    n    v    f    r    x0E  x0F
+    x10  x11  x12  x13  x14  x15  x16  x17
+    x18  x19  x1A  e    x1C  x1D  x1E  x1F
+);
+
+# Printable characters for escapes
+my %UNESCAPES = (
+    0 => "\x00", z => "\x00", N    => "\x85",
+    a => "\x07", b => "\x08", t    => "\x09",
+    n => "\x0a", v => "\x0b", f    => "\x0c",
+    r => "\x0d", e => "\x1b", '\\' => '\\',
+);
+
+# XXX-INGY
+# I(ngy) need to decide if these values should be quoted in
+# YAML::Tiny or not. Probably yes.
+
+# These 3 values have special meaning when unquoted and using the
+# default YAML schema. They need quotes if they are strings.
+my %QUOTE = map { $_ => 1 } qw{
+    null true false
+};
+
+# The commented out form is simpler, but overloaded the Perl regex
+# engine due to recursion and backtracking problems on strings
+# larger than 32,000ish characters. Keep it for reference purposes.
+# qr/\"((?:\\.|[^\"])*)\"/
+my $re_capture_double_quoted = qr/\"([^\\"]*(?:\\.[^\\"]*)*)\"/;
+my $re_capture_single_quoted = qr/\'([^\']*(?:\'\'[^\']*)*)\'/;
+# unquoted re gets trailing space that needs to be stripped
+my $re_capture_unquoted_key  = qr/([^:]+(?::+\S(?:[^:]*|.*?(?=:)))*)(?=\s*\:(?:\s+|$))/;
+my $re_trailing_comment      = qr/(?:\s+\#.*)?/;
+my $re_key_value_separator   = qr/\s*:(?:\s+(?:\#.*)?|$)/;
+
+
+
+
+
+#####################################################################
+# YAML::Tiny Implementation.
+#
+# These are the private methods that do all the work. They may change
+# at any time.
+
+
+###
+# Loader functions:
+
+# Create an object from a file
+sub _load_file {
+    my $class = ref $_[0] ? ref shift : shift;
+
+    # Check the file
+    my $file = shift or $class->_error( 'You did not specify a file name' );
+    $class->_error( "File '$file' does not exist" )
+        unless -e $file;
+    $class->_error( "'$file' is a directory, not a file" )
+        unless -f _;
+    $class->_error( "Insufficient permissions to read '$file'" )
+        unless -r _;
+
+    # Open unbuffered with strict UTF-8 decoding and no translation layers
+    open( my $fh, "<:unix:encoding(UTF-8)", $file );
+    unless ( $fh ) {
+        $class->_error("Failed to open file '$file': $!");
+    }
+
+    # flock if available (or warn if not possible for OS-specific reasons)
+    if ( _can_flock() ) {
+        flock( $fh, Fcntl::LOCK_SH() )
+            or warn "Couldn't lock '$file' for reading: $!";
+    }
+
+    # slurp the contents
+    my $contents = eval {
+        use warnings FATAL => 'utf8';
+        local $/;
+        <$fh>
+    };
+    if ( my $err = $@ ) {
+        $class->_error("Error reading from file '$file': $err");
+    }
+
+    # close the file (release the lock)
+    unless ( close $fh ) {
+        $class->_error("Failed to close file '$file': $!");
+    }
+
+    $class->_load_string( $contents );
+}
+
+# Create an object from a string
+sub _load_string {
+    my $class  = ref $_[0] ? ref shift : shift;
+    my $self   = bless [], $class;
+    my $string = $_[0];
+    eval {
+        unless ( defined $string ) {
+            die \"Did not provide a string to load";
+        }
+
+        # Check if Perl has it marked as characters, but it's internally
+        # inconsistent.  E.g. maybe latin1 got read on a :utf8 layer
+        if ( utf8::is_utf8($string) && ! utf8::valid($string) ) {
+            die \<<'...';
+Read an invalid UTF-8 string (maybe mixed UTF-8 and 8-bit character set).
+Did you decode with lax ":utf8" instead of strict ":encoding(UTF-8)"?
+...
+        }
+
+        # Ensure Unicode character semantics, even for 0x80-0xff
+        utf8::upgrade($string);
+
+        # Check for and strip any leading UTF-8 BOM
+        $string =~ s/^\x{FEFF}//;
+
+        # Check for some special cases
+        return $self unless length $string;
+
+        # Split the file into lines
+        my @lines = grep { ! /^\s*(?:\#.*)?\z/ }
+                split /(?:\015{1,2}\012|\015|\012)/, $string;
+
+        # Strip the initial YAML header
+        @lines and $lines[0] =~ /^\%YAML[: ][\d\.]+.*\z/ and shift @lines;
+
+        # A nibbling parser
+        my $in_document = 0;
+        while ( @lines ) {
+            # Do we have a document header?
+            if ( $lines[0] =~ /^---\s*(?:(.+)\s*)?\z/ ) {
+                # Handle scalar documents
+                shift @lines;
+                if ( defined $1 and $1 !~ /^(?:\#.+|\%YAML[: ][\d\.]+)\z/ ) {
+                    push @$self,
+                        $self->_load_scalar( "$1", [ undef ], \@lines );
+                    next;
+                }
+                $in_document = 1;
+            }
+
+            if ( ! @lines or $lines[0] =~ /^(?:---|\.\.\.)/ ) {
+                # A naked document
+                push @$self, undef;
+                while ( @lines and $lines[0] !~ /^---/ ) {
+                    shift @lines;
+                }
+                $in_document = 0;
+
+            # XXX The final '-+$' is to look for -- which ends up being an
+            # error later.
+            } elsif ( ! $in_document && @$self ) {
+                # only the first document can be explicit
+                die \"YAML::Tiny failed to classify the line '$lines[0]'";
+            } elsif ( $lines[0] =~ /^\s*\-(?:\s|$|-+$)/ ) {
+                # An array at the root
+                my $document = [ ];
+                push @$self, $document;
+                $self->_load_array( $document, [ 0 ], \@lines );
+
+            } elsif ( $lines[0] =~ /^(\s*)\S/ ) {
+                # A hash at the root
+                my $document = { };
+                push @$self, $document;
+                $self->_load_hash( $document, [ length($1) ], \@lines );
+
+            } else {
+                # Shouldn't get here.  @lines have whitespace-only lines
+                # stripped, and previous match is a line with any
+                # non-whitespace.  So this clause should only be reachable via
+                # a perlbug where \s is not symmetric with \S
+
+                # uncoverable statement
+                die \"YAML::Tiny failed to classify the line '$lines[0]'";
+            }
+        }
+    };
+    if ( ref $@ eq 'SCALAR' ) {
+        $self->_error(${$@});
+    } elsif ( $@ ) {
+        $self->_error($@);
+    }
+
+    return $self;
+}
+
+sub _unquote_single {
+    my ($self, $string) = @_;
+    return '' unless length $string;
+    $string =~ s/\'\'/\'/g;
+    return $string;
+}
+
+sub _unquote_double {
+    my ($self, $string) = @_;
+    return '' unless length $string;
+    $string =~ s/\\"/"/g;
+    $string =~
+        s{\\([Nnever\\fartz0b]|x([0-9a-fA-F]{2}))}
+         {(length($1)>1)?pack("H2",$2):$UNESCAPES{$1}}gex;
+    return $string;
+}
+
+# Load a YAML scalar string to the actual Perl scalar
+sub _load_scalar {
+    my ($self, $string, $indent, $lines) = @_;
+
+    # Trim trailing whitespace
+    $string =~ s/\s*\z//;
+
+    # Explitic null/undef
+    return undef if $string eq '~';
+
+    # Single quote
+    if ( $string =~ /^$re_capture_single_quoted$re_trailing_comment\z/ ) {
+        return $self->_unquote_single($1);
+    }
+
+    # Double quote.
+    if ( $string =~ /^$re_capture_double_quoted$re_trailing_comment\z/ ) {
+        return $self->_unquote_double($1);
+    }
+
+    # Special cases
+    if ( $string =~ /^[\'\"!&]/ ) {
+        die \"YAML::Tiny does not support a feature in line '$string'";
+    }
+    return {} if $string =~ /^{}(?:\s+\#.*)?\z/;
+    return [] if $string =~ /^\[\](?:\s+\#.*)?\z/;
+
+    # Regular unquoted string
+    if ( $string !~ /^[>|]/ ) {
+        die \"YAML::Tiny found illegal characters in plain scalar: '$string'"
+            if $string =~ /^(?:-(?:\s|$)|[\@\%\`])/ or
+                $string =~ /:(?:\s|$)/;
+        $string =~ s/\s+#.*\z//;
+        return $string;
+    }
+
+    # Error
+    die \"YAML::Tiny failed to find multi-line scalar content" unless @$lines;
+
+    # Check the indent depth
+    $lines->[0]   =~ /^(\s*)/;
+    $indent->[-1] = length("$1");
+    if ( defined $indent->[-2] and $indent->[-1] <= $indent->[-2] ) {
+        die \"YAML::Tiny found bad indenting in line '$lines->[0]'";
+    }
+
+    # Pull the lines
+    my @multiline = ();
+    while ( @$lines ) {
+        $lines->[0] =~ /^(\s*)/;
+        last unless length($1) >= $indent->[-1];
+        push @multiline, substr(shift(@$lines), length($1));
+    }
+
+    my $j = (substr($string, 0, 1) eq '>') ? ' ' : "\n";
+    my $t = (substr($string, 1, 1) eq '-') ? ''  : "\n";
+    return join( $j, @multiline ) . $t;
+}
+
+# Load an array
+sub _load_array {
+    my ($self, $array, $indent, $lines) = @_;
+
+    while ( @$lines ) {
+        # Check for a new document
+        if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) {
+            while ( @$lines and $lines->[0] !~ /^---/ ) {
+                shift @$lines;
+            }
+            return 1;
+        }
+
+        # Check the indent level
+        $lines->[0] =~ /^(\s*)/;
+        if ( length($1) < $indent->[-1] ) {
+            return 1;
+        } elsif ( length($1) > $indent->[-1] ) {
+            die \"YAML::Tiny found bad indenting in line '$lines->[0]'";
+        }
+
+        if ( $lines->[0] =~ /^(\s*\-\s+)[^\'\"]\S*\s*:(?:\s+|$)/ ) {
+            # Inline nested hash
+            my $indent2 = length("$1");
+            $lines->[0] =~ s/-/ /;
+            push @$array, { };
+            $self->_load_hash( $array->[-1], [ @$indent, $indent2 ], $lines );
+
+        } elsif ( $lines->[0] =~ /^\s*\-\s*\z/ ) {
+            shift @$lines;
+            unless ( @$lines ) {
+                push @$array, undef;
+                return 1;
+            }
+            if ( $lines->[0] =~ /^(\s*)\-/ ) {
+                my $indent2 = length("$1");
+                if ( $indent->[-1] == $indent2 ) {
+                    # Null array entry
+                    push @$array, undef;
+                } else {
+                    # Naked indenter
+                    push @$array, [ ];
+                    $self->_load_array(
+                        $array->[-1], [ @$indent, $indent2 ], $lines
+                    );
+                }
+
+            } elsif ( $lines->[0] =~ /^(\s*)\S/ ) {
+                push @$array, { };
+                $self->_load_hash(
+                    $array->[-1], [ @$indent, length("$1") ], $lines
+                );
+
+            } else {
+                die \"YAML::Tiny failed to classify line '$lines->[0]'";
+            }
+
+        } elsif ( $lines->[0] =~ /^\s*\-(\s*)(.+?)\s*\z/ ) {
+            # Array entry with a value
+            shift @$lines;
+            push @$array, $self->_load_scalar(
+                "$2", [ @$indent, undef ], $lines
+            );
+
+        } elsif ( defined $indent->[-2] and $indent->[-1] == $indent->[-2] ) {
+            # This is probably a structure like the following...
+            # ---
+            # foo:
+            # - list
+            # bar: value
+            #
+            # ... so lets return and let the hash parser handle it
+            return 1;
+
+        } else {
+            die \"YAML::Tiny failed to classify line '$lines->[0]'";
+        }
+    }
+
+    return 1;
+}
+
+# Load a hash
+sub _load_hash {
+    my ($self, $hash, $indent, $lines) = @_;
+
+    while ( @$lines ) {
+        # Check for a new document
+        if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) {
+            while ( @$lines and $lines->[0] !~ /^---/ ) {
+                shift @$lines;
+            }
+            return 1;
+        }
+
+        # Check the indent level
+        $lines->[0] =~ /^(\s*)/;
+        if ( length($1) < $indent->[-1] ) {
+            return 1;
+        } elsif ( length($1) > $indent->[-1] ) {
+            die \"YAML::Tiny found bad indenting in line '$lines->[0]'";
+        }
+
+        # Find the key
+        my $key;
+
+        # Quoted keys
+        if ( $lines->[0] =~
+            s/^\s*$re_capture_single_quoted$re_key_value_separator//
+        ) {
+            $key = $self->_unquote_single($1);
+        }
+        elsif ( $lines->[0] =~
+            s/^\s*$re_capture_double_quoted$re_key_value_separator//
+        ) {
+            $key = $self->_unquote_double($1);
+        }
+        elsif ( $lines->[0] =~
+            s/^\s*$re_capture_unquoted_key$re_key_value_separator//
+        ) {
+            $key = $1;
+            $key =~ s/\s+$//;
+        }
+        elsif ( $lines->[0] =~ /^\s*\?/ ) {
+            die \"YAML::Tiny does not support a feature in line '$lines->[0]'";
+        }
+        else {
+            die \"YAML::Tiny failed to classify line '$lines->[0]'";
+        }
+
+        # Do we have a value?
+        if ( length $lines->[0] ) {
+            # Yes
+            $hash->{$key} = $self->_load_scalar(
+                shift(@$lines), [ @$indent, undef ], $lines
+            );
+        } else {
+            # An indent
+            shift @$lines;
+            unless ( @$lines ) {
+                $hash->{$key} = undef;
+                return 1;
+            }
+            if ( $lines->[0] =~ /^(\s*)-/ ) {
+                $hash->{$key} = [];
+                $self->_load_array(
+                    $hash->{$key}, [ @$indent, length($1) ], $lines
+                );
+            } elsif ( $lines->[0] =~ /^(\s*)./ ) {
+                my $indent2 = length("$1");
+                if ( $indent->[-1] >= $indent2 ) {
+                    # Null hash entry
+                    $hash->{$key} = undef;
+                } else {
+                    $hash->{$key} = {};
+                    $self->_load_hash(
+                        $hash->{$key}, [ @$indent, length($1) ], $lines
+                    );
+                }
+            }
+        }
+    }
+
+    return 1;
+}
+
+
+###
+# Dumper functions:
+
+# Save an object to a file
+sub _dump_file {
+    my $self = shift;
+
+    require Fcntl;
+
+    # Check the file
+    my $file = shift or $self->_error( 'You did not specify a file name' );
+
+    my $fh;
+    # flock if available (or warn if not possible for OS-specific reasons)
+    if ( _can_flock() ) {
+        # Open without truncation (truncate comes after lock)
+        my $flags = Fcntl::O_WRONLY()|Fcntl::O_CREAT();
+        sysopen( $fh, $file, $flags );
+        unless ( $fh ) {
+            $self->_error("Failed to open file '$file' for writing: $!");
+        }
+
+        # Use no translation and strict UTF-8
+        binmode( $fh, ":raw:encoding(UTF-8)");
+
+        flock( $fh, Fcntl::LOCK_EX() )
+            or warn "Couldn't lock '$file' for reading: $!";
+
+        # truncate and spew contents
+        truncate $fh, 0;
+        seek $fh, 0, 0;
+    }
+    else {
+        open $fh, ">:unix:encoding(UTF-8)", $file;
+    }
+
+    # serialize and spew to the handle
+    print {$fh} $self->_dump_string;
+
+    # close the file (release the lock)
+    unless ( close $fh ) {
+        $self->_error("Failed to close file '$file': $!");
+    }
+
+    return 1;
+}
+
+# Save an object to a string
+sub _dump_string {
+    my $self = shift;
+    return '' unless ref $self && @$self;
+
+    # Iterate over the documents
+    my $indent = 0;
+    my @lines  = ();
+
+    eval {
+        foreach my $cursor ( @$self ) {
+            push @lines, '---';
+
+            # An empty document
+            if ( ! defined $cursor ) {
+                # Do nothing
+
+            # A scalar document
+            } elsif ( ! ref $cursor ) {
+                $lines[-1] .= ' ' . $self->_dump_scalar( $cursor );
+
+            # A list at the root
+            } elsif ( ref $cursor eq 'ARRAY' ) {
+                unless ( @$cursor ) {
+                    $lines[-1] .= ' []';
+                    next;
+                }
+                push @lines, $self->_dump_array( $cursor, $indent, {} );
+
+            # A hash at the root
+            } elsif ( ref $cursor eq 'HASH' ) {
+                unless ( %$cursor ) {
+                    $lines[-1] .= ' {}';
+                    next;
+                }
+                push @lines, $self->_dump_hash( $cursor, $indent, {} );
+
+            } else {
+                die \("Cannot serialize " . ref($cursor));
+            }
+        }
+    };
+    if ( ref $@ eq 'SCALAR' ) {
+        $self->_error(${$@});
+    } elsif ( $@ ) {
+        $self->_error($@);
+    }
+
+    join '', map { "$_\n" } @lines;
+}
+
+sub _has_internal_string_value {
+    my $value = shift;
+    my $b_obj = B::svref_2object(\$value);  # for round trip problem
+    return $b_obj->FLAGS & B::SVf_POK();
+}
+
+sub _dump_scalar {
+    my $string = $_[1];
+    my $is_key = $_[2];
+    # Check this before checking length or it winds up looking like a string!
+    my $has_string_flag = _has_internal_string_value($string);
+    return '~'  unless defined $string;
+    return "''" unless length  $string;
+    if (Scalar::Util::looks_like_number($string)) {
+        # keys and values that have been used as strings get quoted
+        if ( $is_key || $has_string_flag ) {
+            return qq['$string'];
+        }
+        else {
+            return $string;
+        }
+    }
+    if ( $string =~ /[\x00-\x09\x0b-\x0d\x0e-\x1f\x7f-\x9f\'\n]/ ) {
+        $string =~ s/\\/\\\\/g;
+        $string =~ s/"/\\"/g;
+        $string =~ s/\n/\\n/g;
+        $string =~ s/[\x85]/\\N/g;
+        $string =~ s/([\x00-\x1f])/\\$UNPRINTABLE[ord($1)]/g;
+        $string =~ s/([\x7f-\x9f])/'\x' . sprintf("%X",ord($1))/ge;
+        return qq|"$string"|;
+    }
+    if ( $string =~ /(?:^[~!@#%&*|>?:,'"`{}\[\]]|^-+$|\s|:\z)/ or
+        $QUOTE{$string}
+    ) {
+        return "'$string'";
+    }
+    return $string;
+}
+
+sub _dump_array {
+    my ($self, $array, $indent, $seen) = @_;
+    if ( $seen->{refaddr($array)}++ ) {
+        die \"YAML::Tiny does not support circular references";
+    }
+    my @lines  = ();
+    foreach my $el ( @$array ) {
+        my $line = ('  ' x $indent) . '-';
+        my $type = ref $el;
+        if ( ! $type ) {
+            $line .= ' ' . $self->_dump_scalar( $el );
+            push @lines, $line;
+
+        } elsif ( $type eq 'ARRAY' ) {
+            if ( @$el ) {
+                push @lines, $line;
+                push @lines, $self->_dump_array( $el, $indent + 1, $seen );
+            } else {
+                $line .= ' []';
+                push @lines, $line;
+            }
+
+        } elsif ( $type eq 'HASH' ) {
+            if ( keys %$el ) {
+                push @lines, $line;
+                push @lines, $self->_dump_hash( $el, $indent + 1, $seen );
+            } else {
+                $line .= ' {}';
+                push @lines, $line;
+            }
+
+        } else {
+            die \"YAML::Tiny does not support $type references";
+        }
+    }
+
+    @lines;
+}
+
+sub _dump_hash {
+    my ($self, $hash, $indent, $seen) = @_;
+    if ( $seen->{refaddr($hash)}++ ) {
+        die \"YAML::Tiny does not support circular references";
+    }
+    my @lines  = ();
+    foreach my $name ( sort keys %$hash ) {
+        my $el   = $hash->{$name};
+        my $line = ('  ' x $indent) . $self->_dump_scalar($name, 1) . ":";
+        my $type = ref $el;
+        if ( ! $type ) {
+            $line .= ' ' . $self->_dump_scalar( $el );
+            push @lines, $line;
+
+        } elsif ( $type eq 'ARRAY' ) {
+            if ( @$el ) {
+                push @lines, $line;
+                push @lines, $self->_dump_array( $el, $indent + 1, $seen );
+            } else {
+                $line .= ' []';
+                push @lines, $line;
+            }
+
+        } elsif ( $type eq 'HASH' ) {
+            if ( keys %$el ) {
+                push @lines, $line;
+                push @lines, $self->_dump_hash( $el, $indent + 1, $seen );
+            } else {
+                $line .= ' {}';
+                push @lines, $line;
+            }
+
+        } else {
+            die \"YAML::Tiny does not support $type references";
+        }
+    }
+
+    @lines;
+}
+
+
+
+#####################################################################
+# DEPRECATED API methods:
+
+# Error storage (DEPRECATED as of 1.57)
+our $errstr    = '';
+
+# Set error
+sub _error {
+    require Carp;
+    $errstr = $_[1];
+    $errstr =~ s/ at \S+ line \d+.*//;
+    Carp::croak( $errstr );
+}
+
+# Retrieve error
+my $errstr_warned;
+sub errstr {
+    require Carp;
+    Carp::carp( "YAML::Tiny->errstr and \$YAML::Tiny::errstr is deprecated" )
+        unless $errstr_warned++;
+    $errstr;
+}
+
+
+
+
+#####################################################################
+# Helper functions. Possibly not needed.
+
+
+# Use to detect nv or iv
+use B;
+
+# XXX-INGY Is flock YAML::Tiny's responsibility?
+# Some platforms can't flock :-(
+# XXX-XDG I think it is.  When reading and writing files, we ought
+# to be locking whenever possible.  People (foolishly) use YAML
+# files for things like session storage, which has race issues.
+my $HAS_FLOCK;
+sub _can_flock {
+    if ( defined $HAS_FLOCK ) {
+        return $HAS_FLOCK;
+    }
+    else {
+        require Config;
+        my $c = \%Config::Config;
+        $HAS_FLOCK = grep { $c->{$_} } qw/d_flock d_fcntl_can_lock d_lockf/;
+        require Fcntl if $HAS_FLOCK;
+        return $HAS_FLOCK;
+    }
+}
+
+
+# XXX-INGY Is this core in 5.8.1? Can we remove this?
+# XXX-XDG Scalar::Util 1.18 didn't land until 5.8.8, so we need this
+#####################################################################
+# Use Scalar::Util if possible, otherwise emulate it
+
+BEGIN {
+    local $@;
+    if ( eval { require Scalar::Util; Scalar::Util->VERSION(1.18); } ) {
+        *refaddr = *Scalar::Util::refaddr;
+    }
+    else {
+        eval <<'END_PERL';
+# Scalar::Util failed to load or too old
+sub refaddr {
+    my $pkg = ref($_[0]) or return undef;
+    if ( !! UNIVERSAL::can($_[0], 'can') ) {
+        bless $_[0], 'Scalar::Util::Fake';
+    } else {
+        $pkg = undef;
+    }
+    "$_[0]" =~ /0x(\w+)/;
+    my $i = do { no warnings 'portable'; hex $1 };
+    bless $_[0], $pkg if defined $pkg;
+    $i;
+}
+END_PERL
+    }
+}
+
+
+
+
+1;
+
+# XXX-INGY Doc notes I'm putting up here. Changing the doc when it's wrong
+# but leaving grey area stuff up here.
+#
+# I would like to change Read/Write to Load/Dump below without
+# changing the actual API names.
+#
+# It might be better to put Load/Dump API in the SYNOPSIS instead of the
+# dubious OO API.
+#
+# null and bool explanations may be outdated.
+
+__END__
+
+#line 1488
@@ -0,0 +1,417 @@
+#line 1
+# !!!!!!!   DO NOT EDIT THIS FILE   !!!!!!!
+# This file is machine-generated by lib/unicore/mktables from the Unicode
+# database, Version 6.3.0.  Any changes made here will be lost!
+
+
+# !!!!!!!   INTERNAL PERL USE ONLY   !!!!!!!
+# This file is for internal use by core Perl only.  The format and even the
+# name or existence of this file are subject to change without notice.  Don't
+# use it directly.  Use Unicode::UCD to access the Unicode character data
+# base.
+
+
+package charnames;
+
+# This module contains machine-generated tables and code for the
+# algorithmically-determinable Unicode character names.  The following
+# routines can be used to translate between name and code point and vice versa
+
+{ # Closure
+
+    # Matches legal code point.  4-6 hex numbers, If there are 6, the first
+    # two must be 10; if there are 5, the first must not be a 0.  Written this
+    # way to decrease backtracking.  The first regex allows the code point to
+    # be at the end of a word, but to work properly, the word shouldn't end
+    # with a valid hex character.  The second one won't match a code point at
+    # the end of a word, and doesn't have the run-on issue
+    my $run_on_code_point_re = qr/(?^aax: (?: 10[0-9A-F]{4} | [1-9A-F][0-9A-F]{4} | [0-9A-F]{4} ) \b)/;
+    my $code_point_re = qr/(?^aa:\b(?^aax: (?: 10[0-9A-F]{4} | [1-9A-F][0-9A-F]{4} | [0-9A-F]{4} ) \b))/;
+
+    # In the following hash, the keys are the bases of names which include
+    # the code point in the name, like CJK UNIFIED IDEOGRAPH-4E01.  The value
+    # of each key is another hash which is used to get the low and high ends
+    # for each range of code points that apply to the name.
+    my %names_ending_in_code_point = (
+'CJK COMPATIBILITY IDEOGRAPH' => 
+{
+'high' => 
+[
+64109,
+64217,
+195101,
+],
+'low' => 
+[
+63744,
+64112,
+194560,
+],
+},
+'CJK UNIFIED IDEOGRAPH' => 
+{
+'high' => 
+[
+19893,
+40908,
+173782,
+177972,
+178205,
+],
+'low' => 
+[
+13312,
+19968,
+131072,
+173824,
+177984,
+],
+},
+
+    );
+
+    # The following hash is a copy of the previous one, except is for loose
+    # matching, so each name has blanks and dashes squeezed out
+    my %loose_names_ending_in_code_point = (
+'CJKCOMPATIBILITYIDEOGRAPH' => 
+{
+'high' => 
+[
+64109,
+64217,
+195101,
+],
+'low' => 
+[
+63744,
+64112,
+194560,
+],
+},
+'CJKUNIFIEDIDEOGRAPH' => 
+{
+'high' => 
+[
+19893,
+40908,
+173782,
+177972,
+178205,
+],
+'low' => 
+[
+13312,
+19968,
+131072,
+173824,
+177984,
+],
+},
+
+    );
+
+    # And the following array gives the inverse mapping from code points to
+    # names.  Lowest code points are first
+    my @code_points_ending_in_code_point = (
+
+{
+'high' => 19893,
+'low' => 13312,
+'name' => 'CJK UNIFIED IDEOGRAPH',
+},
+{
+'high' => 40908,
+'low' => 19968,
+'name' => 'CJK UNIFIED IDEOGRAPH',
+},
+{
+'high' => 64109,
+'low' => 63744,
+'name' => 'CJK COMPATIBILITY IDEOGRAPH',
+},
+{
+'high' => 64217,
+'low' => 64112,
+'name' => 'CJK COMPATIBILITY IDEOGRAPH',
+},
+{
+'high' => 173782,
+'low' => 131072,
+'name' => 'CJK UNIFIED IDEOGRAPH',
+},
+{
+'high' => 177972,
+'low' => 173824,
+'name' => 'CJK UNIFIED IDEOGRAPH',
+},
+{
+'high' => 178205,
+'low' => 177984,
+'name' => 'CJK UNIFIED IDEOGRAPH',
+},
+{
+'high' => 195101,
+'low' => 194560,
+'name' => 'CJK COMPATIBILITY IDEOGRAPH',
+},
+,
+
+    );
+
+    # Convert from code point to Jamo short name for use in composing Hangul
+    # syllable names
+    my %Jamo = (
+4352 => 'G',
+4353 => 'GG',
+4354 => 'N',
+4355 => 'D',
+4356 => 'DD',
+4357 => 'R',
+4358 => 'M',
+4359 => 'B',
+4360 => 'BB',
+4361 => 'S',
+4362 => 'SS',
+4363 => '',
+4364 => 'J',
+4365 => 'JJ',
+4366 => 'C',
+4367 => 'K',
+4368 => 'T',
+4369 => 'P',
+4370 => 'H',
+4449 => 'A',
+4450 => 'AE',
+4451 => 'YA',
+4452 => 'YAE',
+4453 => 'EO',
+4454 => 'E',
+4455 => 'YEO',
+4456 => 'YE',
+4457 => 'O',
+4458 => 'WA',
+4459 => 'WAE',
+4460 => 'OE',
+4461 => 'YO',
+4462 => 'U',
+4463 => 'WEO',
+4464 => 'WE',
+4465 => 'WI',
+4466 => 'YU',
+4467 => 'EU',
+4468 => 'YI',
+4469 => 'I',
+4520 => 'G',
+4521 => 'GG',
+4522 => 'GS',
+4523 => 'N',
+4524 => 'NJ',
+4525 => 'NH',
+4526 => 'D',
+4527 => 'L',
+4528 => 'LG',
+4529 => 'LM',
+4530 => 'LB',
+4531 => 'LS',
+4532 => 'LT',
+4533 => 'LP',
+4534 => 'LH',
+4535 => 'M',
+4536 => 'B',
+4537 => 'BS',
+4538 => 'S',
+4539 => 'SS',
+4540 => 'NG',
+4541 => 'J',
+4542 => 'C',
+4543 => 'K',
+4544 => 'T',
+4545 => 'P',
+4546 => 'H',
+
+    );
+
+    # Leading consonant (can be null)
+    my %Jamo_L = (
+'' => 11,
+'B' => 7,
+'BB' => 8,
+'C' => 14,
+'D' => 3,
+'DD' => 4,
+'G' => 0,
+'GG' => 1,
+'H' => 18,
+'J' => 12,
+'JJ' => 13,
+'K' => 15,
+'M' => 6,
+'N' => 2,
+'P' => 17,
+'R' => 5,
+'S' => 9,
+'SS' => 10,
+'T' => 16,
+
+    );
+
+    # Vowel
+    my %Jamo_V = (
+'A' => 0,
+'AE' => 1,
+'E' => 5,
+'EO' => 4,
+'EU' => 18,
+'I' => 20,
+'O' => 8,
+'OE' => 11,
+'U' => 13,
+'WA' => 9,
+'WAE' => 10,
+'WE' => 15,
+'WEO' => 14,
+'WI' => 16,
+'YA' => 2,
+'YAE' => 3,
+'YE' => 7,
+'YEO' => 6,
+'YI' => 19,
+'YO' => 12,
+'YU' => 17,
+
+    );
+
+    # Optional trailing consonant
+    my %Jamo_T = (
+'B' => 17,
+'BS' => 18,
+'C' => 23,
+'D' => 7,
+'G' => 1,
+'GG' => 2,
+'GS' => 3,
+'H' => 27,
+'J' => 22,
+'K' => 24,
+'L' => 8,
+'LB' => 11,
+'LG' => 9,
+'LH' => 15,
+'LM' => 10,
+'LP' => 14,
+'LS' => 12,
+'LT' => 13,
+'M' => 16,
+'N' => 4,
+'NG' => 21,
+'NH' => 6,
+'NJ' => 5,
+'P' => 26,
+'S' => 19,
+'SS' => 20,
+'T' => 25,
+
+    );
+
+    # Computed re that splits up a Hangul name into LVT or LV syllables
+    my $syllable_re = qr/(|B|BB|C|D|DD|G|GG|H|J|JJ|K|M|N|P|R|S|SS|T)(A|AE|E|EO|EU|I|O|OE|U|WA|WAE|WE|WEO|WI|YA|YAE|YE|YEO|YI|YO|YU)(B|BS|C|D|G|GG|GS|H|J|K|L|LB|LG|LH|LM|LP|LS|LT|M|N|NG|NH|NJ|P|S|SS|T)?/;
+
+    my $HANGUL_SYLLABLE = "HANGUL SYLLABLE ";
+    my $loose_HANGUL_SYLLABLE = "HANGULSYLLABLE";
+
+    # These constants names and values were taken from the Unicode standard,
+    # version 5.1, section 3.12.  They are used in conjunction with Hangul
+    # syllables
+    my $SBase = 0xAC00;
+    my $LBase = 0x1100;
+    my $VBase = 0x1161;
+    my $TBase = 0x11A7;
+    my $SCount = 11172;
+    my $LCount = 19;
+    my $VCount = 21;
+    my $TCount = 28;
+    my $NCount = $VCount * $TCount;
+
+    sub name_to_code_point_special {
+        my ($name, $loose) = @_;
+
+        # Returns undef if not one of the specially handled names; otherwise
+        # returns the code point equivalent to the input name
+        # $loose is non-zero if to use loose matching, 'name' in that case
+        # must be input as upper case with all blanks and dashes squeezed out.
+
+        if ((! $loose && $name =~ s/$HANGUL_SYLLABLE//)
+            || ($loose && $name =~ s/$loose_HANGUL_SYLLABLE//))
+        {
+            return if $name !~ qr/^$syllable_re$/;
+            my $L = $Jamo_L{$1};
+            my $V = $Jamo_V{$2};
+            my $T = (defined $3) ? $Jamo_T{$3} : 0;
+            return ($L * $VCount + $V) * $TCount + $T + $SBase;
+        }
+
+        # Name must end in 'code_point' for this to handle.
+        return if (($loose && $name !~ /^ (.*?) ($run_on_code_point_re) $/x)
+                   || (! $loose && $name !~ /^ (.*) ($code_point_re) $/x));
+
+        my $base = $1;
+        my $code_point = CORE::hex $2;
+        my $names_ref;
+
+        if ($loose) {
+            $names_ref = \%loose_names_ending_in_code_point;
+        }
+        else {
+            return if $base !~ s/-$//;
+            $names_ref = \%names_ending_in_code_point;
+        }
+
+        # Name must be one of the ones which has the code point in it.
+        return if ! $names_ref->{$base};
+
+        # Look through the list of ranges that apply to this name to see if
+        # the code point is in one of them.
+        for (my $i = 0; $i < scalar @{$names_ref->{$base}{'low'}}; $i++) {
+            return if $names_ref->{$base}{'low'}->[$i] > $code_point;
+            next if $names_ref->{$base}{'high'}->[$i] < $code_point;
+
+            # Here, the code point is in the range.
+            return $code_point;
+        }
+
+        # Here, looked like the name had a code point number in it, but
+        # did not match one of the valid ones.
+        return;
+    }
+
+    sub code_point_to_name_special {
+        my $code_point = shift;
+
+        # Returns the name of a code point if algorithmically determinable;
+        # undef if not
+
+        # If in the Hangul range, calculate the name based on Unicode's
+        # algorithm
+        if ($code_point >= $SBase && $code_point <= $SBase + $SCount -1) {
+            use integer;
+            my $SIndex = $code_point - $SBase;
+            my $L = $LBase + $SIndex / $NCount;
+            my $V = $VBase + ($SIndex % $NCount) / $TCount;
+            my $T = $TBase + $SIndex % $TCount;
+            $name = "$HANGUL_SYLLABLE$Jamo{$L}$Jamo{$V}";
+            $name .= $Jamo{$T} if $T != $TBase;
+            return $name;
+        }
+
+        # Look through list of these code points for one in range.
+        foreach my $hash (@code_points_ending_in_code_point) {
+            return if $code_point < $hash->{'low'};
+            if ($code_point <= $hash->{'high'}) {
+                return sprintf("%s-%04X", $hash->{'name'}, $code_point);
+            }
+        }
+        return;            # None found
+    }
+} # End closure
+
+1;
@@ -4,7 +4,7 @@ use strict;
 use DateTime;
 use DateTime::Set;
 
-our $VERSION = "0.20";
+our $VERSION = "1.00";
 
 RT->AddStyleSheets('calendar.css');
 
@@ -103,36 +103,13 @@ sub SearchDefaultCalendar {
     }
 }
 
-package RT::Interface::Web::Menu;
-
-# we should get an add_after method in 4.0.6 (hopefully), but until then
-# shim this in so I don't copy the code.
-unless (RT::Interface::Web::Menu->can('add_after')) {
-        *RT::Interface::Web::Menu::add_after = sub {
-            my $self = shift;
-            my $parent = $self->parent;
-            my $sort_order;
-            for my $contemporary ($parent->children) {
-                if ( $contemporary->key eq $self->key ) {
-                    $sort_order = $contemporary->sort_order + 1;
-                    next;
-                }
-                if ( $sort_order ) {
-                    $contemporary->sort_order( $contemporary->sort_order + 1 );
-                }
-            }
-            $parent->child( @_, sort_order => $sort_order );
-        };
-}
-
-
 1;
 
 __END__
 
 =head1 NAME
 
-RTx::Calendar - Calendar for RT due tasks
+RTx::Calendar - Calendar for RT due dates
 
 =head1 DESCRIPTION
 
@@ -142,104 +119,88 @@ the menu Search->Calendar.
 
 There's a portlet to put on your home page (see Prefs/MyRT.html)
 
-You can also enable ics (ICal) feeds for your default calendar and all
-your private searches in Prefs/Calendar.html. Authentication is magic
-number based so that you can give those feeds to other people.
-
 =head1 INSTALLATION
 
-If you upgrade from 0.02, see next part before.
+=over
 
-You need to install those two modules :
+=item C<perl Makefile.PL>
 
-  * Data::ICal
-  * DateTime::Set
+=item C<make>
 
-Install it like a standard perl module
+=item C<make install>
 
- perl Makefile.PL
- make
- make install
+May need root permissions
 
-If your RT is not in the default path (/opt/rt3) you must set RTHOME
-before doing the Makefile.PL
+=item Edit your F</opt/rt4/etc/RT_SiteConfig.pm>
 
-=head1 CONFIGURATION
+If you are using RT 4.2 or greater, add this line:
 
-=head2 Base configuration
+    Plugin('RTx::Calendar');
 
-In RT 3.8 and later, to enable calendar plugin, you must add something
-like that in your etc/RT_SiteConfig.pm :
+For RT 4.0, add this line:
 
-  Set(@Plugins,(qw(RTx::Calendar)));
+    Set(@Plugins, qw(RTx::Calendar));
 
-To use MyCalendar portlet you must add MyCalendar to
-$HomepageComponents in etc/RT_SiteConfig.pm like that :
+or add C<RTx::Calendar> to your existing C<@Plugins> line.
+
+=item Clear your mason cache
+
+    rm -rf /opt/rt4/var/mason_data/obj
+
+=item Restart your webserver
+
+=back
+
+=head1 CONFIGURATION
+
+=head2 Base configuration
+
+To use the C<MyCalendar> portlet, you must add C<MyCalendar> to
+C<$HomepageComponents> in F<etc/RT_SiteConfig.pm>:
 
   Set($HomepageComponents, [qw(QuickCreate Quicksearch MyCalendar
      MyAdminQueues MySupportQueues MyReminders RefreshHomepage)]);
 
-To enable private searches ICal feeds, you need to give
-CreateSavedSearch and LoadSavedSearch rights to your users.
-
 =head2 Display configuration
 
 You can show the owner in each day box by adding this line to your
-etc/RT_SiteConfig.pm :
+F<etc/RT_SiteConfig.pm>:
 
     Set($CalendarDisplayOwner, 1);
 
 You can change which fields show up in the popup display when you
-mouse over a date in etc/RT_SiteConfig.pm :
+mouse over a date in F<etc/RT_SiteConfig.pm>:
 
-    @CalendarPopupFields = ('Status', 'OwnerObj->Name', 'DueObj->ISO');
-
-=head2 ICAL feed configuration
-
-By default, tickets are todo and reminders event. You can change this
-by setting $RT::ICalTicketType and $RT::ICalReminderType in etc/RT_SiteConfig.pm :
-
-  Set($ICalTicketType,   "Data::ICal::Entry::Event");
-  Set($ICalReminderType ,"Data::ICal::Entry::Todo");
+    Set(@CalendarPopupFields, ('Status', 'OwnerObj->Name', 'DueObj->ISO'));
 
 =head1 USAGE
 
 A small help section is available in /Prefs/Calendar.html
 
-=head1 UPGRADE FROM 0.02
+=head1 AUTHOR
 
-As I've change directory structure, if you upgrade from 0.02 you need
-to delete old files manually. Go in RTHOME/share/html (by default
-/opt/rt3/share/html) and delete those files :
+Best Practical Solutions, LLC E<lt>modules@bestpractical.comE<gt>
 
-  rm -rf Callbacks/RTx-Calendar
-  rm Tools/Calendar.html
-
-RTx-Calendar may work without this but it's not very clean.
+Originally written by Nicolas Chuche E<lt>nchuche@barna.beE<gt>
 
 =head1 BUGS
 
-All bugs should be reported via
-L<http://rt.cpan.org/Public/Dist/Display.html?Name=RTx-Calendar>
-or L<bug-RTx-Calendar@rt.cpan.org>.
-
-=head1 AUTHORS
+All bugs should be reported via email to
 
-Best Practical Solutions
+    L<bug-RTx-Calendar@rt.cpan.org|mailto:bug-RTx-Calendar@rt.cpan.org>
 
-Nicolas Chuche E<lt>nchuche@barna.beE<gt>
+or via the web at
 
-Idea borrowed from redmine's calendar (Thanks Jean-Philippe).
+    L<rt.cpan.org|http://rt.cpan.org/Public/Dist/Display.html?Name=RTx-Calendar>.
 
-=head1 COPYRIGHT
+=head1 LICENSE AND COPYRIGHT
 
-Copyright 2007-2009 by Nicolas Chuche E<lt>nchuche@barna.beE<gt>
+This software is Copyright (c) 2010-2014 by Best Practical Solutions
 
-Copyright 2010-2014 by Best Practical Solutions.
+Copyright 2007-2009 by Nicolas Chuche
 
-This program is free software; you can redistribute it and/or
-modify it under the same terms as Perl itself.
+This is free software, licensed under:
 
-See L<http://www.perl.com/perl/misc/Artistic.html>
+  The GNU General Public License, Version 2, June 1991
 
 =cut