Horde_Date_Parser -> Date_Parser
authorChuck Hagenbuch <chuck@horde.org>
Sun, 21 Dec 2008 00:36:05 +0000 (19:36 -0500)
committerChuck Hagenbuch <chuck@horde.org>
Sun, 21 Dec 2008 00:36:05 +0000 (19:36 -0500)
Horde_Form -> Form
VC -> Vcs (Horde_Vcs, full rewrite not yet done)

228 files changed:
framework/Date_Parser/chronic/History.txt [new file with mode: 0644]
framework/Date_Parser/chronic/README.txt [new file with mode: 0644]
framework/Date_Parser/chronic/lib/chronic.rb [new file with mode: 0644]
framework/Date_Parser/chronic/lib/chronic/chronic.rb [new file with mode: 0644]
framework/Date_Parser/chronic/lib/chronic/grabber.rb [new file with mode: 0644]
framework/Date_Parser/chronic/lib/chronic/handlers.rb [new file with mode: 0644]
framework/Date_Parser/chronic/lib/chronic/ordinal.rb [new file with mode: 0644]
framework/Date_Parser/chronic/lib/chronic/pointer.rb [new file with mode: 0644]
framework/Date_Parser/chronic/lib/chronic/repeater.rb [new file with mode: 0644]
framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_day.rb [new file with mode: 0644]
framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_day_name.rb [new file with mode: 0644]
framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_day_portion.rb [new file with mode: 0644]
framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_fortnight.rb [new file with mode: 0644]
framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_hour.rb [new file with mode: 0644]
framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_minute.rb [new file with mode: 0644]
framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_month.rb [new file with mode: 0644]
framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_month_name.rb [new file with mode: 0644]
framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_season.rb [new file with mode: 0644]
framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_season_name.rb [new file with mode: 0644]
framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_second.rb [new file with mode: 0644]
framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_time.rb [new file with mode: 0644]
framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_week.rb [new file with mode: 0644]
framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_weekend.rb [new file with mode: 0644]
framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_year.rb [new file with mode: 0644]
framework/Date_Parser/chronic/lib/chronic/scalar.rb [new file with mode: 0644]
framework/Date_Parser/chronic/lib/chronic/separator.rb [new file with mode: 0644]
framework/Date_Parser/chronic/lib/chronic/time_zone.rb [new file with mode: 0644]
framework/Date_Parser/chronic/test/suite.rb [new file with mode: 0644]
framework/Date_Parser/chronic/test/test_Chronic.rb [new file with mode: 0644]
framework/Date_Parser/chronic/test/test_Handler.rb [new file with mode: 0644]
framework/Date_Parser/chronic/test/test_RepeaterDayName.rb [new file with mode: 0644]
framework/Date_Parser/chronic/test/test_RepeaterFortnight.rb [new file with mode: 0644]
framework/Date_Parser/chronic/test/test_RepeaterHour.rb [new file with mode: 0644]
framework/Date_Parser/chronic/test/test_RepeaterMonth.rb [new file with mode: 0644]
framework/Date_Parser/chronic/test/test_RepeaterMonthName.rb [new file with mode: 0644]
framework/Date_Parser/chronic/test/test_RepeaterTime.rb [new file with mode: 0644]
framework/Date_Parser/chronic/test/test_RepeaterWeek.rb [new file with mode: 0644]
framework/Date_Parser/chronic/test/test_RepeaterWeekend.rb [new file with mode: 0644]
framework/Date_Parser/chronic/test/test_RepeaterYear.rb [new file with mode: 0644]
framework/Date_Parser/chronic/test/test_Span.rb [new file with mode: 0644]
framework/Date_Parser/chronic/test/test_Time.rb [new file with mode: 0644]
framework/Date_Parser/chronic/test/test_Token.rb [new file with mode: 0644]
framework/Date_Parser/chronic/test/test_parsing.rb [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Parser.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Parser/Exception.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Grabber.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Handlers.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Ordinal.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Pointer.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeater.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Day.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/DayName.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/DayPortion.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Fortnight.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Hour.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Minute.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Month.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/MonthName.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Season.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/SeasonName.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Second.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Time.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Week.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Weekend.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Year.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Scalar.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Separator.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Timezone.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Parser/Tag.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Parser/Token.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Date/Span.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Support/Numerizer.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Support/Numerizer/Locale/Base.php [new file with mode: 0644]
framework/Date_Parser/lib/Horde/Support/Numerizer/Locale/De.php [new file with mode: 0644]
framework/Date_Parser/package.xml [new file with mode: 0644]
framework/Date_Parser/test/Horde/Date/Parser/AllTests.php [new file with mode: 0644]
framework/Date_Parser/test/Horde/Support/AllTests.php [new file with mode: 0644]
framework/Date_Parser/test/Horde/Support/Numerizer/Locale/BaseTest.php [new file with mode: 0644]
framework/Date_Parser/test/Horde/Support/Numerizer/Locale/DeTest.php [new file with mode: 0644]
framework/Form/lib/Horde/Form.php [new file with mode: 0644]
framework/Form/lib/Horde/Form/Renderer.php [new file with mode: 0644]
framework/Form/lib/Horde/Form/Renderer/Xhtml.php [new file with mode: 0644]
framework/Form/lib/Horde/Form/Type.php [new file with mode: 0644]
framework/Form/lib/Horde/Form/Type/Boolean.php [new file with mode: 0644]
framework/Form/lib/Horde/Form/Type/Color.php [new file with mode: 0644]
framework/Form/lib/Horde/Form/Type/CreditCard.php [new file with mode: 0644]
framework/Form/lib/Horde/Form/Type/Date.php [new file with mode: 0644]
framework/Form/lib/Horde/Form/Type/DateTime.php [new file with mode: 0644]
framework/Form/lib/Horde/Form/Type/Email.php [new file with mode: 0644]
framework/Form/lib/Horde/Form/Type/Enum.php [new file with mode: 0644]
framework/Form/lib/Horde/Form/Type/Int.php [new file with mode: 0644]
framework/Form/lib/Horde/Form/Type/Invalid.php [new file with mode: 0644]
framework/Form/lib/Horde/Form/Type/Number.php [new file with mode: 0644]
framework/Form/lib/Horde/Form/Type/Octal.php [new file with mode: 0644]
framework/Form/lib/Horde/Form/Type/Password.php [new file with mode: 0644]
framework/Form/lib/Horde/Form/Type/Phone.php [new file with mode: 0644]
framework/Form/lib/Horde/Form/Type/Phone/Mobile.php [new file with mode: 0644]
framework/Form/lib/Horde/Form/Type/Set.php [new file with mode: 0644]
framework/Form/lib/Horde/Form/Type/String.php [new file with mode: 0644]
framework/Form/lib/Horde/Form/Type/Time.php [new file with mode: 0644]
framework/Form/lib/Horde/Form/VarRenderer.php [new file with mode: 0644]
framework/Form/lib/Horde/Form/VarRenderer/Xhtml.php [new file with mode: 0644]
framework/Form/www/js/maxlength.js [new file with mode: 0644]
framework/Form/www/test.php [new file with mode: 0644]
framework/Form/www/themes/form.css [new file with mode: 0644]
framework/Form/www/themes/graphics/required.png [new file with mode: 0644]
framework/Horde_Date_Parser/chronic/History.txt [deleted file]
framework/Horde_Date_Parser/chronic/README.txt [deleted file]
framework/Horde_Date_Parser/chronic/lib/chronic.rb [deleted file]
framework/Horde_Date_Parser/chronic/lib/chronic/chronic.rb [deleted file]
framework/Horde_Date_Parser/chronic/lib/chronic/grabber.rb [deleted file]
framework/Horde_Date_Parser/chronic/lib/chronic/handlers.rb [deleted file]
framework/Horde_Date_Parser/chronic/lib/chronic/ordinal.rb [deleted file]
framework/Horde_Date_Parser/chronic/lib/chronic/pointer.rb [deleted file]
framework/Horde_Date_Parser/chronic/lib/chronic/repeater.rb [deleted file]
framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_day.rb [deleted file]
framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_day_name.rb [deleted file]
framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_day_portion.rb [deleted file]
framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_fortnight.rb [deleted file]
framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_hour.rb [deleted file]
framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_minute.rb [deleted file]
framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_month.rb [deleted file]
framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_month_name.rb [deleted file]
framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_season.rb [deleted file]
framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_season_name.rb [deleted file]
framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_second.rb [deleted file]
framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_time.rb [deleted file]
framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_week.rb [deleted file]
framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_weekend.rb [deleted file]
framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_year.rb [deleted file]
framework/Horde_Date_Parser/chronic/lib/chronic/scalar.rb [deleted file]
framework/Horde_Date_Parser/chronic/lib/chronic/separator.rb [deleted file]
framework/Horde_Date_Parser/chronic/lib/chronic/time_zone.rb [deleted file]
framework/Horde_Date_Parser/chronic/test/suite.rb [deleted file]
framework/Horde_Date_Parser/chronic/test/test_Chronic.rb [deleted file]
framework/Horde_Date_Parser/chronic/test/test_Handler.rb [deleted file]
framework/Horde_Date_Parser/chronic/test/test_RepeaterDayName.rb [deleted file]
framework/Horde_Date_Parser/chronic/test/test_RepeaterFortnight.rb [deleted file]
framework/Horde_Date_Parser/chronic/test/test_RepeaterHour.rb [deleted file]
framework/Horde_Date_Parser/chronic/test/test_RepeaterMonth.rb [deleted file]
framework/Horde_Date_Parser/chronic/test/test_RepeaterMonthName.rb [deleted file]
framework/Horde_Date_Parser/chronic/test/test_RepeaterTime.rb [deleted file]
framework/Horde_Date_Parser/chronic/test/test_RepeaterWeek.rb [deleted file]
framework/Horde_Date_Parser/chronic/test/test_RepeaterWeekend.rb [deleted file]
framework/Horde_Date_Parser/chronic/test/test_RepeaterYear.rb [deleted file]
framework/Horde_Date_Parser/chronic/test/test_Span.rb [deleted file]
framework/Horde_Date_Parser/chronic/test/test_Time.rb [deleted file]
framework/Horde_Date_Parser/chronic/test/test_Token.rb [deleted file]
framework/Horde_Date_Parser/chronic/test/test_parsing.rb [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Exception.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Grabber.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Handlers.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Ordinal.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Pointer.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeater.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Day.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/DayName.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/DayPortion.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Fortnight.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Hour.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Minute.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Month.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/MonthName.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Season.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/SeasonName.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Second.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Time.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Week.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Weekend.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Year.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Scalar.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Separator.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Timezone.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Tag.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Parser/Token.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Date/Span.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Support/Numerizer.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Support/Numerizer/Locale/Base.php [deleted file]
framework/Horde_Date_Parser/lib/Horde/Support/Numerizer/Locale/De.php [deleted file]
framework/Horde_Date_Parser/package.xml [deleted file]
framework/Horde_Date_Parser/test/Horde/Date/Parser/AllTests.php [deleted file]
framework/Horde_Date_Parser/test/Horde/Support/AllTests.php [deleted file]
framework/Horde_Date_Parser/test/Horde/Support/Numerizer/Locale/BaseTest.php [deleted file]
framework/Horde_Date_Parser/test/Horde/Support/Numerizer/Locale/DeTest.php [deleted file]
framework/Horde_Form/lib/Horde/Form.php [deleted file]
framework/Horde_Form/lib/Horde/Form/Renderer.php [deleted file]
framework/Horde_Form/lib/Horde/Form/Renderer/Xhtml.php [deleted file]
framework/Horde_Form/lib/Horde/Form/Type.php [deleted file]
framework/Horde_Form/lib/Horde/Form/Type/Boolean.php [deleted file]
framework/Horde_Form/lib/Horde/Form/Type/Color.php [deleted file]
framework/Horde_Form/lib/Horde/Form/Type/CreditCard.php [deleted file]
framework/Horde_Form/lib/Horde/Form/Type/Date.php [deleted file]
framework/Horde_Form/lib/Horde/Form/Type/DateTime.php [deleted file]
framework/Horde_Form/lib/Horde/Form/Type/Email.php [deleted file]
framework/Horde_Form/lib/Horde/Form/Type/Enum.php [deleted file]
framework/Horde_Form/lib/Horde/Form/Type/Int.php [deleted file]
framework/Horde_Form/lib/Horde/Form/Type/Invalid.php [deleted file]
framework/Horde_Form/lib/Horde/Form/Type/Number.php [deleted file]
framework/Horde_Form/lib/Horde/Form/Type/Octal.php [deleted file]
framework/Horde_Form/lib/Horde/Form/Type/Password.php [deleted file]
framework/Horde_Form/lib/Horde/Form/Type/Phone.php [deleted file]
framework/Horde_Form/lib/Horde/Form/Type/Phone/Mobile.php [deleted file]
framework/Horde_Form/lib/Horde/Form/Type/Set.php [deleted file]
framework/Horde_Form/lib/Horde/Form/Type/String.php [deleted file]
framework/Horde_Form/lib/Horde/Form/Type/Time.php [deleted file]
framework/Horde_Form/lib/Horde/Form/VarRenderer.php [deleted file]
framework/Horde_Form/lib/Horde/Form/VarRenderer/Xhtml.php [deleted file]
framework/Horde_Form/www/js/maxlength.js [deleted file]
framework/Horde_Form/www/test.php [deleted file]
framework/Horde_Form/www/themes/form.css [deleted file]
framework/Horde_Form/www/themes/graphics/required.png [deleted file]
framework/VC/lib/Horde/VC.php [deleted file]
framework/VC/lib/Horde/VC/Exception.php [deleted file]
framework/VC/lib/Horde/VC/cvs.php [deleted file]
framework/VC/lib/Horde/VC/git.php [deleted file]
framework/VC/lib/Horde/VC/rcs.php [deleted file]
framework/VC/lib/Horde/VC/svn.php [deleted file]
framework/VC/package.xml [deleted file]
framework/Vcs/lib/Horde/Vcs.php [new file with mode: 0644]
framework/Vcs/lib/Horde/Vcs/Cvs.php [new file with mode: 0644]
framework/Vcs/lib/Horde/Vcs/Exception.php [new file with mode: 0644]
framework/Vcs/lib/Horde/Vcs/Git.php [new file with mode: 0644]
framework/Vcs/lib/Horde/Vcs/Rcs.php [new file with mode: 0644]
framework/Vcs/lib/Horde/Vcs/Svn.php [new file with mode: 0644]
framework/Vcs/package.xml [new file with mode: 0644]

diff --git a/framework/Date_Parser/chronic/History.txt b/framework/Date_Parser/chronic/History.txt
new file mode 100644 (file)
index 0000000..0f23646
--- /dev/null
@@ -0,0 +1,53 @@
+= 0.2.3
+
+* fixed 12am/12pm (by Nicholas Schlueter)
+
+= 0.2.2
+
+* added missing files (damn you manifest)
+
+= 0.2.1
+
+* fixed time overflow issue
+* implemented "next" for minute repeater
+* generalized time dealiasing to dealias regardless of day portion and time position
+* added additional token match for cases like "friday evening at 7" and "tomorrow evening at 7"
+* added support for Time#to_s output format: "Mon Apr 02 17:00:00 PDT 2007"
+
+= 0.2.0 2007-03-20
+
+* implemented numerizer, allowing the use of number words (e.g. five weeks ago) (by shalev)
+
+= 0.1.6 2006-01-15
+
+* added 'weekend' support (by eventualbuddha)
+
+= 0.1.5 2006-12-20
+
+* fixed 'aug 20' returning next year if current month is august
+* modified behavior of 'from now'
+* added support for seconds on times, and thus db timestamp format: "2006-12-20 18:04:23"
+* made Hoe compliant
+
+= 0.1.4
+
+* removed verbose error checking code. oops. :-/
+
+= 0.1.3
+
+* improved regexes for word variations (by Josh Goebel)
+* fixed a bug that caused "today at 3am" to return nil if current time is after 3am
+
+= 0.1.2
+
+* removed Date dependency (now works on windows properly without fiddling)
+
+= 0.1.1
+
+* run to_s on incoming object
+* fixed loop loading of repeaters files (out of order on some machines)
+* fixed find_within to use this instead of next (was breaking "today at 6pm")
+
+= 0.1.0
+
+* initial release
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/README.txt b/framework/Date_Parser/chronic/README.txt
new file mode 100644 (file)
index 0000000..2e4f179
--- /dev/null
@@ -0,0 +1,149 @@
+Chronic
+       http://chronic.rubyforge.org/
+       by Tom Preston-Werner
+
+== DESCRIPTION:
+
+Chronic is a natural language date/time parser written in pure Ruby. See below for the wide variety of formats Chronic will parse.
+
+== INSTALLATION:
+
+Chronic can be installed via RubyGems:
+
+  $ sudo gem install chronic
+
+== USAGE:
+
+You can parse strings containing a natural language date using the Chronic.parse method.
+
+  require 'rubygems'
+  require 'chronic'
+
+  Time.now   #=> Sun Aug 27 23:18:25 PDT 2006
+
+  #---
+
+  Chronic.parse('tomorrow')       
+    #=> Mon Aug 28 12:00:00 PDT 2006
+
+  Chronic.parse('monday', :context => :past)
+    #=> Mon Aug 21 12:00:00 PDT 2006
+
+  Chronic.parse('this tuesday 5:00')
+    #=> Tue Aug 29 17:00:00 PDT 2006
+
+  Chronic.parse('this tuesday 5:00', :ambiguous_time_range => :none)
+    #=> Tue Aug 29 05:00:00 PDT 2006
+
+  Chronic.parse('may 27th', :now => Time.local(2000, 1, 1))
+    #=> Sat May 27 12:00:00 PDT 2000
+
+  Chronic.parse('may 27th', :guess => false)
+    #=> Sun May 27 00:00:00 PDT 2007..Mon May 28 00:00:00 PDT 2007
+
+See Chronic.parse for detailed usage instructions.
+
+== EXAMPLES:
+
+Chronic can parse a huge variety of date and time formats. Following is a small sample of strings that will be properly parsed. Parsing is case insensitive and will handle common abbreviations and misspellings.
+
+Simple
+
+  thursday
+  november
+  summer
+  friday 13:00
+  mon 2:35
+  4pm
+  6 in the morning
+  friday 1pm
+  sat 7 in the evening
+  yesterday
+  today
+  tomorrow
+  this tuesday
+  next month
+  last winter
+  this morning
+  last night
+  this second
+  yesterday at 4:00
+  last friday at 20:00
+  last week tuesday
+  tomorrow at 6:45pm
+  afternoon yesterday
+  thursday last week
+
+Complex
+
+  3 years ago
+  5 months before now
+  7 hours ago
+  7 days from now
+  1 week hence
+  in 3 hours
+  1 year ago tomorrow
+  3 months ago saturday at 5:00 pm
+  7 hours before tomorrow at noon
+  3rd wednesday in november
+  3rd month next year
+  3rd thursday this september
+  4th day last week
+
+Specific Dates
+
+  January 5
+  dec 25
+  may 27th
+  October 2006
+  oct 06
+  jan 3 2010
+  february 14, 2004
+  3 jan 2000
+  17 april 85
+  5/27/1979
+  27/5/1979
+  05/06
+  1979-05-27
+  Friday
+  5
+  4:00
+  17:00
+  0800
+
+Specific Times (many of the above with an added time)
+
+  January 5 at 7pm
+  1979-05-27 05:00:00
+  etc
+
+== LIMITATIONS:
+  
+Chronic uses Ruby's built in Time class for all time storage and computation. Because of this, only times that the Time class can handle will be properly parsed. Parsing for times outside of this range will simply return nil. Support for a wider range of times is planned for a future release.
+
+Time zones other than the local one are not currently supported. Support for other time zones is planned for a future release.
+
+== LICENSE:
+
+(The MIT License)
+
+Copyright (c) 2006 Ryan Davis, Zen Spider Software
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/framework/Date_Parser/chronic/lib/chronic.rb b/framework/Date_Parser/chronic/lib/chronic.rb
new file mode 100644 (file)
index 0000000..6d0e7ba
--- /dev/null
@@ -0,0 +1,125 @@
+#=============================================================================
+#
+#  Name:       Chronic
+#  Author:     Tom Preston-Werner
+#  Purpose:    Parse natural language dates and times into Time or
+#              Chronic::Span objects
+#
+#=============================================================================
+
+$:.unshift File.dirname(__FILE__)     # For use/testing when no gem is installed
+
+require 'chronic/chronic'
+require 'chronic/handlers'
+
+require 'chronic/repeater'
+require 'chronic/repeaters/repeater_year'
+require 'chronic/repeaters/repeater_season'
+require 'chronic/repeaters/repeater_season_name'
+require 'chronic/repeaters/repeater_month'
+require 'chronic/repeaters/repeater_month_name'
+require 'chronic/repeaters/repeater_fortnight'
+require 'chronic/repeaters/repeater_week'
+require 'chronic/repeaters/repeater_weekend'
+require 'chronic/repeaters/repeater_day'
+require 'chronic/repeaters/repeater_day_name'
+require 'chronic/repeaters/repeater_day_portion'
+require 'chronic/repeaters/repeater_hour'
+require 'chronic/repeaters/repeater_minute'
+require 'chronic/repeaters/repeater_second'
+require 'chronic/repeaters/repeater_time'
+
+require 'chronic/grabber'
+require 'chronic/pointer'
+require 'chronic/scalar'
+require 'chronic/ordinal'
+require 'chronic/separator'
+require 'chronic/time_zone'
+
+require 'numerizer/numerizer'
+
+module Chronic
+  VERSION = "0.2.3"
+  
+  def self.debug; false; end
+end
+
+alias p_orig p
+
+def p(val)
+  p_orig val
+  puts
+end
+
+# class Time
+#   def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
+#     # extra_seconds = second > 60 ? second - 60 : 0
+#     # extra_minutes = minute > 59 ? minute - 59 : 0
+#     # extra_hours = hour > 23 ? hour - 23 : 0
+#     # extra_days = day > 
+#     
+#     if month > 12
+#       if month % 12 == 0
+#         year += (month - 12) / 12
+#         month = 12
+#       else
+#         year += month / 12
+#         month = month % 12
+#       end
+#     end
+#     
+#     base = Time.local(year, month)
+#     puts base
+#     offset = ((day - 1) * 24 * 60 * 60) + (hour * 60 * 60) + (minute * 60) + second
+#     puts offset.to_s
+#     date = base + offset
+#     puts date
+#     date
+#   end
+# end
+
+class Time
+  def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
+    if second >= 60
+      minute += second / 60
+      second = second % 60
+    end
+    
+    if minute >= 60
+      hour += minute / 60
+      minute = minute % 60
+    end
+    
+    if hour >= 24
+      day += hour / 24
+      hour = hour % 24
+    end
+    
+    # determine if there is a day overflow. this is complicated by our crappy calendar
+    # system (non-constant number of days per month)
+    day <= 56 || raise("day must be no more than 56 (makes month resolution easier)")
+    if day > 28
+      # no month ever has fewer than 28 days, so only do this if necessary
+      leap_year = (year % 4 == 0) && !(year % 100 == 0)
+      leap_year_month_days = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
+      common_year_month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
+      days_this_month = leap_year ? leap_year_month_days[month - 1] : common_year_month_days[month - 1]
+      if day > days_this_month
+        month += day / days_this_month
+        day = day % days_this_month
+      end
+    end
+    
+    if month > 12
+      if month % 12 == 0
+        year += (month - 12) / 12
+        month = 12
+      else
+        year += month / 12
+        month = month % 12
+      end
+    end
+    
+    Time.local(year, month, day, hour, minute, second)
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/lib/chronic/chronic.rb b/framework/Date_Parser/chronic/lib/chronic/chronic.rb
new file mode 100644 (file)
index 0000000..5e7779f
--- /dev/null
@@ -0,0 +1,239 @@
+module Chronic
+  class << self
+    
+    # Parses a string containing a natural language date or time. If the parser
+    # can find a date or time, either a Time or Chronic::Span will be returned 
+    # (depending on the value of <tt>:guess</tt>). If no date or time can be found,
+    # +nil+ will be returned.
+    #
+    # Options are:
+    #
+    # [<tt>:context</tt>]
+    #     <tt>:past</tt> or <tt>:future</tt> (defaults to <tt>:future</tt>)
+    #
+    #     If your string represents a birthday, you can set <tt>:context</tt> to <tt>:past</tt> 
+    #     and if an ambiguous string is given, it will assume it is in the 
+    #     past. Specify <tt>:future</tt> or omit to set a future context.
+    #
+    # [<tt>:now</tt>]
+    #     Time (defaults to Time.now)
+    #
+    #     By setting <tt>:now</tt> to a Time, all computations will be based off
+    #     of that time instead of Time.now
+    #
+    # [<tt>:guess</tt>]
+    #     +true+ or +false+ (defaults to +true+)
+    #
+    #     By default, the parser will guess a single point in time for the
+    #     given date or time. If you'd rather have the entire time span returned,
+    #     set <tt>:guess</tt> to +false+ and a Chronic::Span will be returned.
+    #     
+    # [<tt>:ambiguous_time_range</tt>]
+    #     Integer or <tt>:none</tt> (defaults to <tt>6</tt> (6am-6pm))
+    #
+    #     If an Integer is given, ambiguous times (like 5:00) will be 
+    #     assumed to be within the range of that time in the AM to that time
+    #     in the PM. For example, if you set it to <tt>7</tt>, then the parser will
+    #     look for the time between 7am and 7pm. In the case of 5:00, it would
+    #     assume that means 5:00pm. If <tt>:none</tt> is given, no assumption
+    #     will be made, and the first matching instance of that time will 
+    #     be used.
+    def parse(text, specified_options = {})
+      # get options and set defaults if necessary
+      default_options = {:context => :future,
+                         :now => Time.now,
+                         :guess => true,
+                         :ambiguous_time_range => 6}
+      options = default_options.merge specified_options
+            
+      # ensure the specified options are valid
+      specified_options.keys.each do |key|
+        default_options.keys.include?(key) || raise(InvalidArgumentException, "#{key} is not a valid option key.")
+      end
+      [:past, :future, :none].include?(options[:context]) || raise(InvalidArgumentException, "Invalid value ':#{options[:context]}' for :context specified. Valid values are :past and :future.")
+      
+      # store now for later =)
+      @now = options[:now]
+      
+      # put the text into a normal format to ease scanning
+      text = self.pre_normalize(text)
+          
+      # get base tokens for each word
+      @tokens = self.base_tokenize(text)
+    
+      # scan the tokens with each token scanner
+      [Repeater].each do |tokenizer|
+        @tokens = tokenizer.scan(@tokens, options)
+      end
+      
+      [Grabber, Pointer, Scalar, Ordinal, Separator, TimeZone].each do |tokenizer|
+        @tokens = tokenizer.scan(@tokens)
+      end
+      
+      # strip any non-tagged tokens
+      @tokens = @tokens.select { |token| token.tagged? }
+      
+      if Chronic.debug
+        puts "+---------------------------------------------------"
+        puts "| " + @tokens.to_s
+        puts "+---------------------------------------------------"
+      end
+      
+      # do the heavy lifting
+      begin
+        span = self.tokens_to_span(@tokens, options)
+      rescue
+        raise
+        return nil
+      end
+      
+      # guess a time within a span if required
+      if options[:guess]
+        return self.guess(span)
+      else
+        return span
+      end
+    end
+    
+    # Clean up the specified input text by stripping unwanted characters,
+    # converting idioms to their canonical form, converting number words
+    # to numbers (three => 3), and converting ordinal words to numeric
+    # ordinals (third => 3rd)
+    def pre_normalize(text) #:nodoc:
+      normalized_text = text.to_s.downcase
+      normalized_text = numericize_numbers(normalized_text)
+      normalized_text.gsub!(/['"\.]/, '')
+      normalized_text.gsub!(/([\/\-\,\@])/) { ' ' + $1 + ' ' }
+      normalized_text.gsub!(/\btoday\b/, 'this day')
+      normalized_text.gsub!(/\btomm?orr?ow\b/, 'next day')
+      normalized_text.gsub!(/\byesterday\b/, 'last day')
+      normalized_text.gsub!(/\bnoon\b/, '12:00')
+      normalized_text.gsub!(/\bmidnight\b/, '24:00')
+      normalized_text.gsub!(/\bbefore now\b/, 'past')
+      normalized_text.gsub!(/\bnow\b/, 'this second')
+      normalized_text.gsub!(/\b(ago|before)\b/, 'past')
+      normalized_text.gsub!(/\bthis past\b/, 'last')
+      normalized_text.gsub!(/\bthis last\b/, 'last')
+      normalized_text.gsub!(/\b(?:in|during) the (morning)\b/, '\1')
+      normalized_text.gsub!(/\b(?:in the|during the|at) (afternoon|evening|night)\b/, '\1')
+      normalized_text.gsub!(/\btonight\b/, 'this night')
+      normalized_text.gsub!(/(?=\w)([ap]m|oclock)\b/, ' \1')
+      normalized_text.gsub!(/\b(hence|after|from)\b/, 'future')
+      normalized_text = numericize_ordinals(normalized_text)
+    end
+  
+    # Convert number words to numbers (three => 3)
+    def numericize_numbers(text) #:nodoc:
+      Numerizer.numerize(text)
+    end
+  
+    # Convert ordinal words to numeric ordinals (third => 3rd)
+    def numericize_ordinals(text) #:nodoc:
+      text
+    end
+  
+    # Split the text on spaces and convert each word into
+    # a Token
+    def base_tokenize(text) #:nodoc:
+      text.split(' ').map { |word| Token.new(word) }
+    end
+    
+    # Guess a specific time within the given span
+    def guess(span) #:nodoc:
+      return nil if span.nil?
+      if span.width > 1
+        span.begin + (span.width / 2)
+      else
+        span.begin
+      end
+    end
+  end
+  
+  class Token #:nodoc:
+    attr_accessor :word, :tags
+    
+    def initialize(word)
+      @word = word
+      @tags = []
+    end
+    
+    # Tag this token with the specified tag
+    def tag(new_tag)
+      @tags << new_tag
+    end
+    
+    # Remove all tags of the given class
+    def untag(tag_class)
+      @tags = @tags.select { |m| !m.kind_of? tag_class }
+    end
+    
+    # Return true if this token has any tags
+    def tagged?
+      @tags.size > 0
+    end
+    
+    # Return the Tag that matches the given class
+    def get_tag(tag_class)
+      matches = @tags.select { |m| m.kind_of? tag_class }
+      #matches.size < 2 || raise("Multiple identical tags found")
+      return matches.first
+    end
+    
+    # Print this Token in a pretty way
+    def to_s
+      @word << '(' << @tags.join(', ') << ') '
+    end
+  end
+  
+  # A Span represents a range of time. Since this class extends
+  # Range, you can use #begin and #end to get the beginning and
+  # ending times of the span (they will be of class Time)
+  class Span < Range   
+    # Returns the width of this span in seconds   
+    def width
+      (self.end - self.begin).to_i
+    end
+    
+    # Add a number of seconds to this span, returning the 
+    # resulting Span
+    def +(seconds)
+      Span.new(self.begin + seconds, self.end + seconds)
+    end
+    
+    # Subtract a number of seconds to this span, returning the 
+    # resulting Span
+    def -(seconds)
+      self + -seconds
+    end
+    
+    # Prints this span in a nice fashion
+    def to_s
+      '(' << self.begin.to_s << '..' << self.end.to_s << ')'
+    end
+  end
+
+  # Tokens are tagged with subclassed instances of this class when
+  # they match specific criteria
+  class Tag #:nodoc:
+    attr_accessor :type
+    
+    def initialize(type)
+      @type = type
+    end
+    
+    def start=(s)
+      @now = s
+    end
+  end
+  
+  # Internal exception
+  class ChronicPain < Exception #:nodoc:
+    
+  end
+  
+  # This exception is raised if an invalid argument is provided to
+  # any of Chronic's methods
+  class InvalidArgumentException < Exception
+    
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/lib/chronic/grabber.rb b/framework/Date_Parser/chronic/lib/chronic/grabber.rb
new file mode 100644 (file)
index 0000000..4162a26
--- /dev/null
@@ -0,0 +1,26 @@
+#module Chronic
+
+  class Chronic::Grabber < Chronic::Tag #:nodoc:
+    def self.scan(tokens)
+      tokens.each_index do |i|
+        if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t); next end
+      end
+      tokens
+    end
+    
+    def self.scan_for_all(token)
+      scanner = {/last/ => :last,
+                 /this/ => :this,
+                 /next/ => :next}
+      scanner.keys.each do |scanner_item|
+        return self.new(scanner[scanner_item]) if scanner_item =~ token.word
+      end
+      return nil
+    end
+    
+    def to_s
+      'grabber-' << @type.to_s
+    end
+  end
+
+#end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/lib/chronic/handlers.rb b/framework/Date_Parser/chronic/lib/chronic/handlers.rb
new file mode 100644 (file)
index 0000000..551d632
--- /dev/null
@@ -0,0 +1,469 @@
+module Chronic
+
+       class << self
+         
+         def definitions #:nodoc:
+           @definitions ||= 
+      {:time => [Handler.new([:repeater_time, :repeater_day_portion?], nil)],
+        
+       :date => [Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :repeater_time, :time_zone, :scalar_year], :handle_rdn_rmn_sd_t_tz_sy),
+                 Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),
+                 Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),
+                 Handler.new([:repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),
+                 Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),
+                 Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),
+                 Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),
+                 Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),
+                 Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy),
+                 Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),
+                 Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy)],
+                 
+       # tonight at 7pm
+       :anchor => [Handler.new([:grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
+                   Handler.new([:grabber?, :repeater, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
+                   Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)],
+                   
+       # 3 weeks from now, in 2 months
+       :arrow => [Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p),
+                  Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r),
+                  Handler.new([:scalar, :repeater, :pointer, 'anchor'], :handle_s_r_p_a)],
+                  
+       # 3rd week in march
+       :narrow => [Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r),
+                   Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r)]
+      }
+    end
+    
+    def tokens_to_span(tokens, options) #:nodoc:                   
+      # maybe it's a specific date
+      
+      self.definitions[:date].each do |handler|
+        if handler.match(tokens, self.definitions)
+          puts "-date" if Chronic.debug
+          good_tokens = tokens.select { |o| !o.get_tag Separator }
+          return self.send(handler.handler_method, good_tokens, options)
+        end
+      end
+            
+      # I guess it's not a specific date, maybe it's just an anchor
+            
+      self.definitions[:anchor].each do |handler|
+        if handler.match(tokens, self.definitions)
+          puts "-anchor" if Chronic.debug
+          good_tokens = tokens.select { |o| !o.get_tag Separator }
+          return self.send(handler.handler_method, good_tokens, options)
+        end
+      end
+            
+      # not an anchor, perhaps it's an arrow
+      
+      self.definitions[:arrow].each do |handler|
+        if handler.match(tokens, self.definitions)
+          puts "-arrow" if Chronic.debug
+          good_tokens = tokens.reject { |o| o.get_tag(SeparatorAt) || o.get_tag(SeparatorSlashOrDash) || o.get_tag(SeparatorComma) }
+          return self.send(handler.handler_method, good_tokens, options)
+        end
+      end
+      
+      # not an arrow, let's hope it's a narrow
+      
+      self.definitions[:narrow].each do |handler|
+        if handler.match(tokens, self.definitions)
+          puts "-narrow" if Chronic.debug
+          #good_tokens = tokens.select { |o| !o.get_tag Separator }
+          return self.send(handler.handler_method, tokens, options)
+        end
+      end
+      
+      # I guess you're out of luck!
+      puts "-none" if Chronic.debug
+      return nil
+    end
+    
+    #--------------
+    
+    def day_or_time(day_start, time_tokens, options)
+      outer_span = Span.new(day_start, day_start + (24 * 60 * 60))
+      
+      if !time_tokens.empty?
+        @now = outer_span.begin
+        time = get_anchor(dealias_and_disambiguate_times(time_tokens, options), options)
+        return time
+      else
+        return outer_span
+      end
+    end
+    
+    #--------------
+    
+    def handle_m_d(month, day, time_tokens, options) #:nodoc:
+      month.start = @now
+      span = month.this(options[:context])
+      
+      day_start = Time.local(span.begin.year, span.begin.month, day)
+      
+      day_or_time(day_start, time_tokens, options)
+    end
+    
+    def handle_rmn_sd(tokens, options) #:nodoc:
+      handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(ScalarDay).type, tokens[2..tokens.size], options)
+    end
+    
+    def handle_rmn_od(tokens, options) #:nodoc:
+      handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(OrdinalDay).type, tokens[2..tokens.size], options)
+    end
+    
+    def handle_rmn_sy(tokens, options) #:nodoc:
+      month = tokens[0].get_tag(RepeaterMonthName).index
+      year = tokens[1].get_tag(ScalarYear).type
+      
+      if month == 12
+        next_month_year = year + 1
+        next_month_month = 1
+      else
+        next_month_year = year
+        next_month_month = month + 1
+      end
+      
+      begin
+        Span.new(Time.local(year, month), Time.local(next_month_year, next_month_month))
+      rescue ArgumentError
+        nil
+      end
+    end
+    
+    def handle_rdn_rmn_sd_t_tz_sy(tokens, options) #:nodoc:
+      month = tokens[1].get_tag(RepeaterMonthName).index
+      day = tokens[2].get_tag(ScalarDay).type
+      year = tokens[5].get_tag(ScalarYear).type
+      
+      begin
+        day_start = Time.local(year, month, day)
+        day_or_time(day_start, [tokens[3]], options)
+      rescue ArgumentError
+        nil
+      end
+    end
+    
+    def handle_rmn_sd_sy(tokens, options) #:nodoc:
+      month = tokens[0].get_tag(RepeaterMonthName).index
+      day = tokens[1].get_tag(ScalarDay).type
+      year = tokens[2].get_tag(ScalarYear).type
+      
+      time_tokens = tokens.last(tokens.size - 3)
+      
+      begin
+        day_start = Time.local(year, month, day)
+        day_or_time(day_start, time_tokens, options)
+      rescue ArgumentError
+        nil
+      end
+    end
+    
+    def handle_sd_rmn_sy(tokens, options) #:nodoc:
+      new_tokens = [tokens[1], tokens[0], tokens[2]]
+      time_tokens = tokens.last(tokens.size - 3)
+      self.handle_rmn_sd_sy(new_tokens + time_tokens, options)
+    end
+    
+    def handle_sm_sd_sy(tokens, options) #:nodoc:
+      month = tokens[0].get_tag(ScalarMonth).type
+      day = tokens[1].get_tag(ScalarDay).type
+      year = tokens[2].get_tag(ScalarYear).type
+      
+      time_tokens = tokens.last(tokens.size - 3)
+      
+      begin
+        day_start = Time.local(year, month, day) #:nodoc:
+        day_or_time(day_start, time_tokens, options)
+      rescue ArgumentError
+        nil
+      end
+    end
+    
+    def handle_sd_sm_sy(tokens, options) #:nodoc:
+      new_tokens = [tokens[1], tokens[0], tokens[2]]
+      time_tokens = tokens.last(tokens.size - 3)
+      self.handle_sm_sd_sy(new_tokens + time_tokens, options)
+    end
+    
+    def handle_sy_sm_sd(tokens, options) #:nodoc:
+      new_tokens = [tokens[1], tokens[2], tokens[0]]
+      time_tokens = tokens.last(tokens.size - 3)
+      self.handle_sm_sd_sy(new_tokens + time_tokens, options)
+    end
+    
+    def handle_sm_sy(tokens, options) #:nodoc:
+      month = tokens[0].get_tag(ScalarMonth).type
+      year = tokens[1].get_tag(ScalarYear).type
+      
+      if month == 12
+        next_month_year = year + 1
+        next_month_month = 1
+      else
+        next_month_year = year
+        next_month_month = month + 1
+      end
+      
+      begin
+        Span.new(Time.local(year, month), Time.local(next_month_year, next_month_month))
+      rescue ArgumentError
+        nil
+      end
+    end
+    
+    # anchors
+    
+    def handle_r(tokens, options) #:nodoc:
+      dd_tokens = dealias_and_disambiguate_times(tokens, options)
+      self.get_anchor(dd_tokens, options)
+    end
+    
+    def handle_r_g_r(tokens, options) #:nodoc:
+      new_tokens = [tokens[1], tokens[0], tokens[2]]
+      self.handle_r(new_tokens, options)
+    end
+    
+    # arrows
+    
+    def handle_srp(tokens, span, options) #:nodoc:
+      distance = tokens[0].get_tag(Scalar).type
+      repeater = tokens[1].get_tag(Repeater)
+      pointer = tokens[2].get_tag(Pointer).type
+      
+      repeater.offset(span, distance, pointer)
+    end
+    
+    def handle_s_r_p(tokens, options) #:nodoc:
+      repeater = tokens[1].get_tag(Repeater)
+            
+      # span = 
+      # case true
+      # when [RepeaterYear, RepeaterSeason, RepeaterSeasonName, RepeaterMonth, RepeaterMonthName, RepeaterFortnight, RepeaterWeek].include?(repeater.class)
+      #   self.parse("this hour", :guess => false, :now => @now)
+      # when [RepeaterWeekend, RepeaterDay, RepeaterDayName, RepeaterDayPortion, RepeaterHour].include?(repeater.class)
+      #   self.parse("this minute", :guess => false, :now => @now)
+      # when [RepeaterMinute, RepeaterSecond].include?(repeater.class)
+      #   self.parse("this second", :guess => false, :now => @now)
+      # else
+      #   raise(ChronicPain, "Invalid repeater: #{repeater.class}")
+      # end
+      
+      span = self.parse("this second", :guess => false, :now => @now)
+      
+      self.handle_srp(tokens, span, options)
+    end
+    
+    def handle_p_s_r(tokens, options) #:nodoc:
+      new_tokens = [tokens[1], tokens[2], tokens[0]]
+      self.handle_s_r_p(new_tokens, options)
+    end
+    
+    def handle_s_r_p_a(tokens, options) #:nodoc:
+      anchor_span = get_anchor(tokens[3..tokens.size - 1], options)
+      self.handle_srp(tokens, anchor_span, options)
+    end
+    
+    # narrows
+    
+    def handle_orr(tokens, outer_span, options) #:nodoc:
+      repeater = tokens[1].get_tag(Repeater)
+      repeater.start = outer_span.begin - 1
+      ordinal = tokens[0].get_tag(Ordinal).type
+      span = nil
+      ordinal.times do
+        span = repeater.next(:future)
+        if span.begin > outer_span.end
+          span = nil
+          break
+        end
+      end
+      span
+    end
+    
+    def handle_o_r_s_r(tokens, options) #:nodoc:
+      outer_span = get_anchor([tokens[3]], options)
+      handle_orr(tokens[0..1], outer_span, options)
+    end
+    
+    def handle_o_r_g_r(tokens, options) #:nodoc:
+      outer_span = get_anchor(tokens[2..3], options)
+      handle_orr(tokens[0..1], outer_span, options)
+    end
+    
+    # support methods
+    
+    def get_anchor(tokens, options) #:nodoc:
+      grabber = Grabber.new(:this)
+      pointer = :future
+      
+      repeaters = self.get_repeaters(tokens)
+      repeaters.size.times { tokens.pop }
+                    
+      if tokens.first && tokens.first.get_tag(Grabber)
+        grabber = tokens.first.get_tag(Grabber)
+        tokens.pop
+      end
+      
+      head = repeaters.shift
+      head.start = @now
+                  
+      case grabber.type
+        when :last
+          outer_span = head.next(:past)
+        when :this
+          if repeaters.size > 0
+            outer_span = head.this(:none)
+          else
+            outer_span = head.this(options[:context])
+          end
+        when :next
+          outer_span = head.next(:future)
+        else raise(ChronicPain, "Invalid grabber")
+      end
+      
+      puts "--#{outer_span}" if Chronic.debug
+      anchor = find_within(repeaters, outer_span, pointer)
+    end
+    
+    def get_repeaters(tokens) #:nodoc:
+      repeaters = []
+                 tokens.each do |token|
+                   if t = token.get_tag(Repeater)
+          repeaters << t
+        end
+      end
+      repeaters.sort.reverse
+    end
+    
+    # Recursively finds repeaters within other repeaters.
+    # Returns a Span representing the innermost time span
+    # or nil if no repeater union could be found
+    def find_within(tags, span, pointer) #:nodoc:
+      puts "--#{span}" if Chronic.debug
+      return span if tags.empty?
+      
+      head, *rest = tags
+      head.start = pointer == :future ? span.begin : span.end
+      h = head.this(:none)
+            
+      if span.include?(h.begin) || span.include?(h.end)
+        return find_within(rest, h, pointer)
+      else
+        return nil
+      end
+    end
+    
+    def dealias_and_disambiguate_times(tokens, options) #:nodoc:
+      # handle aliases of am/pm
+      # 5:00 in the morning -> 5:00 am
+      # 7:00 in the evening -> 7:00 pm
+      
+      day_portion_index = nil
+      tokens.each_with_index do |t, i|
+        if t.get_tag(RepeaterDayPortion)
+          day_portion_index = i
+          break
+        end
+      end
+       
+      time_index = nil
+      tokens.each_with_index do |t, i|
+        if t.get_tag(RepeaterTime)
+          time_index = i
+          break
+        end
+      end
+      
+      if (day_portion_index && time_index)
+        t1 = tokens[day_portion_index]
+        t1tag = t1.get_tag(RepeaterDayPortion)
+      
+        if [:morning].include?(t1tag.type)
+          puts '--morning->am' if Chronic.debug
+          t1.untag(RepeaterDayPortion)
+          t1.tag(RepeaterDayPortion.new(:am))
+        elsif [:afternoon, :evening, :night].include?(t1tag.type)
+          puts "--#{t1tag.type}->pm" if Chronic.debug
+          t1.untag(RepeaterDayPortion)
+          t1.tag(RepeaterDayPortion.new(:pm))
+        end
+      end
+      
+      # tokens.each_with_index do |t0, i|
+      #   t1 = tokens[i + 1]
+      #   if t1 && (t1tag = t1.get_tag(RepeaterDayPortion)) && t0.get_tag(RepeaterTime)
+      #     if [:morning].include?(t1tag.type)
+      #       puts '--morning->am' if Chronic.debug
+      #       t1.untag(RepeaterDayPortion)
+      #       t1.tag(RepeaterDayPortion.new(:am))
+      #     elsif [:afternoon, :evening, :night].include?(t1tag.type)
+      #       puts "--#{t1tag.type}->pm" if Chronic.debug
+      #       t1.untag(RepeaterDayPortion)
+      #       t1.tag(RepeaterDayPortion.new(:pm))
+      #     end
+      #   end
+      # end
+            
+      # handle ambiguous times if :ambiguous_time_range is specified
+      if options[:ambiguous_time_range] != :none
+        ttokens = []
+        tokens.each_with_index do |t0, i|
+          ttokens << t0
+          t1 = tokens[i + 1]
+          if t0.get_tag(RepeaterTime) && t0.get_tag(RepeaterTime).type.ambiguous? && (!t1 || !t1.get_tag(RepeaterDayPortion))
+            distoken = Token.new('disambiguator')
+            distoken.tag(RepeaterDayPortion.new(options[:ambiguous_time_range]))
+            ttokens << distoken
+          end
+        end
+        tokens = ttokens
+      end
+      
+      tokens
+    end
+    
+  end
+  
+  class Handler #:nodoc:
+    attr_accessor :pattern, :handler_method
+    
+    def initialize(pattern, handler_method)
+      @pattern = pattern
+      @handler_method = handler_method
+    end
+    
+    def constantize(name)
+      camel = name.to_s.gsub(/(^|_)(.)/) { $2.upcase }
+      ::Chronic.module_eval(camel, __FILE__, __LINE__)
+    end
+    
+    def match(tokens, definitions)
+      token_index = 0
+      @pattern.each do |element|
+        name = element.to_s
+        optional = name.reverse[0..0] == '?'
+        name = name.chop if optional
+        if element.instance_of? Symbol
+          klass = constantize(name)
+          match = tokens[token_index] && !tokens[token_index].tags.select { |o| o.kind_of?(klass) }.empty?
+          return false if !match && !optional
+          (token_index += 1; next) if match
+          next if !match && optional
+        elsif element.instance_of? String
+          return true if optional && token_index == tokens.size
+          sub_handlers = definitions[name.intern] || raise(ChronicPain, "Invalid subset #{name} specified")
+          sub_handlers.each do |sub_handler|
+            return true if sub_handler.match(tokens[token_index..tokens.size], definitions)
+          end
+          return false
+        else
+          raise(ChronicPain, "Invalid match type: #{element.class}")
+        end
+      end
+      return false if token_index != tokens.size
+      return true
+    end
+  end
+  
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/lib/chronic/ordinal.rb b/framework/Date_Parser/chronic/lib/chronic/ordinal.rb
new file mode 100644 (file)
index 0000000..45b8148
--- /dev/null
@@ -0,0 +1,40 @@
+module Chronic
+
+  class Ordinal < Tag #:nodoc:
+    def self.scan(tokens)
+      # for each token
+      tokens.each_index do |i|
+        if t = self.scan_for_ordinals(tokens[i]) then tokens[i].tag(t) end
+        if t = self.scan_for_days(tokens[i]) then tokens[i].tag(t) end
+      end
+      tokens
+    end
+  
+    def self.scan_for_ordinals(token)
+      if token.word =~ /^(\d*)(st|nd|rd|th)$/
+        return Ordinal.new($1.to_i)
+      end
+      return nil
+    end
+    
+    def self.scan_for_days(token)
+      if token.word =~ /^(\d*)(st|nd|rd|th)$/
+        unless $1.to_i > 31
+          return OrdinalDay.new(token.word.to_i)
+        end
+      end
+      return nil
+    end
+    
+    def to_s
+      'ordinal'
+    end
+  end
+  
+  class OrdinalDay < Ordinal #:nodoc:
+    def to_s
+      super << '-day-' << @type.to_s
+    end
+  end
+
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/lib/chronic/pointer.rb b/framework/Date_Parser/chronic/lib/chronic/pointer.rb
new file mode 100644 (file)
index 0000000..224efaf
--- /dev/null
@@ -0,0 +1,27 @@
+module Chronic
+
+  class Pointer < Tag #:nodoc:
+    def self.scan(tokens)
+      # for each token
+      tokens.each_index do |i|
+        if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t) end
+      end
+      tokens
+    end
+  
+    def self.scan_for_all(token)
+      scanner = {/\bpast\b/ => :past,
+                 /\bfuture\b/ => :future,
+                 /\bin\b/ => :future}
+      scanner.keys.each do |scanner_item|
+        return self.new(scanner[scanner_item]) if scanner_item =~ token.word
+      end
+      return nil
+    end
+    
+    def to_s
+      'pointer-' << @type.to_s
+    end
+  end
+
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/lib/chronic/repeater.rb b/framework/Date_Parser/chronic/lib/chronic/repeater.rb
new file mode 100644 (file)
index 0000000..9f80daf
--- /dev/null
@@ -0,0 +1,115 @@
+class Chronic::Repeater < Chronic::Tag #:nodoc:
+  def self.scan(tokens, options)
+    # for each token
+    tokens.each_index do |i|
+      if t = self.scan_for_month_names(tokens[i]) then tokens[i].tag(t); next end
+      if t = self.scan_for_day_names(tokens[i]) then tokens[i].tag(t); next end
+      if t = self.scan_for_day_portions(tokens[i]) then tokens[i].tag(t); next end
+      if t = self.scan_for_times(tokens[i], options) then tokens[i].tag(t); next end
+      if t = self.scan_for_units(tokens[i]) then tokens[i].tag(t); next end
+    end
+    tokens
+  end
+  
+  def self.scan_for_month_names(token)
+    scanner = {/^jan\.?(uary)?$/ => :january,
+               /^feb\.?(ruary)?$/ => :february,
+               /^mar\.?(ch)?$/ => :march,
+               /^apr\.?(il)?$/ => :april,
+               /^may$/ => :may,
+               /^jun\.?e?$/ => :june,
+               /^jul\.?y?$/ => :july,
+               /^aug\.?(ust)?$/ => :august,
+               /^sep\.?(t\.?|tember)?$/ => :september,
+               /^oct\.?(ober)?$/ => :october,
+               /^nov\.?(ember)?$/ => :november,
+               /^dec\.?(ember)?$/ => :december}
+    scanner.keys.each do |scanner_item|
+      return Chronic::RepeaterMonthName.new(scanner[scanner_item]) if scanner_item =~ token.word
+    end
+    return nil
+  end
+  
+  def self.scan_for_day_names(token)
+    scanner = {/^m[ou]n(day)?$/ => :monday,
+               /^t(ue|eu|oo|u|)s(day)?$/ => :tuesday,
+               /^tue$/ => :tuesday,
+               /^we(dnes|nds|nns)day$/ => :wednesday,
+               /^wed$/ => :wednesday,
+               /^th(urs|ers)day$/ => :thursday,
+               /^thu$/ => :thursday,
+               /^fr[iy](day)?$/ => :friday,
+               /^sat(t?[ue]rday)?$/ => :saturday,
+               /^su[nm](day)?$/ => :sunday}
+    scanner.keys.each do |scanner_item|
+      return Chronic::RepeaterDayName.new(scanner[scanner_item]) if scanner_item =~ token.word
+    end
+    return nil
+  end
+  
+  def self.scan_for_day_portions(token)
+    scanner = {/^ams?$/ => :am,
+               /^pms?$/ => :pm,
+               /^mornings?$/ => :morning,
+               /^afternoons?$/ => :afternoon,
+               /^evenings?$/ => :evening,
+               /^(night|nite)s?$/ => :night}
+    scanner.keys.each do |scanner_item|
+      return Chronic::RepeaterDayPortion.new(scanner[scanner_item]) if scanner_item =~ token.word
+    end
+    return nil
+  end
+  
+  def self.scan_for_times(token, options)
+    if token.word =~ /^\d{1,2}(:?\d{2})?([\.:]?\d{2})?$/
+      return Chronic::RepeaterTime.new(token.word, options)
+    end
+    return nil
+  end
+  
+  def self.scan_for_units(token)
+    scanner = {/^years?$/ => :year,
+               /^seasons?$/ => :season,
+               /^months?$/ => :month,
+               /^fortnights?$/ => :fortnight,
+               /^weeks?$/ => :week,
+               /^weekends?$/ => :weekend,
+               /^days?$/ => :day,
+               /^hours?$/ => :hour,
+               /^minutes?$/ => :minute,
+               /^seconds?$/ => :second}
+    scanner.keys.each do |scanner_item|
+      if scanner_item =~ token.word
+        klass_name = 'Chronic::Repeater' + scanner[scanner_item].to_s.capitalize
+        klass = eval(klass_name)
+        return klass.new(scanner[scanner_item]) 
+      end
+    end
+    return nil
+  end
+  
+  def <=>(other)
+    width <=> other.width
+  end
+  
+  # returns the width (in seconds or months) of this repeatable.
+  def width
+    raise("Repeatable#width must be overridden in subclasses")
+  end
+  
+  # returns the next occurance of this repeatable.
+  def next(pointer)
+    !@now.nil? || raise("Start point must be set before calling #next")
+    [:future, :none, :past].include?(pointer) || raise("First argument 'pointer' must be one of :past or :future")
+    #raise("Repeatable#next must be overridden in subclasses")
+  end
+  
+  def this(pointer)
+    !@now.nil? || raise("Start point must be set before calling #this")
+    [:future, :past, :none].include?(pointer) || raise("First argument 'pointer' must be one of :past, :future, :none")
+  end
+  
+  def to_s
+    'repeater'
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_day.rb b/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_day.rb
new file mode 100644 (file)
index 0000000..a92d83f
--- /dev/null
@@ -0,0 +1,47 @@
+class Chronic::RepeaterDay < Chronic::Repeater #:nodoc:
+  DAY_SECONDS = 86_400 # (24 * 60 * 60)
+  
+  def next(pointer)
+    super
+    
+    if !@current_day_start
+      @current_day_start = Time.local(@now.year, @now.month, @now.day)
+    end
+    
+    direction = pointer == :future ? 1 : -1
+    @current_day_start += direction * DAY_SECONDS
+    
+    Chronic::Span.new(@current_day_start, @current_day_start + DAY_SECONDS)
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    case pointer
+    when :future
+      day_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
+      day_end = Time.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
+    when :past
+      day_begin = Time.construct(@now.year, @now.month, @now.day)
+      day_end = Time.construct(@now.year, @now.month, @now.day, @now.hour)
+    when :none
+      day_begin = Time.construct(@now.year, @now.month, @now.day)
+      day_end = Time.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
+    end
+    
+    Chronic::Span.new(day_begin, day_end)
+  end
+  
+  def offset(span, amount, pointer)
+    direction = pointer == :future ? 1 : -1
+    span + direction * amount * DAY_SECONDS
+  end
+  
+  def width
+    DAY_SECONDS
+  end
+  
+  def to_s
+    super << '-day'
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_day_name.rb b/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_day_name.rb
new file mode 100644 (file)
index 0000000..0486a4d
--- /dev/null
@@ -0,0 +1,46 @@
+class Chronic::RepeaterDayName < Chronic::Repeater #:nodoc:
+  DAY_SECONDS = 86400 # (24 * 60 * 60)
+  
+  def next(pointer)
+    super
+    
+    direction = pointer == :future ? 1 : -1
+    
+    if !@current_day_start
+      @current_day_start = Time.construct(@now.year, @now.month, @now.day)
+      @current_day_start += direction * DAY_SECONDS
+
+      day_num = symbol_to_number(@type)
+      
+      while @current_day_start.wday != day_num
+        @current_day_start += direction * DAY_SECONDS
+      end
+    else
+      @current_day_start += direction * 7 * DAY_SECONDS
+    end
+    
+    Chronic::Span.new(@current_day_start, @current_day_start + DAY_SECONDS)
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    pointer = :future if pointer == :none
+    self.next(pointer)
+  end
+  
+  def width
+    DAY_SECONDS
+  end
+  
+  def to_s
+    super << '-dayname-' << @type.to_s
+  end
+  
+  private
+  
+  def symbol_to_number(sym)
+    lookup = {:sunday => 0, :monday => 1, :tuesday => 2, :wednesday => 3, :thursday => 4, :friday => 5, :saturday => 6}
+    lookup[sym] || raise("Invalid symbol specified")
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_day_portion.rb b/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_day_portion.rb
new file mode 100644 (file)
index 0000000..c854933
--- /dev/null
@@ -0,0 +1,93 @@
+class Chronic::RepeaterDayPortion < Chronic::Repeater #:nodoc:
+  @@morning = (6 * 60 * 60)..(12 * 60 * 60) # 6am-12am
+  @@afternoon = (13 * 60 * 60)..(17 * 60 * 60) # 1pm-5pm
+  @@evening = (17 * 60 * 60)..(20 * 60 * 60) # 5pm-8pm
+  @@night = (20 * 60 * 60)..(24 * 60 * 60) # 8pm-12pm
+  
+  def initialize(type)
+    super
+    
+    if type.kind_of? Integer
+      @range = (@type * 60 * 60)..((@type + 12) * 60 * 60)
+    else
+      lookup = {:am => 0..(12 * 60 * 60 - 1),
+                :pm => (12 * 60 * 60)..(24 * 60 * 60 - 1),
+                :morning => @@morning,
+                :afternoon => @@afternoon,
+                :evening => @@evening,
+                :night => @@night}
+      @range = lookup[type]
+      lookup[type] || raise("Invalid type '#{type}' for RepeaterDayPortion")
+    end
+    @range || raise("Range should have been set by now")
+  end
+  
+  def next(pointer)
+    super
+    
+    full_day = 60 * 60 * 24
+    
+    if !@current_span
+      now_seconds = @now - Time.construct(@now.year, @now.month, @now.day)
+      if now_seconds < @range.begin
+        case pointer
+        when :future
+          range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
+        when :past
+          range_start = Time.construct(@now.year, @now.month, @now.day) - full_day + @range.begin
+        end
+      elsif now_seconds > @range.end
+        case pointer
+        when :future
+          range_start = Time.construct(@now.year, @now.month, @now.day) + full_day + @range.begin
+        when :past
+          range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
+        end
+      else
+        case pointer
+        when :future
+          range_start = Time.construct(@now.year, @now.month, @now.day) + full_day + @range.begin
+        when :past
+          range_start = Time.construct(@now.year, @now.month, @now.day) - full_day + @range.begin
+        end
+      end
+      
+      @current_span = Chronic::Span.new(range_start, range_start + (@range.end - @range.begin))
+    else
+      case pointer
+      when :future
+        @current_span += full_day
+      when :past
+        @current_span -= full_day
+      end
+    end
+  end
+  
+  def this(context = :future)
+    super
+    
+    range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
+    @current_span = Chronic::Span.new(range_start, range_start + (@range.end - @range.begin))
+  end
+  
+  def offset(span, amount, pointer)
+    @now = span.begin
+    portion_span = self.next(pointer)
+    direction = pointer == :future ? 1 : -1
+    portion_span + (direction * (amount - 1) * Chronic::RepeaterDay::DAY_SECONDS)
+  end
+  
+  def width
+    @range || raise("Range has not been set")
+    return @current_span.width if @current_span
+    if @type.kind_of? Integer
+      return (12 * 60 * 60)
+    else
+      @range.end - @range.begin
+    end
+  end
+  
+  def to_s
+    super << '-dayportion-' << @type.to_s
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_fortnight.rb b/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_fortnight.rb
new file mode 100644 (file)
index 0000000..058fbb9
--- /dev/null
@@ -0,0 +1,65 @@
+class Chronic::RepeaterFortnight < Chronic::Repeater #:nodoc:
+  FORTNIGHT_SECONDS = 1_209_600 # (14 * 24 * 60 * 60)
+  
+  def next(pointer)
+    super
+    
+    if !@current_fortnight_start
+      case pointer
+      when :future
+        sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+        sunday_repeater.start = @now
+        next_sunday_span = sunday_repeater.next(:future)
+        @current_fortnight_start = next_sunday_span.begin
+      when :past
+        sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+        sunday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
+        2.times { sunday_repeater.next(:past) }
+        last_sunday_span = sunday_repeater.next(:past)
+        @current_fortnight_start = last_sunday_span.begin
+      end
+    else
+      direction = pointer == :future ? 1 : -1
+      @current_fortnight_start += direction * FORTNIGHT_SECONDS
+    end
+    
+    Chronic::Span.new(@current_fortnight_start, @current_fortnight_start + FORTNIGHT_SECONDS)
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    pointer = :future if pointer == :none
+    
+    case pointer
+    when :future
+      this_fortnight_start = Time.construct(@now.year, @now.month, @now.day, @now.hour) + Chronic::RepeaterHour::HOUR_SECONDS
+      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+      sunday_repeater.start = @now
+      sunday_repeater.this(:future)
+      this_sunday_span = sunday_repeater.this(:future)
+      this_fortnight_end = this_sunday_span.begin
+      Chronic::Span.new(this_fortnight_start, this_fortnight_end)
+    when :past
+      this_fortnight_end = Time.construct(@now.year, @now.month, @now.day, @now.hour)
+      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+      sunday_repeater.start = @now
+      last_sunday_span = sunday_repeater.next(:past)
+      this_fortnight_start = last_sunday_span.begin
+      Chronic::Span.new(this_fortnight_start, this_fortnight_end)
+    end
+  end
+  
+  def offset(span, amount, pointer)
+    direction = pointer == :future ? 1 : -1
+    span + direction * amount * FORTNIGHT_SECONDS
+  end
+
+  def width
+    FORTNIGHT_SECONDS
+  end
+  
+  def to_s
+    super << '-fortnight'
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_hour.rb b/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_hour.rb
new file mode 100644 (file)
index 0000000..f38a3f8
--- /dev/null
@@ -0,0 +1,52 @@
+class Chronic::RepeaterHour < Chronic::Repeater #:nodoc:
+  HOUR_SECONDS = 3600 # 60 * 60
+  
+  def next(pointer)
+    super
+    
+    if !@current_hour_start
+      case pointer
+      when :future
+        @current_hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
+      when :past
+        @current_hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour - 1)
+      end
+    else
+      direction = pointer == :future ? 1 : -1
+      @current_hour_start += direction * HOUR_SECONDS
+    end
+    
+    Chronic::Span.new(@current_hour_start, @current_hour_start + HOUR_SECONDS)
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    case pointer
+    when :future
+      hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min + 1)
+      hour_end = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
+    when :past
+      hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour)
+      hour_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
+    when :none
+      hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour)
+      hour_end = hour_begin + HOUR_SECONDS
+    end
+    
+    Chronic::Span.new(hour_start, hour_end)
+  end
+  
+  def offset(span, amount, pointer)
+    direction = pointer == :future ? 1 : -1
+    span + direction * amount * HOUR_SECONDS
+  end
+  
+  def width
+    HOUR_SECONDS
+  end
+  
+  def to_s
+    super << '-hour'
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_minute.rb b/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_minute.rb
new file mode 100644 (file)
index 0000000..342d3cd
--- /dev/null
@@ -0,0 +1,52 @@
+class Chronic::RepeaterMinute < Chronic::Repeater #:nodoc:
+  MINUTE_SECONDS = 60
+  
+  def next(pointer = :future)
+    super
+    
+    if !@current_minute_start
+      case pointer
+      when :future
+        @current_minute_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min + 1)
+      when :past
+        @current_minute_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min - 1)
+      end
+    else
+      direction = pointer == :future ? 1 : -1
+      @current_minute_start += direction * MINUTE_SECONDS
+    end
+    
+    Chronic::Span.new(@current_minute_start, @current_minute_start + MINUTE_SECONDS)
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    case pointer
+    when :future
+      minute_begin = @now
+      minute_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
+    when :past
+      minute_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
+      minute_end = @now
+    when :none
+      minute_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
+      minute_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min) + MINUTE_SECONDS
+    end
+    
+    Chronic::Span.new(minute_begin, minute_end)
+  end
+  
+  def offset(span, amount, pointer)
+    direction = pointer == :future ? 1 : -1
+    span + direction * amount * MINUTE_SECONDS
+  end
+  
+  def width
+    MINUTE_SECONDS
+  end
+  
+  def to_s
+    super << '-minute'
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_month.rb b/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_month.rb
new file mode 100644 (file)
index 0000000..edd89ee
--- /dev/null
@@ -0,0 +1,61 @@
+class Chronic::RepeaterMonth < Chronic::Repeater #:nodoc:
+  MONTH_SECONDS = 2_592_000 # 30 * 24 * 60 * 60
+  YEAR_MONTHS = 12
+  
+  def next(pointer)
+    super
+    
+    if !@current_month_start
+      @current_month_start = offset_by(Time.construct(@now.year, @now.month), 1, pointer)
+    else
+      @current_month_start = offset_by(Time.construct(@current_month_start.year, @current_month_start.month), 1, pointer)
+    end
+    
+    Chronic::Span.new(@current_month_start, Time.construct(@current_month_start.year, @current_month_start.month + 1))
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    case pointer
+    when :future
+      month_start = Time.construct(@now.year, @now.month, @now.day + 1)
+      month_end = self.offset_by(Time.construct(@now.year, @now.month), 1, :future)
+    when :past
+      month_start = Time.construct(@now.year, @now.month)
+      month_end = Time.construct(@now.year, @now.month, @now.day)
+    when :none
+      month_start = Time.construct(@now.year, @now.month)
+      month_end = self.offset_by(Time.construct(@now.year, @now.month), 1, :future)
+    end
+    
+    Chronic::Span.new(month_start, month_end)
+  end
+  
+  def offset(span, amount, pointer)      
+    Chronic::Span.new(offset_by(span.begin, amount, pointer), offset_by(span.end, amount, pointer))
+  end
+  
+  def offset_by(time, amount, pointer) 
+    direction = pointer == :future ? 1 : -1
+    
+    amount_years = direction * amount / YEAR_MONTHS
+    amount_months = direction * amount % YEAR_MONTHS
+    
+    new_year = time.year + amount_years
+    new_month = time.month + amount_months
+    if new_month > YEAR_MONTHS
+      new_year += 1
+      new_month -= YEAR_MONTHS
+    end
+    Time.construct(new_year, new_month, time.day, time.hour, time.min, time.sec)
+  end
+  
+  def width
+    MONTH_SECONDS
+  end
+  
+  def to_s
+    super << '-month'
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_month_name.rb b/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_month_name.rb
new file mode 100644 (file)
index 0000000..1f8b748
--- /dev/null
@@ -0,0 +1,93 @@
+class Chronic::RepeaterMonthName < Chronic::Repeater #:nodoc:
+  MONTH_SECONDS = 2_592_000 # 30 * 24 * 60 * 60
+  
+  def next(pointer)
+    super
+    
+    if !@current_month_begin
+      target_month = symbol_to_number(@type)
+      case pointer
+      when :future
+        if @now.month < target_month
+          @current_month_begin = Time.construct(@now.year, target_month)
+        else @now.month > target_month
+          @current_month_begin = Time.construct(@now.year + 1, target_month)
+        end
+      when :none
+        if @now.month <= target_month
+          @current_month_begin = Time.construct(@now.year, target_month)
+        else @now.month > target_month
+          @current_month_begin = Time.construct(@now.year + 1, target_month)
+        end
+      when :past
+        if @now.month > target_month
+          @current_month_begin = Time.construct(@now.year, target_month)
+        else @now.month < target_month
+          @current_month_begin = Time.construct(@now.year - 1, target_month)
+        end
+      end
+      @current_month_begin || raise("Current month should be set by now")
+    else
+      case pointer
+      when :future
+        @current_month_begin = Time.construct(@current_month_begin.year + 1, @current_month_begin.month)
+      when :past
+        @current_month_begin = Time.construct(@current_month_begin.year - 1, @current_month_begin.month)
+      end
+    end
+    
+    cur_month_year = @current_month_begin.year
+    cur_month_month = @current_month_begin.month
+    
+    if cur_month_month == 12
+      next_month_year = cur_month_year + 1
+      next_month_month = 1
+    else
+      next_month_year = cur_month_year
+      next_month_month = cur_month_month + 1
+    end
+      
+    Chronic::Span.new(@current_month_begin, Time.construct(next_month_year, next_month_month))
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    case pointer
+    when :past
+      self.next(pointer)
+    when :future, :none
+      self.next(:none)
+    end
+  end
+  
+  def width
+    MONTH_SECONDS
+  end
+  
+  def index
+    symbol_to_number(@type)
+  end
+  
+  def to_s
+    super << '-monthname-' << @type.to_s
+  end
+  
+  private
+  
+  def symbol_to_number(sym)
+    lookup = {:january => 1,
+              :february => 2,
+              :march => 3,
+              :april => 4,
+              :may => 5,
+              :june => 6,
+              :july => 7,
+              :august => 8,
+              :september => 9,
+              :october => 10,
+              :november => 11,
+              :december => 12}
+    lookup[sym] || raise("Invalid symbol specified")
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_season.rb b/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_season.rb
new file mode 100644 (file)
index 0000000..a255865
--- /dev/null
@@ -0,0 +1,23 @@
+class Chronic::RepeaterSeason < Chronic::Repeater #:nodoc:
+  SEASON_SECONDS = 7_862_400 # 91 * 24 * 60 * 60
+  
+  def next(pointer)
+    super
+    
+    raise 'Not implemented'
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    raise 'Not implemented'
+  end
+  
+  def width
+    SEASON_SECONDS
+  end
+  
+  def to_s
+    super << '-season'
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_season_name.rb b/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_season_name.rb
new file mode 100644 (file)
index 0000000..adfd1f2
--- /dev/null
@@ -0,0 +1,24 @@
+class Chronic::RepeaterSeasonName < Chronic::RepeaterSeason #:nodoc:
+  @summer = ['jul 21', 'sep 22']
+  @autumn = ['sep 23', 'dec 21']
+  @winter = ['dec 22', 'mar 19']
+  @spring = ['mar 20', 'jul 20']
+  
+  def next(pointer)
+    super
+    raise 'Not implemented'
+  end
+  
+  def this(pointer = :future)
+    super
+    raise 'Not implemented'
+  end
+  
+  def width
+    (91 * 24 * 60 * 60)
+  end
+  
+  def to_s
+    super << '-season-' << @type.to_s
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_second.rb b/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_second.rb
new file mode 100644 (file)
index 0000000..6d05545
--- /dev/null
@@ -0,0 +1,36 @@
+class Chronic::RepeaterSecond < Chronic::Repeater #:nodoc:
+  SECOND_SECONDS = 1 # haha, awesome
+  
+  def next(pointer = :future)
+    super
+    
+    direction = pointer == :future ? 1 : -1
+    
+    if !@second_start
+      @second_start = @now + (direction * SECOND_SECONDS)
+    else
+      @second_start += SECOND_SECONDS * direction
+    end
+    
+    Chronic::Span.new(@second_start, @second_start + SECOND_SECONDS)
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    Chronic::Span.new(@now, @now + 1)
+  end
+  
+  def offset(span, amount, pointer)
+    direction = pointer == :future ? 1 : -1
+    span + direction * amount * SECOND_SECONDS
+  end
+  
+  def width
+    SECOND_SECONDS
+  end
+  
+  def to_s
+    super << '-second'
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_time.rb b/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_time.rb
new file mode 100644 (file)
index 0000000..f856014
--- /dev/null
@@ -0,0 +1,117 @@
+class Chronic::RepeaterTime < Chronic::Repeater #:nodoc:
+  class Tick #:nodoc:
+    attr_accessor :time
+    
+    def initialize(time, ambiguous = false)
+      @time = time
+      @ambiguous = ambiguous
+    end
+    
+    def ambiguous?
+      @ambiguous
+    end
+    
+    def *(other)
+      Tick.new(@time * other, @ambiguous)
+    end
+    
+    def to_f
+      @time.to_f
+    end
+    
+    def to_s
+      @time.to_s + (@ambiguous ? '?' : '')
+    end
+  end
+  
+  def initialize(time, options = {})
+    t = time.gsub(/\:/, '')
+    @type = 
+    if (1..2) === t.size
+      hours = t.to_i
+      hours == 12 ? Tick.new(0 * 60 * 60, true) : Tick.new(hours * 60 * 60, true)
+    elsif t.size == 3
+      Tick.new((t[0..0].to_i * 60 * 60) + (t[1..2].to_i * 60), true)
+    elsif t.size == 4
+      ambiguous = time =~ /:/ && t[0..0].to_i != 0 && t[0..1].to_i <= 12
+      hours = t[0..1].to_i
+      hours == 12 ? Tick.new(0 * 60 * 60 + t[2..3].to_i * 60, ambiguous) : Tick.new(hours * 60 * 60 + t[2..3].to_i * 60, ambiguous)
+    elsif t.size == 5
+      Tick.new(t[0..0].to_i * 60 * 60 + t[1..2].to_i * 60 + t[3..4].to_i, true)
+    elsif t.size == 6
+      ambiguous = time =~ /:/ && t[0..0].to_i != 0 && t[0..1].to_i <= 12
+      hours = t[0..1].to_i
+      hours == 12 ? Tick.new(0 * 60 * 60 + t[2..3].to_i * 60 + t[4..5].to_i, ambiguous) : Tick.new(hours * 60 * 60 + t[2..3].to_i * 60 + t[4..5].to_i, ambiguous)
+    else
+      raise("Time cannot exceed six digits")
+    end
+  end
+  
+  # Return the next past or future Span for the time that this Repeater represents
+  #   pointer - Symbol representing which temporal direction to fetch the next day
+  #             must be either :past or :future
+  def next(pointer)
+    super
+    
+    half_day = 60 * 60 * 12
+    full_day = 60 * 60 * 24
+    
+    first = false
+    
+    unless @current_time
+      first = true
+      midnight = Time.local(@now.year, @now.month, @now.day)
+      yesterday_midnight = midnight - full_day
+      tomorrow_midnight = midnight + full_day
+
+      catch :done do
+        if pointer == :future
+          if @type.ambiguous?
+            [midnight + @type, midnight + half_day + @type, tomorrow_midnight + @type].each do |t|
+              (@current_time = t; throw :done) if t >= @now
+            end
+          else
+            [midnight + @type, tomorrow_midnight + @type].each do |t|
+              (@current_time = t; throw :done) if t >= @now
+            end
+          end
+        else # pointer == :past
+          if @type.ambiguous?
+            [midnight + half_day + @type, midnight + @type, yesterday_midnight + @type * 2].each do |t|
+              (@current_time = t; throw :done) if t <= @now
+            end
+          else
+            [midnight + @type, yesterday_midnight + @type].each do |t|
+              (@current_time = t; throw :done) if t <= @now
+            end
+          end
+        end
+      end
+      
+      @current_time || raise("Current time cannot be nil at this point")
+    end
+    
+    unless first
+      increment = @type.ambiguous? ? half_day : full_day
+      @current_time += pointer == :future ? increment : -increment
+    end
+    
+    Chronic::Span.new(@current_time, @current_time + width)
+  end
+  
+  def this(context = :future)
+    super
+    
+    context = :future if context == :none
+    
+    self.next(context)
+  end
+  
+  def width
+    1
+  end
+  
+  def to_s
+    super << '-time-' << @type.to_s
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_week.rb b/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_week.rb
new file mode 100644 (file)
index 0000000..ec88ff1
--- /dev/null
@@ -0,0 +1,68 @@
+class Chronic::RepeaterWeek < Chronic::Repeater #:nodoc:
+  WEEK_SECONDS = 604800 # (7 * 24 * 60 * 60)
+  
+  def next(pointer)
+    super
+    
+    if !@current_week_start
+      case pointer
+      when :future
+        sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+        sunday_repeater.start = @now
+        next_sunday_span = sunday_repeater.next(:future)
+        @current_week_start = next_sunday_span.begin
+      when :past
+        sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+        sunday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
+        sunday_repeater.next(:past)
+        last_sunday_span = sunday_repeater.next(:past)
+        @current_week_start = last_sunday_span.begin
+      end
+    else
+      direction = pointer == :future ? 1 : -1
+      @current_week_start += direction * WEEK_SECONDS
+    end
+    
+    Chronic::Span.new(@current_week_start, @current_week_start + WEEK_SECONDS)
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    case pointer
+    when :future
+      this_week_start = Time.local(@now.year, @now.month, @now.day, @now.hour) + Chronic::RepeaterHour::HOUR_SECONDS
+      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+      sunday_repeater.start = @now
+      this_sunday_span = sunday_repeater.this(:future)
+      this_week_end = this_sunday_span.begin
+      Chronic::Span.new(this_week_start, this_week_end)
+    when :past
+      this_week_end = Time.local(@now.year, @now.month, @now.day, @now.hour)
+      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+      sunday_repeater.start = @now
+      last_sunday_span = sunday_repeater.next(:past)
+      this_week_start = last_sunday_span.begin
+      Chronic::Span.new(this_week_start, this_week_end)
+    when :none
+      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+      sunday_repeater.start = @now
+      last_sunday_span = sunday_repeater.next(:past)
+      this_week_start = last_sunday_span.begin
+      Chronic::Span.new(this_week_start, this_week_start + WEEK_SECONDS)
+    end
+  end
+  
+  def offset(span, amount, pointer)
+    direction = pointer == :future ? 1 : -1
+    span + direction * amount * WEEK_SECONDS
+  end
+  
+  def width
+    WEEK_SECONDS
+  end
+  
+  def to_s
+    super << '-week'
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_weekend.rb b/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_weekend.rb
new file mode 100644 (file)
index 0000000..f012267
--- /dev/null
@@ -0,0 +1,60 @@
+class Chronic::RepeaterWeekend < Chronic::Repeater #:nodoc:
+  WEEKEND_SECONDS = 172_800 # (2 * 24 * 60 * 60)
+  
+  def next(pointer)
+    super
+    
+    if !@current_week_start
+      case pointer
+      when :future
+        saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
+        saturday_repeater.start = @now
+        next_saturday_span = saturday_repeater.next(:future)
+        @current_week_start = next_saturday_span.begin
+      when :past
+        saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
+        saturday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
+        last_saturday_span = saturday_repeater.next(:past)
+        @current_week_start = last_saturday_span.begin
+      end
+    else
+      direction = pointer == :future ? 1 : -1
+      @current_week_start += direction * Chronic::RepeaterWeek::WEEK_SECONDS
+    end
+    
+    Chronic::Span.new(@current_week_start, @current_week_start + WEEKEND_SECONDS)
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    case pointer
+    when :future, :none
+      saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
+      saturday_repeater.start = @now
+      this_saturday_span = saturday_repeater.this(:future)
+      Chronic::Span.new(this_saturday_span.begin, this_saturday_span.begin + WEEKEND_SECONDS)
+    when :past
+      saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
+      saturday_repeater.start = @now
+      last_saturday_span = saturday_repeater.this(:past)
+      Chronic::Span.new(last_saturday_span.begin, last_saturday_span.begin + WEEKEND_SECONDS)
+    end
+  end
+  
+  def offset(span, amount, pointer)
+    direction = pointer == :future ? 1 : -1
+    weekend = Chronic::RepeaterWeekend.new(:weekend)
+    weekend.start = span.begin
+    start = weekend.next(pointer).begin + (amount - 1) * direction * Chronic::RepeaterWeek::WEEK_SECONDS
+    Chronic::Span.new(start, start + (span.end - span.begin))
+  end
+  
+  def width
+    WEEKEND_SECONDS
+  end
+  
+  def to_s
+    super << '-weekend'
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_year.rb b/framework/Date_Parser/chronic/lib/chronic/repeaters/repeater_year.rb
new file mode 100644 (file)
index 0000000..426371f
--- /dev/null
@@ -0,0 +1,58 @@
+class Chronic::RepeaterYear < Chronic::Repeater #:nodoc:
+  
+  def next(pointer)
+    super
+    
+    if !@current_year_start
+      case pointer
+      when :future
+        @current_year_start = Time.construct(@now.year + 1)
+      when :past
+        @current_year_start = Time.construct(@now.year - 1)
+      end
+    else
+      diff = pointer == :future ? 1 : -1
+      @current_year_start = Time.construct(@current_year_start.year + diff)
+    end
+    
+    Chronic::Span.new(@current_year_start, Time.construct(@current_year_start.year + 1))
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    case pointer
+    when :future
+      this_year_start = Time.construct(@now.year, @now.month, @now.day) + Chronic::RepeaterDay::DAY_SECONDS
+      this_year_end = Time.construct(@now.year + 1, 1, 1)
+    when :past
+      this_year_start = Time.construct(@now.year, 1, 1)
+      this_year_end = Time.construct(@now.year, @now.month, @now.day)
+    when :none
+      this_year_start = Time.construct(@now.year, 1, 1)
+      this_year_end = Time.construct(@now.year + 1, 1, 1)
+    end
+    
+    Chronic::Span.new(this_year_start, this_year_end)
+  end
+  
+  def offset(span, amount, pointer)
+    direction = pointer == :future ? 1 : -1
+    
+    sb = span.begin
+    new_begin = Time.construct(sb.year + (amount * direction), sb.month, sb.day, sb.hour, sb.min, sb.sec)
+    
+    se = span.end
+    new_end = Time.construct(se.year + (amount * direction), se.month, se.day, se.hour, se.min, se.sec)
+    
+    Chronic::Span.new(new_begin, new_end)
+  end
+  
+  def width
+    (365 * 24 * 60 * 60)
+  end
+  
+  def to_s
+    super << '-year'
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/lib/chronic/scalar.rb b/framework/Date_Parser/chronic/lib/chronic/scalar.rb
new file mode 100644 (file)
index 0000000..b08cfee
--- /dev/null
@@ -0,0 +1,74 @@
+module Chronic
+
+  class Scalar < Tag #:nodoc:
+    def self.scan(tokens)
+      # for each token
+      tokens.each_index do |i|
+        if t = self.scan_for_scalars(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
+        if t = self.scan_for_days(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
+        if t = self.scan_for_months(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
+        if t = self.scan_for_years(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
+      end
+      tokens
+    end
+  
+    def self.scan_for_scalars(token, post_token)
+      if token.word =~ /^\d*$/
+        unless post_token && %w{am pm morning afternoon evening night}.include?(post_token)
+          return Scalar.new(token.word.to_i)
+        end
+      end
+      return nil
+    end
+    
+    def self.scan_for_days(token, post_token)
+      if token.word =~ /^\d\d?$/
+        unless token.word.to_i > 31 || (post_token && %w{am pm morning afternoon evening night}.include?(post_token))
+          return ScalarDay.new(token.word.to_i)
+        end
+      end
+      return nil
+    end
+    
+    def self.scan_for_months(token, post_token)
+      if token.word =~ /^\d\d?$/
+        unless token.word.to_i > 12 || (post_token && %w{am pm morning afternoon evening night}.include?(post_token))
+          return ScalarMonth.new(token.word.to_i)
+        end
+      end
+      return nil
+    end
+    
+    def self.scan_for_years(token, post_token)
+      if token.word =~ /^([1-9]\d)?\d\d?$/
+        unless post_token && %w{am pm morning afternoon evening night}.include?(post_token)
+          return ScalarYear.new(token.word.to_i)
+        end
+      end
+      return nil
+    end
+    
+    def to_s
+      'scalar'
+    end
+  end
+  
+  class ScalarDay < Scalar #:nodoc:
+    def to_s
+      super << '-day-' << @type.to_s
+    end
+  end
+  
+  class ScalarMonth < Scalar #:nodoc:
+    def to_s
+      super << '-month-' << @type.to_s
+    end
+  end
+  
+  class ScalarYear < Scalar #:nodoc:
+    def to_s
+      super << '-year-' << @type.to_s
+    end
+  end
+
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/lib/chronic/separator.rb b/framework/Date_Parser/chronic/lib/chronic/separator.rb
new file mode 100644 (file)
index 0000000..86c56e3
--- /dev/null
@@ -0,0 +1,76 @@
+module Chronic
+
+  class Separator < Tag #:nodoc:
+    def self.scan(tokens)
+      tokens.each_index do |i|
+        if t = self.scan_for_commas(tokens[i]) then tokens[i].tag(t); next end
+        if t = self.scan_for_slash_or_dash(tokens[i]) then tokens[i].tag(t); next end
+        if t = self.scan_for_at(tokens[i]) then tokens[i].tag(t); next end
+        if t = self.scan_for_in(tokens[i]) then tokens[i].tag(t); next end
+      end
+      tokens
+    end
+    
+    def self.scan_for_commas(token)
+      scanner = {/^,$/ => :comma}
+      scanner.keys.each do |scanner_item|
+        return SeparatorComma.new(scanner[scanner_item]) if scanner_item =~ token.word
+      end
+      return nil
+    end
+    
+    def self.scan_for_slash_or_dash(token)
+      scanner = {/^-$/ => :dash,
+                 /^\/$/ => :slash}
+      scanner.keys.each do |scanner_item|
+        return SeparatorSlashOrDash.new(scanner[scanner_item]) if scanner_item =~ token.word
+      end
+      return nil
+    end
+    
+    def self.scan_for_at(token)
+      scanner = {/^(at|@)$/ => :at}
+      scanner.keys.each do |scanner_item|
+        return SeparatorAt.new(scanner[scanner_item]) if scanner_item =~ token.word
+      end
+      return nil
+    end
+    
+    def self.scan_for_in(token)
+      scanner = {/^in$/ => :in}
+      scanner.keys.each do |scanner_item|
+        return SeparatorIn.new(scanner[scanner_item]) if scanner_item =~ token.word
+      end
+      return nil
+    end
+    
+    def to_s
+      'separator'
+    end
+  end
+  
+  class SeparatorComma < Separator #:nodoc:
+    def to_s
+      super << '-comma'
+    end
+  end
+  
+  class SeparatorSlashOrDash < Separator #:nodoc:
+    def to_s
+      super << '-slashordash-' << @type.to_s
+    end
+  end
+  
+  class SeparatorAt < Separator #:nodoc:
+    def to_s
+      super << '-at'
+    end
+  end
+  
+  class SeparatorIn < Separator #:nodoc:
+    def to_s
+      super << '-in'
+    end
+  end
+
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/lib/chronic/time_zone.rb b/framework/Date_Parser/chronic/lib/chronic/time_zone.rb
new file mode 100644 (file)
index 0000000..41041ef
--- /dev/null
@@ -0,0 +1,22 @@
+module Chronic
+  class TimeZone < Tag #:nodoc:    
+    def self.scan(tokens)
+      tokens.each_index do |i|
+        if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t); next end
+      end
+      tokens
+    end
+
+    def self.scan_for_all(token)
+      scanner = {/[PMCE][DS]T/i => :tz}
+      scanner.keys.each do |scanner_item|
+        return self.new(scanner[scanner_item]) if scanner_item =~ token.word
+      end
+      return nil
+    end
+
+    def to_s
+      'timezone'
+    end
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/test/suite.rb b/framework/Date_Parser/chronic/test/suite.rb
new file mode 100644 (file)
index 0000000..fa8bdaa
--- /dev/null
@@ -0,0 +1,9 @@
+require 'test/unit'
+
+tests = Dir["#{File.dirname(__FILE__)}/test_*.rb"]
+tests.delete_if { |o| o =~ /test_parsing/ }
+tests.each do |file|
+  require file
+end
+
+require File.dirname(__FILE__) + '/test_parsing.rb'
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/test/test_Chronic.rb b/framework/Date_Parser/chronic/test/test_Chronic.rb
new file mode 100644 (file)
index 0000000..04fedb5
--- /dev/null
@@ -0,0 +1,50 @@
+require 'chronic'
+require 'test/unit'
+
+class TestChronic < Test::Unit::TestCase
+  
+  def setup
+    # Wed Aug 16 14:00:00 UTC 2006
+    @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
+  end
+  
+  def test_post_normalize_am_pm_aliases
+    # affect wanted patterns
+    
+    tokens = [Chronic::Token.new("5:00"), Chronic::Token.new("morning")]
+    tokens[0].tag(Chronic::RepeaterTime.new("5:00"))
+    tokens[1].tag(Chronic::RepeaterDayPortion.new(:morning))
+    
+    assert_equal :morning, tokens[1].tags[0].type
+    
+    tokens = Chronic.dealias_and_disambiguate_times(tokens, {})
+    
+    assert_equal :am, tokens[1].tags[0].type
+    assert_equal 2, tokens.size
+    
+    # don't affect unwanted patterns
+    
+    tokens = [Chronic::Token.new("friday"), Chronic::Token.new("morning")]
+    tokens[0].tag(Chronic::RepeaterDayName.new(:friday))
+    tokens[1].tag(Chronic::RepeaterDayPortion.new(:morning))
+    
+    assert_equal :morning, tokens[1].tags[0].type
+    
+    tokens = Chronic.dealias_and_disambiguate_times(tokens, {})
+    
+    assert_equal :morning, tokens[1].tags[0].type
+    assert_equal 2, tokens.size
+  end
+  
+  def test_guess
+    span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0))
+    assert_equal Time.local(2006, 8, 16, 12), Chronic.guess(span)
+    
+    span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0, 0, 1))
+    assert_equal Time.local(2006, 8, 16, 12), Chronic.guess(span)
+    
+    span = Chronic::Span.new(Time.local(2006, 11), Time.local(2006, 12))
+    assert_equal Time.local(2006, 11, 16), Chronic.guess(span)
+  end
+  
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/test/test_Handler.rb b/framework/Date_Parser/chronic/test/test_Handler.rb
new file mode 100644 (file)
index 0000000..4e36dfe
--- /dev/null
@@ -0,0 +1,110 @@
+require 'chronic'
+require 'test/unit'
+
+class TestHandler < Test::Unit::TestCase
+  
+  def setup
+    # Wed Aug 16 14:00:00 UTC 2006
+    @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
+  end
+
+  def test_handler_class_1
+    handler = Chronic::Handler.new([:repeater], :handler)
+    
+    tokens = [Chronic::Token.new('friday')]
+    tokens[0].tag(Chronic::RepeaterDayName.new(:friday))
+    
+    assert handler.match(tokens, Chronic.definitions)
+    
+    tokens << Chronic::Token.new('afternoon')
+    tokens[1].tag(Chronic::RepeaterDayPortion.new(:afternoon))
+    
+    assert !handler.match(tokens, Chronic.definitions)
+  end
+  
+  def test_handler_class_2
+    handler = Chronic::Handler.new([:repeater, :repeater?], :handler)
+    
+    tokens = [Chronic::Token.new('friday')]
+    tokens[0].tag(Chronic::RepeaterDayName.new(:friday))
+    
+    assert handler.match(tokens, Chronic.definitions)
+    
+    tokens << Chronic::Token.new('afternoon')
+    tokens[1].tag(Chronic::RepeaterDayPortion.new(:afternoon))
+    
+    assert handler.match(tokens, Chronic.definitions)
+    
+    tokens << Chronic::Token.new('afternoon')
+    tokens[2].tag(Chronic::RepeaterDayPortion.new(:afternoon))
+    
+    assert !handler.match(tokens, Chronic.definitions)
+  end
+  
+  def test_handler_class_3
+    handler = Chronic::Handler.new([:repeater, 'time?'], :handler)
+    
+    tokens = [Chronic::Token.new('friday')]
+    tokens[0].tag(Chronic::RepeaterDayName.new(:friday))
+    
+    assert handler.match(tokens, Chronic.definitions)
+    
+    tokens << Chronic::Token.new('afternoon')
+    tokens[1].tag(Chronic::RepeaterDayPortion.new(:afternoon))
+    
+    assert !handler.match(tokens, Chronic.definitions)
+  end
+  
+  def test_handler_class_4
+    handler = Chronic::Handler.new([:repeater_month_name, :scalar_day, 'time?'], :handler)
+    
+    tokens = [Chronic::Token.new('may')]
+    tokens[0].tag(Chronic::RepeaterMonthName.new(:may))
+    
+    assert !handler.match(tokens, Chronic.definitions)
+    
+    tokens << Chronic::Token.new('27')
+    tokens[1].tag(Chronic::ScalarDay.new(27))
+    
+    assert handler.match(tokens, Chronic.definitions)
+  end
+  
+  def test_handler_class_5
+    handler = Chronic::Handler.new([:repeater, 'time?'], :handler)
+    
+    tokens = [Chronic::Token.new('friday')]
+    tokens[0].tag(Chronic::RepeaterDayName.new(:friday))
+    
+    assert handler.match(tokens, Chronic.definitions)
+    
+    tokens << Chronic::Token.new('5:00')
+    tokens[1].tag(Chronic::RepeaterTime.new('5:00'))
+    
+    assert handler.match(tokens, Chronic.definitions)
+    
+    tokens << Chronic::Token.new('pm')
+    tokens[2].tag(Chronic::RepeaterDayPortion.new(:pm))
+    
+    assert handler.match(tokens, Chronic.definitions)
+  end
+  
+  def test_handler_class_6
+    handler = Chronic::Handler.new([:scalar, :repeater, :pointer], :handler)
+    
+    tokens = [Chronic::Token.new('3'),
+              Chronic::Token.new('years'),
+              Chronic::Token.new('past')]
+              
+    tokens[0].tag(Chronic::Scalar.new(3))
+    tokens[1].tag(Chronic::RepeaterYear.new(:year))
+    tokens[2].tag(Chronic::Pointer.new(:past))
+    
+    assert handler.match(tokens, Chronic.definitions)
+  end
+  
+  def test_constantize
+    handler = Chronic::Handler.new([], :handler)
+    assert_equal Chronic::RepeaterTime, handler.constantize(:repeater_time)
+  end
+  
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/test/test_RepeaterDayName.rb b/framework/Date_Parser/chronic/test/test_RepeaterDayName.rb
new file mode 100644 (file)
index 0000000..8e119db
--- /dev/null
@@ -0,0 +1,52 @@
+require 'chronic'
+require 'test/unit'
+
+class TestRepeaterDayName < Test::Unit::TestCase
+  
+  def setup
+    @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
+  end
+  
+  def test_match
+    token = Chronic::Token.new('saturday')
+    repeater = Chronic::Repeater.scan_for_day_names(token)
+    assert_equal Chronic::RepeaterDayName, repeater.class
+    assert_equal :saturday, repeater.type
+    
+    token = Chronic::Token.new('sunday')
+    repeater = Chronic::Repeater.scan_for_day_names(token)
+    assert_equal Chronic::RepeaterDayName, repeater.class
+    assert_equal :sunday, repeater.type
+  end
+
+  def test_next_future
+    mondays = Chronic::RepeaterDayName.new(:monday)
+    mondays.start = @now
+    
+    span = mondays.next(:future)
+    
+    assert_equal Time.local(2006, 8, 21), span.begin
+    assert_equal Time.local(2006, 8, 22), span.end 
+
+    span = mondays.next(:future)
+    
+    assert_equal Time.local(2006, 8, 28), span.begin
+    assert_equal Time.local(2006, 8, 29), span.end
+  end
+  
+  def test_next_past
+    mondays = Chronic::RepeaterDayName.new(:monday)
+    mondays.start = @now
+    
+    span = mondays.next(:past)
+    
+    assert_equal Time.local(2006, 8, 14), span.begin
+    assert_equal Time.local(2006, 8, 15), span.end 
+
+    span = mondays.next(:past)
+    
+    assert_equal Time.local(2006, 8, 7), span.begin
+    assert_equal Time.local(2006, 8, 8), span.end
+  end
+  
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/test/test_RepeaterFortnight.rb b/framework/Date_Parser/chronic/test/test_RepeaterFortnight.rb
new file mode 100644 (file)
index 0000000..cb80c43
--- /dev/null
@@ -0,0 +1,63 @@
+require 'chronic'
+require 'test/unit'
+
+class TestRepeaterFortnight < Test::Unit::TestCase
+  
+  def setup
+    @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
+  end
+
+  def test_next_future
+    fortnights = Chronic::RepeaterFortnight.new(:fortnight)
+    fortnights.start = @now
+    
+    next_fortnight = fortnights.next(:future)
+    assert_equal Time.local(2006, 8, 20), next_fortnight.begin
+    assert_equal Time.local(2006, 9, 3), next_fortnight.end
+    
+    next_next_fortnight = fortnights.next(:future)
+    assert_equal Time.local(2006, 9, 3), next_next_fortnight.begin
+    assert_equal Time.local(2006, 9, 17), next_next_fortnight.end
+  end
+  
+  def test_next_past
+    fortnights = Chronic::RepeaterFortnight.new(:fortnight)
+    fortnights.start = @now
+    
+    last_fortnight = fortnights.next(:past)
+    assert_equal Time.local(2006, 7, 30), last_fortnight.begin
+    assert_equal Time.local(2006, 8, 13), last_fortnight.end
+    
+    last_last_fortnight = fortnights.next(:past)
+    assert_equal Time.local(2006, 7, 16), last_last_fortnight.begin
+    assert_equal Time.local(2006, 7, 30), last_last_fortnight.end
+  end
+  
+  def test_this_future
+    fortnights = Chronic::RepeaterFortnight.new(:fortnight)
+    fortnights.start = @now
+    
+    this_fortnight = fortnights.this(:future)
+    assert_equal Time.local(2006, 8, 16, 15), this_fortnight.begin
+    assert_equal Time.local(2006, 8, 27), this_fortnight.end
+  end
+  
+  def test_this_past
+    fortnights = Chronic::RepeaterFortnight.new(:fortnight)
+    fortnights.start = @now
+    
+    this_fortnight = fortnights.this(:past)
+    assert_equal Time.local(2006, 8, 13, 0), this_fortnight.begin
+    assert_equal Time.local(2006, 8, 16, 14), this_fortnight.end
+  end
+  
+  def test_offset
+    span = Chronic::Span.new(@now, @now + 1)
+    
+    offset_span = Chronic::RepeaterWeek.new(:week).offset(span, 3, :future)
+    
+    assert_equal Time.local(2006, 9, 6, 14), offset_span.begin
+    assert_equal Time.local(2006, 9, 6, 14, 0, 1), offset_span.end
+  end
+  
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/test/test_RepeaterHour.rb b/framework/Date_Parser/chronic/test/test_RepeaterHour.rb
new file mode 100644 (file)
index 0000000..48f37c4
--- /dev/null
@@ -0,0 +1,65 @@
+require 'chronic'
+require 'test/unit'
+
+class TestRepeaterHour < Test::Unit::TestCase
+  
+  def setup
+    @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
+  end
+
+  def test_next_future
+    hours = Chronic::RepeaterHour.new(:hour)
+    hours.start = @now
+    
+    next_hour = hours.next(:future)
+    assert_equal Time.local(2006, 8, 16, 15), next_hour.begin
+    assert_equal Time.local(2006, 8, 16, 16), next_hour.end
+    
+    next_next_hour = hours.next(:future)
+    assert_equal Time.local(2006, 8, 16, 16), next_next_hour.begin
+    assert_equal Time.local(2006, 8, 16, 17), next_next_hour.end
+  end
+  
+  def test_next_past
+    hours = Chronic::RepeaterHour.new(:hour)
+    hours.start = @now
+    
+    past_hour = hours.next(:past)
+    assert_equal Time.local(2006, 8, 16, 13), past_hour.begin
+    assert_equal Time.local(2006, 8, 16, 14), past_hour.end
+    
+    past_past_hour = hours.next(:past)
+    assert_equal Time.local(2006, 8, 16, 12), past_past_hour.begin
+    assert_equal Time.local(2006, 8, 16, 13), past_past_hour.end
+  end
+  
+  def test_this
+    @now = Time.local(2006, 8, 16, 14, 30)
+    
+    hours = Chronic::RepeaterHour.new(:hour)
+    hours.start = @now
+    
+    this_hour = hours.this(:future)
+    assert_equal Time.local(2006, 8, 16, 14, 31), this_hour.begin
+    assert_equal Time.local(2006, 8, 16, 15), this_hour.end
+    
+    this_hour = hours.this(:past)
+    assert_equal Time.local(2006, 8, 16, 14), this_hour.begin
+    assert_equal Time.local(2006, 8, 16, 14, 30), this_hour.end
+  end
+  
+  def test_offset
+    span = Chronic::Span.new(@now, @now + 1)
+    
+    offset_span = Chronic::RepeaterHour.new(:hour).offset(span, 3, :future)
+    
+    assert_equal Time.local(2006, 8, 16, 17), offset_span.begin
+    assert_equal Time.local(2006, 8, 16, 17, 0, 1), offset_span.end
+    
+    offset_span = Chronic::RepeaterHour.new(:hour).offset(span, 24, :past)
+    
+    assert_equal Time.local(2006, 8, 15, 14), offset_span.begin
+    assert_equal Time.local(2006, 8, 15, 14, 0, 1), offset_span.end
+  end
+  
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/test/test_RepeaterMonth.rb b/framework/Date_Parser/chronic/test/test_RepeaterMonth.rb
new file mode 100644 (file)
index 0000000..d0609c5
--- /dev/null
@@ -0,0 +1,47 @@
+require 'chronic'
+require 'test/unit'
+
+class TestRepeaterMonth < Test::Unit::TestCase
+  
+  def setup
+    # Wed Aug 16 14:00:00 2006
+    @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
+  end
+  
+  def test_offset_by
+    # future
+    
+    time = Chronic::RepeaterMonth.new(:month).offset_by(@now, 1, :future)
+    assert_equal Time.local(2006, 9, 16, 14), time
+    
+    time = Chronic::RepeaterMonth.new(:month).offset_by(@now, 5, :future)
+    assert_equal Time.local(2007, 1, 16, 14), time
+    
+    # past
+    
+    time = Chronic::RepeaterMonth.new(:month).offset_by(@now, 1, :past)
+    assert_equal Time.local(2006, 7, 16, 14), time
+    
+    time = Chronic::RepeaterMonth.new(:month).offset_by(@now, 10, :past)
+    assert_equal Time.local(2005, 10, 16, 14), time
+  end
+  
+  def test_offset
+    # future
+    
+    span = Chronic::Span.new(@now, @now + 60)
+    offset_span = Chronic::RepeaterMonth.new(:month).offset(span, 1, :future)
+    
+    assert_equal Time.local(2006, 9, 16, 14), offset_span.begin
+    assert_equal Time.local(2006, 9, 16, 14, 1), offset_span.end
+    
+   # past
+   
+   span = Chronic::Span.new(@now, @now + 60)
+   offset_span = Chronic::RepeaterMonth.new(:month).offset(span, 1, :past)
+   
+   assert_equal Time.local(2006, 7, 16, 14), offset_span.begin
+   assert_equal Time.local(2006, 7, 16, 14, 1), offset_span.end
+  end
+  
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/test/test_RepeaterMonthName.rb b/framework/Date_Parser/chronic/test/test_RepeaterMonthName.rb
new file mode 100644 (file)
index 0000000..6326a45
--- /dev/null
@@ -0,0 +1,57 @@
+require 'chronic'
+require 'test/unit'
+
+class TestRepeaterMonthName < Test::Unit::TestCase
+  
+  def setup
+    # Wed Aug 16 14:00:00 2006
+    @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
+  end
+  
+  def test_next
+    # future
+    
+    mays = Chronic::RepeaterMonthName.new(:may)
+    mays.start = @now
+    
+    next_may = mays.next(:future)
+    assert_equal Time.local(2007, 5), next_may.begin
+    assert_equal Time.local(2007, 6), next_may.end
+    
+    next_next_may = mays.next(:future)
+    assert_equal Time.local(2008, 5), next_next_may.begin
+    assert_equal Time.local(2008, 6), next_next_may.end
+    
+    decembers = Chronic::RepeaterMonthName.new(:december)
+    decembers.start = @now
+    
+    next_december = decembers.next(:future)
+    assert_equal Time.local(2006, 12), next_december.begin
+    assert_equal Time.local(2007, 1), next_december.end
+    
+    # past
+    
+    mays = Chronic::RepeaterMonthName.new(:may)
+    mays.start = @now
+    
+    assert_equal Time.local(2006, 5), mays.next(:past).begin
+    assert_equal Time.local(2005, 5), mays.next(:past).begin
+  end
+  
+  def test_this
+    octobers = Chronic::RepeaterMonthName.new(:october)
+    octobers.start = @now
+    
+    this_october = octobers.this(:future)
+    assert_equal Time.local(2006, 10, 1), this_october.begin
+    assert_equal Time.local(2006, 11, 1), this_october.end
+    
+    aprils = Chronic::RepeaterMonthName.new(:april)
+    aprils.start = @now
+    
+    this_april = aprils.this(:past)
+    assert_equal Time.local(2006, 4, 1), this_april.begin
+    assert_equal Time.local(2006, 5, 1), this_april.end
+  end
+  
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/test/test_RepeaterTime.rb b/framework/Date_Parser/chronic/test/test_RepeaterTime.rb
new file mode 100644 (file)
index 0000000..bb27735
--- /dev/null
@@ -0,0 +1,72 @@
+require 'chronic'
+require 'test/unit'
+
+class TestRepeaterTime < Test::Unit::TestCase
+  
+  def setup
+    # Wed Aug 16 14:00:00 2006
+    @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
+  end
+  
+  def test_next_future
+    t = Chronic::RepeaterTime.new('4:00')
+    t.start = @now
+    
+    assert_equal Time.local(2006, 8, 16, 16), t.next(:future).begin
+    assert_equal Time.local(2006, 8, 17, 4), t.next(:future).begin
+    
+    t = Chronic::RepeaterTime.new('13:00')
+    t.start = @now
+    
+    assert_equal Time.local(2006, 8, 17, 13), t.next(:future).begin
+    assert_equal Time.local(2006, 8, 18, 13), t.next(:future).begin
+    
+    t = Chronic::RepeaterTime.new('0400')
+    t.start = @now
+    
+    assert_equal Time.local(2006, 8, 17, 4), t.next(:future).begin
+    assert_equal Time.local(2006, 8, 18, 4), t.next(:future).begin
+  end
+  
+  def test_next_past
+    t = Chronic::RepeaterTime.new('4:00')
+    t.start = @now
+    
+    assert_equal Time.local(2006, 8, 16, 4), t.next(:past).begin
+    assert_equal Time.local(2006, 8, 15, 16), t.next(:past).begin
+    
+    t = Chronic::RepeaterTime.new('13:00')
+    t.start = @now
+    
+    assert_equal Time.local(2006, 8, 16, 13), t.next(:past).begin
+    assert_equal Time.local(2006, 8, 15, 13), t.next(:past).begin
+  end
+
+  def test_type
+    t1 = Chronic::RepeaterTime.new('4')
+    assert_equal 14_400, t1.type.time
+    
+    t1 = Chronic::RepeaterTime.new('14')
+    assert_equal 50_400, t1.type.time
+    
+    t1 = Chronic::RepeaterTime.new('4:00')
+    assert_equal 14_400, t1.type.time
+    
+    t1 = Chronic::RepeaterTime.new('4:30')
+    assert_equal 16_200, t1.type.time
+    
+    t1 = Chronic::RepeaterTime.new('1400')
+    assert_equal 50_400, t1.type.time
+    
+    t1 = Chronic::RepeaterTime.new('0400')
+    assert_equal 14_400, t1.type.time
+    
+    t1 = Chronic::RepeaterTime.new('04')
+    assert_equal 14_400, t1.type.time
+    
+    t1 = Chronic::RepeaterTime.new('400')
+    assert_equal 14_400, t1.type.time
+  end
+
+  
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/test/test_RepeaterWeek.rb b/framework/Date_Parser/chronic/test/test_RepeaterWeek.rb
new file mode 100644 (file)
index 0000000..084ef4e
--- /dev/null
@@ -0,0 +1,63 @@
+require 'chronic'
+require 'test/unit'
+
+class TestRepeaterWeek < Test::Unit::TestCase
+  
+  def setup
+    @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
+  end
+
+  def test_next_future
+    weeks = Chronic::RepeaterWeek.new(:week)
+    weeks.start = @now
+    
+    next_week = weeks.next(:future)
+    assert_equal Time.local(2006, 8, 20), next_week.begin
+    assert_equal Time.local(2006, 8, 27), next_week.end
+    
+    next_next_week = weeks.next(:future)
+    assert_equal Time.local(2006, 8, 27), next_next_week.begin
+    assert_equal Time.local(2006, 9, 3), next_next_week.end
+  end
+  
+  def test_next_past
+    weeks = Chronic::RepeaterWeek.new(:week)
+    weeks.start = @now
+    
+    last_week = weeks.next(:past)
+    assert_equal Time.local(2006, 8, 6), last_week.begin
+    assert_equal Time.local(2006, 8, 13), last_week.end
+    
+    last_last_week = weeks.next(:past)
+    assert_equal Time.local(2006, 7, 30), last_last_week.begin
+    assert_equal Time.local(2006, 8, 6), last_last_week.end
+  end
+  
+  def test_this_future
+    weeks = Chronic::RepeaterWeek.new(:week)
+    weeks.start = @now
+    
+    this_week = weeks.this(:future)
+    assert_equal Time.local(2006, 8, 16, 15), this_week.begin
+    assert_equal Time.local(2006, 8, 20), this_week.end
+  end
+  
+  def test_this_past
+    weeks = Chronic::RepeaterWeek.new(:week)
+    weeks.start = @now
+    
+    this_week = weeks.this(:past)
+    assert_equal Time.local(2006, 8, 13, 0), this_week.begin
+    assert_equal Time.local(2006, 8, 16, 14), this_week.end
+  end
+  
+  def test_offset
+    span = Chronic::Span.new(@now, @now + 1)
+    
+    offset_span = Chronic::RepeaterWeek.new(:week).offset(span, 3, :future)
+    
+    assert_equal Time.local(2006, 9, 6, 14), offset_span.begin
+    assert_equal Time.local(2006, 9, 6, 14, 0, 1), offset_span.end
+  end
+  
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/test/test_RepeaterWeekend.rb b/framework/Date_Parser/chronic/test/test_RepeaterWeekend.rb
new file mode 100644 (file)
index 0000000..44dc087
--- /dev/null
@@ -0,0 +1,75 @@
+require 'chronic'
+require 'test/unit'
+
+class TestRepeaterWeekend < Test::Unit::TestCase
+  
+  def setup
+    # Wed Aug 16 14:00:00 2006
+    @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
+  end
+  
+  def test_next_future
+    weekend = Chronic::RepeaterWeekend.new(:weekend)
+    weekend.start = @now
+    
+    next_weekend = weekend.next(:future)
+    assert_equal Time.local(2006, 8, 19), next_weekend.begin
+    assert_equal Time.local(2006, 8, 21), next_weekend.end
+  end
+  
+  def test_next_past
+    weekend = Chronic::RepeaterWeekend.new(:weekend)
+    weekend.start = @now
+    
+    next_weekend = weekend.next(:past)
+    assert_equal Time.local(2006, 8, 12), next_weekend.begin
+    assert_equal Time.local(2006, 8, 14), next_weekend.end
+  end
+  
+  def test_this_future
+    weekend = Chronic::RepeaterWeekend.new(:weekend)
+    weekend.start = @now
+    
+    next_weekend = weekend.this(:future)
+    assert_equal Time.local(2006, 8, 19), next_weekend.begin
+    assert_equal Time.local(2006, 8, 21), next_weekend.end
+  end
+  
+  def test_this_past
+    weekend = Chronic::RepeaterWeekend.new(:weekend)
+    weekend.start = @now
+    
+    next_weekend = weekend.this(:past)
+    assert_equal Time.local(2006, 8, 12), next_weekend.begin
+    assert_equal Time.local(2006, 8, 14), next_weekend.end
+  end
+  
+  def test_this_none
+    weekend = Chronic::RepeaterWeekend.new(:weekend)
+    weekend.start = @now
+    
+    next_weekend = weekend.this(:future)
+    assert_equal Time.local(2006, 8, 19), next_weekend.begin
+    assert_equal Time.local(2006, 8, 21), next_weekend.end
+  end
+  
+  def test_offset
+    span = Chronic::Span.new(@now, @now + 1)
+    
+    offset_span = Chronic::RepeaterWeekend.new(:weekend).offset(span, 3, :future)
+    
+    assert_equal Time.local(2006, 9, 2), offset_span.begin
+    assert_equal Time.local(2006, 9, 2, 0, 0, 1), offset_span.end
+    
+    offset_span = Chronic::RepeaterWeekend.new(:weekend).offset(span, 1, :past)
+    
+    assert_equal Time.local(2006, 8, 12), offset_span.begin
+    assert_equal Time.local(2006, 8, 12, 0, 0, 1), offset_span.end
+    
+    offset_span = Chronic::RepeaterWeekend.new(:weekend).offset(span, 0, :future)
+    
+    assert_equal Time.local(2006, 8, 12), offset_span.begin
+    assert_equal Time.local(2006, 8, 12, 0, 0, 1), offset_span.end
+  end
+  
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/test/test_RepeaterYear.rb b/framework/Date_Parser/chronic/test/test_RepeaterYear.rb
new file mode 100644 (file)
index 0000000..eaebe25
--- /dev/null
@@ -0,0 +1,63 @@
+require 'chronic'
+require 'test/unit'
+
+class TestRepeaterYear < Test::Unit::TestCase
+  
+  def setup
+    @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
+  end
+
+  def test_next_future
+    years = Chronic::RepeaterYear.new(:year)
+    years.start = @now
+    
+    next_year = years.next(:future)
+    assert_equal Time.local(2007, 1, 1), next_year.begin
+    assert_equal Time.local(2008, 1, 1), next_year.end
+    
+    next_next_year = years.next(:future)
+    assert_equal Time.local(2008, 1, 1), next_next_year.begin
+    assert_equal Time.local(2009, 1, 1), next_next_year.end
+  end
+  
+  def test_next_past
+    years = Chronic::RepeaterYear.new(:year)
+    years.start = @now
+    
+    last_year = years.next(:past)
+    assert_equal Time.local(2005, 1, 1), last_year.begin
+    assert_equal Time.local(2006, 1, 1), last_year.end
+    
+    last_last_year = years.next(:past)
+    assert_equal Time.local(2004, 1, 1), last_last_year.begin
+    assert_equal Time.local(2005, 1, 1), last_last_year.end
+  end
+  
+  def test_this
+    years = Chronic::RepeaterYear.new(:year)
+    years.start = @now
+    
+    this_year = years.this(:future)
+    assert_equal Time.local(2006, 8, 17), this_year.begin
+    assert_equal Time.local(2007, 1, 1), this_year.end
+    
+    this_year = years.this(:past)
+    assert_equal Time.local(2006, 1, 1), this_year.begin
+    assert_equal Time.local(2006, 8, 16), this_year.end
+  end
+  
+  def test_offset
+    span = Chronic::Span.new(@now, @now + 1)
+    
+    offset_span = Chronic::RepeaterYear.new(:year).offset(span, 3, :future)
+    
+    assert_equal Time.local(2009, 8, 16, 14), offset_span.begin
+    assert_equal Time.local(2009, 8, 16, 14, 0, 1), offset_span.end
+    
+    offset_span = Chronic::RepeaterYear.new(:year).offset(span, 10, :past)
+    
+    assert_equal Time.local(1996, 8, 16, 14), offset_span.begin
+    assert_equal Time.local(1996, 8, 16, 14, 0, 1), offset_span.end
+  end
+  
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/test/test_Span.rb b/framework/Date_Parser/chronic/test/test_Span.rb
new file mode 100644 (file)
index 0000000..099455a
--- /dev/null
@@ -0,0 +1,24 @@
+require 'chronic'
+require 'test/unit'
+
+class TestSpan < Test::Unit::TestCase
+  
+  def setup
+    # Wed Aug 16 14:00:00 UTC 2006
+    @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
+  end
+
+  def test_span_width
+    span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0))
+    assert_equal (60 * 60 * 24), span.width
+  end
+  
+  def test_span_math
+    s = Chronic::Span.new(1, 2)
+    assert_equal 2, (s + 1).begin
+    assert_equal 3, (s + 1).end
+    assert_equal 0, (s - 1).begin
+    assert_equal 1, (s - 1).end
+  end
+  
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/test/test_Time.rb b/framework/Date_Parser/chronic/test/test_Time.rb
new file mode 100644 (file)
index 0000000..3ffc9c0
--- /dev/null
@@ -0,0 +1,50 @@
+require 'chronic'
+require 'test/unit'
+
+class TestTime < Test::Unit::TestCase
+  
+  def setup
+  end
+  
+  def test_normal
+    assert_equal Time.local(2006, 1, 2, 0, 0, 0), Time.construct(2006, 1, 2, 0, 0, 0)
+    assert_equal Time.local(2006, 1, 2, 3, 0, 0), Time.construct(2006, 1, 2, 3, 0, 0)
+    assert_equal Time.local(2006, 1, 2, 3, 4, 0), Time.construct(2006, 1, 2, 3, 4, 0)
+    assert_equal Time.local(2006, 1, 2, 3, 4, 5), Time.construct(2006, 1, 2, 3, 4, 5)
+  end
+  
+  def test_second_overflow
+    assert_equal Time.local(2006, 1, 1, 0, 1, 30), Time.construct(2006, 1, 1, 0, 0, 90)
+    assert_equal Time.local(2006, 1, 1, 0, 5, 0), Time.construct(2006, 1, 1, 0, 0, 300)
+  end
+  
+  def test_minute_overflow
+    assert_equal Time.local(2006, 1, 1, 1, 30), Time.construct(2006, 1, 1, 0, 90)
+    assert_equal Time.local(2006, 1, 1, 5), Time.construct(2006, 1, 1, 0, 300)
+  end
+  
+  def test_hour_overflow
+    assert_equal Time.local(2006, 1, 2, 12), Time.construct(2006, 1, 1, 36)
+    assert_equal Time.local(2006, 1, 7), Time.construct(2006, 1, 1, 144)
+  end
+  
+  def test_day_overflow
+    assert_equal Time.local(2006, 2, 1), Time.construct(2006, 1, 32)
+    assert_equal Time.local(2006, 3, 5), Time.construct(2006, 2, 33)
+    assert_equal Time.local(2004, 3, 4), Time.construct(2004, 2, 33)
+    assert_equal Time.local(2000, 3, 5), Time.construct(2000, 2, 33)
+    
+    assert_nothing_raised do
+      Time.construct(2006, 1, 56)
+    end
+    
+    assert_raise(RuntimeError) do
+      Time.construct(2006, 1, 57)
+    end
+  end
+  
+  def test_month_overflow
+    assert_equal Time.local(2006, 1), Time.construct(2005, 13)
+    assert_equal Time.local(2005, 12), Time.construct(2000, 72)
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/test/test_Token.rb b/framework/Date_Parser/chronic/test/test_Token.rb
new file mode 100644 (file)
index 0000000..80463d1
--- /dev/null
@@ -0,0 +1,26 @@
+require 'chronic'
+require 'test/unit'
+
+class TestToken < Test::Unit::TestCase
+  
+  def setup
+    # Wed Aug 16 14:00:00 UTC 2006
+    @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
+  end
+  
+  def test_token
+    token = Chronic::Token.new('foo')
+    assert_equal 0, token.tags.size
+    assert !token.tagged?
+    token.tag("mytag")
+    assert_equal 1, token.tags.size
+    assert token.tagged?
+    assert_equal String, token.get_tag(String).class
+    token.tag(5)
+    assert_equal 2, token.tags.size
+    token.untag(String)
+    assert_equal 1, token.tags.size
+    assert_equal 'foo', token.word
+  end
+  
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/chronic/test/test_parsing.rb b/framework/Date_Parser/chronic/test/test_parsing.rb
new file mode 100644 (file)
index 0000000..d2216d6
--- /dev/null
@@ -0,0 +1,614 @@
+require 'chronic'
+require 'time'
+require 'test/unit'
+
+class TestParsing < Test::Unit::TestCase
+  # Wed Aug 16 14:00:00 UTC 2006
+  TIME_2006_08_16_14_00_00 = Time.local(2006, 8, 16, 14, 0, 0, 0)
+  
+  def setup
+    @time_2006_08_16_14_00_00 = TIME_2006_08_16_14_00_00
+  end
+  
+  def test_parse_guess_dates
+    # rm_sd
+
+    time = parse_now("may 27")
+    assert_equal Time.local(2007, 5, 27, 12), time
+    
+    time = parse_now("may 28", :context => :past)
+    assert_equal Time.local(2006, 5, 28, 12), time
+    
+    time = parse_now("may 28 5pm", :context => :past)
+    assert_equal Time.local(2006, 5, 28, 17), time
+    
+    time = parse_now("may 28 at 5pm", :context => :past)
+    assert_equal Time.local(2006, 5, 28, 17), time
+    
+    time = parse_now("may 28 at 5:32.19pm", :context => :past)
+    assert_equal Time.local(2006, 5, 28, 17, 32, 19), time
+    
+    # rm_od
+    
+    time = parse_now("may 27th")
+    assert_equal Time.local(2007, 5, 27, 12), time
+    
+    time = parse_now("may 27th", :context => :past)
+    assert_equal Time.local(2006, 5, 27, 12), time
+    
+    time = parse_now("may 27th 5:00 pm", :context => :past)
+    assert_equal Time.local(2006, 5, 27, 17), time
+    
+    time = parse_now("may 27th at 5pm", :context => :past)
+    assert_equal Time.local(2006, 5, 27, 17), time
+    
+    time = parse_now("may 27th at 5", :ambiguous_time_range => :none)
+    assert_equal Time.local(2007, 5, 27, 5), time
+    
+    # rm_sy
+    
+    time = parse_now("June 1979")
+    assert_equal Time.local(1979, 6, 16, 0), time
+    
+    time = parse_now("dec 79")
+    assert_equal Time.local(1979, 12, 16, 12), time
+    
+    # rm_sd_sy
+    
+    time = parse_now("jan 3 2010")
+    assert_equal Time.local(2010, 1, 3, 12), time
+    
+    time = parse_now("jan 3 2010 midnight")
+    assert_equal Time.local(2010, 1, 4, 0), time
+    
+    time = parse_now("jan 3 2010 at midnight")
+    assert_equal Time.local(2010, 1, 4, 0), time
+    
+    time = parse_now("jan 3 2010 at 4", :ambiguous_time_range => :none)
+    assert_equal Time.local(2010, 1, 3, 4), time
+    
+    #time = parse_now("January 12, '00")
+    #assert_equal Time.local(2000, 1, 12, 12), time
+    
+    time = parse_now("may 27 79")
+    assert_equal Time.local(1979, 5, 27, 12), time
+    
+    time = parse_now("may 27 79 4:30")
+    assert_equal Time.local(1979, 5, 27, 16, 30), time
+    
+    time = parse_now("may 27 79 at 4:30", :ambiguous_time_range => :none)
+    assert_equal Time.local(1979, 5, 27, 4, 30), time
+    
+    # sd_rm_sy
+
+    time = parse_now("3 jan 2010")
+    assert_equal Time.local(2010, 1, 3, 12), time
+    
+    time = parse_now("3 jan 2010 4pm")
+    assert_equal Time.local(2010, 1, 3, 16), time
+    
+    # sm_sd_sy
+    
+    time = parse_now("5/27/1979")
+    assert_equal Time.local(1979, 5, 27, 12), time
+    
+    time = parse_now("5/27/1979 4am")
+    assert_equal Time.local(1979, 5, 27, 4), time
+    
+    # sd_sm_sy
+    
+    time = parse_now("27/5/1979")
+    assert_equal Time.local(1979, 5, 27, 12), time
+    
+    time = parse_now("27/5/1979 @ 0700")
+    assert_equal Time.local(1979, 5, 27, 7), time
+    
+    # sm_sy
+    
+    time = parse_now("05/06")
+    assert_equal Time.local(2006, 5, 16, 12), time
+    
+    time = parse_now("12/06")
+    assert_equal Time.local(2006, 12, 16, 12), time
+    
+    time = parse_now("13/06")
+    assert_equal nil, time
+    
+    # sy_sm_sd
+    
+    time = parse_now("2000-1-1")
+    assert_equal Time.local(2000, 1, 1, 12), time
+    
+    time = parse_now("2006-08-20")
+    assert_equal Time.local(2006, 8, 20, 12), time
+    
+    time = parse_now("2006-08-20 7pm")
+    assert_equal Time.local(2006, 8, 20, 19), time
+    
+    time = parse_now("2006-08-20 03:00")
+    assert_equal Time.local(2006, 8, 20, 3), time
+    
+    time = parse_now("2006-08-20 03:30:30")
+    assert_equal Time.local(2006, 8, 20, 3, 30, 30), time
+    
+    time = parse_now("2006-08-20 15:30:30")
+    assert_equal Time.local(2006, 8, 20, 15, 30, 30), time
+    
+    time = parse_now("2006-08-20 15:30.30")
+    assert_equal Time.local(2006, 8, 20, 15, 30, 30), time
+    
+    # rdn_rm_rd_rt_rtz_ry
+    
+    time = parse_now("Mon Apr 02 17:00:00 PDT 2007")
+    assert_equal Time.local(2007, 4, 2, 17), time
+    
+    now = Time.now
+    time = parse_now(now.to_s)
+    assert_equal now.to_s, time.to_s
+    
+    # rm_sd_rt
+    
+    #time = parse_now("jan 5 13:00")
+    #assert_equal Time.local(2007, 1, 5, 13), time
+    
+    # due to limitations of the Time class, these don't work
+    
+    time = parse_now("may 40")
+    assert_equal nil, time
+    
+    time = parse_now("may 27 40")
+    assert_equal nil, time
+    
+    time = parse_now("1800-08-20")
+    assert_equal nil, time
+  end
+  
+  def test_foo
+    Chronic.parse('two months ago this friday')
+  end
+
+  def test_parse_guess_r
+    time = parse_now("friday")
+    assert_equal Time.local(2006, 8, 18, 12), time
+    
+    time = parse_now("tue")
+    assert_equal Time.local(2006, 8, 22, 12), time
+    
+    time = parse_now("5")
+    assert_equal Time.local(2006, 8, 16, 17), time
+    
+    time = Chronic.parse("5", :now => Time.local(2006, 8, 16, 3, 0, 0, 0), :ambiguous_time_range => :none)
+    assert_equal Time.local(2006, 8, 16, 5), time
+    
+    time = parse_now("13:00")
+    assert_equal Time.local(2006, 8, 17, 13), time
+    
+    time = parse_now("13:45")
+    assert_equal Time.local(2006, 8, 17, 13, 45), time
+    
+    time = parse_now("november")
+    assert_equal Time.local(2006, 11, 16), time
+  end
+  
+  def test_parse_guess_rr
+    time = parse_now("friday 13:00")
+    assert_equal Time.local(2006, 8, 18, 13), time
+    
+    time = parse_now("monday 4:00")
+    assert_equal Time.local(2006, 8, 21, 16), time
+    
+    time = parse_now("sat 4:00", :ambiguous_time_range => :none)
+    assert_equal Time.local(2006, 8, 19, 4), time
+    
+    time = parse_now("sunday 4:20", :ambiguous_time_range => :none)
+    assert_equal Time.local(2006, 8, 20, 4, 20), time
+    
+    time = parse_now("4 pm")
+    assert_equal Time.local(2006, 8, 16, 16), time
+    
+    time = parse_now("4 am", :ambiguous_time_range => :none)
+    assert_equal Time.local(2006, 8, 16, 4), time
+    
+    time = parse_now("12 pm")
+    assert_equal Time.local(2006, 8, 16, 12), time
+    
+    time = parse_now("12:01 pm")
+    assert_equal Time.local(2006, 8, 16, 12, 1), time
+    
+    time = parse_now("12:01 am")
+    assert_equal Time.local(2006, 8, 16, 0, 1), time
+    
+    time = parse_now("12 am")
+    assert_equal Time.local(2006, 8, 16), time
+    
+    time = parse_now("4:00 in the morning")
+    assert_equal Time.local(2006, 8, 16, 4), time
+    
+    time = parse_now("november 4")
+    assert_equal Time.local(2006, 11, 4, 12), time
+    
+    time = parse_now("aug 24")
+    assert_equal Time.local(2006, 8, 24, 12), time
+  end
+  
+  def test_parse_guess_rrr
+    time = parse_now("friday 1 pm")
+    assert_equal Time.local(2006, 8, 18, 13), time
+    
+    time = parse_now("friday 11 at night")
+    assert_equal Time.local(2006, 8, 18, 23), time
+    
+    time = parse_now("friday 11 in the evening")
+    assert_equal Time.local(2006, 8, 18, 23), time
+    
+    time = parse_now("sunday 6am")
+    assert_equal Time.local(2006, 8, 20, 6), time
+    
+    time = parse_now("friday evening at 7")
+    assert_equal Time.local(2006, 8, 18, 19), time
+  end
+  
+  def test_parse_guess_gr
+    # year
+    
+    time = parse_now("this year")
+    assert_equal Time.local(2006, 10, 24, 12, 30), time
+    
+    time = parse_now("this year", :context => :past)
+    assert_equal Time.local(2006, 4, 24, 12, 30), time
+    
+    # month
+    
+    time = parse_now("this month")
+    assert_equal Time.local(2006, 8, 24, 12), time
+    
+    time = parse_now("this month", :context => :past)
+    assert_equal Time.local(2006, 8, 8, 12), time
+    
+    time = Chronic.parse("next month", :now => Time.local(2006, 11, 15))
+    assert_equal Time.local(2006, 12, 16, 12), time
+    
+    # month name
+    
+    time = parse_now("last november")
+    assert_equal Time.local(2005, 11, 16), time
+    
+    # fortnight
+    
+    time = parse_now("this fortnight")
+    assert_equal Time.local(2006, 8, 21, 19, 30), time
+    
+    time = parse_now("this fortnight", :context => :past)
+    assert_equal Time.local(2006, 8, 14, 19), time
+    
+    # week
+    
+    time = parse_now("this week")
+    assert_equal Time.local(2006, 8, 18, 7, 30), time
+    
+    time = parse_now("this week", :context => :past)
+    assert_equal Time.local(2006, 8, 14, 19), time
+    
+    # weekend
+    
+    time = parse_now("this weekend")
+    assert_equal Time.local(2006, 8, 20), time
+    
+    time = parse_now("this weekend", :context => :past)
+    assert_equal Time.local(2006, 8, 13), time
+    
+    time = parse_now("last weekend")
+    assert_equal Time.local(2006, 8, 13), time
+    
+    # day
+    
+    time = parse_now("this day")
+    assert_equal Time.local(2006, 8, 16, 19, 30), time
+    
+    time = parse_now("this day", :context => :past)
+    assert_equal Time.local(2006, 8, 16, 7), time
+    
+    time = parse_now("today")
+    assert_equal Time.local(2006, 8, 16, 19, 30), time
+    
+    time = parse_now("yesterday")
+    assert_equal Time.local(2006, 8, 15, 12), time
+    
+    time = parse_now("tomorrow")
+    assert_equal Time.local(2006, 8, 17, 12), time
+    
+    # day name
+    
+    time = parse_now("this tuesday")
+    assert_equal Time.local(2006, 8, 22, 12), time
+    
+    time = parse_now("next tuesday")
+    assert_equal Time.local(2006, 8, 22, 12), time
+    
+    time = parse_now("last tuesday")
+    assert_equal Time.local(2006, 8, 15, 12), time
+    
+    time = parse_now("this wed")
+    assert_equal Time.local(2006, 8, 23, 12), time
+    
+    time = parse_now("next wed")
+    assert_equal Time.local(2006, 8, 23, 12), time
+    
+    time = parse_now("last wed")
+    assert_equal Time.local(2006, 8, 9, 12), time
+    
+    # day portion
+    
+    time = parse_now("this morning")
+    assert_equal Time.local(2006, 8, 16, 9), time
+    
+    time = parse_now("tonight")
+    assert_equal Time.local(2006, 8, 16, 22), time
+    
+    # minute
+    
+    time = parse_now("next minute")
+    assert_equal Time.local(2006, 8, 16, 14, 1, 30), time
+    
+    # second
+    
+    time = parse_now("this second")
+    assert_equal Time.local(2006, 8, 16, 14), time
+    
+    time = parse_now("this second", :context => :past)
+    assert_equal Time.local(2006, 8, 16, 14), time
+    
+    time = parse_now("next second")
+    assert_equal Time.local(2006, 8, 16, 14, 0, 1), time
+    
+    time = parse_now("last second")
+    assert_equal Time.local(2006, 8, 16, 13, 59, 59), time
+  end
+  
+  def test_parse_guess_grr    
+    time = parse_now("yesterday at 4:00")
+    assert_equal Time.local(2006, 8, 15, 16), time
+    
+    time = parse_now("today at 9:00")
+    assert_equal Time.local(2006, 8, 16, 9), time
+    
+    time = parse_now("today at 2100")
+    assert_equal Time.local(2006, 8, 16, 21), time
+    
+    time = parse_now("this day at 0900")
+    assert_equal Time.local(2006, 8, 16, 9), time
+    
+    time = parse_now("tomorrow at 0900")
+    assert_equal Time.local(2006, 8, 17, 9), time
+    
+    time = parse_now("yesterday at 4:00", :ambiguous_time_range => :none)
+    assert_equal Time.local(2006, 8, 15, 4), time
+    
+    time = parse_now("last friday at 4:00")
+    assert_equal Time.local(2006, 8, 11, 16), time
+    
+    time = parse_now("next wed 4:00")
+    assert_equal Time.local(2006, 8, 23, 16), time
+    
+    time = parse_now("yesterday afternoon")
+    assert_equal Time.local(2006, 8, 15, 15), time
+    
+    time = parse_now("last week tuesday")
+    assert_equal Time.local(2006, 8, 8, 12), time
+    
+    time = parse_now("tonight at 7")
+    assert_equal Time.local(2006, 8, 16, 19), time
+    
+    time = parse_now("tonight 7")
+    assert_equal Time.local(2006, 8, 16, 19), time
+    
+    time = parse_now("7 tonight")
+    assert_equal Time.local(2006, 8, 16, 19), time
+  end
+    
+  def test_parse_guess_grrr
+    time = parse_now("today at 6:00pm")
+    assert_equal Time.local(2006, 8, 16, 18), time
+    
+    time = parse_now("today at 6:00am")
+    assert_equal Time.local(2006, 8, 16, 6), time
+    
+    time = parse_now("this day 1800")
+    assert_equal Time.local(2006, 8, 16, 18), time
+    
+    time = parse_now("yesterday at 4:00pm")
+    assert_equal Time.local(2006, 8, 15, 16), time
+    
+    time = parse_now("tomorrow evening at 7")
+    assert_equal Time.local(2006, 8, 17, 19), time
+    
+    time = parse_now("tomorrow morning at 5:30")
+    assert_equal Time.local(2006, 8, 17, 5, 30), time
+    
+    time = parse_now("next monday at 12:01 am")
+    assert_equal Time.local(2006, 8, 21, 00, 1), time
+    
+    time = parse_now("next monday at 12:01 pm")
+    assert_equal Time.local(2006, 8, 21, 12, 1), time
+  end
+  
+  def test_parse_guess_rgr
+    time = parse_now("afternoon yesterday")
+    assert_equal Time.local(2006, 8, 15, 15), time
+    
+    time = parse_now("tuesday last week")
+    assert_equal Time.local(2006, 8, 8, 12), time
+  end
+  
+  def test_parse_guess_s_r_p
+    # past
+    
+    time = parse_now("3 years ago")
+    assert_equal Time.local(2003, 8, 16, 14), time
+    
+    time = parse_now("1 month ago")
+    assert_equal Time.local(2006, 7, 16, 14), time
+    
+    time = parse_now("1 fortnight ago")
+    assert_equal Time.local(2006, 8, 2, 14), time
+    
+    time = parse_now("2 fortnights ago")
+    assert_equal Time.local(2006, 7, 19, 14), time
+    
+    time = parse_now("3 weeks ago")
+    assert_equal Time.local(2006, 7, 26, 14), time
+    
+    time = parse_now("2 weekends ago")
+    assert_equal Time.local(2006, 8, 5), time
+    
+    time = parse_now("3 days ago")
+    assert_equal Time.local(2006, 8, 13, 14), time
+    
+    #time = parse_now("1 monday ago")
+    #assert_equal Time.local(2006, 8, 14, 12), time
+    
+    time = parse_now("5 mornings ago")
+    assert_equal Time.local(2006, 8, 12, 9), time
+    
+    time = parse_now("7 hours ago")
+    assert_equal Time.local(2006, 8, 16, 7), time
+    
+    time = parse_now("3 minutes ago")
+    assert_equal Time.local(2006, 8, 16, 13, 57), time
+    
+    time = parse_now("20 seconds before now")
+    assert_equal Time.local(2006, 8, 16, 13, 59, 40), time
+
+    # future
+    
+    time = parse_now("3 years from now")
+    assert_equal Time.local(2009, 8, 16, 14, 0, 0), time
+    
+    time = parse_now("6 months hence")
+    assert_equal Time.local(2007, 2, 16, 14), time
+    
+    time = parse_now("3 fortnights hence")
+    assert_equal Time.local(2006, 9, 27, 14), time
+    
+    time = parse_now("1 week from now")
+    assert_equal Time.local(2006, 8, 23, 14, 0, 0), time
+    
+    time = parse_now("1 weekend from now")
+    assert_equal Time.local(2006, 8, 19), time
+    
+    time = parse_now("2 weekends from now")
+    assert_equal Time.local(2006, 8, 26), time
+    
+    time = parse_now("1 day hence")
+    assert_equal Time.local(2006, 8, 17, 14), time
+    
+    time = parse_now("5 mornings hence")
+    assert_equal Time.local(2006, 8, 21, 9), time
+    
+    time = parse_now("1 hour from now")
+    assert_equal Time.local(2006, 8, 16, 15), time
+    
+    time = parse_now("20 minutes hence")
+    assert_equal Time.local(2006, 8, 16, 14, 20), time
+    
+    time = parse_now("20 seconds from now")
+    assert_equal Time.local(2006, 8, 16, 14, 0, 20), time
+    
+    time = Chronic.parse("2 months ago", :now => Time.parse("2007-03-07 23:30"))
+    assert_equal Time.local(2007, 1, 7, 23, 30), time
+  end
+  
+  def test_parse_guess_p_s_r
+    time = parse_now("in 3 hours")
+    assert_equal Time.local(2006, 8, 16, 17), time
+  end
+  
+  def test_parse_guess_s_r_p_a
+    # past
+    
+    time = parse_now("3 years ago tomorrow")
+    assert_equal Time.local(2003, 8, 17, 12), time
+    
+    time = parse_now("3 years ago this friday")
+    assert_equal Time.local(2003, 8, 18, 12), time
+    
+    time = parse_now("3 months ago saturday at 5:00 pm")
+    assert_equal Time.local(2006, 5, 19, 17), time
+    
+    time = parse_now("2 days from this second")
+    assert_equal Time.local(2006, 8, 18, 14), time
+    
+    time = parse_now("7 hours before tomorrow at midnight")
+    assert_equal Time.local(2006, 8, 17, 17), time
+    
+    # future
+  end
+  
+  def test_parse_guess_o_r_s_r
+    time = parse_now("3rd wednesday in november")
+    assert_equal Time.local(2006, 11, 15, 12), time
+    
+    time = parse_now("10th wednesday in november")
+    assert_equal nil, time
+    
+    # time = parse_now("3rd wednesday in 2007")
+    # assert_equal Time.local(2007, 1, 20, 12), time
+  end
+  
+  def test_parse_guess_o_r_g_r
+    time = parse_now("3rd month next year")
+    assert_equal Time.local(2007, 3, 16, 12, 30), time
+    
+    time = parse_now("3rd thursday this september")
+    assert_equal Time.local(2006, 9, 21, 12), time
+    
+    time = parse_now("4th day last week")
+    assert_equal Time.local(2006, 8, 9, 12), time
+  end
+  
+  def test_parse_guess_nonsense
+    time = parse_now("some stupid nonsense")
+    assert_equal nil, time
+  end
+  
+  def test_parse_span
+    span = parse_now("friday", :guess => false)
+    assert_equal Time.local(2006, 8, 18), span.begin
+    assert_equal Time.local(2006, 8, 19), span.end
+    
+    span = parse_now("november", :guess => false)
+    assert_equal Time.local(2006, 11), span.begin
+    assert_equal Time.local(2006, 12), span.end
+    
+    span = Chronic.parse("weekend" , :now => @time_2006_08_16_14_00_00, :guess => false)
+    assert_equal Time.local(2006, 8, 19), span.begin
+    assert_equal Time.local(2006, 8, 21), span.end
+  end
+  
+  def test_parse_words
+    assert_equal parse_now("33 days from now"), parse_now("thirty-three days from now")
+    assert_equal parse_now("2867532 seconds from now"), parse_now("two million eight hundred and sixty seven thousand five hundred and thirty two seconds from now")
+    assert_equal parse_now("may 10th"), parse_now("may tenth")
+  end
+  
+  def test_parse_only_complete_pointers
+    assert_equal parse_now("eat pasty buns today at 2pm"), @time_2006_08_16_14_00_00
+    assert_equal parse_now("futuristically speaking today at 2pm"), @time_2006_08_16_14_00_00
+    assert_equal parse_now("meeting today at 2pm"), @time_2006_08_16_14_00_00
+  end
+  
+  def test_argument_validation
+    assert_raise(Chronic::InvalidArgumentException) do
+      time = Chronic.parse("may 27", :foo => :bar)
+    end
+    
+    assert_raise(Chronic::InvalidArgumentException) do
+      time = Chronic.parse("may 27", :context => :bar)
+    end
+  end
+  
+  private
+  def parse_now(string, options={})
+    Chronic.parse(string, {:now => TIME_2006_08_16_14_00_00 }.merge(options))
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/lib/Horde/Date/Parser.php b/framework/Date_Parser/lib/Horde/Date/Parser.php
new file mode 100644 (file)
index 0000000..6406ce5
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+/**
+ *
+ */
+class Horde_Date_Parser
+{
+    public static function factory($args = array())
+    {
+        $locale = isset($args['locale']) ? $args['locale'] : null;
+        if ($locale && strtolower($locale) != 'base') {
+            $locale = str_replace(' ', '_', ucwords(str_replace('_', ' ', strtolower($locale))));
+            $class = 'Horde_Date_Parser_Locale_' . $locale;
+            if (class_exists($class)) {
+                return new $class($args);
+            }
+
+            $language = array_shift(explode('_', $locale));
+            if ($language != $locale) {
+                $class = 'Horde_Date_Parser_Locale_' . $language;
+                if (class_exists($class)) {
+                    return new $class($args);
+                }
+            }
+        }
+
+        return new Horde_Date_Parser_Locale_Base($args);
+    }
+
+    public static function componentFactory($component, $args = array())
+    {
+        $locale = isset($args['locale']) ? $args['locale'] : null;
+        if ($locale && strtolower($locale) != 'base') {
+            $locale = str_replace(' ', '_', ucwords(str_replace('_', ' ', strtolower($locale))));
+            $class = 'Horde_Date_Parser_Locale_' . $locale . '_' . $component;
+            if (class_exists($class)) {
+                return new $class($args);
+            }
+
+            $language = array_shift(explode('_', $locale));
+            if ($language != $locale) {
+                $class = 'Horde_Date_Parser_Locale_' . $language . '_' . $component;
+                if (class_exists($class)) {
+                    return new $class($args);
+                }
+            }
+        }
+
+        $class = 'Horde_Date_Parser_Locale_Base_' . $component;
+        return new $class($args);
+    }
+
+}
diff --git a/framework/Date_Parser/lib/Horde/Date/Parser/Exception.php b/framework/Date_Parser/lib/Horde/Date/Parser/Exception.php
new file mode 100644 (file)
index 0000000..c6a8ac0
--- /dev/null
@@ -0,0 +1,4 @@
+<?php
+class Horde_Date_Parser_Exception extends Exception
+{
+}
diff --git a/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base.php b/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base.php
new file mode 100644 (file)
index 0000000..436250c
--- /dev/null
@@ -0,0 +1,152 @@
+module Chronic
+  class << self
+
+    # Parses a string containing a natural language date or time. If the parser
+    # can find a date or time, either a Time or Chronic::Span will be returned
+    # (depending on the value of <tt>:guess</tt>). If no date or time can be found,
+    # +nil+ will be returned.
+    #
+    # Options are:
+    #
+    # [<tt>:context</tt>]
+    #     <tt>:past</tt> or <tt>:future</tt> (defaults to <tt>:future</tt>)
+    #
+    #     If your string represents a birthday, you can set <tt>:context</tt> to <tt>:past</tt>
+    #     and if an ambiguous string is given, it will assume it is in the
+    #     past. Specify <tt>:future</tt> or omit to set a future context.
+    #
+    # [<tt>:now</tt>]
+    #     Time (defaults to Time.now)
+    #
+    #     By setting <tt>:now</tt> to a Time, all computations will be based off
+    #     of that time instead of Time.now
+    #
+    # [<tt>:guess</tt>]
+    #     +true+ or +false+ (defaults to +true+)
+    #
+    #     By default, the parser will guess a single point in time for the
+    #     given date or time. If you'd rather have the entire time span returned,
+    #     set <tt>:guess</tt> to +false+ and a Chronic::Span will be returned.
+    #
+    # [<tt>:ambiguous_time_range</tt>]
+    #     Integer or <tt>:none</tt> (defaults to <tt>6</tt> (6am-6pm))
+    #
+    #     If an Integer is given, ambiguous times (like 5:00) will be
+    #     assumed to be within the range of that time in the AM to that time
+    #     in the PM. For example, if you set it to <tt>7</tt>, then the parser will
+    #     look for the time between 7am and 7pm. In the case of 5:00, it would
+    #     assume that means 5:00pm. If <tt>:none</tt> is given, no assumption
+    #     will be made, and the first matching instance of that time will
+    #     be used.
+    def parse(text, specified_options = {})
+      # get options and set defaults if necessary
+      default_options = {:context => :future,
+                         :now => Time.now,
+                         :guess => true,
+                         :ambiguous_time_range => 6}
+      options = default_options.merge specified_options
+
+      # ensure the specified options are valid
+      specified_options.keys.each do |key|
+        default_options.keys.include?(key) || raise(InvalidArgumentException, "#{key} is not a valid option key.")
+      end
+      [:past, :future, :none].include?(options[:context]) || raise(InvalidArgumentException, "Invalid value ':#{options[:context]}' for :context specified. Valid values are :past and :future.")
+
+      # store now for later =)
+      @now = options[:now]
+
+      # put the text into a normal format to ease scanning
+      text = self.pre_normalize(text)
+
+      # get base tokens for each word
+      @tokens = self.base_tokenize(text)
+
+      # scan the tokens with each token scanner
+      [Repeater].each do |tokenizer|
+        @tokens = tokenizer.scan(@tokens, options)
+      end
+
+      [Grabber, Pointer, Scalar, Ordinal, Separator, TimeZone].each do |tokenizer|
+        @tokens = tokenizer.scan(@tokens)
+      end
+
+      # strip any non-tagged tokens
+      @tokens = @tokens.select { |token| token.tagged? }
+
+      if Chronic.debug
+        puts "+---------------------------------------------------"
+        puts "| " + @tokens.to_s
+        puts "+---------------------------------------------------"
+      end
+
+      # do the heavy lifting
+      begin
+        span = self.tokens_to_span(@tokens, options)
+      rescue
+        raise
+        return nil
+      end
+
+      # guess a time within a span if required
+      if options[:guess]
+        return self.guess(span)
+      else
+        return span
+      end
+    end
+
+    # Clean up the specified input text by stripping unwanted characters,
+    # converting idioms to their canonical form, converting number words
+    # to numbers (three => 3), and converting ordinal words to numeric
+    # ordinals (third => 3rd)
+    def pre_normalize(text) #:nodoc:
+      normalized_text = text.to_s.downcase
+      normalized_text = numericize_numbers(normalized_text)
+      normalized_text.gsub!(/['"\.]/, '')
+      normalized_text.gsub!(/([\/\-\,\@])/) { ' ' + $1 + ' ' }
+      normalized_text.gsub!(/\btoday\b/, 'this day')
+      normalized_text.gsub!(/\btomm?orr?ow\b/, 'next day')
+      normalized_text.gsub!(/\byesterday\b/, 'last day')
+      normalized_text.gsub!(/\bnoon\b/, '12:00')
+      normalized_text.gsub!(/\bmidnight\b/, '24:00')
+      normalized_text.gsub!(/\bbefore now\b/, 'past')
+      normalized_text.gsub!(/\bnow\b/, 'this second')
+      normalized_text.gsub!(/\b(ago|before)\b/, 'past')
+      normalized_text.gsub!(/\bthis past\b/, 'last')
+      normalized_text.gsub!(/\bthis last\b/, 'last')
+      normalized_text.gsub!(/\b(?:in|during) the (morning)\b/, '\1')
+      normalized_text.gsub!(/\b(?:in the|during the|at) (afternoon|evening|night)\b/, '\1')
+      normalized_text.gsub!(/\btonight\b/, 'this night')
+      normalized_text.gsub!(/(?=\w)([ap]m|oclock)\b/, ' \1')
+      normalized_text.gsub!(/\b(hence|after|from)\b/, 'future')
+      normalized_text = numericize_ordinals(normalized_text)
+    end
+
+    # Convert number words to numbers (three => 3)
+    def numericize_numbers(text) #:nodoc:
+      Numerizer.numerize(text)
+    end
+
+    # Convert ordinal words to numeric ordinals (third => 3rd)
+    def numericize_ordinals(text) #:nodoc:
+      text
+    end
+
+    # Split the text on spaces and convert each word into
+    # a Token
+    def base_tokenize(text) #:nodoc:
+      text.split(' ').map { |word| Token.new(word) }
+    end
+
+    # Guess a specific time within the given span
+    def guess(span) #:nodoc:
+      return nil if span.nil?
+      if span.width > 1
+        span.begin + (span.width / 2)
+      else
+        span.begin
+      end
+    end
+  end
+
+end
diff --git a/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Grabber.php b/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Grabber.php
new file mode 100644 (file)
index 0000000..4162a26
--- /dev/null
@@ -0,0 +1,26 @@
+#module Chronic
+
+  class Chronic::Grabber < Chronic::Tag #:nodoc:
+    def self.scan(tokens)
+      tokens.each_index do |i|
+        if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t); next end
+      end
+      tokens
+    end
+    
+    def self.scan_for_all(token)
+      scanner = {/last/ => :last,
+                 /this/ => :this,
+                 /next/ => :next}
+      scanner.keys.each do |scanner_item|
+        return self.new(scanner[scanner_item]) if scanner_item =~ token.word
+      end
+      return nil
+    end
+    
+    def to_s
+      'grabber-' << @type.to_s
+    end
+  end
+
+#end
\ No newline at end of file
diff --git a/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Handlers.php b/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Handlers.php
new file mode 100644 (file)
index 0000000..551d632
--- /dev/null
@@ -0,0 +1,469 @@
+module Chronic
+
+       class << self
+         
+         def definitions #:nodoc:
+           @definitions ||= 
+      {:time => [Handler.new([:repeater_time, :repeater_day_portion?], nil)],
+        
+       :date => [Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :repeater_time, :time_zone, :scalar_year], :handle_rdn_rmn_sd_t_tz_sy),
+                 Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),
+                 Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),
+                 Handler.new([:repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),
+                 Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),
+                 Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),
+                 Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),
+                 Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),
+                 Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy),
+                 Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),
+                 Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy)],
+                 
+       # tonight at 7pm
+       :anchor => [Handler.new([:grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
+                   Handler.new([:grabber?, :repeater, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
+                   Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)],
+                   
+       # 3 weeks from now, in 2 months
+       :arrow => [Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p),
+                  Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r),
+                  Handler.new([:scalar, :repeater, :pointer, 'anchor'], :handle_s_r_p_a)],
+                  
+       # 3rd week in march
+       :narrow => [Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r),
+                   Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r)]
+      }
+    end
+    
+    def tokens_to_span(tokens, options) #:nodoc:                   
+      # maybe it's a specific date
+      
+      self.definitions[:date].each do |handler|
+        if handler.match(tokens, self.definitions)
+          puts "-date" if Chronic.debug
+          good_tokens = tokens.select { |o| !o.get_tag Separator }
+          return self.send(handler.handler_method, good_tokens, options)
+        end
+      end
+            
+      # I guess it's not a specific date, maybe it's just an anchor
+            
+      self.definitions[:anchor].each do |handler|
+        if handler.match(tokens, self.definitions)
+          puts "-anchor" if Chronic.debug
+          good_tokens = tokens.select { |o| !o.get_tag Separator }
+          return self.send(handler.handler_method, good_tokens, options)
+        end
+      end
+            
+      # not an anchor, perhaps it's an arrow
+      
+      self.definitions[:arrow].each do |handler|
+        if handler.match(tokens, self.definitions)
+          puts "-arrow" if Chronic.debug
+          good_tokens = tokens.reject { |o| o.get_tag(SeparatorAt) || o.get_tag(SeparatorSlashOrDash) || o.get_tag(SeparatorComma) }
+          return self.send(handler.handler_method, good_tokens, options)
+        end
+      end
+      
+      # not an arrow, let's hope it's a narrow
+      
+      self.definitions[:narrow].each do |handler|
+        if handler.match(tokens, self.definitions)
+          puts "-narrow" if Chronic.debug
+          #good_tokens = tokens.select { |o| !o.get_tag Separator }
+          return self.send(handler.handler_method, tokens, options)
+        end
+      end
+      
+      # I guess you're out of luck!
+      puts "-none" if Chronic.debug
+      return nil
+    end
+    
+    #--------------
+    
+    def day_or_time(day_start, time_tokens, options)
+      outer_span = Span.new(day_start, day_start + (24 * 60 * 60))
+      
+      if !time_tokens.empty?
+        @now = outer_span.begin
+        time = get_anchor(dealias_and_disambiguate_times(time_tokens, options), options)
+        return time
+      else
+        return outer_span
+      end
+    end
+    
+    #--------------
+    
+    def handle_m_d(month, day, time_tokens, options) #:nodoc:
+      month.start = @now
+      span = month.this(options[:context])
+      
+      day_start = Time.local(span.begin.year, span.begin.month, day)
+      
+      day_or_time(day_start, time_tokens, options)
+    end
+    
+    def handle_rmn_sd(tokens, options) #:nodoc:
+      handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(ScalarDay).type, tokens[2..tokens.size], options)
+    end
+    
+    def handle_rmn_od(tokens, options) #:nodoc:
+      handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(OrdinalDay).type, tokens[2..tokens.size], options)
+    end
+    
+    def handle_rmn_sy(tokens, options) #:nodoc:
+      month = tokens[0].get_tag(RepeaterMonthName).index
+      year = tokens[1].get_tag(ScalarYear).type
+      
+      if month == 12
+        next_month_year = year + 1
+        next_month_month = 1
+      else
+        next_month_year = year
+        next_month_month = month + 1
+      end
+      
+      begin
+        Span.new(Time.local(year, month), Time.local(next_month_year, next_month_month))
+      rescue ArgumentError
+        nil
+      end
+    end
+    
+    def handle_rdn_rmn_sd_t_tz_sy(tokens, options) #:nodoc:
+      month = tokens[1].get_tag(RepeaterMonthName).index
+      day = tokens[2].get_tag(ScalarDay).type
+      year = tokens[5].get_tag(ScalarYear).type
+      
+      begin
+        day_start = Time.local(year, month, day)
+        day_or_time(day_start, [tokens[3]], options)
+      rescue ArgumentError
+        nil
+      end
+    end
+    
+    def handle_rmn_sd_sy(tokens, options) #:nodoc:
+      month = tokens[0].get_tag(RepeaterMonthName).index
+      day = tokens[1].get_tag(ScalarDay).type
+      year = tokens[2].get_tag(ScalarYear).type
+      
+      time_tokens = tokens.last(tokens.size - 3)
+      
+      begin
+        day_start = Time.local(year, month, day)
+        day_or_time(day_start, time_tokens, options)
+      rescue ArgumentError
+        nil
+      end
+    end
+    
+    def handle_sd_rmn_sy(tokens, options) #:nodoc:
+      new_tokens = [tokens[1], tokens[0], tokens[2]]
+      time_tokens = tokens.last(tokens.size - 3)
+      self.handle_rmn_sd_sy(new_tokens + time_tokens, options)
+    end
+    
+    def handle_sm_sd_sy(tokens, options) #:nodoc:
+      month = tokens[0].get_tag(ScalarMonth).type
+      day = tokens[1].get_tag(ScalarDay).type
+      year = tokens[2].get_tag(ScalarYear).type
+      
+      time_tokens = tokens.last(tokens.size - 3)
+      
+      begin
+        day_start = Time.local(year, month, day) #:nodoc:
+        day_or_time(day_start, time_tokens, options)
+      rescue ArgumentError
+        nil
+      end
+    end
+    
+    def handle_sd_sm_sy(tokens, options) #:nodoc:
+      new_tokens = [tokens[1], tokens[0], tokens[2]]
+      time_tokens = tokens.last(tokens.size - 3)
+      self.handle_sm_sd_sy(new_tokens + time_tokens, options)
+    end
+    
+    def handle_sy_sm_sd(tokens, options) #:nodoc:
+      new_tokens = [tokens[1], tokens[2], tokens[0]]
+      time_tokens = tokens.last(tokens.size - 3)
+      self.handle_sm_sd_sy(new_tokens + time_tokens, options)
+    end
+    
+    def handle_sm_sy(tokens, options) #:nodoc:
+      month = tokens[0].get_tag(ScalarMonth).type
+      year = tokens[1].get_tag(ScalarYear).type
+      
+      if month == 12
+        next_month_year = year + 1
+        next_month_month = 1
+      else
+        next_month_year = year
+        next_month_month = month + 1
+      end
+      
+      begin
+        Span.new(Time.local(year, month), Time.local(next_month_year, next_month_month))
+      rescue ArgumentError
+        nil
+      end
+    end
+    
+    # anchors
+    
+    def handle_r(tokens, options) #:nodoc:
+      dd_tokens = dealias_and_disambiguate_times(tokens, options)
+      self.get_anchor(dd_tokens, options)
+    end
+    
+    def handle_r_g_r(tokens, options) #:nodoc:
+      new_tokens = [tokens[1], tokens[0], tokens[2]]
+      self.handle_r(new_tokens, options)
+    end
+    
+    # arrows
+    
+    def handle_srp(tokens, span, options) #:nodoc:
+      distance = tokens[0].get_tag(Scalar).type
+      repeater = tokens[1].get_tag(Repeater)
+      pointer = tokens[2].get_tag(Pointer).type
+      
+      repeater.offset(span, distance, pointer)
+    end
+    
+    def handle_s_r_p(tokens, options) #:nodoc:
+      repeater = tokens[1].get_tag(Repeater)
+            
+      # span = 
+      # case true
+      # when [RepeaterYear, RepeaterSeason, RepeaterSeasonName, RepeaterMonth, RepeaterMonthName, RepeaterFortnight, RepeaterWeek].include?(repeater.class)
+      #   self.parse("this hour", :guess => false, :now => @now)
+      # when [RepeaterWeekend, RepeaterDay, RepeaterDayName, RepeaterDayPortion, RepeaterHour].include?(repeater.class)
+      #   self.parse("this minute", :guess => false, :now => @now)
+      # when [RepeaterMinute, RepeaterSecond].include?(repeater.class)
+      #   self.parse("this second", :guess => false, :now => @now)
+      # else
+      #   raise(ChronicPain, "Invalid repeater: #{repeater.class}")
+      # end
+      
+      span = self.parse("this second", :guess => false, :now => @now)
+      
+      self.handle_srp(tokens, span, options)
+    end
+    
+    def handle_p_s_r(tokens, options) #:nodoc:
+      new_tokens = [tokens[1], tokens[2], tokens[0]]
+      self.handle_s_r_p(new_tokens, options)
+    end
+    
+    def handle_s_r_p_a(tokens, options) #:nodoc:
+      anchor_span = get_anchor(tokens[3..tokens.size - 1], options)
+      self.handle_srp(tokens, anchor_span, options)
+    end
+    
+    # narrows
+    
+    def handle_orr(tokens, outer_span, options) #:nodoc:
+      repeater = tokens[1].get_tag(Repeater)
+      repeater.start = outer_span.begin - 1
+      ordinal = tokens[0].get_tag(Ordinal).type
+      span = nil
+      ordinal.times do
+        span = repeater.next(:future)
+        if span.begin > outer_span.end
+          span = nil
+          break
+        end
+      end
+      span
+    end
+    
+    def handle_o_r_s_r(tokens, options) #:nodoc:
+      outer_span = get_anchor([tokens[3]], options)
+      handle_orr(tokens[0..1], outer_span, options)
+    end
+    
+    def handle_o_r_g_r(tokens, options) #:nodoc:
+      outer_span = get_anchor(tokens[2..3], options)
+      handle_orr(tokens[0..1], outer_span, options)
+    end
+    
+    # support methods
+    
+    def get_anchor(tokens, options) #:nodoc:
+      grabber = Grabber.new(:this)
+      pointer = :future
+      
+      repeaters = self.get_repeaters(tokens)
+      repeaters.size.times { tokens.pop }
+                    
+      if tokens.first && tokens.first.get_tag(Grabber)
+        grabber = tokens.first.get_tag(Grabber)
+        tokens.pop
+      end
+      
+      head = repeaters.shift
+      head.start = @now
+                  
+      case grabber.type
+        when :last
+          outer_span = head.next(:past)
+        when :this
+          if repeaters.size > 0
+            outer_span = head.this(:none)
+          else
+            outer_span = head.this(options[:context])
+          end
+        when :next
+          outer_span = head.next(:future)
+        else raise(ChronicPain, "Invalid grabber")
+      end
+      
+      puts "--#{outer_span}" if Chronic.debug
+      anchor = find_within(repeaters, outer_span, pointer)
+    end
+    
+    def get_repeaters(tokens) #:nodoc:
+      repeaters = []
+                 tokens.each do |token|
+                   if t = token.get_tag(Repeater)
+          repeaters << t
+        end
+      end
+      repeaters.sort.reverse
+    end
+    
+    # Recursively finds repeaters within other repeaters.
+    # Returns a Span representing the innermost time span
+    # or nil if no repeater union could be found
+    def find_within(tags, span, pointer) #:nodoc:
+      puts "--#{span}" if Chronic.debug
+      return span if tags.empty?
+      
+      head, *rest = tags
+      head.start = pointer == :future ? span.begin : span.end
+      h = head.this(:none)
+            
+      if span.include?(h.begin) || span.include?(h.end)
+        return find_within(rest, h, pointer)
+      else
+        return nil
+      end
+    end
+    
+    def dealias_and_disambiguate_times(tokens, options) #:nodoc:
+      # handle aliases of am/pm
+      # 5:00 in the morning -> 5:00 am
+      # 7:00 in the evening -> 7:00 pm
+      
+      day_portion_index = nil
+      tokens.each_with_index do |t, i|
+        if t.get_tag(RepeaterDayPortion)
+          day_portion_index = i
+          break
+        end
+      end
+       
+      time_index = nil
+      tokens.each_with_index do |t, i|
+        if t.get_tag(RepeaterTime)
+          time_index = i
+          break
+        end
+      end
+      
+      if (day_portion_index && time_index)
+        t1 = tokens[day_portion_index]
+        t1tag = t1.get_tag(RepeaterDayPortion)
+      
+        if [:morning].include?(t1tag.type)
+          puts '--morning->am' if Chronic.debug
+          t1.untag(RepeaterDayPortion)
+          t1.tag(RepeaterDayPortion.new(:am))
+        elsif [:afternoon, :evening, :night].include?(t1tag.type)
+          puts "--#{t1tag.type}->pm" if Chronic.debug
+          t1.untag(RepeaterDayPortion)
+          t1.tag(RepeaterDayPortion.new(:pm))
+        end
+      end
+      
+      # tokens.each_with_index do |t0, i|
+      #   t1 = tokens[i + 1]
+      #   if t1 && (t1tag = t1.get_tag(RepeaterDayPortion)) && t0.get_tag(RepeaterTime)
+      #     if [:morning].include?(t1tag.type)
+      #       puts '--morning->am' if Chronic.debug
+      #       t1.untag(RepeaterDayPortion)
+      #       t1.tag(RepeaterDayPortion.new(:am))
+      #     elsif [:afternoon, :evening, :night].include?(t1tag.type)
+      #       puts "--#{t1tag.type}->pm" if Chronic.debug
+      #       t1.untag(RepeaterDayPortion)
+      #       t1.tag(RepeaterDayPortion.new(:pm))
+      #     end
+      #   end
+      # end
+            
+      # handle ambiguous times if :ambiguous_time_range is specified
+      if options[:ambiguous_time_range] != :none
+        ttokens = []
+        tokens.each_with_index do |t0, i|
+          ttokens << t0
+          t1 = tokens[i + 1]
+          if t0.get_tag(RepeaterTime) && t0.get_tag(RepeaterTime).type.ambiguous? && (!t1 || !t1.get_tag(RepeaterDayPortion))
+            distoken = Token.new('disambiguator')
+            distoken.tag(RepeaterDayPortion.new(options[:ambiguous_time_range]))
+            ttokens << distoken
+          end
+        end
+        tokens = ttokens
+      end
+      
+      tokens
+    end
+    
+  end
+  
+  class Handler #:nodoc:
+    attr_accessor :pattern, :handler_method
+    
+    def initialize(pattern, handler_method)
+      @pattern = pattern
+      @handler_method = handler_method
+    end
+    
+    def constantize(name)
+      camel = name.to_s.gsub(/(^|_)(.)/) { $2.upcase }
+      ::Chronic.module_eval(camel, __FILE__, __LINE__)
+    end
+    
+    def match(tokens, definitions)
+      token_index = 0
+      @pattern.each do |element|
+        name = element.to_s
+        optional = name.reverse[0..0] == '?'
+        name = name.chop if optional
+        if element.instance_of? Symbol
+          klass = constantize(name)
+          match = tokens[token_index] && !tokens[token_index].tags.select { |o| o.kind_of?(klass) }.empty?
+          return false if !match && !optional
+          (token_index += 1; next) if match
+          next if !match && optional
+        elsif element.instance_of? String
+          return true if optional && token_index == tokens.size
+          sub_handlers = definitions[name.intern] || raise(ChronicPain, "Invalid subset #{name} specified")
+          sub_handlers.each do |sub_handler|
+            return true if sub_handler.match(tokens[token_index..tokens.size], definitions)
+          end
+          return false
+        else
+          raise(ChronicPain, "Invalid match type: #{element.class}")
+        end
+      end
+      return false if token_index != tokens.size
+      return true
+    end
+  end
+  
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Ordinal.php b/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Ordinal.php
new file mode 100644 (file)
index 0000000..45b8148
--- /dev/null
@@ -0,0 +1,40 @@
+module Chronic
+
+  class Ordinal < Tag #:nodoc:
+    def self.scan(tokens)
+      # for each token
+      tokens.each_index do |i|
+        if t = self.scan_for_ordinals(tokens[i]) then tokens[i].tag(t) end
+        if t = self.scan_for_days(tokens[i]) then tokens[i].tag(t) end
+      end
+      tokens
+    end
+  
+    def self.scan_for_ordinals(token)
+      if token.word =~ /^(\d*)(st|nd|rd|th)$/
+        return Ordinal.new($1.to_i)
+      end
+      return nil
+    end
+    
+    def self.scan_for_days(token)
+      if token.word =~ /^(\d*)(st|nd|rd|th)$/
+        unless $1.to_i > 31
+          return OrdinalDay.new(token.word.to_i)
+        end
+      end
+      return nil
+    end
+    
+    def to_s
+      'ordinal'
+    end
+  end
+  
+  class OrdinalDay < Ordinal #:nodoc:
+    def to_s
+      super << '-day-' << @type.to_s
+    end
+  end
+
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Pointer.php b/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Pointer.php
new file mode 100644 (file)
index 0000000..224efaf
--- /dev/null
@@ -0,0 +1,27 @@
+module Chronic
+
+  class Pointer < Tag #:nodoc:
+    def self.scan(tokens)
+      # for each token
+      tokens.each_index do |i|
+        if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t) end
+      end
+      tokens
+    end
+  
+    def self.scan_for_all(token)
+      scanner = {/\bpast\b/ => :past,
+                 /\bfuture\b/ => :future,
+                 /\bin\b/ => :future}
+      scanner.keys.each do |scanner_item|
+        return self.new(scanner[scanner_item]) if scanner_item =~ token.word
+      end
+      return nil
+    end
+    
+    def to_s
+      'pointer-' << @type.to_s
+    end
+  end
+
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeater.php b/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeater.php
new file mode 100644 (file)
index 0000000..9f80daf
--- /dev/null
@@ -0,0 +1,115 @@
+class Chronic::Repeater < Chronic::Tag #:nodoc:
+  def self.scan(tokens, options)
+    # for each token
+    tokens.each_index do |i|
+      if t = self.scan_for_month_names(tokens[i]) then tokens[i].tag(t); next end
+      if t = self.scan_for_day_names(tokens[i]) then tokens[i].tag(t); next end
+      if t = self.scan_for_day_portions(tokens[i]) then tokens[i].tag(t); next end
+      if t = self.scan_for_times(tokens[i], options) then tokens[i].tag(t); next end
+      if t = self.scan_for_units(tokens[i]) then tokens[i].tag(t); next end
+    end
+    tokens
+  end
+  
+  def self.scan_for_month_names(token)
+    scanner = {/^jan\.?(uary)?$/ => :january,
+               /^feb\.?(ruary)?$/ => :february,
+               /^mar\.?(ch)?$/ => :march,
+               /^apr\.?(il)?$/ => :april,
+               /^may$/ => :may,
+               /^jun\.?e?$/ => :june,
+               /^jul\.?y?$/ => :july,
+               /^aug\.?(ust)?$/ => :august,
+               /^sep\.?(t\.?|tember)?$/ => :september,
+               /^oct\.?(ober)?$/ => :october,
+               /^nov\.?(ember)?$/ => :november,
+               /^dec\.?(ember)?$/ => :december}
+    scanner.keys.each do |scanner_item|
+      return Chronic::RepeaterMonthName.new(scanner[scanner_item]) if scanner_item =~ token.word
+    end
+    return nil
+  end
+  
+  def self.scan_for_day_names(token)
+    scanner = {/^m[ou]n(day)?$/ => :monday,
+               /^t(ue|eu|oo|u|)s(day)?$/ => :tuesday,
+               /^tue$/ => :tuesday,
+               /^we(dnes|nds|nns)day$/ => :wednesday,
+               /^wed$/ => :wednesday,
+               /^th(urs|ers)day$/ => :thursday,
+               /^thu$/ => :thursday,
+               /^fr[iy](day)?$/ => :friday,
+               /^sat(t?[ue]rday)?$/ => :saturday,
+               /^su[nm](day)?$/ => :sunday}
+    scanner.keys.each do |scanner_item|
+      return Chronic::RepeaterDayName.new(scanner[scanner_item]) if scanner_item =~ token.word
+    end
+    return nil
+  end
+  
+  def self.scan_for_day_portions(token)
+    scanner = {/^ams?$/ => :am,
+               /^pms?$/ => :pm,
+               /^mornings?$/ => :morning,
+               /^afternoons?$/ => :afternoon,
+               /^evenings?$/ => :evening,
+               /^(night|nite)s?$/ => :night}
+    scanner.keys.each do |scanner_item|
+      return Chronic::RepeaterDayPortion.new(scanner[scanner_item]) if scanner_item =~ token.word
+    end
+    return nil
+  end
+  
+  def self.scan_for_times(token, options)
+    if token.word =~ /^\d{1,2}(:?\d{2})?([\.:]?\d{2})?$/
+      return Chronic::RepeaterTime.new(token.word, options)
+    end
+    return nil
+  end
+  
+  def self.scan_for_units(token)
+    scanner = {/^years?$/ => :year,
+               /^seasons?$/ => :season,
+               /^months?$/ => :month,
+               /^fortnights?$/ => :fortnight,
+               /^weeks?$/ => :week,
+               /^weekends?$/ => :weekend,
+               /^days?$/ => :day,
+               /^hours?$/ => :hour,
+               /^minutes?$/ => :minute,
+               /^seconds?$/ => :second}
+    scanner.keys.each do |scanner_item|
+      if scanner_item =~ token.word
+        klass_name = 'Chronic::Repeater' + scanner[scanner_item].to_s.capitalize
+        klass = eval(klass_name)
+        return klass.new(scanner[scanner_item]) 
+      end
+    end
+    return nil
+  end
+  
+  def <=>(other)
+    width <=> other.width
+  end
+  
+  # returns the width (in seconds or months) of this repeatable.
+  def width
+    raise("Repeatable#width must be overridden in subclasses")
+  end
+  
+  # returns the next occurance of this repeatable.
+  def next(pointer)
+    !@now.nil? || raise("Start point must be set before calling #next")
+    [:future, :none, :past].include?(pointer) || raise("First argument 'pointer' must be one of :past or :future")
+    #raise("Repeatable#next must be overridden in subclasses")
+  end
+  
+  def this(pointer)
+    !@now.nil? || raise("Start point must be set before calling #this")
+    [:future, :past, :none].include?(pointer) || raise("First argument 'pointer' must be one of :past, :future, :none")
+  end
+  
+  def to_s
+    'repeater'
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Day.php b/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Day.php
new file mode 100644 (file)
index 0000000..a92d83f
--- /dev/null
@@ -0,0 +1,47 @@
+class Chronic::RepeaterDay < Chronic::Repeater #:nodoc:
+  DAY_SECONDS = 86_400 # (24 * 60 * 60)
+  
+  def next(pointer)
+    super
+    
+    if !@current_day_start
+      @current_day_start = Time.local(@now.year, @now.month, @now.day)
+    end
+    
+    direction = pointer == :future ? 1 : -1
+    @current_day_start += direction * DAY_SECONDS
+    
+    Chronic::Span.new(@current_day_start, @current_day_start + DAY_SECONDS)
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    case pointer
+    when :future
+      day_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
+      day_end = Time.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
+    when :past
+      day_begin = Time.construct(@now.year, @now.month, @now.day)
+      day_end = Time.construct(@now.year, @now.month, @now.day, @now.hour)
+    when :none
+      day_begin = Time.construct(@now.year, @now.month, @now.day)
+      day_end = Time.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
+    end
+    
+    Chronic::Span.new(day_begin, day_end)
+  end
+  
+  def offset(span, amount, pointer)
+    direction = pointer == :future ? 1 : -1
+    span + direction * amount * DAY_SECONDS
+  end
+  
+  def width
+    DAY_SECONDS
+  end
+  
+  def to_s
+    super << '-day'
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/DayName.php b/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/DayName.php
new file mode 100644 (file)
index 0000000..0486a4d
--- /dev/null
@@ -0,0 +1,46 @@
+class Chronic::RepeaterDayName < Chronic::Repeater #:nodoc:
+  DAY_SECONDS = 86400 # (24 * 60 * 60)
+  
+  def next(pointer)
+    super
+    
+    direction = pointer == :future ? 1 : -1
+    
+    if !@current_day_start
+      @current_day_start = Time.construct(@now.year, @now.month, @now.day)
+      @current_day_start += direction * DAY_SECONDS
+
+      day_num = symbol_to_number(@type)
+      
+      while @current_day_start.wday != day_num
+        @current_day_start += direction * DAY_SECONDS
+      end
+    else
+      @current_day_start += direction * 7 * DAY_SECONDS
+    end
+    
+    Chronic::Span.new(@current_day_start, @current_day_start + DAY_SECONDS)
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    pointer = :future if pointer == :none
+    self.next(pointer)
+  end
+  
+  def width
+    DAY_SECONDS
+  end
+  
+  def to_s
+    super << '-dayname-' << @type.to_s
+  end
+  
+  private
+  
+  def symbol_to_number(sym)
+    lookup = {:sunday => 0, :monday => 1, :tuesday => 2, :wednesday => 3, :thursday => 4, :friday => 5, :saturday => 6}
+    lookup[sym] || raise("Invalid symbol specified")
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/DayPortion.php b/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/DayPortion.php
new file mode 100644 (file)
index 0000000..c854933
--- /dev/null
@@ -0,0 +1,93 @@
+class Chronic::RepeaterDayPortion < Chronic::Repeater #:nodoc:
+  @@morning = (6 * 60 * 60)..(12 * 60 * 60) # 6am-12am
+  @@afternoon = (13 * 60 * 60)..(17 * 60 * 60) # 1pm-5pm
+  @@evening = (17 * 60 * 60)..(20 * 60 * 60) # 5pm-8pm
+  @@night = (20 * 60 * 60)..(24 * 60 * 60) # 8pm-12pm
+  
+  def initialize(type)
+    super
+    
+    if type.kind_of? Integer
+      @range = (@type * 60 * 60)..((@type + 12) * 60 * 60)
+    else
+      lookup = {:am => 0..(12 * 60 * 60 - 1),
+                :pm => (12 * 60 * 60)..(24 * 60 * 60 - 1),
+                :morning => @@morning,
+                :afternoon => @@afternoon,
+                :evening => @@evening,
+                :night => @@night}
+      @range = lookup[type]
+      lookup[type] || raise("Invalid type '#{type}' for RepeaterDayPortion")
+    end
+    @range || raise("Range should have been set by now")
+  end
+  
+  def next(pointer)
+    super
+    
+    full_day = 60 * 60 * 24
+    
+    if !@current_span
+      now_seconds = @now - Time.construct(@now.year, @now.month, @now.day)
+      if now_seconds < @range.begin
+        case pointer
+        when :future
+          range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
+        when :past
+          range_start = Time.construct(@now.year, @now.month, @now.day) - full_day + @range.begin
+        end
+      elsif now_seconds > @range.end
+        case pointer
+        when :future
+          range_start = Time.construct(@now.year, @now.month, @now.day) + full_day + @range.begin
+        when :past
+          range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
+        end
+      else
+        case pointer
+        when :future
+          range_start = Time.construct(@now.year, @now.month, @now.day) + full_day + @range.begin
+        when :past
+          range_start = Time.construct(@now.year, @now.month, @now.day) - full_day + @range.begin
+        end
+      end
+      
+      @current_span = Chronic::Span.new(range_start, range_start + (@range.end - @range.begin))
+    else
+      case pointer
+      when :future
+        @current_span += full_day
+      when :past
+        @current_span -= full_day
+      end
+    end
+  end
+  
+  def this(context = :future)
+    super
+    
+    range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
+    @current_span = Chronic::Span.new(range_start, range_start + (@range.end - @range.begin))
+  end
+  
+  def offset(span, amount, pointer)
+    @now = span.begin
+    portion_span = self.next(pointer)
+    direction = pointer == :future ? 1 : -1
+    portion_span + (direction * (amount - 1) * Chronic::RepeaterDay::DAY_SECONDS)
+  end
+  
+  def width
+    @range || raise("Range has not been set")
+    return @current_span.width if @current_span
+    if @type.kind_of? Integer
+      return (12 * 60 * 60)
+    else
+      @range.end - @range.begin
+    end
+  end
+  
+  def to_s
+    super << '-dayportion-' << @type.to_s
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Fortnight.php b/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Fortnight.php
new file mode 100644 (file)
index 0000000..058fbb9
--- /dev/null
@@ -0,0 +1,65 @@
+class Chronic::RepeaterFortnight < Chronic::Repeater #:nodoc:
+  FORTNIGHT_SECONDS = 1_209_600 # (14 * 24 * 60 * 60)
+  
+  def next(pointer)
+    super
+    
+    if !@current_fortnight_start
+      case pointer
+      when :future
+        sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+        sunday_repeater.start = @now
+        next_sunday_span = sunday_repeater.next(:future)
+        @current_fortnight_start = next_sunday_span.begin
+      when :past
+        sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+        sunday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
+        2.times { sunday_repeater.next(:past) }
+        last_sunday_span = sunday_repeater.next(:past)
+        @current_fortnight_start = last_sunday_span.begin
+      end
+    else
+      direction = pointer == :future ? 1 : -1
+      @current_fortnight_start += direction * FORTNIGHT_SECONDS
+    end
+    
+    Chronic::Span.new(@current_fortnight_start, @current_fortnight_start + FORTNIGHT_SECONDS)
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    pointer = :future if pointer == :none
+    
+    case pointer
+    when :future
+      this_fortnight_start = Time.construct(@now.year, @now.month, @now.day, @now.hour) + Chronic::RepeaterHour::HOUR_SECONDS
+      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+      sunday_repeater.start = @now
+      sunday_repeater.this(:future)
+      this_sunday_span = sunday_repeater.this(:future)
+      this_fortnight_end = this_sunday_span.begin
+      Chronic::Span.new(this_fortnight_start, this_fortnight_end)
+    when :past
+      this_fortnight_end = Time.construct(@now.year, @now.month, @now.day, @now.hour)
+      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+      sunday_repeater.start = @now
+      last_sunday_span = sunday_repeater.next(:past)
+      this_fortnight_start = last_sunday_span.begin
+      Chronic::Span.new(this_fortnight_start, this_fortnight_end)
+    end
+  end
+  
+  def offset(span, amount, pointer)
+    direction = pointer == :future ? 1 : -1
+    span + direction * amount * FORTNIGHT_SECONDS
+  end
+
+  def width
+    FORTNIGHT_SECONDS
+  end
+  
+  def to_s
+    super << '-fortnight'
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Hour.php b/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Hour.php
new file mode 100644 (file)
index 0000000..f38a3f8
--- /dev/null
@@ -0,0 +1,52 @@
+class Chronic::RepeaterHour < Chronic::Repeater #:nodoc:
+  HOUR_SECONDS = 3600 # 60 * 60
+  
+  def next(pointer)
+    super
+    
+    if !@current_hour_start
+      case pointer
+      when :future
+        @current_hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
+      when :past
+        @current_hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour - 1)
+      end
+    else
+      direction = pointer == :future ? 1 : -1
+      @current_hour_start += direction * HOUR_SECONDS
+    end
+    
+    Chronic::Span.new(@current_hour_start, @current_hour_start + HOUR_SECONDS)
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    case pointer
+    when :future
+      hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min + 1)
+      hour_end = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
+    when :past
+      hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour)
+      hour_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
+    when :none
+      hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour)
+      hour_end = hour_begin + HOUR_SECONDS
+    end
+    
+    Chronic::Span.new(hour_start, hour_end)
+  end
+  
+  def offset(span, amount, pointer)
+    direction = pointer == :future ? 1 : -1
+    span + direction * amount * HOUR_SECONDS
+  end
+  
+  def width
+    HOUR_SECONDS
+  end
+  
+  def to_s
+    super << '-hour'
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Minute.php b/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Minute.php
new file mode 100644 (file)
index 0000000..342d3cd
--- /dev/null
@@ -0,0 +1,52 @@
+class Chronic::RepeaterMinute < Chronic::Repeater #:nodoc:
+  MINUTE_SECONDS = 60
+  
+  def next(pointer = :future)
+    super
+    
+    if !@current_minute_start
+      case pointer
+      when :future
+        @current_minute_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min + 1)
+      when :past
+        @current_minute_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min - 1)
+      end
+    else
+      direction = pointer == :future ? 1 : -1
+      @current_minute_start += direction * MINUTE_SECONDS
+    end
+    
+    Chronic::Span.new(@current_minute_start, @current_minute_start + MINUTE_SECONDS)
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    case pointer
+    when :future
+      minute_begin = @now
+      minute_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
+    when :past
+      minute_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
+      minute_end = @now
+    when :none
+      minute_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
+      minute_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min) + MINUTE_SECONDS
+    end
+    
+    Chronic::Span.new(minute_begin, minute_end)
+  end
+  
+  def offset(span, amount, pointer)
+    direction = pointer == :future ? 1 : -1
+    span + direction * amount * MINUTE_SECONDS
+  end
+  
+  def width
+    MINUTE_SECONDS
+  end
+  
+  def to_s
+    super << '-minute'
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Month.php b/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Month.php
new file mode 100644 (file)
index 0000000..edd89ee
--- /dev/null
@@ -0,0 +1,61 @@
+class Chronic::RepeaterMonth < Chronic::Repeater #:nodoc:
+  MONTH_SECONDS = 2_592_000 # 30 * 24 * 60 * 60
+  YEAR_MONTHS = 12
+  
+  def next(pointer)
+    super
+    
+    if !@current_month_start
+      @current_month_start = offset_by(Time.construct(@now.year, @now.month), 1, pointer)
+    else
+      @current_month_start = offset_by(Time.construct(@current_month_start.year, @current_month_start.month), 1, pointer)
+    end
+    
+    Chronic::Span.new(@current_month_start, Time.construct(@current_month_start.year, @current_month_start.month + 1))
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    case pointer
+    when :future
+      month_start = Time.construct(@now.year, @now.month, @now.day + 1)
+      month_end = self.offset_by(Time.construct(@now.year, @now.month), 1, :future)
+    when :past
+      month_start = Time.construct(@now.year, @now.month)
+      month_end = Time.construct(@now.year, @now.month, @now.day)
+    when :none
+      month_start = Time.construct(@now.year, @now.month)
+      month_end = self.offset_by(Time.construct(@now.year, @now.month), 1, :future)
+    end
+    
+    Chronic::Span.new(month_start, month_end)
+  end
+  
+  def offset(span, amount, pointer)      
+    Chronic::Span.new(offset_by(span.begin, amount, pointer), offset_by(span.end, amount, pointer))
+  end
+  
+  def offset_by(time, amount, pointer) 
+    direction = pointer == :future ? 1 : -1
+    
+    amount_years = direction * amount / YEAR_MONTHS
+    amount_months = direction * amount % YEAR_MONTHS
+    
+    new_year = time.year + amount_years
+    new_month = time.month + amount_months
+    if new_month > YEAR_MONTHS
+      new_year += 1
+      new_month -= YEAR_MONTHS
+    end
+    Time.construct(new_year, new_month, time.day, time.hour, time.min, time.sec)
+  end
+  
+  def width
+    MONTH_SECONDS
+  end
+  
+  def to_s
+    super << '-month'
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/MonthName.php b/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/MonthName.php
new file mode 100644 (file)
index 0000000..1f8b748
--- /dev/null
@@ -0,0 +1,93 @@
+class Chronic::RepeaterMonthName < Chronic::Repeater #:nodoc:
+  MONTH_SECONDS = 2_592_000 # 30 * 24 * 60 * 60
+  
+  def next(pointer)
+    super
+    
+    if !@current_month_begin
+      target_month = symbol_to_number(@type)
+      case pointer
+      when :future
+        if @now.month < target_month
+          @current_month_begin = Time.construct(@now.year, target_month)
+        else @now.month > target_month
+          @current_month_begin = Time.construct(@now.year + 1, target_month)
+        end
+      when :none
+        if @now.month <= target_month
+          @current_month_begin = Time.construct(@now.year, target_month)
+        else @now.month > target_month
+          @current_month_begin = Time.construct(@now.year + 1, target_month)
+        end
+      when :past
+        if @now.month > target_month
+          @current_month_begin = Time.construct(@now.year, target_month)
+        else @now.month < target_month
+          @current_month_begin = Time.construct(@now.year - 1, target_month)
+        end
+      end
+      @current_month_begin || raise("Current month should be set by now")
+    else
+      case pointer
+      when :future
+        @current_month_begin = Time.construct(@current_month_begin.year + 1, @current_month_begin.month)
+      when :past
+        @current_month_begin = Time.construct(@current_month_begin.year - 1, @current_month_begin.month)
+      end
+    end
+    
+    cur_month_year = @current_month_begin.year
+    cur_month_month = @current_month_begin.month
+    
+    if cur_month_month == 12
+      next_month_year = cur_month_year + 1
+      next_month_month = 1
+    else
+      next_month_year = cur_month_year
+      next_month_month = cur_month_month + 1
+    end
+      
+    Chronic::Span.new(@current_month_begin, Time.construct(next_month_year, next_month_month))
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    case pointer
+    when :past
+      self.next(pointer)
+    when :future, :none
+      self.next(:none)
+    end
+  end
+  
+  def width
+    MONTH_SECONDS
+  end
+  
+  def index
+    symbol_to_number(@type)
+  end
+  
+  def to_s
+    super << '-monthname-' << @type.to_s
+  end
+  
+  private
+  
+  def symbol_to_number(sym)
+    lookup = {:january => 1,
+              :february => 2,
+              :march => 3,
+              :april => 4,
+              :may => 5,
+              :june => 6,
+              :july => 7,
+              :august => 8,
+              :september => 9,
+              :october => 10,
+              :november => 11,
+              :december => 12}
+    lookup[sym] || raise("Invalid symbol specified")
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Season.php b/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Season.php
new file mode 100644 (file)
index 0000000..a255865
--- /dev/null
@@ -0,0 +1,23 @@
+class Chronic::RepeaterSeason < Chronic::Repeater #:nodoc:
+  SEASON_SECONDS = 7_862_400 # 91 * 24 * 60 * 60
+  
+  def next(pointer)
+    super
+    
+    raise 'Not implemented'
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    raise 'Not implemented'
+  end
+  
+  def width
+    SEASON_SECONDS
+  end
+  
+  def to_s
+    super << '-season'
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/SeasonName.php b/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/SeasonName.php
new file mode 100644 (file)
index 0000000..adfd1f2
--- /dev/null
@@ -0,0 +1,24 @@
+class Chronic::RepeaterSeasonName < Chronic::RepeaterSeason #:nodoc:
+  @summer = ['jul 21', 'sep 22']
+  @autumn = ['sep 23', 'dec 21']
+  @winter = ['dec 22', 'mar 19']
+  @spring = ['mar 20', 'jul 20']
+  
+  def next(pointer)
+    super
+    raise 'Not implemented'
+  end
+  
+  def this(pointer = :future)
+    super
+    raise 'Not implemented'
+  end
+  
+  def width
+    (91 * 24 * 60 * 60)
+  end
+  
+  def to_s
+    super << '-season-' << @type.to_s
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Second.php b/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Second.php
new file mode 100644 (file)
index 0000000..6d05545
--- /dev/null
@@ -0,0 +1,36 @@
+class Chronic::RepeaterSecond < Chronic::Repeater #:nodoc:
+  SECOND_SECONDS = 1 # haha, awesome
+  
+  def next(pointer = :future)
+    super
+    
+    direction = pointer == :future ? 1 : -1
+    
+    if !@second_start
+      @second_start = @now + (direction * SECOND_SECONDS)
+    else
+      @second_start += SECOND_SECONDS * direction
+    end
+    
+    Chronic::Span.new(@second_start, @second_start + SECOND_SECONDS)
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    Chronic::Span.new(@now, @now + 1)
+  end
+  
+  def offset(span, amount, pointer)
+    direction = pointer == :future ? 1 : -1
+    span + direction * amount * SECOND_SECONDS
+  end
+  
+  def width
+    SECOND_SECONDS
+  end
+  
+  def to_s
+    super << '-second'
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Time.php b/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Time.php
new file mode 100644 (file)
index 0000000..f856014
--- /dev/null
@@ -0,0 +1,117 @@
+class Chronic::RepeaterTime < Chronic::Repeater #:nodoc:
+  class Tick #:nodoc:
+    attr_accessor :time
+    
+    def initialize(time, ambiguous = false)
+      @time = time
+      @ambiguous = ambiguous
+    end
+    
+    def ambiguous?
+      @ambiguous
+    end
+    
+    def *(other)
+      Tick.new(@time * other, @ambiguous)
+    end
+    
+    def to_f
+      @time.to_f
+    end
+    
+    def to_s
+      @time.to_s + (@ambiguous ? '?' : '')
+    end
+  end
+  
+  def initialize(time, options = {})
+    t = time.gsub(/\:/, '')
+    @type = 
+    if (1..2) === t.size
+      hours = t.to_i
+      hours == 12 ? Tick.new(0 * 60 * 60, true) : Tick.new(hours * 60 * 60, true)
+    elsif t.size == 3
+      Tick.new((t[0..0].to_i * 60 * 60) + (t[1..2].to_i * 60), true)
+    elsif t.size == 4
+      ambiguous = time =~ /:/ && t[0..0].to_i != 0 && t[0..1].to_i <= 12
+      hours = t[0..1].to_i
+      hours == 12 ? Tick.new(0 * 60 * 60 + t[2..3].to_i * 60, ambiguous) : Tick.new(hours * 60 * 60 + t[2..3].to_i * 60, ambiguous)
+    elsif t.size == 5
+      Tick.new(t[0..0].to_i * 60 * 60 + t[1..2].to_i * 60 + t[3..4].to_i, true)
+    elsif t.size == 6
+      ambiguous = time =~ /:/ && t[0..0].to_i != 0 && t[0..1].to_i <= 12
+      hours = t[0..1].to_i
+      hours == 12 ? Tick.new(0 * 60 * 60 + t[2..3].to_i * 60 + t[4..5].to_i, ambiguous) : Tick.new(hours * 60 * 60 + t[2..3].to_i * 60 + t[4..5].to_i, ambiguous)
+    else
+      raise("Time cannot exceed six digits")
+    end
+  end
+  
+  # Return the next past or future Span for the time that this Repeater represents
+  #   pointer - Symbol representing which temporal direction to fetch the next day
+  #             must be either :past or :future
+  def next(pointer)
+    super
+    
+    half_day = 60 * 60 * 12
+    full_day = 60 * 60 * 24
+    
+    first = false
+    
+    unless @current_time
+      first = true
+      midnight = Time.local(@now.year, @now.month, @now.day)
+      yesterday_midnight = midnight - full_day
+      tomorrow_midnight = midnight + full_day
+
+      catch :done do
+        if pointer == :future
+          if @type.ambiguous?
+            [midnight + @type, midnight + half_day + @type, tomorrow_midnight + @type].each do |t|
+              (@current_time = t; throw :done) if t >= @now
+            end
+          else
+            [midnight + @type, tomorrow_midnight + @type].each do |t|
+              (@current_time = t; throw :done) if t >= @now
+            end
+          end
+        else # pointer == :past
+          if @type.ambiguous?
+            [midnight + half_day + @type, midnight + @type, yesterday_midnight + @type * 2].each do |t|
+              (@current_time = t; throw :done) if t <= @now
+            end
+          else
+            [midnight + @type, yesterday_midnight + @type].each do |t|
+              (@current_time = t; throw :done) if t <= @now
+            end
+          end
+        end
+      end
+      
+      @current_time || raise("Current time cannot be nil at this point")
+    end
+    
+    unless first
+      increment = @type.ambiguous? ? half_day : full_day
+      @current_time += pointer == :future ? increment : -increment
+    end
+    
+    Chronic::Span.new(@current_time, @current_time + width)
+  end
+  
+  def this(context = :future)
+    super
+    
+    context = :future if context == :none
+    
+    self.next(context)
+  end
+  
+  def width
+    1
+  end
+  
+  def to_s
+    super << '-time-' << @type.to_s
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Week.php b/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Week.php
new file mode 100644 (file)
index 0000000..ec88ff1
--- /dev/null
@@ -0,0 +1,68 @@
+class Chronic::RepeaterWeek < Chronic::Repeater #:nodoc:
+  WEEK_SECONDS = 604800 # (7 * 24 * 60 * 60)
+  
+  def next(pointer)
+    super
+    
+    if !@current_week_start
+      case pointer
+      when :future
+        sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+        sunday_repeater.start = @now
+        next_sunday_span = sunday_repeater.next(:future)
+        @current_week_start = next_sunday_span.begin
+      when :past
+        sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+        sunday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
+        sunday_repeater.next(:past)
+        last_sunday_span = sunday_repeater.next(:past)
+        @current_week_start = last_sunday_span.begin
+      end
+    else
+      direction = pointer == :future ? 1 : -1
+      @current_week_start += direction * WEEK_SECONDS
+    end
+    
+    Chronic::Span.new(@current_week_start, @current_week_start + WEEK_SECONDS)
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    case pointer
+    when :future
+      this_week_start = Time.local(@now.year, @now.month, @now.day, @now.hour) + Chronic::RepeaterHour::HOUR_SECONDS
+      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+      sunday_repeater.start = @now
+      this_sunday_span = sunday_repeater.this(:future)
+      this_week_end = this_sunday_span.begin
+      Chronic::Span.new(this_week_start, this_week_end)
+    when :past
+      this_week_end = Time.local(@now.year, @now.month, @now.day, @now.hour)
+      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+      sunday_repeater.start = @now
+      last_sunday_span = sunday_repeater.next(:past)
+      this_week_start = last_sunday_span.begin
+      Chronic::Span.new(this_week_start, this_week_end)
+    when :none
+      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
+      sunday_repeater.start = @now
+      last_sunday_span = sunday_repeater.next(:past)
+      this_week_start = last_sunday_span.begin
+      Chronic::Span.new(this_week_start, this_week_start + WEEK_SECONDS)
+    end
+  end
+  
+  def offset(span, amount, pointer)
+    direction = pointer == :future ? 1 : -1
+    span + direction * amount * WEEK_SECONDS
+  end
+  
+  def width
+    WEEK_SECONDS
+  end
+  
+  def to_s
+    super << '-week'
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Weekend.php b/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Weekend.php
new file mode 100644 (file)
index 0000000..f012267
--- /dev/null
@@ -0,0 +1,60 @@
+class Chronic::RepeaterWeekend < Chronic::Repeater #:nodoc:
+  WEEKEND_SECONDS = 172_800 # (2 * 24 * 60 * 60)
+  
+  def next(pointer)
+    super
+    
+    if !@current_week_start
+      case pointer
+      when :future
+        saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
+        saturday_repeater.start = @now
+        next_saturday_span = saturday_repeater.next(:future)
+        @current_week_start = next_saturday_span.begin
+      when :past
+        saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
+        saturday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
+        last_saturday_span = saturday_repeater.next(:past)
+        @current_week_start = last_saturday_span.begin
+      end
+    else
+      direction = pointer == :future ? 1 : -1
+      @current_week_start += direction * Chronic::RepeaterWeek::WEEK_SECONDS
+    end
+    
+    Chronic::Span.new(@current_week_start, @current_week_start + WEEKEND_SECONDS)
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    case pointer
+    when :future, :none
+      saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
+      saturday_repeater.start = @now
+      this_saturday_span = saturday_repeater.this(:future)
+      Chronic::Span.new(this_saturday_span.begin, this_saturday_span.begin + WEEKEND_SECONDS)
+    when :past
+      saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
+      saturday_repeater.start = @now
+      last_saturday_span = saturday_repeater.this(:past)
+      Chronic::Span.new(last_saturday_span.begin, last_saturday_span.begin + WEEKEND_SECONDS)
+    end
+  end
+  
+  def offset(span, amount, pointer)
+    direction = pointer == :future ? 1 : -1
+    weekend = Chronic::RepeaterWeekend.new(:weekend)
+    weekend.start = span.begin
+    start = weekend.next(pointer).begin + (amount - 1) * direction * Chronic::RepeaterWeek::WEEK_SECONDS
+    Chronic::Span.new(start, start + (span.end - span.begin))
+  end
+  
+  def width
+    WEEKEND_SECONDS
+  end
+  
+  def to_s
+    super << '-weekend'
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Year.php b/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Year.php
new file mode 100644 (file)
index 0000000..426371f
--- /dev/null
@@ -0,0 +1,58 @@
+class Chronic::RepeaterYear < Chronic::Repeater #:nodoc:
+  
+  def next(pointer)
+    super
+    
+    if !@current_year_start
+      case pointer
+      when :future
+        @current_year_start = Time.construct(@now.year + 1)
+      when :past
+        @current_year_start = Time.construct(@now.year - 1)
+      end
+    else
+      diff = pointer == :future ? 1 : -1
+      @current_year_start = Time.construct(@current_year_start.year + diff)
+    end
+    
+    Chronic::Span.new(@current_year_start, Time.construct(@current_year_start.year + 1))
+  end
+  
+  def this(pointer = :future)
+    super
+    
+    case pointer
+    when :future
+      this_year_start = Time.construct(@now.year, @now.month, @now.day) + Chronic::RepeaterDay::DAY_SECONDS
+      this_year_end = Time.construct(@now.year + 1, 1, 1)
+    when :past
+      this_year_start = Time.construct(@now.year, 1, 1)
+      this_year_end = Time.construct(@now.year, @now.month, @now.day)
+    when :none
+      this_year_start = Time.construct(@now.year, 1, 1)
+      this_year_end = Time.construct(@now.year + 1, 1, 1)
+    end
+    
+    Chronic::Span.new(this_year_start, this_year_end)
+  end
+  
+  def offset(span, amount, pointer)
+    direction = pointer == :future ? 1 : -1
+    
+    sb = span.begin
+    new_begin = Time.construct(sb.year + (amount * direction), sb.month, sb.day, sb.hour, sb.min, sb.sec)
+    
+    se = span.end
+    new_end = Time.construct(se.year + (amount * direction), se.month, se.day, se.hour, se.min, se.sec)
+    
+    Chronic::Span.new(new_begin, new_end)
+  end
+  
+  def width
+    (365 * 24 * 60 * 60)
+  end
+  
+  def to_s
+    super << '-year'
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Scalar.php b/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Scalar.php
new file mode 100644 (file)
index 0000000..b08cfee
--- /dev/null
@@ -0,0 +1,74 @@
+module Chronic
+
+  class Scalar < Tag #:nodoc:
+    def self.scan(tokens)
+      # for each token
+      tokens.each_index do |i|
+        if t = self.scan_for_scalars(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
+        if t = self.scan_for_days(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
+        if t = self.scan_for_months(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
+        if t = self.scan_for_years(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
+      end
+      tokens
+    end
+  
+    def self.scan_for_scalars(token, post_token)
+      if token.word =~ /^\d*$/
+        unless post_token && %w{am pm morning afternoon evening night}.include?(post_token)
+          return Scalar.new(token.word.to_i)
+        end
+      end
+      return nil
+    end
+    
+    def self.scan_for_days(token, post_token)
+      if token.word =~ /^\d\d?$/
+        unless token.word.to_i > 31 || (post_token && %w{am pm morning afternoon evening night}.include?(post_token))
+          return ScalarDay.new(token.word.to_i)
+        end
+      end
+      return nil
+    end
+    
+    def self.scan_for_months(token, post_token)
+      if token.word =~ /^\d\d?$/
+        unless token.word.to_i > 12 || (post_token && %w{am pm morning afternoon evening night}.include?(post_token))
+          return ScalarMonth.new(token.word.to_i)
+        end
+      end
+      return nil
+    end
+    
+    def self.scan_for_years(token, post_token)
+      if token.word =~ /^([1-9]\d)?\d\d?$/
+        unless post_token && %w{am pm morning afternoon evening night}.include?(post_token)
+          return ScalarYear.new(token.word.to_i)
+        end
+      end
+      return nil
+    end
+    
+    def to_s
+      'scalar'
+    end
+  end
+  
+  class ScalarDay < Scalar #:nodoc:
+    def to_s
+      super << '-day-' << @type.to_s
+    end
+  end
+  
+  class ScalarMonth < Scalar #:nodoc:
+    def to_s
+      super << '-month-' << @type.to_s
+    end
+  end
+  
+  class ScalarYear < Scalar #:nodoc:
+    def to_s
+      super << '-year-' << @type.to_s
+    end
+  end
+
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Separator.php b/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Separator.php
new file mode 100644 (file)
index 0000000..86c56e3
--- /dev/null
@@ -0,0 +1,76 @@
+module Chronic
+
+  class Separator < Tag #:nodoc:
+    def self.scan(tokens)
+      tokens.each_index do |i|
+        if t = self.scan_for_commas(tokens[i]) then tokens[i].tag(t); next end
+        if t = self.scan_for_slash_or_dash(tokens[i]) then tokens[i].tag(t); next end
+        if t = self.scan_for_at(tokens[i]) then tokens[i].tag(t); next end
+        if t = self.scan_for_in(tokens[i]) then tokens[i].tag(t); next end
+      end
+      tokens
+    end
+    
+    def self.scan_for_commas(token)
+      scanner = {/^,$/ => :comma}
+      scanner.keys.each do |scanner_item|
+        return SeparatorComma.new(scanner[scanner_item]) if scanner_item =~ token.word
+      end
+      return nil
+    end
+    
+    def self.scan_for_slash_or_dash(token)
+      scanner = {/^-$/ => :dash,
+                 /^\/$/ => :slash}
+      scanner.keys.each do |scanner_item|
+        return SeparatorSlashOrDash.new(scanner[scanner_item]) if scanner_item =~ token.word
+      end
+      return nil
+    end
+    
+    def self.scan_for_at(token)
+      scanner = {/^(at|@)$/ => :at}
+      scanner.keys.each do |scanner_item|
+        return SeparatorAt.new(scanner[scanner_item]) if scanner_item =~ token.word
+      end
+      return nil
+    end
+    
+    def self.scan_for_in(token)
+      scanner = {/^in$/ => :in}
+      scanner.keys.each do |scanner_item|
+        return SeparatorIn.new(scanner[scanner_item]) if scanner_item =~ token.word
+      end
+      return nil
+    end
+    
+    def to_s
+      'separator'
+    end
+  end
+  
+  class SeparatorComma < Separator #:nodoc:
+    def to_s
+      super << '-comma'
+    end
+  end
+  
+  class SeparatorSlashOrDash < Separator #:nodoc:
+    def to_s
+      super << '-slashordash-' << @type.to_s
+    end
+  end
+  
+  class SeparatorAt < Separator #:nodoc:
+    def to_s
+      super << '-at'
+    end
+  end
+  
+  class SeparatorIn < Separator #:nodoc:
+    def to_s
+      super << '-in'
+    end
+  end
+
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Timezone.php b/framework/Date_Parser/lib/Horde/Date/Parser/Locale/Base/Timezone.php
new file mode 100644 (file)
index 0000000..41041ef
--- /dev/null
@@ -0,0 +1,22 @@
+module Chronic
+  class TimeZone < Tag #:nodoc:    
+    def self.scan(tokens)
+      tokens.each_index do |i|
+        if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t); next end
+      end
+      tokens
+    end
+
+    def self.scan_for_all(token)
+      scanner = {/[PMCE][DS]T/i => :tz}
+      scanner.keys.each do |scanner_item|
+        return self.new(scanner[scanner_item]) if scanner_item =~ token.word
+      end
+      return nil
+    end
+
+    def to_s
+      'timezone'
+    end
+  end
+end
\ No newline at end of file
diff --git a/framework/Date_Parser/lib/Horde/Date/Parser/Tag.php b/framework/Date_Parser/lib/Horde/Date/Parser/Tag.php
new file mode 100644 (file)
index 0000000..a079640
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+/**
+ * Tokens are tagged with subclassed instances of this class when
+ * they match specific criteria
+ */
+class Horde_Date_Parser_Tag
+{
+    public $type;
+    public $now;
+
+    public function __construct($type)
+    {
+        $this->type = $type;
+    }
+
+    public function start($s)
+    {
+        $this->now = $s;
+    }
+
+}
diff --git a/framework/Date_Parser/lib/Horde/Date/Parser/Token.php b/framework/Date_Parser/lib/Horde/Date/Parser/Token.php
new file mode 100644 (file)
index 0000000..16b3b70
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+class Horde_Date_Parser_Token
+{
+    public $word;
+    public $tags;
+
+    public function __construct($word)
+    {
+        $this->word = $word;
+        $this->tags = array();
+    }
+
+    /**
+     * Tag this token with the specified tag
+     */
+    public function tag($new_tag)
+    {
+        $this->tags[] = $new_tag;
+    }
+
+    /**
+     * Remove all tags of the given class
+     */
+    public function untag($tag_class)
+    {
+        $this->tags = array_filter($this->tags, create_function('$t', 'return $t instanceof ' . $tag_class));
+    }
+
+    /**
+     * Return true if this token has any tags
+     */
+    public function tagged()
+    {
+        return count($this->tags) > 0;
+    }
+
+    /**
+     * Return the Tag that matches the given class
+     */
+    public function getTag($tag_class)
+    {
+        $matches = array_filter($this->tags, create_function('$t', 'return $t instanceof ' . $tag_class));
+        return array_shift($matches);
+    }
+
+    /**
+     * Print this Token in a pretty way
+     */
+    public function __toString()
+    {
+        return '(' . implode(', ', $this->tags) . ') ';
+    }
+
+}
diff --git a/framework/Date_Parser/lib/Horde/Date/Span.php b/framework/Date_Parser/lib/Horde/Date/Span.php
new file mode 100644 (file)
index 0000000..d61c24c
--- /dev/null
@@ -0,0 +1,27 @@
+  # A Span represents a range of time. Since this class extends
+  # Range, you can use #begin and #end to get the beginning and
+  # ending times of the span (they will be of class Time)
+  class Span < Range
+    # Returns the width of this span in seconds
+    def width
+      (self.end - self.begin).to_i
+    end
+
+    # Add a number of seconds to this span, returning the
+    # resulting Span
+    def +(seconds)
+      Span.new(self.begin + seconds, self.end + seconds)
+    end
+
+    # Subtract a number of seconds to this span, returning the
+    # resulting Span
+    def -(seconds)
+      self + -seconds
+    end
+
+    # Prints this span in a nice fashion
+    def to_s
+      '(' << self.begin.to_s << '..' << self.end.to_s << ')'
+    end
+  end
+
diff --git a/framework/Date_Parser/lib/Horde/Support/Numerizer.php b/framework/Date_Parser/lib/Horde/Support/Numerizer.php
new file mode 100644 (file)
index 0000000..062f704
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+/**
+ *
+ */
+class Horde_Support_Numerizer
+{
+    public static function factory($args = array())
+    {
+        $locale = isset($args['locale']) ? $args['locale'] : null;
+        if ($locale && strtolower($locale) != 'base') {
+            $locale = str_replace(' ', '_', ucwords(str_replace('_', ' ', strtolower($locale))));
+            $class = 'Horde_Support_Numerizer_Locale_' . $locale;
+            if (class_exists($class)) {
+                return new $class($args);
+            }
+
+            $language = array_shift(explode('_', $locale));
+            if ($language != $locale) {
+                $class = 'Horde_Support_Numerizer_Locale_' . $language;
+                if (class_exists($class)) {
+                    return new $class($args);
+                }
+            }
+        }
+
+        return new Horde_Support_Numerizer_Locale_Base($args);
+    }
+
+}
diff --git a/framework/Date_Parser/lib/Horde/Support/Numerizer/Locale/Base.php b/framework/Date_Parser/lib/Horde/Support/Numerizer/Locale/Base.php
new file mode 100644 (file)
index 0000000..b6cc380
--- /dev/null
@@ -0,0 +1,149 @@
+<?php
+class Horde_Support_Numerizer_Locale_Base
+{
+    public $DIRECT_NUMS = array(
+        'eleven' => '11',
+        'twelve' => '12',
+        'thirteen' => '13',
+        'fourteen' => '14',
+        'fifteen' => '15',
+        'sixteen' => '16',
+        'seventeen' => '17',
+        'eighteen' => '18',
+        'nineteen' => '19',
+        'ninteen' => '19',      // Common mis-spelling
+        'zero' => '0',
+        'one' => '1',
+        'two' => '2',
+        'three' => '3',
+        'four(\W|$)' => '4$1',  // The weird regex is so that it matches four but not fourty
+        'five' => '5',
+        'six(\W|$)' => '6$1',
+        'seven(\W|$)' => '7$1',
+        'eight(\W|$)' => '8$1',
+        'nine(\W|$)' => '9$1',
+        'ten' => '10',
+        '\ba[\b^$]' => '1',     // doesn't make sense for an 'a' at the end to be a 1
+    );
+
+    public $TEN_PREFIXES = array(
+        'twenty' => 20,
+        'thirty' => 30,
+        'fourty' => 40,
+        'fifty' => 50,
+        'sixty' => 60,
+        'seventy' => 70,
+        'eighty' => 80,
+        'ninety' => 90,
+    );
+
+    public $BIG_PREFIXES = array(
+        'hundred' => 100,
+        'thousand' => 1000,
+        'million' => 1000000,
+        'billion' => 1000000000,
+        'trillion' => 1000000000000,
+    );
+
+    public function numerize($string)
+    {
+        // preprocess
+        $string = $this->_splitHyphenatedWords($string);
+        $string = $this->_hideAHalf($string);
+
+        $string = $this->_directReplacements($string);
+        $string = $this->_replaceTenPrefixes($string);
+        $string = $this->_replaceBigPrefixes($string);
+        $string = $this->_fractionalAddition($string);
+
+        return $string;
+    }
+
+    /**
+     * will mutilate hyphenated-words but shouldn't matter for date extraction
+     */
+    protected function _splitHyphenatedWords($string)
+    {
+        return preg_replace('/ +|([^\d])-([^d])/', '$1 $2', $string);
+    }
+
+    /**
+     * take the 'a' out so it doesn't turn into a 1, save the half for the end
+     */
+    protected function _hideAHalf($string)
+    {
+        return str_replace('a half', 'haAlf', $string);
+    }
+
+    /**
+     * easy/direct replacements
+     */
+    protected function _directReplacements($string)
+    {
+        foreach ($this->DIRECT_NUMS as $dn => $dn_replacement) {
+            $string = preg_replace("/$dn/i", $dn_replacement, $string);
+        }
+        return $string;
+    }
+
+    /**
+     * ten, twenty, etc.
+     */
+    protected function _replaceTenPrefixes($string)
+    {
+        foreach ($this->TEN_PREFIXES as $tp => $tp_replacement) {
+            $string = preg_replace_callback(
+                "/(?:$tp)( *\d(?=[^\d]|\$))*/i",
+                create_function(
+                    '$m',
+                    'return ' . $tp_replacement . ' + (isset($m[1]) ? (int)$m[1] : 0);'
+                ),
+                $string);
+        }
+        return $string;
+    }
+
+    /**
+     * hundreds, thousands, millions, etc.
+     */
+    protected function _replaceBigPrefixes($string)
+    {
+        foreach ($this->BIG_PREFIXES as $bp => $bp_replacement) {
+            $string = preg_replace_callback(
+                '/(\d*) *' . $bp . '/i',
+                create_function(
+                    '$m',
+                    'return ' . $bp_replacement . ' * (int)$m[1];'
+                ),
+                $string);
+            $string = $this->_andition($string);
+        }
+        return $string;
+    }
+
+    protected function _andition($string)
+    {
+        while (true) {
+            if (preg_match('/(\d+)( | and )(\d+)(?=[^\w]|$)/i', $string, $sc, PREG_OFFSET_CAPTURE)) {
+                if (preg_match('/and/', $sc[2][0]) || $sc[1][0] > $sc[3][0]) {
+                    $string = substr($string, 0, $sc[1][1]) . ((int)$sc[1][0] + (int)$sc[3][0]) . substr($string, $sc[3][1] + strlen($sc[3][0]));
+                    continue;
+                }
+            }
+            break;
+        }
+        return $string;
+    }
+
+    protected function _fractionalAddition($string)
+    {
+        return preg_replace_callback(
+            '/(\d+)(?: | and |-)*haAlf/i',
+            create_function(
+                '$m',
+                'return (string)((float)$m[1] + 0.5);'
+            ),
+            $string);
+    }
+
+}
diff --git a/framework/Date_Parser/lib/Horde/Support/Numerizer/Locale/De.php b/framework/Date_Parser/lib/Horde/Support/Numerizer/Locale/De.php
new file mode 100644 (file)
index 0000000..2e13fae
--- /dev/null
@@ -0,0 +1,110 @@
+<?php
+class Horde_Support_Numerizer_Locale_De extends Horde_Support_Numerizer_Locale_Base
+{
+    public $DIRECT_NUMS = array(
+        'dreizehn' => 13,
+        'vierzehn' => 14,
+        'fünfzehn' => 15,
+        'sechzehn' => 16,
+        'siebzehn' => 17,
+        'achtzehn' => 18,
+        'neunzehn' => 19,
+        'eins' => 1,
+        'zwei' => 2,
+        'zwo' => 2,
+        'drei' => 3,
+        'vier' => 4,
+        'fünf' => 5,
+        'sechs' => 6,
+        'sieben' => 7,
+        'acht' => 8,
+        'neun' => 9,
+        'zehn' => 10,
+        'elf' => 11,
+        'zwölf' => 12,
+        'eine?' => 1,
+    );
+
+    public $TEN_PREFIXES = array(
+        'zwanzig' => 20,
+        'dreißig' => 30,
+        'vierzig' => 40,
+        'fünfzig' => 50,
+        'sechzig' => 60,
+        'siebzig' => 70,
+        'achtzig' => 80,
+        'neunzig' => 90,
+    );
+
+    public $BIG_PREFIXES = array(
+        'hundert' => 100,
+        'tausend' => 1000,
+        'million' => 1000000,
+        'milliarde' => 1000000000,
+        'billion' => 1000000000000,
+    );
+
+    /**
+     * Rules:
+     *
+     * - there are irregular word for 11 and 12 like in English
+     * - numbers below one million are written together (1 M = "eine Million", 100 = "einhundert")
+     * - "a" is declinable (see above, "one" = "eins", "a" = "ein/eine")
+     * - numbers below 100 are flipped compared to english, and have an "and = "und" (21 = "twenty-one" = "einundzwanzig")
+     */
+    public function numerize($string)
+    {
+        // preprocess?
+
+        $string = $this->_replaceTenPrefixes($string);
+        $string = $this->_directReplacements($string);
+        $string = $this->_replaceBigPrefixes($string);
+        $string = $this->_fractionalAddition($string);
+        $string = $this->_andition($string);
+
+        return $string;
+    }
+
+    /**
+     * ten, twenty, etc.
+     */
+    protected function _replaceTenPrefixes($string)
+    {
+        foreach ($this->TEN_PREFIXES as $tp => $tp_replacement) {
+            $string = preg_replace_callback(
+                "/(?:$tp)( *\d(?=[^\d]|\$))*/i",
+                create_function(
+                    '$m',
+                    'return ' . $tp_replacement . ' + (isset($m[1]) ? (int)$m[1] : 0);'
+                ),
+                $string);
+        }
+        return $string;
+    }
+
+    /**
+     * hundreds, thousands, millions, etc.
+     */
+    protected function _replaceBigPrefixes($string)
+    {
+        foreach ($this->BIG_PREFIXES as $bp => $bp_replacement) {
+            $string = preg_replace_callback(
+                '/(\d*) *' . $bp . '/i',
+                create_function(
+                    '$m',
+                    '$factor = (int)$m[1]; if (!$factor) $factor = 1; return (' . $bp_replacement . ' * $factor) . "und";'
+                ),
+                $string);
+        }
+        return $string;
+    }
+
+    protected function _andition($string)
+    {
+        while (preg_match('/(\d+)((?:und)+)(\d*)(?=[^\w]|$)/i', $string, $sc, PREG_OFFSET_CAPTURE)) {
+            $string = substr($string, 0, $sc[1][1]) . ((int)$sc[1][0] + (int)$sc[3][0]) . substr($string, $sc[3][1] + strlen($sc[3][0]));
+        }
+        return $string;
+    }
+
+}
diff --git a/framework/Date_Parser/package.xml b/framework/Date_Parser/package.xml
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/framework/Date_Parser/test/Horde/Date/Parser/AllTests.php b/framework/Date_Parser/test/Horde/Date/Parser/AllTests.php
new file mode 100644 (file)
index 0000000..53d8bde
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+/**
+ * @category   Horde
+ * @package    Date_Parser
+ * @subpackage UnitTests
+ * @copyright  2008 The Horde Project (http://www.horde.org/)
+ * @license    http://opensource.org/licenses/bsd-license.php
+ */
+
+if (!defined('PHPUnit_MAIN_METHOD')) {
+    define('PHPUnit_MAIN_METHOD', 'Horde_Date_Parser_AllTests::main');
+}
+
+require_once 'PHPUnit/Framework/TestSuite.php';
+require_once 'PHPUnit/TextUI/TestRunner.php';
+
+class Horde_Date_Parser_AllTests {
+
+    public static function main()
+    {
+        PHPUnit_TextUI_TestRunner::run(self::suite());
+    }
+
+    public static function suite()
+    {
+        set_include_path(dirname(__FILE__) . '/../../../lib' . PATH_SEPARATOR . get_include_path());
+        if (!spl_autoload_functions()) {
+            spl_autoload_register(create_function('$class', '$filename = str_replace(array(\'::\', \'_\'), \'/\', $class); include "$filename.php";'));
+        }
+
+        $suite = new PHPUnit_Framework_TestSuite('Horde Framework - Horde_Date_Parser');
+
+        $basedir = dirname(__FILE__);
+        $baseregexp = preg_quote($basedir . DIRECTORY_SEPARATOR, '/');
+
+        foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($basedir)) as $file) {
+            if ($file->isFile() && preg_match('/Test.php$/', $file->getFilename())) {
+                $pathname = $file->getPathname();
+                require $pathname;
+
+                $class = str_replace(DIRECTORY_SEPARATOR, '_',
+                                     preg_replace("/^$baseregexp(.*)\.php/", '\\1', $pathname));
+                $suite->addTestSuite('Horde_Date_Parser_' . $class);
+            }
+        }
+
+        return $suite;
+    }
+
+}
+
+if (PHPUnit_MAIN_METHOD == 'Horde_Date_Parser_AllTests::main') {
+    Horde_Date_Parser_AllTests::main();
+}
diff --git a/framework/Date_Parser/test/Horde/Support/AllTests.php b/framework/Date_Parser/test/Horde/Support/AllTests.php
new file mode 100644 (file)
index 0000000..7fac8b1
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+/**
+ * @category   Horde
+ * @package    Support
+ * @subpackage UnitTests
+ * @copyright  2008 The Horde Project (http://www.horde.org/)
+ * @license    http://opensource.org/licenses/bsd-license.php
+ */
+
+if (!defined('PHPUnit_MAIN_METHOD')) {
+    define('PHPUnit_MAIN_METHOD', 'Horde_Support_AllTests::main');
+}
+
+require_once 'PHPUnit/Framework/TestSuite.php';
+require_once 'PHPUnit/TextUI/TestRunner.php';
+
+class Horde_Support_AllTests {
+
+    public static function main()
+    {
+        PHPUnit_TextUI_TestRunner::run(self::suite());
+    }
+
+    public static function suite()
+    {
+        set_include_path(dirname(__FILE__) . '/../../../lib' . PATH_SEPARATOR . get_include_path());
+        if (!spl_autoload_functions()) {
+            spl_autoload_register(create_function('$class', '$filename = str_replace(array(\'::\', \'_\'), \'/\', $class); include "$filename.php";'));
+        }
+
+        $suite = new PHPUnit_Framework_TestSuite('Horde Framework - Horde_Support');
+
+        $basedir = dirname(__FILE__);
+        $baseregexp = preg_quote($basedir . DIRECTORY_SEPARATOR, '/');
+
+        foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($basedir)) as $file) {
+            if ($file->isFile() && preg_match('/Test.php$/', $file->getFilename())) {
+                $pathname = $file->getPathname();
+                require $pathname;
+
+                $class = str_replace(DIRECTORY_SEPARATOR, '_',
+                                     preg_replace("/^$baseregexp(.*)\.php/", '\\1', $pathname));
+                $suite->addTestSuite('Horde_Support_' . $class);
+            }
+        }
+
+        return $suite;
+    }
+
+}
+
+if (PHPUnit_MAIN_METHOD == 'Horde_Support_AllTests::main') {
+    Horde_Support_AllTests::main();
+}
diff --git a/framework/Date_Parser/test/Horde/Support/Numerizer/Locale/BaseTest.php b/framework/Date_Parser/test/Horde/Support/Numerizer/Locale/BaseTest.php
new file mode 100644 (file)
index 0000000..0643e33
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+/**
+ * @category   Horde
+ * @package    Horde_Support
+ * @subpackage UnitTests
+ */
+
+/**
+ * @category   Horde
+ * @package    Horde_Support
+ * @subpackage UnitTests
+ */
+class Horde_Support_Numerizer_Locale_BaseTest extends PHPUnit_Framework_TestCase
+{
+    public function testStraightParsing()
+    {
+        $numerizer = Horde_Support_Numerizer::factory();
+        $strings = array(
+            1 => 'one',
+            5 => 'five',
+            10 => 'ten',
+            11 => 'eleven',
+            12 => 'twelve',
+            13 => 'thirteen',
+            14 => 'fourteen',
+            15 => 'fifteen',
+            16 => 'sixteen',
+            17 => 'seventeen',
+            18 => 'eighteen',
+            19 => 'nineteen',
+            20 => 'twenty',
+            27 => 'twenty seven',
+            31 => 'thirty-one',
+            59 => 'fifty nine',
+            100 => 'a hundred',
+            100 => 'one hundred',
+            150 => 'one hundred and fifty',
+            // 150 => 'one fifty',
+            200 => 'two-hundred',
+            500 => '5 hundred',
+            999 => 'nine hundred and ninety nine',
+            1000 => 'one thousand',
+            1200 => 'twelve hundred',
+            1200 => 'one thousand two hundred',
+            17000 => 'seventeen thousand',
+            21473 => 'twentyone-thousand-four-hundred-and-seventy-three',
+            74002 => 'seventy four thousand and two',
+            99999 => 'ninety nine thousand nine hundred ninety nine',
+            100000 => '100 thousand',
+            250000 => 'two hundred fifty thousand',
+            1000000 => 'one million',
+            1250007 => 'one million two hundred fifty thousand and seven',
+            1000000000 => 'one billion',
+            1000000001 => 'one billion and one',
+        );
+
+        foreach ($strings as $key => $string) {
+            $this->assertEquals($key, (int)$numerizer->numerize($string));
+        }
+    }
+
+}
diff --git a/framework/Date_Parser/test/Horde/Support/Numerizer/Locale/DeTest.php b/framework/Date_Parser/test/Horde/Support/Numerizer/Locale/DeTest.php
new file mode 100644 (file)
index 0000000..806d9bb
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+/**
+ * @category   Horde
+ * @package    Horde_Support
+ * @subpackage UnitTests
+ */
+
+/**
+ * @category   Horde
+ * @package    Horde_Support
+ * @subpackage UnitTests
+ */
+class Horde_Support_Numerizer_Locale_DeTest extends PHPUnit_Framework_TestCase
+{
+    public function testStraightParsing()
+    {
+        $numerizer = Horde_Support_Numerizer::factory(array('locale' => 'de'));
+        $strings = array(
+            array(1, 'eins'),
+            array(5, 'fünf'),
+            array(10, 'zehn'),
+            array(11, 'elf'),
+            array(12, 'zwölf'),
+            array(13, 'dreizehn'),
+            array(14, 'vierzehn'),
+            array(15, 'fünfzehn'),
+            array(16, 'sechzehn'),
+            array(17, 'siebzehn'),
+            array(18, 'achtzehn'),
+            array(19, 'neunzehn'),
+            array(20, 'zwanzig'),
+            array(27, 'siebenundzwanzig'),
+            array(31, 'einunddreißig'),
+            array(59, 'neunundfünfzig'),
+            array(100, 'einhundert'),
+            array(100, 'ein hundert'),
+            array(150, 'hundertundfünfzig'),
+            array(150, 'einhundertundfünfzig'),
+            array(200, 'zweihundert'),
+            array(500, 'fünfhundert'),
+            array(999, 'neunhundertneunundneunzig'),
+            array(1000, 'eintausend'),
+            array(1200, 'zwölfhundert'),
+            array(1200, 'eintausendzweihundert'),
+            array(17000, 'siebzehntausend'),
+            array(21473, 'einundzwanzigtausendvierhundertdreiundsiebzig'),
+            array(74002, 'vierundsiebzigtausendzwei'),
+            array(74002, 'vierundsiebzigtausendundzwei'),
+            array(99999, 'neunundneunzigtausendneunhundertneunundneunzig'),
+            array(100000, 'hunderttausend'),
+            array(100000, 'einhunderttausend'),
+            array(250000, 'zweihundertfünfzigtausend'),
+            array(1000000, 'eine million'),
+            array(1250007, 'eine million zweihundertfünfzigtausendundsieben'),
+            array(1000000000, 'eine milliarde'),
+            array(1000000001, 'eine milliarde und eins'),
+        );
+
+        foreach ($strings as $pair) {
+            $this->assertEquals((string)$pair[0], $numerizer->numerize($pair[1]));
+        }
+    }
+
+}
diff --git a/framework/Form/lib/Horde/Form.php b/framework/Form/lib/Horde/Form.php
new file mode 100644 (file)
index 0000000..f995eea
--- /dev/null
@@ -0,0 +1,3946 @@
+<?php
+/**
+ * @package Horde_Form
+ */
+
+/** String */
+include_once 'Horde/String.php';
+
+/**
+ * Horde_Form Master Class.
+ *
+ * The Horde_Form:: package provides form rendering, validation, and
+ * other functionality for the Horde Application Framework.
+ *
+ * $Horde: incubator/Horde_Form/Horde/Form.php,v 1.19 2008/08/26 15:32:55 selsky Exp $
+ *
+ * Copyright 2001-2007 Robert E. Coyle <robertecoyle@hotmail.com>
+ * Copyright 2001-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Robert E. Coyle <robertecoyle@hotmail.com>
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @since   Horde 3.0
+ * @package Horde_Form
+ */
+class Horde_Form {
+
+    protected $_name = '';
+    protected $_title = '';
+    protected $_vars;
+    protected $_errors = array();
+    protected $_submitted = null;
+    protected $_sections = array();
+    protected $_open_section = null;
+    protected $_currentSection = array();
+    protected $_variables = array();
+    protected $_hiddenVariables = array();
+    protected $_useFormToken = true;
+    protected $_autofilled = false;
+    protected $_help = false;
+
+    public function __construct($vars, $title = '', $name = null)
+    {
+        if (is_null($name)) {
+            $name = String::lower(get_class($this));
+        }
+
+        $this->_vars = $vars;
+        $this->_title = $title;
+        $this->_name = $name;
+    }
+
+    public function setVars($vars)
+    {
+        $this->_vars = $vars;
+    }
+
+    public function getVars()
+    {
+        return $this->_vars;
+    }
+
+    public function getTitle()
+    {
+        return $this->_title;
+    }
+
+    public function setTitle($title)
+    {
+        $this->_title = $title;
+    }
+
+    public function getName()
+    {
+        return $this->_name;
+    }
+
+    /**
+     * Sets or gets whether the form should be verified by tokens.
+     * Tokens are used to verify that a form is only submitted once.
+     *
+     * @param boolean $token  If specified, sets whether to use form tokens.
+     *
+     * @return boolean  Whether form tokens are being used.
+     */
+    public function useToken($token = null)
+    {
+        if (!is_null($token)) {
+            $this->_useFormToken = $token;
+        }
+        return $this->_useFormToken;
+    }
+
+    /**
+     * Get the renderer for this form, either a custom renderer or the
+     * standard one.
+     *
+     * To use a custom form renderer, your form class needs to
+     * override this function:
+     * <code>
+     * function getRenderer()
+     * {
+     *     return new CustomFormRenderer();
+     * }
+     * </code>
+     *
+     * ... where CustomFormRenderer is the classname of the custom
+     * renderer class, which should extend Horde_Form_Renderer.
+     *
+     * @param array $params  A hash of renderer-specific parameters.
+     *
+     * @return object Horde_Form_Renderer  The form renderer.
+     */
+    function getRenderer($params = array())
+    {
+        return new Horde_Form_Renderer_Xhtml($params);
+    }
+
+    function getType($type, $params = array())
+    {
+        $type_class = 'Horde_Form_Type_' . $type;
+        if (!class_exists($type_class)) {
+            Horde::fatal(PEAR::raiseError(sprintf('Nonexistant class "%s" for field type "%s"', $type_class, $type)), __FILE__, __LINE__);
+        }
+        $type_ob = new $type_class();
+        call_user_func_array(array(&$type_ob, 'init'), $params);
+        return $type_ob;
+    }
+
+    public function setSection($section = '', $desc = '', $image = '', $expanded = true)
+    {
+        $this->_currentSection = $section;
+        if (!count($this->_sections) && !$this->getOpenSection()) {
+            $this->setOpenSection($section);
+        }
+        $this->_sections[$section]['desc'] = $desc;
+        $this->_sections[$section]['expanded'] = $expanded;
+        $this->_sections[$section]['image'] = $image;
+    }
+
+    public function getSections()
+    {
+        return $this->_sections;
+    }
+
+    public function getSectionDesc($section)
+    {
+        return $this->_sections[$section]['desc'];
+    }
+
+    public function getSectionImage($section)
+    {
+        return $this->_sections[$section]['image'];
+    }
+
+    public function setOpenSection($section)
+    {
+        $this->_vars->set('__formOpenSection', $section);
+    }
+
+    public function getOpenSection()
+    {
+        return $this->_vars->get('__formOpenSection');
+    }
+
+    public function getSectionExpandedState($section, $boolean = false)
+    {
+        if ($boolean) {
+            /* Only the boolean value is required. */
+            return $this->_sections[$section]['expanded'];
+        }
+
+        /* Need to return the values for use in styles. */
+        if ($this->_sections[$section]['expanded']) {
+            return 'block';
+        } else {
+            return 'none';
+        }
+    }
+
+    /**
+     * TODO
+     */
+    public function addVariable($humanName, $varName, $type, $required,
+                                $readonly = false, $description = null,
+                                $params = array())
+    {
+        return $this->insertVariableBefore(null, $humanName, $varName, $type,
+                                           $required, $readonly, $description,
+                                           $params);
+    }
+
+    /**
+     * TODO
+     */
+    public function insertVariableBefore($before, $humanName, $varName, $type,
+                                         $required, $readonly = false,
+                                         $description = null, $params = array())
+    {
+        $type = $this->getType($type, $params);
+        $var = new Horde_Form_Variable($humanName, $varName, $type,
+                                       $required, $readonly, $description);
+
+        /* Set the form object reference in the var. */
+        $var->setFormOb($this);
+
+        if ($var->getType() instanceof Horde_Form_Type_enum &&
+            count($var->getValues()) == 1) {
+            $vals = array_keys($var->getValues());
+            $this->_vars->add($var->varName, $vals[0]);
+            $var->_autofilled = true;
+        }
+        if (empty($this->_currentSection)) {
+            $this->_currentSection = '__base';
+        }
+
+        if (is_null($before)) {
+            $this->_variables[$this->_currentSection][] = &$var;
+        } else {
+            $num = 0;
+            while (isset($this->_variables[$this->_currentSection][$num]) &&
+                   $this->_variables[$this->_currentSection][$num]->getVarName() != $before) {
+                $num++;
+            }
+            if (!isset($this->_variables[$this->_currentSection][$num])) {
+                $this->_variables[$this->_currentSection][] = &$var;
+            } else {
+                $this->_variables[$this->_currentSection] = array_merge(
+                    array_slice($this->_variables[$this->_currentSection], 0, $num),
+                    array(&$var),
+                    array_slice($this->_variables[$this->_currentSection], $num));
+            }
+        }
+
+        return $var;
+    }
+
+    /**
+     * Removes a variable from the form.
+     *
+     * As only variables can be passed by reference, you need to call this
+     * method this way if want to pass a variable name:
+     * <code>
+     * $form->removeVariable($var = 'varname');
+     * </code>
+     *
+     * @param Horde_Form_Variable|string $var  Either the variable's name or
+     *                                         the variable to remove from the
+     *                                         form.
+     *
+     * @return boolean  True if the variable was found (and deleted).
+     */
+    public function removeVariable(&$var)
+    {
+        foreach (array_keys($this->_variables) as $section) {
+            foreach (array_keys($this->_variables[$section]) as $i) {
+                if ((is_a($var, 'Horde_Form_Variable') && $this->_variables[$section][$i] === $var) ||
+                    ($this->_variables[$section][$i]->getVarName() == $var)) {
+                    // Slice out the variable to be removed.
+                    $this->_variables[$this->_currentSection] = array_merge(
+                        array_slice($this->_variables[$this->_currentSection], 0, $i),
+                        array_slice($this->_variables[$this->_currentSection], $i + 1));
+
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * TODO
+     */
+    public function addHidden($varName, $type, $required, $params = array())
+    {
+        $type = $this->getType($type, $params);
+        $var = new Horde_Form_Variable('', $varName, $type, $required);
+        $var->hide();
+        $this->_hiddenVariables[] = &$var;
+        return $var;
+    }
+
+    public function getVariables($flat = true, $withHidden = false)
+    {
+        if ($flat) {
+            $vars = array();
+            foreach ($this->_variables as $section) {
+                foreach ($section as $var) {
+                    $vars[] = $var;
+                }
+            }
+            if ($withHidden) {
+                foreach ($this->_hiddenVariables as $var) {
+                    $vars[] = $var;
+                }
+            }
+            return $vars;
+        } else {
+            return $this->_variables;
+        }
+    }
+
+    public function getHiddenVariables()
+    {
+        return $this->_hiddenVariables;
+    }
+
+    /**
+     * Preserve the variables/values from another Horde_Form object.
+     */
+    public function preserve(Horde_Form $form)
+    {
+        /* OLD IMPLEMENTATION
+        if ($this->_useFormToken) {
+            $this->_preserveVarByPost($this->_name . '_formToken', Horde_Token::generateId($this->_name));
+        }
+
+        $variables = $this->getVariables();
+        foreach ($variables as $var) {
+            $varname = $var->getVarName();
+
+            switch (get_class($var->getType()) {
+            case 'passwordconfirm':
+            case 'emailconfirm':
+                $this->preserveVarByPost($this->_vars, $varname . '[original]');
+                $this->preserveVarByPost($this->_vars, $varname . '[confirm]');
+                break;
+
+            case 'monthyear':
+                $this->preserveVarByPost($this->_vars, $varname . '[month]');
+                $this->preserveVarByPost($this->_vars, $varname . '[year]');
+                break;
+
+            case 'monthdayyear':
+                $this->preserveVarByPost($this->_vars, $varname . '[month]');
+                $this->preserveVarByPost($this->_vars, $varname . '[day]');
+                $this->preserveVarByPost($this->_vars, $varname . '[year]');
+                break;
+            }
+
+            $this->preserveVarByPost($this->_vars, $varname);
+        }
+        foreach ($this->_hiddenVariables as $var) {
+            $this->preserveVarByPost($this->_vars, $var->getVarName());
+        }
+        */
+    }
+
+    /**
+     * Does the action of validating the form, checking if it really has been
+     * submitted by calling isSubmitted() and if true does any onSubmit()
+     * calls for var types in the form. The _submitted var is then rechecked.
+     *
+     * @param boolean         $canAutofill  Can the form be valid without
+     *                                      being submitted?
+     *
+     * @return boolean  True if the form is valid.
+     */
+    public function validate($canAutoFill = false)
+    {
+        /* Get submitted status. */
+        if ($this->isSubmitted() || $canAutoFill) {
+            /* Form was submitted or can autofill; check for any variable
+             * types' onSubmit(). */
+            $this->onSubmit($this->_vars);
+
+            /* Recheck submitted status. */
+            if (!$this->isSubmitted() && !$canAutoFill) {
+                return false;
+            }
+        } else {
+            /* Form has not been submitted; return false. */
+            return false;
+        }
+
+        $message = '';
+        $this->_autofilled = true;
+
+        if ($this->_useFormToken) {
+            global $conf;
+            if (isset($conf['token'])) {
+                /* If there is a configured token system, set it up. */
+                $tokenSource = Horde_Token::factory($conf['token']['driver'], Horde::getDriverConfig('token', $conf['token']['driver']));
+            } else {
+                /* Default to the file system if no config. */
+                $tokenSource = Horde_Token::factory('file');
+            }
+            if (!$tokenSource->verify($this->_vars->get($this->_name . '_formToken'))) {
+                $this->_errors['_formToken'] = _("This form has already been processed.");
+            }
+        }
+
+        foreach ($this->getVariables() as $var) {
+            $this->_autofilled = $var->_autofilled && $this->_autofilled;
+            if (!$var->validate($this->_vars, $message)) {
+                $this->_errors[$var->getVarName()] = $message;
+            }
+        }
+
+        if ($this->_autofilled) {
+            unset($this->_errors['_formToken']);
+        }
+
+        foreach ($this->_hiddenVariables as $var) {
+            if (!$var->validate($this->_vars, $message)) {
+                $this->_errors[$var->getVarName()] = $message;
+            }
+        }
+
+        return $this->isValid();
+    }
+
+    public function clearValidation()
+    {
+        $this->_errors = array();
+    }
+
+    public function getError($var)
+    {
+        if (is_a($var, 'Horde_Form_Variable')) {
+            $name = $var->getVarName();
+        } else {
+            $name = $var;
+        }
+        return isset($this->_errors[$name]) ? $this->_errors[$name] : null;
+    }
+
+    public function setError($var, $message)
+    {
+        if (is_a($var, 'Horde_Form_Variable')) {
+            $name = $var->getVarName();
+        } else {
+            $name = $var;
+        }
+        $this->_errors[$name] = $message;
+    }
+
+    public function clearError($var)
+    {
+        if (is_a($var, 'Horde_Form_Variable')) {
+            $name = $var->getVarName();
+        } else {
+            $name = $var;
+        }
+        unset($this->_errors[$name]);
+    }
+
+    public function isValid()
+    {
+        return ($this->_autofilled || !count($this->_errors));
+    }
+
+    public function execute()
+    {
+        throw new Horde_Form_Exception('Subclass must overide execute()');
+    }
+
+    /**
+     * Fetch the field values of the submitted form.
+     *
+     * @param array $info      Array to be filled with the submitted field
+     *                         values.
+     */
+    public function getInfo(&$info)
+    {
+        $this->_getInfoFromVariables($this->getVariables(), $info);
+        $this->_getInfoFromVariables($this->_hiddenVariables, $info);
+    }
+
+    /**
+     * Fetch the field values from a given array of variables.
+     *
+     * @access private
+     *
+     * @param array  $variables  An array of Horde_Form_Variable objects to
+     *                           fetch from.
+     * @param array  $info       The array to be filled with the submitted
+     *                           field values.
+     */
+    protected function _getInfoFromVariables($variables, &$info)
+    {
+        foreach ($variables as $var) {
+            if ($var->isArrayVal()) {
+                $var->getInfo($this->_vars, $values);
+                if (is_array($values)) {
+                    $varName = str_replace('[]', '', $var->getVarName());
+                    foreach ($values as $i => $val) {
+                        $info[$i][$varName] = $val;
+                    }
+                }
+            } else {
+                if (Horde_Array::getArrayParts($var->getVarName(), $base, $keys)) {
+                    if (!isset($info[$base])) {
+                        $info[$base] = array();
+                    }
+                    $pointer = &$info[$base];
+                    while (count($keys)) {
+                        $key = array_shift($keys);
+                        if (!isset($pointer[$key])) {
+                            $pointer[$key] = array();
+                        }
+                        $pointer = &$pointer[$key];
+                    }
+                    $var->getInfo($this->_vars, $pointer);
+                } else {
+                    $var->getInfo($this->_vars, $info[$var->getVarName()]);
+                }
+            }
+        }
+    }
+
+    public function hasHelp()
+    {
+        return $this->_help;
+    }
+
+    /**
+     * Determines if this form has been submitted or not. If the class
+     * var _submitted is null then it will check for the presence of
+     * the formname in the form variables.
+     *
+     * Other events can explicitly set the _submitted variable to
+     * false to indicate a form submit but not for actual posting of
+     * data (eg. onChange events to update the display of fields).
+     *
+     * @return boolean  True or false indicating if the form has been
+     *                  submitted.
+     */
+    public function isSubmitted()
+    {
+        if (is_null($this->_submitted)) {
+            if ($this->_vars->get('formname') == $this->getName()) {
+                $this->_submitted = true;
+            } else {
+                $this->_submitted = false;
+            }
+        }
+
+        return $this->_submitted;
+    }
+
+    /**
+     * Checks if there is anything to do on the submission of the form by
+     * looping through each variable's onSubmit() function.
+     */
+    public function onSubmit()
+    {
+        /* Loop through all vars and check if there's anything to do on
+         * submit. */
+        $variables = $this->getVariables();
+        foreach ($variables as $var) {
+            $var->type->onSubmit($var, $this->_vars);
+            /* If changes to var being tracked don't register the form as
+             * submitted if old value and new value differ. */
+            if ($var->getOption('trackchange')) {
+                $varname = $var->getVarName();
+                if (!is_null($this->_vars->get('formname')) &&
+                    $this->_vars->get($varname) != $this->_vars->get('__old_' . $varname)) {
+                    $this->_submitted = false;
+                }
+            }
+        }
+    }
+
+    /**
+     * Explicitly sets the state of the form submit.
+     *
+     * An event can override the automatic determination of the submit state
+     * in the isSubmitted() function.
+     *
+     * @param boolean $state  Whether to set the state of the form as being
+     *                        submitted.
+     */
+    public function setSubmitted($state = true)
+    {
+        $this->_submitted = $state;
+    }
+
+}
+
+/**
+ * Horde_Form_Type Class
+ *
+ * @author  Robert E. Coyle <robertecoyle@hotmail.com>
+ * @package Horde_Form
+ */
+class Horde_Form_Type {
+
+    protected function __get($property)
+    {
+        $prop = '_' . $property;
+        return isset($this->$prop) ? $this->$prop : null;
+    }
+
+    protected function __set($property, $value)
+    {
+        $prop = '_' . $property;
+        $this->$prop = $value;
+    }
+
+    protected function __isset($property)
+    {
+        $prop = '_' . $property;
+        return isset($this->$prop);
+    }
+
+    protected function __unset($property)
+    {
+        $prop = '_' . $property;
+        unset($this->$prop);
+    }
+
+    public function init()
+    {
+    }
+
+    public function onSubmit()
+    {
+    }
+
+    public function isValid($var, $vars, $value, &$message)
+    {
+        $message = '<strong>Error:</strong> Horde_Form_Type::isValid() called - should be overridden<br />';
+        return false;
+    }
+
+    function getInfo($vars, $var, &$info)
+    {
+        $info = $var->getValue($vars);
+    }
+
+}
+
+class Horde_Form_Type_number extends Horde_Form_Type {
+
+    var $_fraction;
+
+    function init($fraction = null)
+    {
+        $this->_fraction = $fraction;
+    }
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        if ($var->isRequired() && empty($value) && ((string)(double)$value !== $value)) {
+            $message = _("This field is required.");
+            return false;
+        } elseif (empty($value)) {
+            return true;
+        }
+
+        /* If matched, then this is a correct numeric value. */
+        if (preg_match($this->_getValidationPattern(), $value)) {
+            return true;
+        }
+
+        $message = _("This field must be a valid number.");
+        return false;
+    }
+
+    function _getValidationPattern()
+    {
+        static $pattern = '';
+        if (!empty($pattern)) {
+            return $pattern;
+        }
+
+        /* Get current locale information. */
+        $linfo = NLS::getLocaleInfo();
+
+        /* Build the pattern. */
+        $pattern = '(-)?';
+
+        /* Only check thousands separators if locale has any. */
+        if (!empty($linfo['mon_thousands_sep'])) {
+            /* Regex to check for correct thousands separators (if any). */
+            $pattern .= '((\d+)|((\d{0,3}?)([' . $linfo['mon_thousands_sep'] . ']\d{3})*?))';
+        } else {
+            /* No locale thousands separator, check for only digits. */
+            $pattern .= '(\d+)';
+        }
+        /* If no decimal point specified default to dot. */
+        if (empty($linfo['mon_decimal_point'])) {
+            $linfo['mon_decimal_point'] = '.';
+        }
+        /* Regex to check for correct decimals (if any). */
+        if (empty($this->_fraction)) {
+            $fraction = '*';
+        } else {
+            $fraction = '{0,' . $this->_fraction . '}';
+        }
+        $pattern .= '([' . $linfo['mon_decimal_point'] . '](\d' . $fraction . '))?';
+
+        /* Put together the whole regex pattern. */
+        $pattern = '/^' . $pattern . '$/';
+
+        return $pattern;
+    }
+
+    function getInfo($vars, $var, &$info)
+    {
+        $value = $vars->get($var->getVarName());
+        $linfo = NLS::getLocaleInfo();
+        $value = str_replace($linfo['mon_thousands_sep'], '', $value);
+        $info = str_replace($linfo['mon_decimal_point'], '.', $value);
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array('name' => _("Number"));
+    }
+
+}
+
+class Horde_Form_Type_int extends Horde_Form_Type {
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        if ($var->isRequired() && empty($value) && ((string)(int)$value !== $value)) {
+            $message = _("This field is required.");
+            return false;
+        }
+
+        if (empty($value) || preg_match('/^[0-9]+$/', $value)) {
+            return true;
+        }
+
+        $message = _("This field may only contain integers.");
+        return false;
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array('name' => _("Integer"));
+    }
+
+}
+
+class Horde_Form_Type_octal extends Horde_Form_Type {
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        if ($var->isRequired() && empty($value) && ((string)(int)$value !== $value)) {
+            $message = _("This field is required.");
+            return false;
+        }
+
+        if (empty($value) || preg_match('/^[0-7]+$/', $value)) {
+            return true;
+        }
+
+        $message = _("This field may only contain octal values.");
+        return false;
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array('name' => _("Octal"));
+    }
+
+}
+
+class Horde_Form_Type_intlist extends Horde_Form_Type {
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        if (empty($value) && $var->isRequired()) {
+            $message = _("This field is required.");
+            return false;
+        }
+
+        if (empty($value) || preg_match('/^[0-9 ,]+$/', $value)) {
+            return true;
+        }
+
+        $message = _("This field must be a comma or space separated list of integers");
+        return false;
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array('name' => _("Integer list"));
+    }
+
+}
+
+class Horde_Form_Type_text extends Horde_Form_Type {
+
+    var $_regex;
+    var $_size;
+    var $_maxlength;
+
+    /**
+     * The initialisation function for the text variable type.
+     *
+     * @access private
+     *
+     * @param string $regex       Any valid PHP PCRE pattern syntax that
+     *                            needs to be matched for the field to be
+     *                            considered valid. If left empty validity
+     *                            will be checked only for required fields
+     *                            whether they are empty or not.
+     *                            If using this regex test it is advisable
+     *                            to enter a description for this field to
+     *                            warn the user what is expected, as the
+     *                            generated error message is quite generic
+     *                            and will not give any indication where
+     *                            the regex failed.
+     * @param integer $size       The size of the input field.
+     * @param integer $maxlength  The max number of characters.
+     */
+    function init($regex = '', $size = 40, $maxlength = null)
+    {
+        $this->_regex     = $regex;
+        $this->_size      = $size;
+        $this->_maxlength = $maxlength;
+    }
+
+    public function isValid($var, $vars, $value, &$message)
+    {
+        $valid = true;
+
+        if (!empty($this->_maxlength) && String::length($value) > $this->_maxlength) {
+            $valid = false;
+            $message = sprintf(_("Value is over the maximum length of %s."), $this->_maxlength);
+        } elseif ($var->isRequired() && empty($this->_regex)) {
+            if (!($valid = strlen(trim($value)) > 0)) {
+                $message = _("This field is required.");
+            }
+        } elseif (strlen($this->_regex)) {
+            if (!($valid = preg_match($this->_regex, $value))) {
+                $message = _("You must enter a valid value.");
+            }
+        }
+
+        return $valid;
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array(
+            'name' => _("Text"),
+            'params' => array(
+                'regex'     => array('label' => _("Regex"),
+                                     'type'  => 'text'),
+                'size'      => array('label' => _("Size"),
+                                     'type'  => 'int'),
+                'maxlength' => array('label' => _("Maximum length"),
+                                     'type'  => 'int')));
+    }
+
+}
+
+class Horde_Form_Type_stringlist extends Horde_Form_Type_text {
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array(
+            'name' => _("String list"),
+            'params' => array(
+                'regex'     => array('label' => _("Regex"),
+                                     'type'  => 'text'),
+                'size'      => array('label' => _("Size"),
+                                     'type'  => 'int'),
+                'maxlength' => array('label' => _("Maximum length"),
+                                     'type'  => 'int')),
+        );
+    }
+
+}
+
+class Horde_Form_Type_phone extends Horde_Form_Type {
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        $valid = true;
+
+        if ($var->isRequired()) {
+            $valid = strlen(trim($value)) > 0;
+            if (!$valid) {
+                $message = _("This field is required.");
+            }
+        } else {
+            $valid = preg_match('/^\+?[\d()\-\/ ]*$/', $value);
+            if (!$valid) {
+                $message = _("You must enter a valid phone number, digits only with an optional '+' for the international dialing prefix.");
+            }
+        }
+
+        return $valid;
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array('name' => _("Phone number"));
+    }
+
+}
+
+class Horde_Form_Type_cellphone extends Horde_Form_Type_phone {
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array('name' => _("Mobile phone number"));
+    }
+
+}
+
+class Horde_Form_Type_ipaddress extends Horde_Form_Type_text {
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        $valid = true;
+
+        if (strlen(trim($value)) > 0) {
+            $ip = explode('.', $value);
+            $valid = (count($ip) == 4);
+            if ($valid) {
+                foreach ($ip as $part) {
+                    if (!is_numeric($part) ||
+                        $part > 255 ||
+                        $part < 0) {
+                        $valid = false;
+                        break;
+                    }
+                }
+            }
+
+            if (!$valid) {
+                $message = _("Please enter a valid IP address.");
+            }
+        } elseif ($var->isRequired()) {
+            $valid = false;
+            $message = _("This field is required.");
+        }
+
+        return $valid;
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array('name' => _("IP address"));
+    }
+
+}
+
+class Horde_Form_Type_longtext extends Horde_Form_Type_text {
+
+    var $_rows;
+    var $_cols;
+    var $_helper = array();
+
+    function init($rows = 8, $cols = 80, $helper = array())
+    {
+        if (!is_array($helper)) {
+            $helper = array($helper);
+        }
+
+        $this->_rows = $rows;
+        $this->_cols = $cols;
+        $this->_helper = $helper;
+    }
+
+    function hasHelper($option = '')
+    {
+        if (empty($option)) {
+            /* No option specified, check if any helpers have been
+             * activated. */
+            return !empty($this->_helper);
+        } elseif (empty($this->_helper)) {
+            /* No helpers activated at all, return false. */
+            return false;
+        } else {
+            /* Check if given helper has been activated. */
+            return in_array($option, $this->_helper);
+        }
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array(
+            'name' => _("Long text"),
+            'params' => array(
+                'rows'   => array('label' => _("Number of rows"),
+                                  'type'  => 'int'),
+                'cols'   => array('label' => _("Number of columns"),
+                                  'type'  => 'int'),
+                'helper' => array('label' => _("Helper?"),
+                                  'type'  => 'boolean')));
+    }
+
+}
+
+class Horde_Form_Type_countedtext extends Horde_Form_Type_longtext {
+
+    var $_chars;
+
+    function init($rows = null, $cols = null, $chars = 1000)
+    {
+        parent::init($rows, $cols);
+        $this->_chars = $chars;
+    }
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        $valid = true;
+
+        $length = String::length(trim($value));
+
+        if ($var->isRequired() && $length <= 0) {
+            $valid = false;
+            $message = _("This field is required.");
+        } elseif ($length > $this->_chars) {
+            $valid = false;
+            $message = sprintf(_("There are too many characters in this field. You have entered %s characters; you must enter less than %s."), String::length(trim($value)), $this->_chars);
+        }
+
+        return $valid;
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array(
+            'name' => _("Counted text"),
+            'params' => array(
+                'rows'  => array('label' => _("Number of rows"),
+                                 'type'  => 'int'),
+                'cols'  => array('label' => _("Number of columns"),
+                                 'type'  => 'int'),
+                'chars' => array('label' => _("Number of characters"),
+                                 'type'  => 'int')));
+    }
+
+}
+
+class Horde_Form_Type_address extends Horde_Form_Type_longtext {
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array(
+            'name' => _("Address"),
+            'params' => array(
+                'rows' => array('label' => _("Number of rows"),
+                                'type'  => 'int'),
+                'cols' => array('label' => _("Number of columns"),
+                                'type'  => 'int')));
+    }
+
+}
+
+class Horde_Form_Type_addresslink extends Horde_Form_Type {
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        return true;
+    }
+
+}
+
+class Horde_Form_Type_file extends Horde_Form_Type {
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        if ($var->isRequired()) {
+            $uploaded = Browser::wasFileUploaded($var->getVarName());
+            if (is_a($uploaded, 'PEAR_Error')) {
+                $message = $uploaded->getMessage();
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    function getInfo($vars, $var, &$info)
+    {
+        $name = $var->getVarName();
+        $uploaded = Browser::wasFileUploaded($name);
+        if ($uploaded === true) {
+            $info['name'] = $_FILES[$name]['name'];
+            $info['type'] = $_FILES[$name]['type'];
+            $info['tmp_name'] = $_FILES[$name]['tmp_name'];
+            $info['file'] = $_FILES[$name]['tmp_name'];
+            $info['error'] = $_FILES[$name]['error'];
+            $info['size'] = $_FILES[$name]['size'];
+        }
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array('name' => _("File upload"));
+    }
+
+}
+
+class Horde_Form_Type_image extends Horde_Form_Type {
+
+    /**
+     * Has a file been uploaded on this form submit?
+     *
+     * @var boolean
+     */
+    var $_uploaded = null;
+
+    /**
+     * Show the upload button?
+     *
+     * @var boolean
+     */
+    var $_show_upload = true;
+
+    /**
+     * Show the option to upload also original non-modified image?
+     *
+     * @var boolean
+     */
+    var $_show_keeporig = false;
+
+    /**
+     * Limit the file size?
+     *
+     * @var integer
+     */
+    var $_max_filesize = null;
+
+    /**
+     * Hash containing the previously uploaded image info.
+     *
+     * @var array
+     */
+    var $_img = array();
+
+    function init($show_upload = true, $show_keeporig = false, $max_filesize = null)
+    {
+        $this->_show_upload   = $show_upload;
+        $this->_show_keeporig = $show_keeporig;
+        $this->_max_filesize  = $max_filesize;
+    }
+
+    function onSubmit($var, $vars)
+    {
+        /* Get the upload. */
+        $this->_getUpload($vars, $var);
+
+        /* If this was done through the upload button override the submitted
+         * value of the form. */
+        if ($vars->get('_do_' . $var->getVarName())) {
+            $var->form->setSubmitted(false);
+        }
+    }
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        $field = $vars->get($var->getVarName());
+
+        /* Get the upload. */
+        $this->_getUpload($vars, $var);
+
+        /* The upload generated a PEAR Error. */
+        if (is_a($this->_uploaded, 'PEAR_Error')) {
+            /* Not required and no image upload attempted. */
+            if (!$var->isRequired() && empty($field['img']) &&
+                $this->_uploaded->getCode() == UPLOAD_ERR_NO_FILE) {
+                return true;
+            }
+
+            if (($this->_uploaded->getCode() == UPLOAD_ERR_NO_FILE) &&
+                empty($field['img'])) {
+                /* Nothing uploaded and no older upload. */
+                $message = _("This field is required.");
+                return false;
+            } elseif (!empty($field['img'])) {
+                /* Nothing uploaded but older upload present. */
+                return true;
+            } else {
+                /* Some other error message. */
+                $message = $this->_uploaded->getMessage();
+                return false;
+            }
+        } elseif ($this->_max_filesize &&
+                  $this->_img['size'] > $this->_max_filesize) {
+            $message = sprintf(_("The image file was larger than the maximum allowed size (%d bytes)."), $this->_max_filesize);
+            return false;
+        }
+
+        return true;
+    }
+
+    function getInfo($vars, $var, &$info)
+    {
+        /* Get the upload. */
+        $this->_getUpload($vars, $var);
+
+        /* Get image params stored in the hidden field. */
+        $value = $var->getValue($vars);
+        $info = $this->_img;
+        if (empty($info['file'])) {
+            unset($info['file']);
+            return;
+        }
+        if ($this->_show_keeporig) {
+            $info['keep_orig'] = !empty($value['keep_orig']);
+        }
+
+        /* Set the uploaded value (either true or PEAR_Error). */
+        $info['uploaded'] = &$this->_uploaded;
+
+        /* If a modified file exists move it over the original. */
+        if ($this->_show_keeporig && $info['keep_orig']) {
+            /* Requested the saving of original file also. */
+            $info['orig_file'] = Horde::getTempDir() . '/' . $info['file'];
+            $info['file'] = Horde::getTempDir() . '/mod_' . $info['file'];
+            /* Check if a modified file actually exists. */
+            if (!file_exists($info['file'])) {
+                $info['file'] = $info['orig_file'];
+                unset($info['orig_file']);
+            }
+        } else {
+            /* Saving of original not required. */
+            $mod_file = Horde::getTempDir() . '/mod_' . $info['file'];
+            $info['file'] = Horde::getTempDir() . '/' . $info['file'];
+
+            if (file_exists($mod_file)) {
+                /* Unlink first (has to be done on Windows machines?) */
+                unlink($info['file']);
+                rename($mod_file, $info['file']);
+            }
+        }
+    }
+
+    /**
+     * Gets the upload and sets up the upload data array. Either
+     * fetches an upload done with this submit or retries stored
+     * upload info.
+     */
+    function _getUpload($vars, $var)
+    {
+        /* Don't bother with this function if already called and set
+         * up vars. */
+        if (!empty($this->_img)) {
+            return true;
+        }
+
+        /* Check if file has been uploaded. */
+        $varname = $var->getVarName();
+        $this->_uploaded = Browser::wasFileUploaded($varname . '[new]');
+
+        if ($this->_uploaded === true) {
+            /* A file has been uploaded on this submit. Save to temp dir for
+             * preview work. */
+            $this->_img['type'] = $this->getUploadedFileType($varname . '[new]');
+
+            /* Get the other parts of the upload. */
+            Horde_Array::getArrayParts($varname . '[new]', $base, $keys);
+
+            /* Get the temporary file name. */
+            $keys_path = array_merge(array($base, 'tmp_name'), $keys);
+            $this->_img['file'] = Horde_Array::getElement($_FILES, $keys_path);
+
+            /* Get the actual file name. */
+            $keys_path= array_merge(array($base, 'name'), $keys);
+            $this->_img['name'] = Horde_Array::getElement($_FILES, $keys_path);
+
+            /* Get the file size. */
+            $keys_path= array_merge(array($base, 'size'), $keys);
+            $this->_img['size'] = Horde_Array::getElement($_FILES, $keys_path);
+
+            /* Get any existing values for the image upload field. */
+            $upload = $vars->get($var->getVarName());
+            $upload['img'] = @unserialize($upload['img']);
+
+            /* Get the temp file if already one uploaded, otherwise create a
+             * new temporary file. */
+            if (!empty($upload['img']['file'])) {
+                $tmp_file = Horde::getTempDir() . '/' . $upload['img']['file'];
+            } else {
+                $tmp_file = Horde::getTempFile('Horde', false);
+            }
+
+            /* Move the browser created temp file to the new temp file. */
+            move_uploaded_file($this->_img['file'], $tmp_file);
+            $this->_img['file'] = basename($tmp_file);
+
+            /* Store the uploaded image file data to the hidden field. */
+            $upload['img'] = serialize($this->_img);
+            $vars->set($var->getVarName(), $upload);
+        } elseif ($this->_uploaded) {
+            /* File has not been uploaded. */
+            $upload = $vars->get($var->getVarName());
+            if ($this->_uploaded->getCode() == 4 && !empty($upload['img'])) {
+                $this->_img = @unserialize($upload['img']);
+            }
+        }
+    }
+
+    function getUploadedFileType($field)
+    {
+        /* Get any index on the field name. */
+        $index = Horde_Array::getArrayParts($field, $base, $keys);
+
+        if ($index) {
+            /* Index present, fetch the mime type var to check. */
+            $keys_path = array_merge(array($base, 'type'), $keys);
+            $type = Horde_Array::getElement($_FILES, $keys_path);
+            $keys_path= array_merge(array($base, 'tmp_name'), $keys);
+            $tmp_name = Horde_Array::getElement($_FILES, $keys_path);
+        } else {
+            /* No index, simple set up of vars to check. */
+            $type = $_FILES[$field]['type'];
+            $tmp_name = $_FILES[$field]['tmp_name'];
+        }
+
+        if (empty($type) || ($type == 'application/octet-stream')) {
+            /* Type wasn't set on upload, try analising the upload. */
+            global $conf;
+            require_once 'Horde/MIME/Magic.php';
+            if (!($type = MIME_Magic::analyzeFile($tmp_name, isset($conf['mime']['magic_db']) ? $conf['mime']['magic_db'] : null))) {
+                if ($index) {
+                    /* Get the name value. */
+                    $keys_path = array_merge(array($base, 'name'), $keys);
+                    $name = Horde_Array::getElement($_FILES, $keys_path);
+
+                    /* Work out the type from the file name. */
+                    $type = MIME_Magic::filenameToMIME($name);
+
+                    /* Set the type. */
+                    $keys_path = array_merge(array($base, 'type'), $keys);
+                    Horde_Array::getElement($_FILES, $keys_path, $type);
+                } else {
+                    /* Work out the type from the file name. */
+                    $type = MIME_Magic::filenameToMIME($_FILES[$field]['name']);
+
+                    /* Set the type. */
+                    $_FILES[$field]['type'] = MIME_Magic::filenameToMIME($_FILES[$field]['name']);
+                }
+            }
+        }
+
+        return $type;
+    }
+
+    /**
+     * Loads any existing image data into the image field. Requires that the
+     * array $image passed to it contains the structure:
+     *   $image['load']['file'] - the filename of the image;
+     *   $image['load']['data'] - the raw image data.
+     *
+     * @param array $image  The image array.
+     */
+    function loadImageData(&$image)
+    {
+        /* No existing image data to load. */
+        if (!isset($image['load'])) {
+            return;
+        }
+
+        /* Save the data to the temp dir. */
+        $tmp_file = Horde::getTempDir() . '/' . $image['load']['file'];
+        if ($fd = fopen($tmp_file, 'w')) {
+            fwrite($fd, $image['load']['data']);
+            fclose($fd);
+        }
+
+        $image['img'] = serialize(array('file' => $image['load']['file']));
+        unset($image['load']);
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array(
+            'name' => _("Image upload"),
+            'params' => array(
+                'show_upload'   => array('label' => _("Show upload?"),
+                                         'type'  => 'boolean'),
+                'show_keeporig' => array('label' => _("Show option to keep original?"),
+                                         'type'  => 'boolean'),
+                'max_filesize'  => array('label' => _("Maximum file size in bytes"),
+                                         'type'  => 'int')));
+    }
+
+}
+
+class Horde_Form_Type_boolean extends Horde_Form_Type {
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        return true;
+    }
+
+    function getInfo($vars, $var, &$info)
+    {
+        $info = String::lower($vars->get($var->getVarName())) == 'on';
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array('name' => _("True or false"));
+    }
+
+}
+
+class Horde_Form_Type_link extends Horde_Form_Type {
+
+    /**
+     * List of hashes containing link parameters. Possible keys: 'url', 'text',
+     * 'target', 'onclick', 'title', 'accesskey'.
+     *
+     * @var array
+     */
+    var $values;
+
+    function init($values)
+    {
+        $this->values = $values;
+    }
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        return true;
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array(
+            'name' => _("Link"),
+            'params' => array(
+                'url' => array(
+                    'label' => _("Link URL"),
+                    'type' => 'text'),
+                'text' => array(
+                    'label' => _("Link text"),
+                    'type' => 'text'),
+                'target' => array(
+                    'label' => _("Link target"),
+                    'type' => 'text'),
+                'onclick' => array(
+                    'label' => _("Onclick event"),
+                    'type' => 'text'),
+                'title' => array(
+                    'label' => _("Link title attribute"),
+                    'type' => 'text'),
+                'accesskey' => array(
+                    'label' => _("Link access key"),
+                    'type' => 'text')));
+    }
+
+}
+
+class Horde_Form_Type_email extends Horde_Form_Type {
+
+    var $_allow_multi = false;
+    var $_strip_domain = false;
+    var $_link_compose = false;
+    var $_check_smtp = false;
+    var $_link_name;
+
+    /**
+     * A string containing valid delimiters (default is just comma).
+     *
+     * @var string
+     */
+    var $_delimiters = ',';
+
+    function init($allow_multi = false, $strip_domain = false,
+                  $link_compose = false, $link_name = null,
+                  $delimiters = ',')
+    {
+        $this->_allow_multi = $allow_multi;
+        $this->_strip_domain = $strip_domain;
+        $this->_link_compose = $link_compose;
+        $this->_link_name = $link_name;
+        $this->_delimiters = $delimiters;
+    }
+
+    /**
+     */
+    function isValid($var, $vars, $value, &$message)
+    {
+        // Split into individual addresses.
+        $emails = $this->splitEmailAddresses($value);
+
+        // Check for too many.
+        if (!$this->_allow_multi && count($emails) > 1) {
+            $message = _("Only one email address is allowed.");
+            return false;
+        }
+
+        // Check for all valid and at least one non-empty.
+        $nonEmpty = 0;
+        foreach ($emails as $email) {
+            if (!strlen($email)) {
+                continue;
+            }
+            if (!$this->validateEmailAddress($email)) {
+                $message = sprintf(_("\"%s\" is not a valid email address."), $email);
+                return false;
+            }
+            ++$nonEmpty;
+        }
+
+        if (!$nonEmpty && $var->isRequired()) {
+            if ($this->_allow_multi) {
+                $message = _("You must enter at least one email address.");
+            } else {
+                $message = _("You must enter an email address.");
+            }
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Explodes an RFC 2822 string, ignoring a delimiter if preceded
+     * by a "\" character, or if the delimiter is inside single or
+     * double quotes.
+     *
+     * @param string $string     The RFC 822 string.
+     *
+     * @return array  The exploded string in an array.
+     */
+    function splitEmailAddresses($string)
+    {
+        $quotes = array('"', "'");
+        $emails = array();
+        $pos = 0;
+        $in_quote = null;
+        $in_group = false;
+        $prev = null;
+
+        if (!strlen($string)) {
+            return array();
+        }
+
+        $char = $string[0];
+        if (in_array($char, $quotes)) {
+            $in_quote = $char;
+        } elseif ($char == ':') {
+            $in_group = true;
+        } elseif (strpos($this->_delimiters, $char) !== false) {
+            $emails[] = '';
+            $pos = 1;
+        }
+
+        for ($i = 1, $iMax = strlen($string); $i < $iMax; ++$i) {
+            $char = $string[$i];
+            if (in_array($char, $quotes)) {
+                if ($prev !== '\\') {
+                    if ($in_quote === $char) {
+                        $in_quote = null;
+                    } elseif (is_null($in_quote)) {
+                        $in_quote = $char;
+                    }
+                }
+            } elseif ($in_group) {
+                if ($char == ';') {
+                    $emails[] = substr($string, $pos, $i - $pos + 1);
+                    $pos = $i + 1;
+                    $in_group = false;
+                }
+            } elseif ($char == ':') {
+                $in_group = true;
+            } elseif (strpos($this->_delimiters, $char) !== false &&
+                      $prev !== '\\' &&
+                      is_null($in_quote)) {
+                $emails[] = substr($string, $pos, $i - $pos);
+                $pos = $i + 1;
+            }
+            $prev = $char;
+        }
+
+        if ($pos != $i) {
+            /* The string ended without a delimiter. */
+            $emails[] = substr($string, $pos, $i - $pos);
+        }
+
+        return $emails;
+    }
+
+    /**
+     * RFC(2)822 Email Parser.
+     *
+     * By Cal Henderson <cal@iamcal.com>
+     * This code is licensed under a Creative Commons Attribution-ShareAlike 2.5 License
+     * http://creativecommons.org/licenses/by-sa/2.5/
+     *
+     * http://code.iamcal.com/php/rfc822/
+     *
+     * http://iamcal.com/publish/articles/php/parsing_email
+     *
+     * Revision 4
+     *
+     * @param string $email An individual email address to validate.
+     *
+     * @return boolean
+     */
+    function validateEmailAddress($email)
+    {
+        static $comment_regexp, $email_regexp;
+        if ($comment_regexp === null) {
+            $this->_defineValidationRegexps($comment_regexp, $email_regexp);
+        }
+
+        // We need to strip comments first (repeat until we can't find
+        // any more).
+        while (true) {
+            $new = preg_replace("!$comment_regexp!", '', $email);
+            if (strlen($new) == strlen($email)){
+                break;
+            }
+            $email = $new;
+        }
+
+        // Now match what's left.
+        $result = (bool)preg_match("!^$email_regexp$!", $email);
+        if ($result && $this->_check_smtp) {
+            $result = $this->validateEmailAddressSmtp($email);
+        }
+
+        return $result;
+    }
+
+    /**
+     * Attempt partial delivery of mail to an address to validate it.
+     *
+     * @param string $email An individual email address to validate.
+     *
+     * @return boolean
+     */
+    function validateEmailAddressSmtp($email)
+    {
+        list(, $maildomain) = explode('@', $email, 2);
+
+        // Try to get the real mailserver from MX records.
+        if (function_exists('getmxrr') &&
+            @getmxrr($maildomain, $mxhosts, $mxpriorities)) {
+            // MX record found.
+            array_multisort($mxpriorities, $mxhosts);
+            $mailhost = $mxhosts[0];
+        } else {
+            // No MX record found, try the root domain as the mail
+            // server.
+            $mailhost = $maildomain;
+        }
+
+        $fp = @fsockopen($mailhost, 25, $errno, $errstr, 5);
+        if (!$fp) {
+            return false;
+        }
+
+        // Read initial response.
+        fgets($fp, 4096);
+
+        // HELO
+        fputs($fp, "HELO $mailhost\r\n");
+        fgets($fp, 4096);
+
+        // MAIL FROM
+        fputs($fp, "MAIL FROM: <root@example.com>\r\n");
+        fgets($fp, 4096);
+
+        // RCPT TO - gets the result we want.
+        fputs($fp, "RCPT TO: <$email>\r\n");
+        $result = trim(fgets($fp, 4096));
+
+        // QUIT
+        fputs($fp, "QUIT\r\n");
+        fgets($fp, 4096);
+        fclose($fp);
+
+        return substr($result, 0, 1) == '2';
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array(
+            'name' => _("Email"),
+            'params' => array(
+                'allow_multi' => array('label' => _("Allow multiple addresses?"),
+                                       'type'  => 'boolean'),
+                'strip_domain' => array('label' => _("Protect address from spammers?"),
+                                        'type' => 'boolean'),
+                'link_compose' => array('label' => _("Link the email address to the compose page when displaying?"),
+                                        'type' => 'boolean'),
+                'link_name' => array('label' => _("The name to use when linking to the compose page"),
+                                     'type' => 'text'),
+                'delimiters' => array('label' => _("Character to split multiple addresses with"),
+                                      'type' => 'text'),
+            ),
+        );
+    }
+
+    /**
+     * RFC(2)822 Email Parser.
+     *
+     * By Cal Henderson <cal@iamcal.com>
+     * This code is licensed under a Creative Commons Attribution-ShareAlike 2.5 License
+     * http://creativecommons.org/licenses/by-sa/2.5/
+     *
+     * http://code.iamcal.com/php/rfc822/
+     *
+     * http://iamcal.com/publish/articles/php/parsing_email
+     *
+     * Revision 4
+     *
+     * @param string &$comment The regexp for comments.
+     * @param string &$addr_spec The regexp for email addresses.
+     */
+    function _defineValidationRegexps(&$comment, &$addr_spec)
+    {
+        /**
+         * NO-WS-CTL       =       %d1-8 /         ; US-ASCII control characters
+         *                         %d11 /          ;  that do not include the
+         *                         %d12 /          ;  carriage return, line feed,
+         *                         %d14-31 /       ;  and white space characters
+         *                         %d127
+         * ALPHA          =  %x41-5A / %x61-7A   ; A-Z / a-z
+         * DIGIT          =  %x30-39
+         */
+        $no_ws_ctl  = "[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]";
+        $alpha      = "[\\x41-\\x5a\\x61-\\x7a]";
+        $digit      = "[\\x30-\\x39]";
+        $cr         = "\\x0d";
+        $lf         = "\\x0a";
+        $crlf       = "($cr$lf)";
+
+        /**
+         * obs-char        =       %d0-9 / %d11 /          ; %d0-127 except CR and
+         *                         %d12 / %d14-127         ;  LF
+         * obs-text        =       *LF *CR *(obs-char *LF *CR)
+         * text            =       %d1-9 /         ; Characters excluding CR and LF
+         *                         %d11 /
+         *                         %d12 /
+         *                         %d14-127 /
+         *                         obs-text
+         * obs-qp          =       "\" (%d0-127)
+         * quoted-pair     =       ("\" text) / obs-qp
+         */
+        $obs_char       = "[\\x00-\\x09\\x0b\\x0c\\x0e-\\x7f]";
+        $obs_text       = "($lf*$cr*($obs_char$lf*$cr*)*)";
+        $text           = "([\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f]|$obs_text)";
+        $obs_qp         = "(\\x5c[\\x00-\\x7f])";
+        $quoted_pair    = "(\\x5c$text|$obs_qp)";
+
+        /**
+         * obs-FWS         =       1*WSP *(CRLF 1*WSP)
+         * FWS             =       ([*WSP CRLF] 1*WSP) /   ; Folding white space
+         *                         obs-FWS
+         * ctext           =       NO-WS-CTL /     ; Non white space controls
+         *                         %d33-39 /       ; The rest of the US-ASCII
+         *                         %d42-91 /       ;  characters not including "(",
+         *                         %d93-126        ;  ")", or "\"
+         * ccontent        =       ctext / quoted-pair / comment
+         * comment         =       "(" *([FWS] ccontent) [FWS] ")"
+         * CFWS            =       *([FWS] comment) (([FWS] comment) / FWS)
+         *
+         * @note: We translate ccontent only partially to avoid an
+         * infinite loop. Instead, we'll recursively strip comments
+         * before processing the input.
+         */
+        $wsp        = "[\\x20\\x09]";
+        $obs_fws    = "($wsp+($crlf$wsp+)*)";
+        $fws        = "((($wsp*$crlf)?$wsp+)|$obs_fws)";
+        $ctext      = "($no_ws_ctl|[\\x21-\\x27\\x2A-\\x5b\\x5d-\\x7e])";
+        $ccontent   = "($ctext|$quoted_pair)";
+        $comment    = "(\\x28($fws?$ccontent)*$fws?\\x29)";
+        $cfws       = "(($fws?$comment)*($fws?$comment|$fws))";
+        $cfws       = "$fws*";
+
+        /**
+         * atext           =       ALPHA / DIGIT / ; Any character except controls,
+         *                         "!" / "#" /     ;  SP, and specials.
+         *                         "$" / "%" /     ;  Used for atoms
+         *                         "&" / "'" /
+         *                         "*" / "+" /
+         *                         "-" / "/" /
+         *                         "=" / "?" /
+         *                         "^" / "_" /
+         *                         "`" / "{" /
+         *                         "|" / "}" /
+         *                         "~"
+         * atom            =       [CFWS] 1*atext [CFWS]
+         */
+        $atext      = "($alpha|$digit|[\\x21\\x23-\\x27\\x2a\\x2b\\x2d\\x2e\\x3d\\x3f\\x5e\\x5f\\x60\\x7b-\\x7e])";
+        $atom       = "($cfws?$atext+$cfws?)";
+
+        /**
+         * qtext           =       NO-WS-CTL /     ; Non white space controls
+         *                         %d33 /          ; The rest of the US-ASCII
+         *                         %d35-91 /       ;  characters not including "\"
+         *                         %d93-126        ;  or the quote character
+         * qcontent        =       qtext / quoted-pair
+         * quoted-string   =       [CFWS]
+         *                         DQUOTE *([FWS] qcontent) [FWS] DQUOTE
+         *                         [CFWS]
+         * word            =       atom / quoted-string
+         */
+        $qtext      = "($no_ws_ctl|[\\x21\\x23-\\x5b\\x5d-\\x7e])";
+        $qcontent   = "($qtext|$quoted_pair)";
+        $quoted_string  = "($cfws?\\x22($fws?$qcontent)*$fws?\\x22$cfws?)";
+        $word       = "($atom|$quoted_string)";
+
+        /**
+         * obs-local-part  =       word *("." word)
+         * obs-domain      =       atom *("." atom)
+         */
+        $obs_local_part = "($word(\\x2e$word)*)";
+        $obs_domain = "($atom(\\x2e$atom)*)";
+
+        /**
+         * dot-atom-text   =       1*atext *("." 1*atext)
+         * dot-atom        =       [CFWS] dot-atom-text [CFWS]
+         */
+        $dot_atom_text  = "($atext+(\\x2e$atext+)*)";
+        $dot_atom   = "($cfws?$dot_atom_text$cfws?)";
+
+        /**
+         * domain-literal  =       [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS]
+         * dcontent        =       dtext / quoted-pair
+         * dtext           =       NO-WS-CTL /     ; Non white space controls
+         *
+         *                         %d33-90 /       ; The rest of the US-ASCII
+         *                         %d94-126        ;  characters not including "[",
+         *                                         ;  "]", or "\"
+         */
+        $dtext      = "($no_ws_ctl|[\\x21-\\x5a\\x5e-\\x7e])";
+        $dcontent   = "($dtext|$quoted_pair)";
+        $domain_literal = "($cfws?\\x5b($fws?$dcontent)*$fws?\\x5d$cfws?)";
+
+        /**
+         * local-part      =       dot-atom / quoted-string / obs-local-part
+         * domain          =       dot-atom / domain-literal / obs-domain
+         * addr-spec       =       local-part "@" domain
+         */
+        $local_part = "($dot_atom|$quoted_string|$obs_local_part)";
+        $domain     = "($dot_atom|$domain_literal|$obs_domain)";
+        $addr_spec  = "($local_part\\x40$domain)";
+    }
+
+}
+
+class Horde_Form_Type_matrix extends Horde_Form_Type {
+
+    var $_cols;
+    var $_rows;
+    var $_matrix;
+    var $_new_input;
+
+    /**
+     * Initializes the variable.
+     *
+     * @example
+     * init(array('Column A', 'Column B'),
+     *      array(1 => 'Row One', 2 => 'Row 2', 3 => 'Row 3'),
+     *      array(array(true, true, false),
+     *            array(true, false, true),
+     *            array(fasle, true, false)),
+     *      array('Row 4', 'Row 5'));
+     *
+     * @param array $cols               A list of column headers.
+     * @param array $rows               A hash with row IDs as the keys and row
+     *                                  labels as the values.
+     * @param array $matrix             A two dimensional hash with the field
+     *                                  values.
+     * @param boolean|array $new_input  If true, a free text field to add a new
+     *                                  row is displayed on the top, a select
+     *                                  box if this parameter is a value.
+     */
+    function init($cols, $rows = array(), $matrix = array(), $new_input = false)
+    {
+        $this->_cols       = $cols;
+        $this->_rows       = $rows;
+        $this->_matrix     = $matrix;
+        $this->_new_input  = $new_input;
+    }
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        return true;
+    }
+
+    function getInfo($vars, $var, &$info)
+    {
+        $values = $vars->get($var->getVarName());
+        if (!empty($values['n']['r']) && isset($values['n']['v'])) {
+            $new_row = $values['n']['r'];
+            $values['r'][$new_row] = $values['n']['v'];
+            unset($values['n']);
+        }
+
+        $info = (isset($values['r']) ? $values['r'] : array());
+    }
+
+    function about()
+    {
+        return array(
+            'name' => _("Field matrix"),
+            'params' => array(
+                'cols' => array('label' => _("Column titles"),
+                                'type'  => 'stringlist')));
+    }
+
+}
+
+class Horde_Form_Type_emailConfirm extends Horde_Form_Type {
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        if ($var->isRequired() && empty($value['original'])) {
+            $message = _("This field is required.");
+            return false;
+        }
+
+        if ($value['original'] != $value['confirm']) {
+            $message = _("Email addresses must match.");
+            return false;
+        } else {
+            $parser = new Mail_RFC822();
+            $parsed_email = $parser->parseAddressList($value['original'], false, true);
+
+            if (count($parsed_email) > 1) {
+                $message = _("Only one email address allowed.");
+                return false;
+            }
+            if (empty($parsed_email[0]->mailbox)) {
+                $message = _("You did not enter a valid email address.");
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array('name' => _("Email with confirmation"));
+    }
+
+}
+
+class Horde_Form_Type_password extends Horde_Form_Type {
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        $valid = true;
+
+        if ($var->isRequired()) {
+            $valid = strlen(trim($value)) > 0;
+
+            if (!$valid) {
+                $message = _("This field is required.");
+            }
+        }
+
+        return $valid;
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array('name' => _("Password"));
+    }
+
+}
+
+class Horde_Form_Type_passwordconfirm extends Horde_Form_Type {
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        if ($var->isRequired() && empty($value['original'])) {
+            $message = _("This field is required.");
+            return false;
+        }
+
+        if ($value['original'] != $value['confirm']) {
+            $message = _("Passwords must match.");
+            return false;
+        }
+
+        return true;
+    }
+
+    function getInfo($vars, $var, &$info)
+    {
+        $value = $vars->get($var->getVarName());
+        $info = $value['original'];
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array('name' => _("Password with confirmation"));
+    }
+
+}
+
+class Horde_Form_Type_enum extends Horde_Form_Type {
+
+    var $_values;
+    var $_prompt;
+
+    function init($values, $prompt = null)
+    {
+        $this->_values = $values;
+
+        if ($prompt === true) {
+            $this->_prompt = _("-- select --");
+        } else {
+            $this->_prompt = $prompt;
+        }
+    }
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        if ($var->isRequired() && $value == '' && !isset($this->_values[$value])) {
+            $message = _("This field is required.");
+            return false;
+        }
+
+        if (count($this->_values) == 0 || isset($this->_values[$value]) ||
+            ($this->_prompt && empty($value))) {
+            return true;
+        }
+
+        $message = _("Invalid data.");
+        return false;
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array(
+            'name' => _("Drop down list"),
+            'params' => array(
+                'values' => array('label' => _("Values to select from"),
+                                  'type'  => 'stringlist'),
+                'prompt' => array('label' => _("Prompt text"),
+                                  'type'  => 'text')));
+    }
+
+}
+
+class Horde_Form_Type_mlenum extends Horde_Form_Type {
+
+    var $_values;
+    var $_prompts;
+
+    function init(&$values, $prompts = null)
+    {
+        $this->_values = &$values;
+
+        if ($prompts === true) {
+            $this->_prompts = array(_("-- select --"), _("-- select --"));
+        } elseif (!is_array($prompts)) {
+            $this->_prompts = array($prompts, $prompts);
+        } else {
+            $this->_prompts = $prompts;
+        }
+    }
+
+    function onSubmit($var, $vars)
+    {
+        $varname = $var->getVarName();
+        $value = $vars->get($varname);
+
+        if ($value['1'] != $value['old']) {
+            $var->form->setSubmitted(false);
+        }
+    }
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        if ($var->isRequired() && (empty($value['1']) || empty($value['2']))) {
+            $message = _("This field is required.");
+            return false;
+        }
+
+        if (!count($this->_values) || isset($this->_values[$value['1']]) ||
+            (!empty($this->_prompts) && empty($value['1']))) {
+            return true;
+        }
+
+        $message = _("Invalid data.");
+        return false;
+    }
+
+    function getInfo($vars, &$var, &$info)
+    {
+        $info = $vars->get($var->getVarName());
+        return $info['2'];
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array(
+            'name' => _("Multi-level drop down lists"),
+            'params' => array(
+                'values' => array('label' => _("Values to select from"),
+                                  'type'  => 'stringlist'),
+                'prompt' => array('label' => _("Prompt text"),
+                                  'type'  => 'text')));
+    }
+
+}
+
+class Horde_Form_Type_multienum extends Horde_Form_Type_enum {
+
+    var $size = 5;
+
+    function init($values, $size = null)
+    {
+        if (!is_null($size)) {
+            $this->size = (int)$size;
+        }
+
+        parent::init($values);
+    }
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        if (is_array($value)) {
+            foreach ($value as $val) {
+                if (!$this->isValid($var, $vars, $val, $message)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        if (empty($value) && ((string)(int)$value !== $value)) {
+            if ($var->isRequired()) {
+                $message = _("This field is required.");
+                return false;
+            } else {
+                return true;
+            }
+        }
+
+        if (count($this->_values) == 0 || isset($this->_values[$value])) {
+            return true;
+        }
+
+        $message = _("Invalid data.");
+        return false;
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array(
+            'name' => _("Multiple selection"),
+            'params' => array(
+                'values' => array('label' => _("Values"),
+                                  'type'  => 'stringlist'),
+                'size'   => array('label' => _("Size"),
+                                  'type'  => 'int'))
+        );
+    }
+
+}
+
+class Horde_Form_Type_keyval_multienum extends Horde_Form_Type_multienum {
+
+    function getInfo($vars, $var, &$info)
+    {
+        $value = $vars->get($var->getVarName());
+        $info = array();
+        foreach ($value as $key) {
+            $info[$key] = $this->_values[$key];
+        }
+    }
+
+}
+
+class Horde_Form_Type_radio extends Horde_Form_Type_enum {
+
+    /* Entirely implemented by Horde_Form_Type_enum; just a different
+     * view. */
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array(
+            'name' => _("Radio selection"),
+            'params' => array(
+                'values' => array('label' => _("Values"),
+                                  'type'  => 'stringlist')));
+    }
+
+}
+
+class Horde_Form_Type_set extends Horde_Form_Type {
+
+    var $_values;
+    var $_checkAll = false;
+
+    function init(&$values, $checkAll = false)
+    {
+        $this->_values = $values;
+        $this->_checkAll = $checkAll;
+    }
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        if (count($this->_values) == 0 || count($value) == 0) {
+            return true;
+        }
+        foreach ($value as $item) {
+            if (!isset($this->_values[$item])) {
+                $error = true;
+                break;
+            }
+        }
+        if (!isset($error)) {
+            return true;
+        }
+
+        $message = _("Invalid data.");
+        return false;
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array(
+            'name' => _("Set"),
+            'params' => array(
+                'values' => array('label' => _("Values"),
+                                  'type'  => 'stringlist')));
+    }
+
+}
+
+class Horde_Form_Type_date extends Horde_Form_Type {
+
+    var $_format;
+
+    function init($format = '%a %d %B')
+    {
+        $this->_format = $format;
+    }
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        $valid = true;
+
+        if ($var->isRequired()) {
+            $valid = strlen(trim($value)) > 0;
+
+            if (!$valid) {
+                $message = sprintf(_("%s is required"), $var->getHumanName());
+            }
+        }
+
+        return $valid;
+    }
+
+    /**
+     * @static
+     */
+    function getAgo($timestamp)
+    {
+        if ($timestamp === null) {
+            return '';
+        }
+
+        $diffdays = Date_Calc::dateDiff(date('j', $timestamp),
+                                        date('n', $timestamp),
+                                        date('Y', $timestamp),
+                                        date('j'), date('n'), date('Y'));
+
+        /* An error occured. */
+        if ($diffdays == -1) {
+            return;
+        }
+
+        $ago = $diffdays * Date_Calc::compareDates(date('j', $timestamp),
+                                                   date('n', $timestamp),
+                                                   date('Y', $timestamp),
+                                                   date('j'), date('n'),
+                                                   date('Y'));
+        if ($ago < -1) {
+            return sprintf(_(" (%s days ago)"), $diffdays);
+        } elseif ($ago == -1) {
+            return _(" (yesterday)");
+        } elseif ($ago == 0) {
+            return _(" (today)");
+        } elseif ($ago == 1) {
+            return _(" (tomorrow)");
+        } else {
+            return sprintf(_(" (in %s days)"), $diffdays);
+        }
+    }
+
+    function getFormattedTime($timestamp, $format = null, $showago = true)
+    {
+        if (empty($format)) {
+            $format = $this->_format;
+        }
+        if (!empty($timestamp)) {
+            return strftime($format, $timestamp) . ($showago ? Horde_Form_Type_date::getAgo($timestamp) : '');
+        } else {
+            return '';
+        }
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array('name' => _("Date"));
+    }
+
+}
+
+class Horde_Form_Type_time extends Horde_Form_Type {
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        if ($var->isRequired() && empty($value) && ((string)(double)$value !== $value)) {
+            $message = _("This field is required.");
+            return false;
+        }
+
+        if (empty($value) || preg_match('/^[0-2]?[0-9]:[0-5][0-9]$/', $value)) {
+            return true;
+        }
+
+        $message = _("This field may only contain numbers and the colon.");
+        return false;
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array('name' => _("Time"));
+    }
+
+}
+
+class Horde_Form_Type_hourminutesecond extends Horde_Form_Type {
+
+    var $_show_seconds;
+
+    function init($show_seconds = false)
+    {
+        $this->_show_seconds = $show_seconds;
+    }
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        $time = $vars->get($var->getVarName());
+        if (!$this->_show_seconds && !isset($time['second'])) {
+            $time['second'] = 0;
+        }
+
+        if (!$this->emptyTimeArray($time) && !$this->checktime($time['hour'], $time['minute'], $time['second'])) {
+            $message = _("Please enter a valid time.");
+            return false;
+        } elseif ($this->emptyTimeArray($time) && $var->isRequired()) {
+            $message = _("This field is required.");
+            return false;
+        }
+
+        return true;
+    }
+
+    function checktime($hour, $minute, $second)
+    {
+        if (!isset($hour) || $hour == '' || ($hour < 0 || $hour > 23)) {
+            return false;
+        }
+        if (!isset($minute) || $minute == '' || ($minute < 0 || $minute > 60)) {
+            return false;
+        }
+        if (!isset($second) || $second === '' || ($second < 0 || $second > 60)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Return the time supplied as a Horde_Date object.
+     *
+     * @param string $time_in  Date in one of the three formats supported by
+     *                         Horde_Form and Horde_Date (ISO format
+     *                         YYYY-MM-DD HH:MM:SS, timestamp YYYYMMDDHHMMSS and
+     *                         UNIX epoch).
+     *
+     * @return Date  The time object.
+     */
+    function getTimeOb($time_in)
+    {
+        if (is_array($time_in)) {
+            if (!$this->emptyTimeArray($time_in)) {
+                $time_in = sprintf('1970-01-01 %02d:%02d:%02d', $time_in['hour'], $time_in['minute'], $this->_show_seconds ? $time_in['second'] : 0);
+            }
+        }
+
+        return new Horde_Date($time_in);
+    }
+
+    /**
+     * Return the time supplied split up into an array.
+     *
+     * @param string $time_in  Time in one of the three formats supported by
+     *                         Horde_Form and Horde_Date (ISO format
+     *                         YYYY-MM-DD HH:MM:SS, timestamp YYYYMMDDHHMMSS and
+     *                         UNIX epoch).
+     *
+     * @return array  Array with three elements - hour, minute and seconds.
+     */
+    function getTimeParts($time_in)
+    {
+        if (is_array($time_in)) {
+            /* This is probably a failed isValid input so just return the
+             * parts as they are. */
+            return $time_in;
+        } elseif (empty($time_in)) {
+            /* This is just an empty field so return empty parts. */
+            return array('hour' => '', 'minute' => '', 'second' => '');
+        }
+        $time = $this->getTimeOb($time_in);
+        return array('hour' => $time->hour,
+                     'minute' => $time->min,
+                     'second' => $time->sec);
+    }
+
+    function emptyTimeArray($time)
+    {
+        return (is_array($time) && empty($time['hour']) && empty($time['minute']) && empty($time['second']));
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array(
+            'name' => _("Time selection"),
+            'params' => array(
+                'seconds' => array('label' => _("Show seconds?"),
+                                  'type'  => 'boolean')));
+    }
+
+}
+
+class Horde_Form_Type_monthyear extends Horde_Form_Type {
+
+    var $_start_year;
+    var $_end_year;
+
+    function init($start_year = null, $end_year = null)
+    {
+        if (empty($start_year)) {
+            $start_year = 1920;
+        }
+        if (empty($end_year)) {
+            $end_year = date('Y');
+        }
+
+        $this->_start_year = $start_year;
+        $this->_end_year = $end_year;
+    }
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        if (!$var->isRequired()) {
+            return true;
+        }
+
+        if (!$vars->get($this->getMonthVar($var)) ||
+            !$vars->get($this->getYearVar($var))) {
+            $message = _("Please enter a month and a year.");
+            return false;
+        }
+
+        return true;
+    }
+
+    function getMonthVar($var)
+    {
+        return $var->getVarName() . '[month]';
+    }
+
+    function getYearVar($var)
+    {
+        return $var->getVarName() . '[year]';
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array('name' => _("Month and year"),
+                     'params' => array(
+                         'start_year' => array('label' => _("Start year"),
+                                               'type'  => 'int'),
+                         'end_year'   => array('label' => _("End year"),
+                                               'type'  => 'int')));
+    }
+
+}
+
+class Horde_Form_Type_monthdayyear extends Horde_Form_Type {
+
+    var $_start_year;
+    var $_end_year;
+    var $_picker;
+    var $_format_in = null;
+    var $_format_out = '%x';
+
+    /**
+     * Return the date supplied as a Horde_Date object.
+     *
+     * @param integer $start_year  The first available year for input.
+     * @param integer $end_year    The last available year for input.
+     * @param boolean $picker      Do we show the DHTML calendar?
+     * @param integer $format_in   The format to use when sending the date
+     *                             for storage. Defaults to Unix epoch.
+     *                             Similar to the strftime() function.
+     * @param integer $format_out  The format to use when displaying the
+     *                             date. Similar to the strftime() function.
+     */
+    function init($start_year = '', $end_year = '', $picker = true,
+                  $format_in = null, $format_out = '%x')
+    {
+        if (empty($start_year)) {
+            $start_year = date('Y');
+        }
+        if (empty($end_year)) {
+            $end_year = date('Y') + 10;
+        }
+
+        $this->_start_year = $start_year;
+        $this->_end_year = $end_year;
+        $this->_picker = $picker;
+        $this->_format_in = $format_in;
+        $this->_format_out = $format_out;
+    }
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        $date = $vars->get($var->getVarName());
+        $empty = $this->emptyDateArray($date);
+
+        if ($empty == 1 && $var->isRequired()) {
+            $message = _("This field is required.");
+            return false;
+        } elseif ($empty == 0 && !checkdate($date['month'], $date['day'], $date['year'])) {
+            $message = _("Please enter a valid date, check the number of days in the month.");
+            return false;
+        } elseif ($empty == -1) {
+            $message = _("Select all date components.");
+            return false;
+        }
+
+        return true;
+    }
+
+    function emptyDateArray($date)
+    {
+        if (!is_array($date)) {
+            return empty($date);
+        }
+
+        $empty = 0;
+        /* Check each date array component. */
+        foreach ($date as $key => $val) {
+            if (empty($val)) {
+                $empty++;
+            }
+        }
+
+        /* Check state of empty. */
+        if ($empty == 0) {
+            /* If no empty parts return 0. */
+            return 0;
+        } elseif ($empty == count($date)) {
+            /* If all empty parts return 1. */
+            return 1;
+        } else {
+            /* If some empty parts return -1. */
+            return -1;
+        }
+    }
+
+    /**
+     * Return the date supplied split up into an array.
+     *
+     * @param string $date_in  Date in one of the three formats supported by
+     *                         Horde_Form and Horde_Date (ISO format
+     *                         YYYY-MM-DD HH:MM:SS, timestamp YYYYMMDDHHMMSS
+     *                         and UNIX epoch) plus the fourth YYYY-MM-DD.
+     *
+     * @return array  Array with three elements - year, month and day.
+     */
+    function getDateParts($date_in)
+    {
+        if (is_array($date_in)) {
+            /* This is probably a failed isValid input so just return
+             * the parts as they are. */
+            return $date_in;
+        } elseif (empty($date_in)) {
+            /* This is just an empty field so return empty parts. */
+            return array('year' => '', 'month' => '', 'day' => '');
+        }
+
+        $date = $this->getDateOb($date_in);
+        return array('year' => $date->year,
+                     'month' => $date->month,
+                     'day' => $date->mday);
+    }
+
+    /**
+     * Return the date supplied as a Horde_Date object.
+     *
+     * @param string $date_in  Date in one of the three formats supported by
+     *                         Horde_Form and Horde_Date (ISO format
+     *                         YYYY-MM-DD HH:MM:SS, timestamp YYYYMMDDHHMMSS
+     *                         and UNIX epoch) plus the fourth YYYY-MM-DD.
+     *
+     * @return Date  The date object.
+     */
+    function getDateOb($date_in)
+    {
+        if (is_array($date_in)) {
+            /* If passed an array change it to the ISO format. */
+            if ($this->emptyDateArray($date_in) == 0) {
+                $date_in = sprintf('%04d-%02d-%02d 00:00:00',
+                                   $date_in['year'],
+                                   $date_in['month'],
+                                   $date_in['day']);
+            }
+        } elseif (preg_match('/^\d{4}-?\d{2}-?\d{2}$/', $date_in)) {
+            /* Fix the date if it is the shortened ISO. */
+            $date_in = $date_in . ' 00:00:00';
+        }
+
+        return new Horde_Date($date_in);
+    }
+
+    /**
+     * Return the date supplied as a Horde_Date object.
+     *
+     * @param string $date  Either an already set up Horde_Date object or a
+     *                      string date in one of the three formats supported
+     *                      by Horde_Form and Horde_Date (ISO format
+     *                      YYYY-MM-DD HH:MM:SS, timestamp YYYYMMDDHHMMSS and
+     *                      UNIX epoch) plus the fourth YYYY-MM-DD.
+     *
+     * @return string  The date formatted according to the $format_out
+     *                 parameter when setting up the monthdayyear field.
+     */
+    function formatDate($date)
+    {
+        if (!is_a($date, 'Date')) {
+            $date = $this->getDateOb($date);
+        }
+
+        return $date->strftime($this->_format_out);
+    }
+
+    /**
+     * Insert the date input through the form into $info array, in the format
+     * specified by the $format_in parameter when setting up monthdayyear
+     * field.
+     */
+    function getInfo($vars, &$var, &$info)
+    {
+        $info = $this->_validateAndFormat($var->getValue($vars), $var);
+    }
+
+    /**
+     * Validate/format a date submission.
+     */
+    function _validateAndFormat($value, $var)
+    {
+        /* If any component is empty consider it a bad date and return the
+         * default. */
+        if ($this->emptyDateArray($value) == 1) {
+            return $var->getDefault();
+        } else {
+            $date = $this->getDateOb($value);
+            if ($this->_format_in === null) {
+                return $date->timestamp();
+            } else {
+                return $date->strftime($this->_format_in);
+            }
+        }
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array(
+            'name' => _("Date selection"),
+            'params' => array(
+                'start_year' => array('label' => _("Start year"),
+                                      'type'  => 'int'),
+                'end_year'   => array('label' => _("End year"),
+                                      'type'  => 'int'),
+                'picker'     => array('label' => _("Show picker?"),
+                                      'type'  => 'boolean'),
+                'format_in'  => array('label' => _("Storage format"),
+                                      'type'  => 'text'),
+                'format_out' => array('label' => _("Display format"),
+                                      'type'  => 'text')));
+    }
+
+}
+
+/**
+ * @since Horde 3.2
+ */
+class Horde_Form_Type_datetime extends Horde_Form_Type {
+
+    var $_mdy;
+    var $_hms;
+
+    /**
+     * Return the date supplied as a Horde_Date object.
+     *
+     * @param integer $start_year  The first available year for input.
+     * @param integer $end_year    The last available year for input.
+     * @param boolean $picker      Do we show the DHTML calendar?
+     * @param integer $format_in   The format to use when sending the date
+     *                             for storage. Defaults to Unix epoch.
+     *                             Similar to the strftime() function.
+     * @param integer $format_out  The format to use when displaying the
+     *                             date. Similar to the strftime() function.
+     * @param boolean $show_seconds Include a form input for seconds.
+     */
+    function init($start_year = '', $end_year = '', $picker = true,
+                  $format_in = null, $format_out = '%x', $show_seconds = false)
+    {
+        $this->_mdy = new Horde_Form_Type_monthdayyear();
+        $this->_mdy->init($start_year, $end_year, $picker, $format_in, $format_out);
+
+        $this->_hms = new Horde_Form_Type_hourminutesecond();
+        $this->_hms->init($show_seconds);
+    }
+
+    function isValid(&$var, &$vars, $value, &$message)
+    {
+        if ($var->isRequired()) {
+            return $this->_mdy->isValid($var, $vars, $value, $message) &&
+                $this->_hms->isValid($var, $vars, $value, $message);
+        }
+        return true;
+    }
+
+    function getInfo(&$vars, &$var, &$info)
+    {
+        /* If any component is empty consider it a bad date and return the
+         * default. */
+        $value = $var->getValue($vars);
+        if ($this->emptyDateArray($value) == 1 || $this->emptyTimeArray($value)) {
+            $info = $var->getDefault();
+            return;
+        }
+
+        $date = $this->getDateOb($value);
+        $time = $this->getTimeOb($value);
+        $date->hour = $time->hour;
+        $date->min = $time->min;
+        $date->sec = $time->sec;
+        if (is_null($this->format_in)) {
+            $info = $date->timestamp();
+        } else {
+            $info = $date->strftime($this->format_in);
+        }
+    }
+
+    function __get($property)
+    {
+        if ($property == 'show_seconds') {
+            return $this->_hms->$property;
+        } else {
+            return $this->_mdy->$property;
+        }
+    }
+
+    function __set($property, $value)
+    {
+        if ($property == 'show_seconds') {
+            $this->_hms->$property = $value;
+        } else {
+            $this->_mdy->$property = $value;
+        }
+    }
+
+    function checktime($hour, $minute, $second)
+    {
+        return $this->_hms->checktime($hour, $minute, $second);
+    }
+
+    function getTimeOb($time_in)
+    {
+        return $this->_hms->getTimeOb($time_in);
+    }
+
+    function getTimeParts($time_in)
+    {
+        return $this->_hms->getTimeParts($time_in);
+    }
+
+    function emptyTimeArray($time)
+    {
+        return $this->_hms->emptyTimeArray($time);
+    }
+
+    function emptyDateArray($date)
+    {
+        return $this->_mdy->emptyDateArray($date);
+    }
+
+    function getDateParts($date_in)
+    {
+        return $this->_mdy->getDateParts($date_in);
+    }
+
+    function getDateOb($date_in)
+    {
+        return $this->_mdy->getDateOb($date_in);
+    }
+
+    function formatDate($date)
+    {
+        if ($date === null) {
+            return '';
+        }
+        return $this->_mdy->formatDate($date);
+    }
+
+    function about()
+    {
+        return array(
+            'name' => _("Date and time selection"),
+            'params' => array(
+                'start_year' => array('label' => _("Start year"),
+                                      'type'  => 'int'),
+                'end_year'   => array('label' => _("End year"),
+                                      'type'  => 'int'),
+                'picker'     => array('label' => _("Show picker?"),
+                                      'type'  => 'boolean'),
+                'format_in'  => array('label' => _("Storage format"),
+                                      'type'  => 'text'),
+                'format_out' => array('label' => _("Display format"),
+                                      'type'  => 'text'),
+                'seconds'    => array('label' => _("Show seconds?"),
+                                      'type'  => 'boolean')));
+    }
+
+}
+
+class Horde_Form_Type_colorpicker extends Horde_Form_Type {
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        if ($var->isRequired() && empty($value)) {
+            $message = _("This field is required.");
+            return false;
+        }
+
+        if (empty($value) || preg_match('/^#([0-9a-z]){6}$/i', $value)) {
+            return true;
+        }
+
+        $message = _("This field must contain a color code in the RGB Hex format, for example '#1234af'.");
+        return false;
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array('name' => _("Colour selection"));
+    }
+
+}
+
+class Horde_Form_Type_sorter extends Horde_Form_Type {
+
+    var $_instance;
+    var $_values;
+    var $_size;
+    var $_header;
+
+    function init($values, $size = 8, $header = '')
+    {
+        static $horde_sorter_instance = 0;
+
+        /* Get the next progressive instance count for the horde
+         * sorter so that multiple sorters can be used on one page. */
+        $horde_sorter_instance++;
+        $this->_instance = 'horde_sorter_' . $horde_sorter_instance;
+        $this->_values = $values;
+        $this->_size   = $size;
+        $this->_header = $header;
+    }
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        return true;
+    }
+
+    function getOptions($keys = null)
+    {
+        $html = '';
+        if ($this->_header) {
+            $html .= '<option value="">' . htmlspecialchars($this->_header) . '</option>';
+        }
+
+        if (empty($keys)) {
+            $keys = array_keys($this->_values);
+        } else {
+            $keys = explode("\t", $keys['array']);
+        }
+        foreach ($keys as $sl_key) {
+            $html .= '<option value="' . $sl_key . '">' . htmlspecialchars($this->_values[$sl_key]) . '</option>';
+        }
+
+        return $html;
+    }
+
+    function getInfo($vars, &$var, &$info)
+    {
+        $value = $vars->get($var->getVarName());
+        $info = explode("\t", $value['array']);
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array(
+            'name' => _("Sort order selection"),
+            'params' => array(
+                'values' => array('label' => _("Values"),
+                                  'type'  => 'stringlist'),
+                'size'   => array('label' => _("Size"),
+                                  'type'  => 'int'),
+                'header' => array('label' => _("Header"),
+                                  'type'  => 'text')));
+    }
+
+}
+
+class Horde_Form_Type_selectfiles extends Horde_Form_Type {
+
+    /**
+     * The text to use in the link.
+     *
+     * @var string
+     */
+    var $_link_text;
+
+    /**
+     * The style to use for the link.
+     *
+     * @var string
+     */
+    var $_link_style;
+
+    /**
+     *  Create the link with an icon instead of text?
+     *
+     * @var boolean
+     */
+    var $_icon;
+
+    /**
+     * Contains gollem selectfile selectionID
+     *
+     * @var string
+     */
+    var $_selectid;
+
+    function init($selectid, $link_text = null, $link_style = '',
+                  $icon = false)
+    {
+        $this->_selectid = $selectid;
+        if (is_null($link_text)) {
+            $link_text = _("Select Files");
+        }
+        $this->_link_text = $link_text;
+        $this->_link_style = $link_style;
+        $this->_icon = $icon;
+    }
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        return true;
+    }
+
+    function getInfo($var, &$vars, &$info)
+    {
+        $value = $vars->getValue($var);
+        $info = $GLOBALS['registry']->call('files/selectlistResults', array($value));
+    }
+
+    function about()
+    {
+        return array(
+            'name' => _("File selection"),
+            'params' => array(
+                'selectid'   => array('label' => _("Id"),
+                                      'type' => 'text'),
+                'link_text'  => array('label' => _("Link text"),
+                                      'type' => 'text'),
+                'link_style' => array('label' => _("Link style"),
+                                      'type' => 'text'),
+                'icon'       => array('label' => _("Show icon?"),
+                                      'type' => 'boolean')));
+    }
+
+}
+
+class Horde_Form_Type_assign extends Horde_Form_Type {
+
+    var $_leftValues;
+    var $_rightValues;
+    var $_leftHeader;
+    var $_rightHeader;
+    var $_size;
+    var $_width;
+
+    function init($leftValues, $rightValues, $leftHeader = '',
+                  $rightHeader = '', $size = 8, $width = '200px')
+    {
+        $this->_leftValues = $leftValues;
+        $this->_rightValues = $rightValues;
+        $this->_leftHeader = $leftHeader;
+        $this->_rightHeader = $rightHeader;
+        $this->_size = $size;
+        $this->_width = $width;
+    }
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        return true;
+    }
+
+    function setValues($side, $values)
+    {
+        if ($side) {
+            $this->_rightValues = $values;
+        } else {
+            $this->_leftValues = $values;
+        }
+    }
+
+    function getHeader($side)
+    {
+        return $side ? $this->_rightHeader : $this->_leftHeader;
+    }
+
+    function getOptions($side, $formname, $varname)
+    {
+        $html = '';
+        $headers = false;
+        if ($side) {
+            $values = $this->_rightValues;
+            if (!empty($this->_rightHeader)) {
+                $values = array('' => $this->_rightHeader) + $values;
+                $headers = true;
+            }
+        } else {
+            $values = $this->_leftValues;
+            if (!empty($this->_leftHeader)) {
+                $values = array('' => $this->_leftHeader) + $values;
+                $headers = true;
+            }
+        }
+
+        foreach ($values as $key => $val) {
+            $html .= '<option value="' . htmlspecialchars($key) . '"';
+            if ($headers) {
+                $headers = false;
+            } else {
+                $html .= ' ondblclick="Horde_Form_Assign.move(\'' . $formname . '\', \'' . $varname . '\', ' . (int)$side . ');"';
+            }
+            $html .= '>' . htmlspecialchars($val) . '</option>';
+        }
+
+        return $html;
+    }
+
+    function getInfo($vars, &$var, &$info)
+    {
+        $value = $vars->get($var->getVarName() . '__values');
+        if (strpos($value, "\t\t") === false) {
+            $left = $value;
+            $right = '';
+        } else {
+            list($left, $right) = explode("\t\t", $value);
+        }
+        if (empty($left)) {
+            $info['left'] = array();
+        } else {
+            $info['left'] = explode("\t", $left);
+        }
+        if (empty($right)) {
+            $info['right'] = array();
+        } else {
+            $info['right'] = explode("\t", $right);
+        }
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array(
+            'name' => _("Assignment columns"),
+            'params' => array(
+                'leftValues'  => array('label' => _("Left values"),
+                                       'type'  => 'stringlist'),
+                'rightValues' => array('label' => _("Right values"),
+                                       'type'  => 'stringlist'),
+                'leftHeader'  => array('label' => _("Left header"),
+                                       'type'  => 'text'),
+                'rightHeader' => array('label' => _("Right header"),
+                                       'type'  => 'text'),
+                'size'        => array('label' => _("Size"),
+                                       'type'  => 'int'),
+                'width'       => array('label' => _("Width in CSS units"),
+                                       'type'  => 'text')));
+    }
+
+}
+
+class Horde_Form_Type_creditcard extends Horde_Form_Type {
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        if (empty($value) && $var->isRequired()) {
+            $message = _("This field is required.");
+            return false;
+        }
+
+        if (!empty($value)) {
+            /* getCardType() will also verify the checksum. */
+            $type = $this->getCardType($value);
+            if ($type === false || $type == 'unknown') {
+                $message = _("This does not seem to be a valid card number.");
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    function getChecksum($ccnum)
+    {
+        $len = strlen($ccnum);
+        if (!is_long($len / 2)) {
+            $weight = 2;
+            $digit = $ccnum[0];
+        } elseif (is_long($len / 2)) {
+            $weight = 1;
+            $digit = $ccnum[0] * 2;
+        }
+        if ($digit > 9) {
+            $digit = $digit - 9;
+        }
+        $i = 1;
+        $checksum = $digit;
+        while ($i < $len) {
+            if ($ccnum[$i] != ' ') {
+                $digit = $ccnum[$i] * $weight;
+                $weight = ($weight == 1) ? 2 : 1;
+                if ($digit > 9) {
+                    $digit = $digit - 9;
+                }
+                $checksum += $digit;
+            }
+            $i++;
+        }
+
+        return $checksum;
+    }
+
+    function getCardType($ccnum)
+    {
+        $sum = $this->getChecksum($ccnum);
+        $l = strlen($ccnum);
+
+        // Screen checksum.
+        if (($sum % 10) != 0) {
+            return false;
+        }
+
+        // Check for Visa.
+        if ((($l == 16) || ($l == 13)) &&
+            ($ccnum[0] == 4)) {
+            return 'visa';
+        }
+
+        // Check for MasterCard.
+        if (($l == 16) &&
+            ($ccnum[0] == 5) &&
+            ($ccnum[1] >= 1) &&
+            ($ccnum[1] <= 5)) {
+            return 'mastercard';
+        }
+
+        // Check for Amex.
+        if (($l == 15) &&
+            ($ccnum[0] == 3) &&
+            (($ccnum[1] == 4) || ($ccnum[1] == 7))) {
+            return 'amex';
+        }
+
+        // Check for Discover (Novus).
+        if (strlen($ccnum) == 16 &&
+            substr($ccnum, 0, 4) == '6011') {
+            return 'discover';
+        }
+
+        // If we got this far, then no card matched.
+        return 'unknown';
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array('name' => _("Credit card number"));
+    }
+
+}
+
+class Horde_Form_Type_obrowser extends Horde_Form_Type {
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        return true;
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array('name' => _("Relationship browser"));
+    }
+
+}
+
+class Horde_Form_Type_dblookup extends Horde_Form_Type_enum {
+
+    function init($dsn, $sql, $prompt = null)
+    {
+        $values = array();
+        $db = DB::connect($dsn);
+        if (!is_a($db, 'PEAR_Error')) {
+            // Set DB portability options.
+            switch ($db->phptype) {
+            case 'mssql':
+                $db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM);
+                break;
+            default:
+                $db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS);
+            }
+
+            $col = $db->getCol($sql);
+            if (!is_a($col, 'PEAR_Error')) {
+                $values = Horde_Array::combine($col, $col);
+            }
+        }
+        parent::init($values, $prompt);
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array(
+            'name' => _("Database lookup"),
+            'params' => array(
+                'dsn' => array('label' => _("DSN (see http://pear.php.net/manual/en/package.database.db.intro-dsn.php)"),
+                               'type'  => 'text'),
+                'sql' => array('label' => _("SQL statement for value lookups"),
+                               'type'  => 'text'),
+                'prompt' => array('label' => _("Prompt text"),
+                                  'type'  => 'text'))
+            );
+    }
+
+}
+
+class Horde_Form_Type_figlet extends Horde_Form_Type {
+
+    var $_text;
+    var $_font;
+
+    function init($text, $font)
+    {
+        $this->_text = $text;
+        $this->_font = $font;
+    }
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        if (empty($value) && $var->isRequired()) {
+            $message = _("This field is required.");
+            return false;
+        }
+
+        if (String::lower($value) != String::lower($this->_text)) {
+            $message = _("The text you entered did not match the text on the screen.");
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Return info about field type.
+     */
+    function about()
+    {
+        return array(
+            'name' => _("Figlet CAPTCHA"),
+            'params' => array(
+                'text' => array('label' => _("Text"),
+                                'type'  => 'text'),
+                'font' => array('label' => _("Figlet font"),
+                                'type'  => 'text'))
+            );
+    }
+
+}
+
+class Horde_Form_Type_invalid extends Horde_Form_Type {
+
+    var $message;
+
+    function init($message)
+    {
+        $this->message = $message;
+    }
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        return false;
+    }
+
+}
+
+/**
+ * This class represents a single form variable that may be rendered as one or
+ * more form fields.
+ *
+ * @author  Robert E. Coyle <robertecoyle@hotmail.com>
+ * @package Horde_Form
+ */
+class Horde_Form_Variable {
+
+    /**
+     * The form instance this variable is assigned to.
+     *
+     * @var Horde_Form
+     */
+    var $form;
+
+    /**
+     * A short description of this variable's purpose.
+     *
+     * @var string
+     */
+    var $humanName;
+
+    /**
+     * The internally used name.
+     *
+     * @var string
+     */
+    var $varName;
+
+    /**
+     * A {@link Horde_Form_Type} instance.
+     *
+     * @var Horde_Form_Type
+     */
+    var $type;
+
+    /**
+     * Whether this is a required variable.
+     *
+     * @var boolean
+     */
+    var $required;
+
+    /**
+     * Whether this is a readonly variable.
+     *
+     * @var boolean
+     */
+    var $readonly;
+
+    /**
+     * A long description of the variable's purpose, special instructions, etc.
+     *
+     * @var string
+     */
+    var $description;
+
+    /**
+     * The variable help text.
+     *
+     * @var string
+     */
+    var $help;
+
+    /**
+     * Whether this is an array variable.
+     *
+     * @var boolean
+     */
+    var $_arrayVal;
+
+    /**
+     * The default value.
+     *
+     * @var mixed
+     */
+    var $_defValue = null;
+
+    /**
+     * A {@link Horde_Form_Action} instance.
+     *
+     * @var Horde_Form_Action
+     */
+    var $_action;
+
+    /**
+     * Whether this variable is disabled.
+     *
+     * @var boolean
+     */
+    var $_disabled = false;
+
+    /**
+     * TODO
+     *
+     * @var boolean
+     */
+    var $_autofilled = false;
+
+    /**
+     * Whether this is a hidden variable.
+     *
+     * @var boolean
+     */
+    var $_hidden = false;
+
+    /**
+     * TODO
+     *
+     * @var array
+     */
+    var $_options = array();
+
+    /**
+     * Variable constructor.
+     *
+     * @param string $humanName      A short description of the variable's
+     *                               purpose.
+     * @param string $varName        The internally used name.
+     * @param Horde_Form_Type $type  A {@link Horde_Form_Type} instance.
+     * @param boolean $required      Whether this is a required variable.
+     * @param boolean $readonly      Whether this is a readonly variable.
+     * @param string $description    A long description of the variable's
+     *                               purpose, special instructions, etc.
+     */
+    function Horde_Form_Variable($humanName, $varName, $type, $required,
+                                 $readonly = false, $description = null)
+    {
+        $this->humanName   = $humanName;
+        $this->varName     = $varName;
+        $this->type        = $type;
+        $this->required    = $required;
+        $this->readonly    = $readonly;
+        $this->description = $description;
+        $this->_arrayVal   = (strpos($varName, '[]') !== false);
+    }
+
+    /**
+     * Assign this variable to the specified form.
+     *
+     * @param Horde_Form $form  The form instance to assign this variable to.
+     */
+    function setFormOb($form)
+    {
+        $this->form = $form;
+    }
+
+    /**
+     * Sets a default value for this variable.
+     *
+     * @param mixed $value  A variable value.
+     */
+    function setDefault($value)
+    {
+        $this->_defValue = $value;
+    }
+
+    /**
+     * Returns this variable's default value.
+     *
+     * @return mixed  This variable's default value.
+     */
+    function getDefault()
+    {
+        return $this->_defValue;
+    }
+
+    /**
+     * Assigns an action to this variable.
+     *
+     * Example:
+     * <code>
+     * $v = $form->addVariable('My Variable', 'var1', 'text', false);
+     * $action = new Horde_Form_Action_submit;
+     * $v->setAction($action);
+     * </code>
+     *
+     * @param Horde_Form_Action $action  A {@link Horde_Form_Action} instance.
+     */
+    function setAction($action)
+    {
+        $this->_action = $action;
+    }
+
+    /**
+     * Returns whether this variable has an attached action.
+     *
+     * @return boolean  True if this variable has an attached action.
+     */
+    function hasAction()
+    {
+        return !is_null($this->_action);
+    }
+
+    /**
+     * Makes this a hidden variable.
+     */
+    function hide()
+    {
+        $this->_hidden = true;
+    }
+
+    /**
+     * Returns whether this is a hidden variable.
+     *
+     * @return boolean  True if this a hidden variable.
+     */
+    function isHidden()
+    {
+        return $this->_hidden;
+    }
+
+    /**
+     * Disables this variable.
+     */
+    function disable()
+    {
+        $this->_disabled = true;
+    }
+
+    /**
+     * Returns whether this variable is disabled.
+     *
+     * @return boolean  True if this variable is disabled.
+     */
+    function isDisabled()
+    {
+        return $this->_disabled;
+    }
+
+    /**
+     * Return the short description of this variable.
+     *
+     * @return string  A short description
+     */
+    function getHumanName()
+    {
+        return $this->humanName;
+    }
+
+    /**
+     * Returns the internally used variable name.
+     *
+     * @return string  This variable's internal name.
+     */
+    function getVarName()
+    {
+        return $this->varName;
+    }
+
+    /**
+     * Returns this variable's type.
+     *
+     * @return Horde_Form_Type  This variable's {@link Horde_Form_Type}
+     *                          instance.
+     */
+    function getType()
+    {
+        return $this->type;
+    }
+
+    /**
+     * Returns whether this is a required variable.
+     *
+     * @return boolean  True if this is a required variable.
+     */
+    function isRequired()
+    {
+        return $this->required;
+    }
+
+    /**
+     * Returns whether this is a readonly variable.
+     *
+     * @return boolean  True if this a readonly variable.
+     */
+    function isReadonly()
+    {
+        return $this->readonly;
+    }
+
+    /**
+     * Returns the possible values of this variable.
+     *
+     * @return array  The possible values of this variable or null.
+     */
+    function getValues()
+    {
+        return $this->type->values;
+    }
+
+    /**
+     * Returns whether this variable has a long description.
+     *
+     * @return boolean  True if this variable has a long description.
+     */
+    function hasDescription()
+    {
+        return !empty($this->description);
+    }
+
+    /**
+     * Returns this variable's long description.
+     *
+     * @return string  This variable's long description.
+     */
+    function getDescription()
+    {
+        return $this->description;
+    }
+
+    /**
+     * Returns whether this is an array variable.
+     *
+     * @return boolean  True if this an array variable.
+     */
+    function isArrayVal()
+    {
+        return $this->_arrayVal;
+    }
+
+    /**
+     * Returns whether this variable is to upload a file.
+     *
+     * @return boolean  True if variable is to upload a file.
+     */
+    function isUpload()
+    {
+        return ($this->type instanceof Horde_Form_Type_file);
+    }
+
+    /**
+     * Assigns a help text to this variable.
+     *
+     * @param string $help  The variable help text.
+     */
+    function setHelp($help)
+    {
+        $this->form->_help = true;
+        $this->help = $help;
+    }
+
+    /**
+     * Returns whether this variable has some help text assigned.
+     *
+     * @return boolean  True if this variable has a help text.
+     */
+    function hasHelp()
+    {
+        return !empty($this->help);
+    }
+
+    /**
+     * Returns the help text of this variable.
+     *
+     * @return string  This variable's help text.
+     */
+    function getHelp()
+    {
+        return $this->help;
+    }
+
+    /**
+     * Sets a variable option.
+     *
+     * @param string $option  The option name.
+     * @param mixed $val      The option's value.
+     */
+    function setOption($option, $val)
+    {
+        $this->_options[$option] = $val;
+    }
+
+    /**
+     * Returns a variable option's value.
+     *
+     * @param string $option  The option name.
+     *
+     * @return mixed          The option's value.
+     */
+    function getOption($option)
+    {
+        return isset($this->_options[$option]) ? $this->_options[$option] : null;
+    }
+
+    /**
+     * Processes the submitted value of this variable according to the rules of
+     * the variable type.
+     *
+     * @param Variables $vars  The {@link Variables} instance of the submitted
+     *                         form.
+     * @param mixed $info      A variable passed by reference that will be
+     *                         assigned the processed value of the submitted
+     *                         variable value.
+     *
+     * @return mixed  Depending on the variable type.
+     */
+    function getInfo($vars, &$info)
+    {
+        return $this->type->getInfo($vars, $this, $info);
+    }
+
+    /**
+     * Returns whether this variable if it had the "trackchange" option set
+     * has actually been changed.
+     *
+     * @param Variables $vars  The {@link Variables} instance of the submitted
+     *                         form.
+     *
+     * @return boolean  Null if this variable doesn't have the "trackchange"
+     *                  option set or the form wasn't submitted yet. A boolean
+     *                  indicating whether the variable was changed otherwise.
+     */
+    function wasChanged($vars)
+    {
+        if (!$this->getOption('trackchange')) {
+            return null;
+        }
+        $old = $vars->get('__old_' . $this->getVarName());
+        if (is_null($old)) {
+            return null;
+        }
+        return $old != $vars->get($this->getVarName());
+    }
+
+    /**
+     * Validates this variable.
+     *
+     * @param Variables $vars  The {@link Variables} instance of the submitted
+     *                         form.
+     * @param string $message  A variable passed by reference that will be
+     *                         assigned a descriptive error message if
+     *                         validation failed.
+     *
+     * @return boolean  True if the variable validated.
+     */
+    function validate($vars, &$message)
+    {
+        if ($this->_arrayVal) {
+            $vals = $this->getValue($vars);
+            if (!is_array($vals)) {
+                if ($this->required) {
+                    $message = _("This field is required.");
+                    return false;
+                } else {
+                    return true;
+                }
+            }
+            foreach ($vals as $i => $value) {
+                if ($value === null && $this->required) {
+                    $message = _("This field is required.");
+                    return false;
+                } else {
+                    if (!$this->type->isValid($this, $vars, $value, $message)) {
+                        return false;
+                    }
+                }
+            }
+        } else {
+            $value = $this->getValue($vars);
+            return $this->type->isValid($this, $vars, $value, $message);
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns the submitted or default value of this variable.
+     * If an action is attached to this variable, the value will get passed to
+     * the action object.
+     *
+     * @param Variables $vars  The {@link Variables} instance of the submitted
+     *                         form.
+     * @param integer $index   If the variable is an array variable, this
+     *                         specifies the array element to return.
+     *
+     * @return mixed  The variable or element value.
+     */
+    function getValue($vars, $index = null)
+    {
+        if ($this->_arrayVal) {
+            $name = str_replace('[]', '', $this->varName);
+        } else {
+            $name = $this->varName;
+        }
+        $value = $vars->getExists($name, $wasset);
+
+        if (!$wasset) {
+            $value = $this->getDefault();
+        }
+
+        if ($this->_arrayVal && !is_null($index)) {
+            if (!$wasset && !is_array($value)) {
+                $return = $value;
+            } else {
+                $return = isset($value[$index]) ? $value[$index] : null;
+            }
+        } else {
+            $return = $value;
+        }
+
+        if ($this->hasAction()) {
+            $this->_action->setValues($vars, $return, $this->_arrayVal);
+        }
+
+        return $return;
+    }
+
+}
diff --git a/framework/Form/lib/Horde/Form/Renderer.php b/framework/Form/lib/Horde/Form/Renderer.php
new file mode 100644 (file)
index 0000000..867f114
--- /dev/null
@@ -0,0 +1,116 @@
+<?php
+/**
+ * @package Horde_Form
+ */
+
+/**
+ * The Horde_Form_Renderer class provides HTML and other renderings of
+ * forms for the Horde_Form:: package.
+ *
+ * $Horde: incubator/Horde_Form/Horde/Form/Renderer.php,v 1.7 2007/09/16 19:51:08 chuck Exp $
+ *
+ * Copyright 2001-2007 Robert E. Coyle <robertecoyle@hotmail.com>
+ * Copyright 2005-2007 Matt Warden <mwarden@gmail.com>
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Robert E. Coyle <robertecoyle@hotmail.com>
+ * @author  Matt Warden <mwarden@gmail.com>
+ * @package Horde_Form
+ */
+abstract class Horde_Form_Renderer {
+
+    var $_name;
+    var $_requiredLegend = false;
+    var $_helpMarker = '?';
+    var $_onLoadJS = array();
+    var $_showHeader = true;
+    var $_cols = 2;
+    var $_varRenderer = null;
+    var $_firstField = null;
+    var $_stripedRows = true;
+
+    protected $_submit = array();
+    protected $_reset = false;
+
+    /**
+     * Does the title of the form contain HTML? If so, you are responsible for
+     * doing any needed escaping/sanitization yourself. Otherwise the title
+     * will be run through htmlspecialchars() before being output.
+     *
+     * @var boolean
+     */
+    var $_encodeTitle = true;
+
+    /**
+     * Construct a new Horde_Form_Renderer::.
+     *
+     * @param array $params  This is a hash of renderer-specific parameters.
+     *                       Possible keys:<code>
+     *                       'encode_title': @see $_encodeTitle</code>
+     */
+    function __construct($params = array())
+    {
+        if (isset($params['encode_title'])) {
+            $this->encodeTitle($params['encode_title']);
+        }
+
+        $this->_varRenderer = new Horde_Form_VarRenderer_Xhtml;
+    }
+
+    abstract public function renderActive($form, $action, $method = 'get', $enctype = null, $focus = true);
+
+    public function setButtons($submit, $reset = false)
+    {
+        if ($submit === true || is_null($submit) || empty($submit)) {
+            /* Default to 'Submit'. */
+            $submit = array(_("Submit"));
+        } elseif (!is_array($submit)) {
+            /* Default to array if not passed. */
+            $submit = array($submit);
+        }
+        /* Only if $reset is strictly true insert default 'Reset'. */
+        if ($reset === true) {
+            $reset = _("Reset");
+        }
+
+        $this->_submit = $submit;
+        $this->_reset = $reset;
+
+        return $this;
+    }
+
+    public function addButtons($buttons)
+    {
+        if (!is_array($buttons)) {
+            $buttons = array($buttons);
+        }
+
+        $this->_submit = array_merge($this->_submit, $buttons);
+    }
+
+    public function showHeader($bool)
+    {
+        $this->_showHeader = $bool;
+    }
+
+    /**
+     * Sets or returns whether the form title should be encoded with
+     * htmlspecialchars().
+     *
+     * @param boolean $encode  If true, the form title gets encoded.  If false
+     *                         the title can contain HTML, but the class user
+     *                         is responsible to encode any special characters.
+     *
+     * @return boolean  Whether the form title should be encoded.
+     */
+    function encodeTitle($encode = null)
+    {
+        if (!is_null($encode)) {
+            $this->_encodeTitle = $encode;
+        }
+        return $this->_encodeTitle = $encode;
+    }
+
+}
diff --git a/framework/Form/lib/Horde/Form/Renderer/Xhtml.php b/framework/Form/lib/Horde/Form/Renderer/Xhtml.php
new file mode 100644 (file)
index 0000000..3caac34
--- /dev/null
@@ -0,0 +1,455 @@
+<?php
+/**
+ */
+class Horde_Form_Renderer_Xhtml extends Horde_Form_Renderer {
+
+    protected $_enctype = 'multipart/form-data';
+
+    function _renderSectionTabs($form)
+    {
+        /* If javascript is not available, do not render tabs. */
+        if (!$GLOBALS['browser']->hasFeature('javascript')) {
+            return;
+        }
+
+        $open_section = $form->getOpenSection();
+
+        /* Add the javascript for the toggling the sections. */
+        Horde::addScriptFile('form_sections.js', 'horde', true);
+        echo '<script type="text/javascript">' . "\n" .
+            sprintf('var sections_%1$s = new Horde_Form_Sections(\'%1$s\', \'%2$s\');',
+                    $form->getName(),
+                    $open_section) .
+            '</script>';
+
+        /* Loop through the sections and print out a tab for each. */
+        echo "<div class=\"tabset\">\n";
+        $js = array();
+        foreach ($form->_sections as $section => $val) {
+            $class = ($section == $open_section) ? ' class="activeTab"' : '';
+            $tabid = htmlspecialchars($form->getName() . '_tab_' . $section);
+            $js[$linkid] = sprintf('sections_%s.toggle(\'%s\'); return false;"',
+                                   $form->getName(),
+                                   $section);
+            printf('<div%s id="%s"><a href="#" id="%s">%s%s</a></div>' . "\n",
+                   $class,
+                   $tabid,
+                   '_tablink_' . $section,
+                   $form->getSectionImage($section),
+                   $form->getSectionDesc($section));
+        }
+        echo "</div>\n";
+
+        // This doesn't help a whole lot now, but if there is a way to
+        // buffer output of JS, then we can keep JS separated from
+        // markup, whereas before the onclicks were assigned as an
+        // HTML attribute.
+        echo '<script type="text/javascript">' . "\n";
+        echo 'if (document.getElementById) {' . "\n";
+        echo '    addEvent(window, \'load\', function() {' . "\n";
+        foreach ($js as $id => $onclick) {
+            $line = '
+if (document.getElementById(%1$s)){
+    document.getElementById(%1$s).onclick = function() {
+        %2$s
+    };
+}';
+            printf($line, $id, $onclick);
+        }
+        echo '    });}</script>' . "\n";
+    }
+
+    function _renderSectionBegin($form, $section)
+    {
+        // Stripe alternate rows if that option is turned on.
+        if ($this->_stripedRows) {
+            Horde::addScriptFile('stripe.js', 'horde', true);
+            $class = 'striped';
+        } else {
+            $class = '';
+        }
+
+        $open_section = $form->getOpenSection();
+        if (empty($open_section)) {
+            $open_section = '__base';
+        }
+
+        // include a general class name for styling purposes. also helps select
+        // ULs, which only get a className currently if they are striped.
+        printf('<fieldset id="%s" class="%s form-section %s">',
+               htmlspecialchars($form->getName() . '_section_' . $section),
+               ($open_section == $section ? 'form-sectionshown' : 'form-sectionhidden'),
+               $class);
+    }
+
+    function _renderSectionEnd()
+    {
+        echo '</fieldset>';
+    }
+
+    function preserveVarByPost($vars, $varname, $alt_varname = '')
+    {
+        $value = $vars->getExists($varname, $wasset);
+
+        if ($alt_varname) {
+            $varname = $alt_varname;
+        }
+
+        if ($wasset) {
+            $this->_preserveVarByPost($varname, $value);
+        }
+    }
+
+    function _preserveVarByPost($varname, $value)
+    {
+        if (is_array($value)) {
+            foreach ($value as $id => $val) {
+                $this->_preserveVarByPost($varname . '[' . $id . ']', $val);
+            }
+        } else {
+            $varname = htmlspecialchars($varname);
+            $value = htmlspecialchars($value);
+            printf('<input type="hidden" id="%1$s" name="%1$s" value="%2$s" />'."\n",
+                   $varname,
+                   $value);
+        }
+    }
+
+    function listFormVars($form)
+    {
+        $variables = $form->getVariables(true, true);
+        $vars = array();
+        if ($variables) {
+            foreach ($variables as $var) {
+                if (is_object($var)) {
+                    if (!$var->isReadonly()) {
+                        $vars[$var->getVarName()] = 1;
+                    }
+                } else {
+                    $vars[$var] = 1;
+                }
+            }
+        }
+        require_once 'Horde/NLS.php';
+        echo '<input type="hidden" name="_formvars" value="'
+            . htmlspecialchars(serialize($vars), ENT_QUOTES, NLS::getCharset())
+            . '" />';
+    }
+
+    public function renderActive($form, $action, $method = 'get', $enctype = null, $focus = true)
+    {
+        $this->_name = $form->getName();
+
+        echo "<form class=\"horde-form\" action=\"$action\" method=\"$method\""
+            . (empty($this->_name) ? '' : ' id="' . $this->_name. '"')
+            . (is_null($this->_enctype) ? '' : ' enctype="' . $this->_enctype . '"')
+            . ">\n";
+        echo Util::formInput();
+
+        $this->listFormVars($form);
+
+        if (!empty($this->_name)) {
+            $this->_preserveVarByPost('formname', $this->_name);
+        }
+
+        if ($form->useToken()) {
+            $this->_preserveVarByPost($this->_name . '_formToken', Horde_Token::generateId($this->_name));
+        }
+
+        if (count($form->getSections())) {
+            $this->_preserveVarByPost('__formOpenSection', $form->getOpenSection());
+        }
+
+        $vars = $form->getVars();
+
+        $variables = $form->getVariables();
+        foreach ($variables as $var) {
+            if ($var->getOption('trackchange')) {
+                $varname = $var->getVarName();
+                $this->preserveVarByPost($vars, $varname, '__old_' . $varname);
+            }
+        }
+
+        foreach ($form->getHiddenVariables() as $var) {
+            $this->preserveVarByPost($vars, $var->getVarName());
+        }
+
+        $this->_renderBeginActive($form->getTitle());
+        $this->_renderForm($form, true);
+        $this->submit($this->_submit, $this->_reset);
+
+        echo "\n</fieldset>\n</form>\n";
+        if ($focus && !empty($this->_firstField)) {
+            echo '<script type="text/javascript">
+try {
+    document.getElementById("'. $this->_firstField .'").focus();
+} catch (e) {}
+</script>
+';
+        }
+    }
+
+    function renderInactive($form)
+    {
+        $this->_name = $form->getName();
+        $this->_renderBeginInactive($form->getTitle());
+        $this->_renderForm($form, false);
+    }
+
+    function _renderForm($form, $active)
+    {
+        $vars = $form->getVars();
+
+        /* If help is present 3 columns are needed. */
+        $this->_cols = $form->hasHelp() ? 3 : 2;
+
+        $variables = $form->getVariables(false);
+
+        /* Check for a form token error. */
+        if (($tokenError = $form->getError('_formToken')) !== null) {
+            printf('<p class="form-error">%s</p>'."\n", $tokenError);
+        }
+
+        $error_section = null;
+        reset($variables);
+        if (count($variables) > 1 || key($variables) != '__base') {
+            $this->_renderSectionTabs($form);
+        }
+
+        foreach ($variables as $section_id => $section) {
+            $this->_renderSectionBegin($form, $section_id);
+            foreach ($section as $var) {
+                switch (get_class($var->type)) {
+                case 'Horde_Form_Type_header':
+                    $this->_renderHeader($var->getHumanName(), $form->getError($var->getVarName()));
+                    break;
+
+                case 'Horde_Form_Type_description':
+                    $this->_renderDescription($var->getHumanName());
+                    break;
+
+                case 'Horde_Form_Type_spacer':
+                    $this->_renderSpacer();
+                    break;
+
+                default:
+                    $isInput = ($active && !$var->isReadonly());
+                    $format = $isInput ? 'Input' : 'Display';
+                    $begin = "_renderVar${format}Begin";
+                    $end = "_renderVar${format}End";
+
+                    $this->$begin($form, $var);
+                    echo $this->_varRenderer->render($form, $var, $vars, $isInput);
+                    $this->$end($form, $var);
+
+                    /* Print any javascript if actions present. */
+                    if ($var->hasAction()) {
+                        $var->_action->printJavaScript();
+                    }
+
+                    /* Keep first field. */
+                    if ($active && empty($this->_firstField) && !$var->isReadonly()
+                        && !$var->isHidden()) {
+                        $this->_firstField = $var->getVarName();
+                    }
+
+                    /* Keep section with first error. */
+                    if (is_null($error_section) && $form->getError($var)) {
+                        $error_section = $section_id;
+                    }
+                }
+            }
+
+            $this->_renderSectionEnd();
+        }
+
+        if (!is_null($error_section)) {
+            echo '<script type="text/javascript">' .
+                "\n" . sprintf('sections_%s.toggle(\'%s\');',
+                               $form->getName(),
+                               $error_section) .
+                "\n</script>\n";
+        }
+
+        echo '</fieldset>' . $this->_varRenderer->renderEnd();
+    }
+
+    function submit($submit = null, $reset = false)
+    {
+        if (is_null($submit) || empty($submit)) {
+            $submit = _("Submit");
+        }
+        if ($reset === true) {
+            $reset = _("Reset");
+        }
+        $this->_renderSubmit($submit, $reset);
+    }
+
+    /**
+     * Implementation specific begin function.
+     */
+    function _renderBeginActive($name)
+    {
+        echo '<fieldset class="horde-form" id="fieldset_' . htmlspecialchars($this->_name) . '">'."\n";
+        if ($this->_showHeader) {
+            $this->_renderSectionHeader($name);
+        }
+        if ($this->_requiredLegend) {
+            echo '<div class="form-error-example">' . $this->_requiredMarker
+                . ' &#61; ' . _("Required Field") . '</div>'."\n";
+        }
+    }
+
+    /**
+     * Implementation specific begin function.
+     */
+    function _renderBeginInactive($name)
+    {
+        echo '<fieldset class="horde-form" id="fieldset_' . htmlspecialchars($this->_name) . '">';
+        if ($this->_showHeader) {
+            $this->_renderSectionHeader($name);
+        }
+    }
+
+    function _renderHeader($header, $error = '')
+    {
+        echo '<div class="form-header">'. $header . '</div>';
+        if (!empty($error)) {
+            echo '<div class="form-error">'. $error . '</div>';
+        }
+    }
+
+    function _renderDescription($description)
+    {
+        echo '<div class="form-description">'. $description . '</div>';
+    }
+
+    function _renderSpacer()
+    {
+        // TODO: fix this later so we're not inserting nonsemantic elements just for spacing
+        // ... maybe append form-spacer to class of next or previous element
+        echo '<div class="form-spacer">&nbsp;</div>';
+    }
+
+    function _renderSubmit($submit, $reset)
+    {
+        echo '<fieldset class="form-buttons">'."\n";
+        if (!is_array($submit)) $submit = array($submit);
+        foreach ($submit as $submitbutton) {
+            echo '<input class="button" name="submitbutton" type="submit"';
+            // allow for default-value submit buttons (e.g. _renderSubmit(""))
+            if (!empty($submitbutton)) {
+                echo ' value="'. $submitbutton .'"';
+            }
+            echo ' />'."\n";
+        }
+        if (!empty($reset)) {
+            echo '<input class="button" name="resetbutton" type="reset"
+                value="'. $reset .'" />'."\n";
+        }
+    }
+
+    /**
+     * Renders the beginning of an writeable form entry, including the label
+     * and any form error related to this variable.
+     *
+     * @access private
+     * @author Matt Warden <mwarden@gmail.com>
+     * @author  Robert E. Coyle <robertecoyle@hotmail.com>
+     */
+    function _renderVarInputBegin($form, $var, $readonly = false)
+    {
+        // get error message for variable, if any
+        $message = $form->getError($var);
+        // if no message, then no error
+        $isvalid = empty($message);
+
+        $classnames = 'form-input'
+            . (!$isvalid ? ' form-error' : '')
+            . ($var->isRequired() ? ' form-required' : '');
+
+        echo '<div class="', $classnames, '">';
+
+        if (!$isvalid) {
+            echo '<p class="form-error">', $message, '</p>', "\n";
+        }
+
+        printf('<label%s>%s</label>',
+            ($readonly ? '' : ' for="'. $var->getVarName() .'"'),
+            $var->getHumanName());
+    }
+
+    /**
+     * Renders the end of an writeable form entry, including any form notes
+     * and help info.
+     *
+     * @access private
+     * @author Matt Warden <mwarden@gmail.com>
+     * @author  Robert E. Coyle <robertecoyle@hotmail.com>
+     */
+    function _renderVarInputEnd($form, $var)
+    {
+        /* Display any help for the field. */
+        if ($var->hasHelp()) {
+            global $registry;
+            if (isset($registry) && is_a($registry, 'Registry')) {
+                $help = Help::link($GLOBALS['registry']->getApp(), $var->getHelp());
+            } else {
+                $help = @htmlspecialchars($var->getHelp());
+            }
+            echo '<p class="form-hint">', $help, '</p>';
+        }
+
+        /* Display any description for the field. */
+        if ($var->hasDescription()) {
+            echo '<div class="form-note"><p>', $var->getDescription(), '</p></div>';
+        } else {
+            echo '<br class="clear" />';
+        }
+
+        echo '</div>';
+    }
+
+    /**
+     * Renders the beginning of a readonly form entry.
+     *
+     * @access private
+     * @author Matt Warden <mwarden@gmail.com>
+     * @author  Robert E. Coyle <robertecoyle@hotmail.com>
+     */
+    function _renderVarDisplayBegin($form, $var)
+    {
+        return $this->_renderVarInputBegin($form, $var, true);
+    }
+
+    /**
+     * Renders the end of a readonly form entry. Help and notes are not
+     * applicable.
+     *
+     * @access private
+     * @author Matt Warden <mwarden@gmail.com>
+     * @author  Robert E. Coyle <robertecoyle@hotmail.com>
+     */
+    function _renderVarDisplayEnd()
+    {
+        echo '</div>';
+    }
+
+    /**
+     * Renders the header for the section.
+     *
+     * @access private
+     * @author Matt Warden <mwarden@gmail.com>
+     * @author  Robert E. Coyle <robertecoyle@hotmail.com>
+     * @param string $title section header title
+     */
+    function _renderSectionHeader($title)
+    {
+        if (!empty($title)) {
+            echo "\n".'<legend>';
+            echo $this->_encodeTitle ? htmlspecialchars($title) : $title;
+            echo '</legend>'."\n";
+        }
+    }
+
+}
diff --git a/framework/Form/lib/Horde/Form/Type.php b/framework/Form/lib/Horde/Form/Type.php
new file mode 100644 (file)
index 0000000..707bb71
--- /dev/null
@@ -0,0 +1,106 @@
+<?php
+/**
+ * Horde_Form_Type Class
+ *
+ * @author  Robert E. Coyle <robertecoyle@hotmail.com>
+ * @package Horde_Form
+ */
+abstract class Horde_Form_Type {
+
+    protected $_properties = array();
+
+    /**
+     * Type constructor. Takes a hash of key/value parameters.
+     *
+     * @param array $properties Any type properties to initialize.
+     */
+    public function __construct($properties = array())
+    {
+        $this->_properties = array();
+        $vars = array_keys(get_object_vars($this));
+        foreach ($vars as $var) {
+            $this->_properties[] = substr($var, 1);
+        }
+
+        if ($this->_properties && $properties) {
+            $properties = array_combine(array_slice($this->_properties, 0, count($properties)), $properties);
+            foreach ($properties as $property => $value) {
+                $this->__set($property, $value);
+            }
+        }
+    }
+
+    /**
+     */
+    abstract public function isValid($var, $vars, $value, &$message);
+
+    /**
+     */
+    function getInfo($vars, $var, &$info)
+    {
+        $info = $var->getValue($vars);
+    }
+
+    /**
+     */
+    public function onSubmit()
+    {
+    }
+
+    /**
+     * To get the 'escape' property of a type:
+     *   $escape = $type->escape;
+     * If the property is not set this will return null.
+     *
+     * @param string $property The property to retrieve.
+     */
+    protected function __get($property)
+    {
+        if (in_array($property, $this->_properties)) {
+            $prop = '_' . $property;
+            return $this->$prop;
+        }
+
+        return null;
+    }
+
+    /**
+     * To set the 'escape' property of a type to true:
+     *   $type->escape = true;
+     *
+     * @param string $property The property name to set.
+     * @param mixed $value The property value.
+     */
+    protected function __set($property, $value)
+    {
+        if (in_array($property, $this->_properties)) {
+            $prop = '_' . $property;
+            $this->$prop = $value;
+        }
+    }
+
+    /**
+     * To check if a type has a property named 'escape':
+     *  if (isset($type->escape)) { ... }
+     *
+     * @param string $property Property name to check existance of.
+     */
+    protected function __isset($property)
+    {
+        $prop = '_' . $property;
+        return isset($this->$prop);
+    }
+
+    /**
+     * To unset a Type property named 'escape':
+     *   unset($type->escape);
+     *
+     * @param string $property Property name to unset.
+     */
+    protected function __unset($property)
+    {
+        $prop = '_' . $property;
+        unset($this->$prop);
+    }
+
+}
diff --git a/framework/Form/lib/Horde/Form/Type/Boolean.php b/framework/Form/lib/Horde/Form/Type/Boolean.php
new file mode 100644 (file)
index 0000000..9ae0e86
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+/**
+ * An on/off value
+ */
+class Horde_Form_Type_Boolean extends Horde_Form_Type {
+
+    public function isValid($var, $vars, $value, &$message)
+    {
+        return true;
+    }
+
+    public function getInfo($vars, $var, &$info)
+    {
+        $info = String::lower($vars->get($var->name)) == 'on';
+    }
+
+}
diff --git a/framework/Form/lib/Horde/Form/Type/Color.php b/framework/Form/lib/Horde/Form/Type/Color.php
new file mode 100644 (file)
index 0000000..383a16a
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+/**
+ * Color
+ */
+class Horde_Form_Type_Color extends Horde_Form_Type {
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        if ($var->required && empty($value)) {
+            $message = _("This field is required.");
+            return false;
+        }
+
+        if (empty($value) || preg_match('/^#([0-9a-z]){6}$/i', $value)) {
+            return true;
+        }
+
+        $message = _("This field must contain a color code in the RGB Hex format, for example '#1234af'.");
+        return false;
+    }
+
+}
diff --git a/framework/Form/lib/Horde/Form/Type/CreditCard.php b/framework/Form/lib/Horde/Form/Type/CreditCard.php
new file mode 100644 (file)
index 0000000..294834c
--- /dev/null
@@ -0,0 +1,97 @@
+<?php
+/**
+ * Credit card number
+ */
+class Horde_Form_Type_creditcard extends Horde_Form_Type {
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        if (empty($value) && $var->required) {
+            $message = _("This field is required.");
+            return false;
+        }
+
+        if (!empty($value)) {
+            /* getCardType() will also verify the checksum. */
+            $type = $this->getCardType($value);
+            if ($type === false || $type == 'unknown') {
+                $message = _("This does not seem to be a valid card number.");
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    function getChecksum($ccnum)
+    {
+        $len = strlen($ccnum);
+        if (!is_long($len / 2)) {
+            $weight = 2;
+            $digit = $ccnum[0];
+        } elseif (is_long($len / 2)) {
+            $weight = 1;
+            $digit = $ccnum[0] * 2;
+        }
+        if ($digit > 9) {
+            $digit = $digit - 9;
+        }
+        $i = 1;
+        $checksum = $digit;
+        while ($i < $len) {
+            if ($ccnum[$i] != ' ') {
+                $digit = $ccnum[$i] * $weight;
+                $weight = ($weight == 1) ? 2 : 1;
+                if ($digit > 9) {
+                    $digit = $digit - 9;
+                }
+                $checksum += $digit;
+            }
+            $i++;
+        }
+
+        return $checksum;
+    }
+
+    function getCardType($ccnum)
+    {
+        $sum = $this->getChecksum($ccnum);
+        $l = strlen($ccnum);
+
+        // Screen checksum.
+        if (($sum % 10) != 0) {
+            return false;
+        }
+
+        // Check for Visa.
+        if ((($l == 16) || ($l == 13)) &&
+            ($ccnum[0] == 4)) {
+            return 'visa';
+        }
+
+        // Check for MasterCard.
+        if (($l == 16) &&
+            ($ccnum[0] == 5) &&
+            ($ccnum[1] >= 1) &&
+            ($ccnum[1] <= 5)) {
+            return 'mastercard';
+        }
+
+        // Check for Amex.
+        if (($l == 15) &&
+            ($ccnum[0] == 3) &&
+            (($ccnum[1] == 4) || ($ccnum[1] == 7))) {
+            return 'amex';
+        }
+
+        // Check for Discover (Novus).
+        if (strlen($ccnum) == 16 &&
+            substr($ccnum, 0, 4) == '6011') {
+            return 'discover';
+        }
+
+        // If we got this far, then no card matched.
+        return 'unknown';
+    }
+
+}
diff --git a/framework/Form/lib/Horde/Form/Type/Date.php b/framework/Form/lib/Horde/Form/Type/Date.php
new file mode 100644 (file)
index 0000000..c4415e7
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+/**
+ * Date
+ */
+class Horde_Form_Type_Date extends Horde_Form_Type {
+
+    var $_format = '%a %d %B';
+
+    public function isValid($var, $vars, $value, &$message)
+    {
+        $valid = true;
+
+        if ($var->required) {
+            $valid = strlen(trim($value)) > 0;
+
+            if (!$valid) {
+                $message = _("This field is required.");
+            }
+        }
+
+        return $valid;
+    }
+
+    public static function getAgo($timestamp)
+    {
+        if ($timestamp === null) {
+            return '';
+        }
+
+        $diffdays = Date_Calc::dateDiff(date('j', $timestamp),
+                                        date('n', $timestamp),
+                                        date('Y', $timestamp),
+                                        date('j'), date('n'), date('Y'));
+
+        /* An error occured. */
+        if ($diffdays == -1) {
+            return;
+        }
+
+        $ago = $diffdays * Date_Calc::compareDates(date('j', $timestamp),
+                                                   date('n', $timestamp),
+                                                   date('Y', $timestamp),
+                                                   date('j'), date('n'),
+                                                   date('Y'));
+        if ($ago < -1) {
+            return sprintf(_(" (%s days ago)"), $diffdays);
+        } elseif ($ago == -1) {
+            return _(" (yesterday)");
+        } elseif ($ago == 0) {
+            return _(" (today)");
+        } elseif ($ago == 1) {
+            return _(" (tomorrow)");
+        } else {
+            return sprintf(_(" (in %s days)"), $diffdays);
+        }
+    }
+
+    public function getFormattedTime($timestamp, $format = null, $showago = true)
+    {
+        if (empty($format)) {
+            $format = $this->_format;
+        }
+        if (!empty($timestamp)) {
+            return strftime($format, $timestamp) . ($showago ? self::getAgo($timestamp) : '');
+        } else {
+            return '';
+        }
+    }
+
+}
diff --git a/framework/Form/lib/Horde/Form/Type/DateTime.php b/framework/Form/lib/Horde/Form/Type/DateTime.php
new file mode 100644 (file)
index 0000000..98adaa0
--- /dev/null
@@ -0,0 +1,125 @@
+<?php
+/**
+ * Date and time selection
+ */
+class Horde_Form_Type_DateTime extends Horde_Form_Type {
+
+    var $_date;
+    var $_time;
+
+    /**
+     * Return the date supplied as a Horde_Date object.
+     *
+     * @param integer $start_year  The first available year for input.
+     * @param integer $end_year    The last available year for input.
+     * @param boolean $picker      Do we show the DHTML calendar?
+     * @param integer $format_in   The format to use when sending the date
+     *                             for storage. Defaults to Unix epoch.
+     *                             Similar to the strftime() function.
+     * @param integer $format_out  The format to use when displaying the
+     *                             date. Similar to the strftime() function.
+     * @param boolean $show_seconds Include a form input for seconds.
+     */
+    function init($start_year = '', $end_year = '', $picker = true,
+                  $format_in = null, $format_out = '%x', $show_seconds = false)
+    {
+        $this->_date = new Horde_Form_Type_Date();
+        $this->_date->init($start_year, $end_year, $picker, $format_in, $format_out);
+
+        $this->_time = new Horde_Form_Type_Time();
+        $this->_time->init($show_seconds);
+    }
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        if ($var->required) {
+            return $this->_date->isValid($var, $vars, $value, $message) &&
+                $this->_time->isValid($var, $vars, $value, $message);
+        }
+        return true;
+    }
+
+    function getInfo(&$vars, &$var, &$info)
+    {
+        /* If any component is empty consider it a bad date and return the
+         * default. */
+        $value = $var->getValue($vars);
+        if ($this->emptyDateArray($value) == 1 || $this->emptyTimeArray($value)) {
+            $info = $var->getDefault();
+            return;
+        }
+
+        $date = $this->getDateOb($value);
+        $time = $this->getTimeOb($value);
+        $date->hour = $time->hour;
+        $date->min = $time->min;
+        $date->sec = $time->sec;
+        if (is_null($this->format_in)) {
+            $info = $date->timestamp();
+        } else {
+            $info = $date->strftime($this->format_in);
+        }
+    }
+
+    function __get($property)
+    {
+        if ($property == 'show_seconds') {
+            return $this->_time->$property;
+        } else {
+            return $this->_date->$property;
+        }
+    }
+
+    function __set($property, $value)
+    {
+        if ($property == 'show_seconds') {
+            $this->_time->$property = $value;
+        } else {
+            $this->_date->$property = $value;
+        }
+    }
+
+    function checktime($hour, $minute, $second)
+    {
+        return $this->_time->checktime($hour, $minute, $second);
+    }
+
+    function getTimeOb($time_in)
+    {
+        return $this->_time->getTimeOb($time_in);
+    }
+
+    function getTimeParts($time_in)
+    {
+        return $this->_time->getTimeParts($time_in);
+    }
+
+    function emptyTimeArray($time)
+    {
+        return $this->_time->emptyTimeArray($time);
+    }
+
+    function emptyDateArray($date)
+    {
+        return $this->_date->emptyDateArray($date);
+    }
+
+    function getDateParts($date_in)
+    {
+        return $this->_date->getDateParts($date_in);
+    }
+
+    function getDateOb($date_in)
+    {
+        return $this->_date->getDateOb($date_in);
+    }
+
+    function formatDate($date)
+    {
+        if ($date === null) {
+            return '';
+        }
+        return $this->_date->formatDate($date);
+    }
+
+}
diff --git a/framework/Form/lib/Horde/Form/Type/Email.php b/framework/Form/lib/Horde/Form/Type/Email.php
new file mode 100644 (file)
index 0000000..96cf756
--- /dev/null
@@ -0,0 +1,452 @@
+<?php
+/**
+ * Email
+ */
+class Horde_Form_Type_Email extends Horde_Form_Type {
+
+    /**
+     * Allow multiple addresses?
+     *
+     * @type boolean
+     * @var boolean
+    */
+    var $_allow_multi = false;
+
+    /**
+     * Strip domain from the address?
+     *
+     * @type boolean
+     * @var boolean
+     */
+    var $_strip_domain = false;
+
+    /**
+     * Make displayed email addresses clickable?
+     *
+     * @type boolean
+     * @var boolean
+     */
+    var $_link_compose = false;
+
+    /**
+     * The compose name to use
+     *
+     * @type text
+     * @var boolean
+     */
+    var $_link_name;
+
+    /**
+     * The character to separate multiple email addresses
+     *
+     * @type text
+     * @var string
+     */
+    var $_delimiters = ',';
+
+    /**
+     * Contact the target mail server to see if the email address is deliverable?
+     *
+     * @type boolean
+     * @var boolean
+     */
+    var $_check_smtp = false;
+
+    /**
+     */
+    public function init($allow_multi = false, $strip_domain = false,
+                  $link_compose = false, $link_name = null,
+                  $delimiters = ',')
+    {
+        $this->_allow_multi = $allow_multi;
+        $this->_strip_domain = $strip_domain;
+        $this->_link_compose = $link_compose;
+        $this->_link_name = $link_name;
+        $this->_delimiters = $delimiters;
+    }
+
+    /**
+     */
+    public function isValid($var, $vars, $value, &$message)
+    {
+        // Split into individual addresses.
+        $emails = $this->splitEmailAddresses($value);
+
+        // Check for too many.
+        if (!$this->_allow_multi && count($emails) > 1) {
+            $message = _("Only one email address is allowed.");
+            return false;
+        }
+
+        // Check for all valid and at least one non-empty.
+        $nonEmpty = 0;
+        foreach ($emails as $email) {
+            if (!strlen($email)) {
+                continue;
+            }
+            if (!$this->validateEmailAddress($email)) {
+                $message = sprintf(_("\"%s\" is not a valid email address."), $email);
+                return false;
+            }
+            ++$nonEmpty;
+        }
+
+        if (!$nonEmpty && $var->required) {
+            if ($this->_allow_multi) {
+                $message = _("You must enter at least one email address.");
+            } else {
+                $message = _("You must enter an email address.");
+            }
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Explodes an RFC 2822 string, ignoring a delimiter if preceded
+     * by a "\" character, or if the delimiter is inside single or
+     * double quotes.
+     *
+     * @param string $string     The RFC 822 string.
+     *
+     * @return array  The exploded string in an array.
+     */
+    public function splitEmailAddresses($string)
+    {
+        $quotes = array('"', "'");
+        $emails = array();
+        $pos = 0;
+        $in_quote = null;
+        $in_group = false;
+        $prev = null;
+
+        if (!strlen($string)) {
+            return array();
+        }
+
+        $char = $string[0];
+        if (in_array($char, $quotes)) {
+            $in_quote = $char;
+        } elseif ($char == ':') {
+            $in_group = true;
+        } elseif (strpos($this->_delimiters, $char) !== false) {
+            $emails[] = '';
+            $pos = 1;
+        }
+
+        for ($i = 1, $iMax = strlen($string); $i < $iMax; ++$i) {
+            $char = $string[$i];
+            if (in_array($char, $quotes)) {
+                if ($prev !== '\\') {
+                    if ($in_quote === $char) {
+                        $in_quote = null;
+                    } elseif (is_null($in_quote)) {
+                        $in_quote = $char;
+                    }
+                }
+            } elseif ($in_group) {
+                if ($char == ';') {
+                    $emails[] = substr($string, $pos, $i - $pos + 1);
+                    $pos = $i + 1;
+                    $in_group = false;
+                }
+            } elseif ($char == ':') {
+                $in_group = true;
+            } elseif (strpos($this->_delimiters, $char) !== false &&
+                      $prev !== '\\' &&
+                      is_null($in_quote)) {
+                $emails[] = substr($string, $pos, $i - $pos);
+                $pos = $i + 1;
+            }
+            $prev = $char;
+        }
+
+        if ($pos != $i) {
+            /* The string ended without a delimiter. */
+            $emails[] = substr($string, $pos, $i - $pos);
+        }
+
+        return $emails;
+    }
+
+    /**
+     * RFC(2)822 Email Parser.
+     *
+     * By Cal Henderson <cal@iamcal.com>
+     * This code is licensed under a Creative Commons Attribution-ShareAlike 2.5 License
+     * http://creativecommons.org/licenses/by-sa/2.5/
+     *
+     * http://code.iamcal.com/php/rfc822/
+     *
+     * http://iamcal.com/publish/articles/php/parsing_email
+     *
+     * Revision 4
+     *
+     * @param string $email An individual email address to validate.
+     *
+     * @return boolean
+     */
+    public function validateEmailAddress($email)
+    {
+        static $comment_regexp, $email_regexp;
+        if ($comment_regexp === null) {
+            $this->_defineValidationRegexps($comment_regexp, $email_regexp);
+        }
+
+        // We need to strip comments first (repeat until we can't find
+        // any more).
+        while (true) {
+            $new = preg_replace("!$comment_regexp!", '', $email);
+            if (strlen($new) == strlen($email)){
+                break;
+            }
+            $email = $new;
+        }
+
+        // Now match what's left.
+        $result = (bool)preg_match("!^$email_regexp$!", $email);
+        if ($result && $this->_check_smtp) {
+            $result = $this->validateEmailAddressSmtp($email);
+        }
+
+        return $result;
+    }
+
+    /**
+     * Attempt partial delivery of mail to an address to validate it.
+     *
+     * @param string $email An individual email address to validate.
+     *
+     * @return boolean
+     */
+    public function validateEmailAddressSmtp($email)
+    {
+        list(, $maildomain) = explode('@', $email, 2);
+
+        // Try to get the real mailserver from MX records.
+        if (function_exists('getmxrr') &&
+            @getmxrr($maildomain, $mxhosts, $mxpriorities)) {
+            // MX record found.
+            array_multisort($mxpriorities, $mxhosts);
+            $mailhost = $mxhosts[0];
+        } else {
+            // No MX record found, try the root domain as the mail
+            // server.
+            $mailhost = $maildomain;
+        }
+
+        $fp = @fsockopen($mailhost, 25, $errno, $errstr, 5);
+        if (!$fp) {
+            return false;
+        }
+
+        // Read initial response.
+        fgets($fp, 4096);
+
+        // HELO
+        fputs($fp, "HELO $mailhost\r\n");
+        fgets($fp, 4096);
+
+        // MAIL FROM
+        fputs($fp, "MAIL FROM: <root@example.com>\r\n");
+        fgets($fp, 4096);
+
+        // RCPT TO - gets the result we want.
+        fputs($fp, "RCPT TO: <$email>\r\n");
+        $result = trim(fgets($fp, 4096));
+
+        // QUIT
+        fputs($fp, "QUIT\r\n");
+        fgets($fp, 4096);
+        fclose($fp);
+
+        return substr($result, 0, 1) == '2';
+    }
+
+    /**
+     * RFC(2)822 Email Parser.
+     *
+     * By Cal Henderson <cal@iamcal.com>
+     * This code is licensed under a Creative Commons Attribution-ShareAlike 2.5 License
+     * http://creativecommons.org/licenses/by-sa/2.5/
+     *
+     * http://code.iamcal.com/php/rfc822/
+     *
+     * http://iamcal.com/publish/articles/php/parsing_email
+     *
+     * Revision 4
+     *
+     * @param string &$comment The regexp for comments.
+     * @param string &$addr_spec The regexp for email addresses.
+     */
+    protected function _defineValidationRegexps(&$comment, &$addr_spec)
+    {
+        /**
+         * NO-WS-CTL       =       %d1-8 /         ; US-ASCII control characters
+         *                         %d11 /          ;  that do not include the
+         *                         %d12 /          ;  carriage return, line feed,
+         *                         %d14-31 /       ;  and white space characters
+         *                         %d127
+         * ALPHA          =  %x41-5A / %x61-7A   ; A-Z / a-z
+         * DIGIT          =  %x30-39
+         */
+        $no_ws_ctl  = "[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]";
+        $alpha      = "[\\x41-\\x5a\\x61-\\x7a]";
+        $digit      = "[\\x30-\\x39]";
+        $cr         = "\\x0d";
+        $lf         = "\\x0a";
+        $crlf       = "($cr$lf)";
+
+        /**
+         * obs-char        =       %d0-9 / %d11 /          ; %d0-127 except CR and
+         *                         %d12 / %d14-127         ;  LF
+         * obs-text        =       *LF *CR *(obs-char *LF *CR)
+         * text            =       %d1-9 /         ; Characters excluding CR and LF
+         *                         %d11 /
+         *                         %d12 /
+         *                         %d14-127 /
+         *                         obs-text
+         * obs-qp          =       "\" (%d0-127)
+         * quoted-pair     =       ("\" text) / obs-qp
+         */
+        $obs_char       = "[\\x00-\\x09\\x0b\\x0c\\x0e-\\x7f]";
+        $obs_text       = "($lf*$cr*($obs_char$lf*$cr*)*)";
+        $text           = "([\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f]|$obs_text)";
+        $obs_qp         = "(\\x5c[\\x00-\\x7f])";
+        $quoted_pair    = "(\\x5c$text|$obs_qp)";
+
+        /**
+         * obs-FWS         =       1*WSP *(CRLF 1*WSP)
+         * FWS             =       ([*WSP CRLF] 1*WSP) /   ; Folding white space
+         *                         obs-FWS
+         * ctext           =       NO-WS-CTL /     ; Non white space controls
+         *                         %d33-39 /       ; The rest of the US-ASCII
+         *                         %d42-91 /       ;  characters not including "(",
+         *                         %d93-126        ;  ")", or "\"
+         * ccontent        =       ctext / quoted-pair / comment
+         * comment         =       "(" *([FWS] ccontent) [FWS] ")"
+         * CFWS            =       *([FWS] comment) (([FWS] comment) / FWS)
+         *
+         * @note: We translate ccontent only partially to avoid an
+         * infinite loop. Instead, we'll recursively strip comments
+         * before processing the input.
+         */
+        $wsp        = "[\\x20\\x09]";
+        $obs_fws    = "($wsp+($crlf$wsp+)*)";
+        $fws        = "((($wsp*$crlf)?$wsp+)|$obs_fws)";
+        $ctext      = "($no_ws_ctl|[\\x21-\\x27\\x2A-\\x5b\\x5d-\\x7e])";
+        $ccontent   = "($ctext|$quoted_pair)";
+        $comment    = "(\\x28($fws?$ccontent)*$fws?\\x29)";
+        $cfws       = "(($fws?$comment)*($fws?$comment|$fws))";
+        $cfws       = "$fws*";
+
+        /**
+         * atext           =       ALPHA / DIGIT / ; Any character except controls,
+         *                         "!" / "#" /     ;  SP, and specials.
+         *                         "$" / "%" /     ;  Used for atoms
+         *                         "&" / "'" /
+         *                         "*" / "+" /
+         *                         "-" / "/" /
+         *                         "=" / "?" /
+         *                         "^" / "_" /
+         *                         "`" / "{" /
+         *                         "|" / "}" /
+         *                         "~"
+         * atom            =       [CFWS] 1*atext [CFWS]
+         */
+        $atext      = "($alpha|$digit|[\\x21\\x23-\\x27\\x2a\\x2b\\x2d\\x2e\\x3d\\x3f\\x5e\\x5f\\x60\\x7b-\\x7e])";
+        $atom       = "($cfws?$atext+$cfws?)";
+
+        /**
+         * qtext           =       NO-WS-CTL /     ; Non white space controls
+         *                         %d33 /          ; The rest of the US-ASCII
+         *                         %d35-91 /       ;  characters not including "\"
+         *                         %d93-126        ;  or the quote character
+         * qcontent        =       qtext / quoted-pair
+         * quoted-string   =       [CFWS]
+         *                         DQUOTE *([FWS] qcontent) [FWS] DQUOTE
+         *                         [CFWS]
+         * word            =       atom / quoted-string
+         */
+        $qtext      = "($no_ws_ctl|[\\x21\\x23-\\x5b\\x5d-\\x7e])";
+        $qcontent   = "($qtext|$quoted_pair)";
+        $quoted_string  = "($cfws?\\x22($fws?$qcontent)*$fws?\\x22$cfws?)";
+        $word       = "($atom|$quoted_string)";
+
+        /**
+         * obs-local-part  =       word *("." word)
+         * obs-domain      =       atom *("." atom)
+         */
+        $obs_local_part = "($word(\\x2e$word)*)";
+        $obs_domain = "($atom(\\x2e$atom)*)";
+
+        /**
+         * dot-atom-text   =       1*atext *("." 1*atext)
+         * dot-atom        =       [CFWS] dot-atom-text [CFWS]
+         */
+        $dot_atom_text  = "($atext+(\\x2e$atext+)*)";
+        $dot_atom   = "($cfws?$dot_atom_text$cfws?)";
+
+        /**
+         * domain-literal  =       [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS]
+         * dcontent        =       dtext / quoted-pair
+         * dtext           =       NO-WS-CTL /     ; Non white space controls
+         *
+         *                         %d33-90 /       ; The rest of the US-ASCII
+         *                         %d94-126        ;  characters not including "[",
+         *                                         ;  "]", or "\"
+         */
+        $dtext      = "($no_ws_ctl|[\\x21-\\x5a\\x5e-\\x7e])";
+        $dcontent   = "($dtext|$quoted_pair)";
+        $domain_literal = "($cfws?\\x5b($fws?$dcontent)*$fws?\\x5d$cfws?)";
+
+        /**
+         * local-part      =       dot-atom / quoted-string / obs-local-part
+         * domain          =       dot-atom / domain-literal / obs-domain
+         * addr-spec       =       local-part "@" domain
+         */
+        $local_part = "($dot_atom|$quoted_string|$obs_local_part)";
+        $domain     = "($dot_atom|$domain_literal|$obs_domain)";
+        $addr_spec  = "($local_part\\x40$domain)";
+    }
+
+}
+
+/**
+ * Email with confirmation
+ */
+class Horde_Form_Type_EmailConfirm extends Horde_Form_Type {
+
+    public function isValid($var, $vars, $value, &$message)
+    {
+        if ($var->required && empty($value['original'])) {
+            $message = _("This field is required.");
+            return false;
+        }
+
+        if ($value['original'] != $value['confirm']) {
+            $message = _("Email addresses must match.");
+            return false;
+        } else {
+            require_once 'Horde/MIME.php';
+            $parsed_email = MIME::parseAddressList($value['original'], false,
+                                                   true);
+            if (is_a($parsed_email, 'PEAR_Error')) {
+                $message = $parsed_email->getMessage();
+                return false;
+            }
+            if (count($parsed_email) > 1) {
+                $message = _("Only one email address allowed.");
+                return false;
+            }
+            if (empty($parsed_email[0]->mailbox)) {
+                $message = _("You did not enter a valid email address.");
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+}
diff --git a/framework/Form/lib/Horde/Form/Type/Enum.php b/framework/Form/lib/Horde/Form/Type/Enum.php
new file mode 100644 (file)
index 0000000..c5e638e
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Choose one from a list of values
+ */
+class Horde_Form_Type_Enum extends Horde_Form_Type {
+
+    /**
+     * List of values to choose from
+     *
+     * @type stringlist
+     * @var array
+     */
+    protected $_values = array();
+
+    /**
+     * Initial prompt value, if any
+     *
+     * @type text
+     * @var string
+     */
+    protected $_prompt;
+
+    /**
+     */
+    public function isValid($var, $vars, $value, &$message)
+    {
+        if ($var->required && $value == '' && !isset($this->_values[$value])) {
+            $message = _("This field is required.");
+            return false;
+        }
+
+        if (count($this->_values) == 0 || isset($this->_values[$value]) ||
+            ($this->_prompt && empty($value))) {
+            return true;
+        }
+
+        $message = _("Invalid data.");
+        return false;
+    }
+
+}
diff --git a/framework/Form/lib/Horde/Form/Type/Int.php b/framework/Form/lib/Horde/Form/Type/Int.php
new file mode 100644 (file)
index 0000000..31375a7
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+/**
+ * Integer
+ */
+class Horde_Form_Type_Int extends Horde_Form_Type {
+
+    public function isValid($var, $vars, $value, &$message)
+    {
+        if ($var->required && empty($value) && ((string)(int)$value !== $value)) {
+            $message = _("This field is required.");
+            return false;
+        }
+
+        if (empty($value) || preg_match('/^[0-9]+$/', $value)) {
+            return true;
+        }
+
+        $message = _("This field may only contain integers.");
+        return false;
+    }
+
+}
diff --git a/framework/Form/lib/Horde/Form/Type/Invalid.php b/framework/Form/lib/Horde/Form/Type/Invalid.php
new file mode 100644 (file)
index 0000000..20ee3c3
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+class Horde_Form_Type_invalid extends Horde_Form_Type {
+
+    var $message;
+
+    function init($message)
+    {
+        $this->message = $message;
+    }
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        return false;
+    }
+
+}
diff --git a/framework/Form/lib/Horde/Form/Type/Number.php b/framework/Form/lib/Horde/Form/Type/Number.php
new file mode 100644 (file)
index 0000000..6b58fd6
--- /dev/null
@@ -0,0 +1,80 @@
+<?php
+/**
+ * Number
+ */
+class Horde_Form_Type_Number extends Horde_Form_Type {
+
+    /**
+     */
+    protected $_fraction;
+
+    public function isValid($var, $vars, $value, &$message)
+    {
+        if ($var->required && empty($value) && ((string)(double)$value !== $value)) {
+            $message = _("This field is required.");
+            return false;
+        } elseif (empty($value)) {
+            return true;
+        }
+
+        /* If matched, then this is a correct numeric value. */
+        if (preg_match($this->_getValidationPattern(), $value)) {
+            return true;
+        }
+
+        $message = _("This field must be a valid number.");
+        return false;
+    }
+
+    /**
+     */
+    public function getInfo($vars, $var, &$info)
+    {
+        $value = $vars->get($var->name);
+        $linfo = NLS::getLocaleInfo();
+        $value = str_replace($linfo['mon_thousands_sep'], '', $value);
+        $info = str_replace($linfo['mon_decimal_point'], '.', $value);
+    }
+
+    /**
+     */
+    protected function _getValidationPattern()
+    {
+        static $pattern = '';
+        if (!empty($pattern)) {
+            return $pattern;
+        }
+
+        /* Get current locale information. */
+        $linfo = NLS::getLocaleInfo();
+
+        /* Build the pattern. */
+        $pattern = '(-)?';
+
+        /* Only check thousands separators if locale has any. */
+        if (!empty($linfo['mon_thousands_sep'])) {
+            /* Regex to check for correct thousands separators (if any). */
+            $pattern .= '((\d+)|((\d{0,3}?)([' . $linfo['mon_thousands_sep'] . ']\d{3})*?))';
+        } else {
+            /* No locale thousands separator, check for only digits. */
+            $pattern .= '(\d+)';
+        }
+        /* If no decimal point specified default to dot. */
+        if (empty($linfo['mon_decimal_point'])) {
+            $linfo['mon_decimal_point'] = '.';
+        }
+        /* Regex to check for correct decimals (if any). */
+        if (empty($this->_fraction)) {
+            $fraction = '*';
+        } else {
+            $fraction = '{0,' . $this->_fraction . '}';
+        }
+        $pattern .= '([' . $linfo['mon_decimal_point'] . '](\d' . $fraction . '))?';
+
+        /* Put together the whole regex pattern. */
+        $pattern = '/^' . $pattern . '$/';
+
+        return $pattern;
+    }
+
+}
diff --git a/framework/Form/lib/Horde/Form/Type/Octal.php b/framework/Form/lib/Horde/Form/Type/Octal.php
new file mode 100644 (file)
index 0000000..111cdb7
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+/**
+ * Octal
+ */
+class Horde_Form_Type_Octal extends Horde_Form_Type {
+
+    function isValid($var, $vars, $value, &$message)
+    {
+        if ($var->required && empty($value) && ((string)(int)$value !== $value)) {
+            $message = _("This field is required.");
+            return false;
+        }
+
+        if (empty($value) || preg_match('/^[0-7]+$/', $value)) {
+            return true;
+        }
+
+        $message = _("This field may only contain octal values.");
+        return false;
+    }
+
+}
diff --git a/framework/Form/lib/Horde/Form/Type/Password.php b/framework/Form/lib/Horde/Form/Type/Password.php
new file mode 100644 (file)
index 0000000..a47a178
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Password
+ */
+class Horde_Form_Type_Password extends Horde_Form_Type {
+
+    public function isValid($var, $vars, $value, &$message)
+    {
+        $valid = true;
+
+        if ($var->required) {
+            $valid = strlen(trim($value)) > 0;
+
+            if (!$valid) {
+                $message = _("This field is required.");
+            }
+        }
+
+        return $valid;
+    }
+
+}
+
+
+/**
+ * Password with confirmation
+ */
+class Horde_Form_Type_passwordConfirm extends Horde_Form_Type {
+
+    public function isValid($var, $vars, $value, &$message)
+    {
+        if ($var->required && empty($value['original'])) {
+            $message = _("This field is required.");
+            return false;
+        }
+
+        if ($value['original'] != $value['confirm']) {
+            $message = _("Passwords must match.");
+            return false;
+        }
+
+        return true;
+    }
+
+    function getInfo($vars, $var, &$info)
+    {
+        $value = $vars->get($var->name);
+        $info = $value['original'];
+    }
+
+}
diff --git a/framework/Form/lib/Horde/Form/Type/Phone.php b/framework/Form/lib/Horde/Form/Type/Phone.php
new file mode 100644 (file)
index 0000000..488fa57
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+/**
+ * Phone number
+ */
+class Horde_Form_Type_Phone extends Horde_Form_Type {
+
+    public function isValid($var, $vars, $value, &$message)
+    {
+        $valid = true;
+
+        if ($var->required) {
+            $valid = strlen(trim($value)) > 0;
+            if (!$valid) {
+                $message = _("This field is required.");
+            }
+        } else {
+            $valid = preg_match('/^\+?[\d()\-\/ ]*$/', $value);
+            if (!$valid) {
+                $message = _("You must enter a valid phone number, digits only with an optional '+' for the international dialing prefix.");
+            }
+        }
+
+        return $valid;
+    }
+
+}
diff --git a/framework/Form/lib/Horde/Form/Type/Phone/Mobile.php b/framework/Form/lib/Horde/Form/Type/Phone/Mobile.php
new file mode 100644 (file)
index 0000000..5c97969
--- /dev/null
@@ -0,0 +1,5 @@
+<?php
+/**
+ * Mobile Phone Number
+ */
+class Horde_Form_Type_Phone_Mobile extends Horde_Form_Type_Phone {}
diff --git a/framework/Form/lib/Horde/Form/Type/Set.php b/framework/Form/lib/Horde/Form/Type/Set.php
new file mode 100644 (file)
index 0000000..db805ad
--- /dev/null
@@ -0,0 +1,79 @@
+<?php
+/**
+ * Set of values
+ */
+class Horde_Form_Type_Set extends Horde_Form_Type {
+
+    /**
+     * Values
+     *
+     * @type stringlist
+     * @var string
+     */
+    protected $_values;
+
+    public function isValid($var, $vars, $value, &$message)
+    {
+        if (count($this->_values) == 0 || count($value) == 0) {
+            return true;
+        }
+        foreach ($value as $item) {
+            if (!isset($this->_values[$item])) {
+                $error = true;
+                break;
+            }
+        }
+        if (!isset($error)) {
+            return true;
+        }
+
+        $message = _("Invalid data.");
+        return false;
+    }
+
+}
+
+class Horde_Form_Type_multienum extends Horde_Form_Type_enum {
+
+    public function isValid($var, $vars, $value, &$message)
+    {
+        if (is_array($value)) {
+            foreach ($value as $val) {
+                if (!$this->isValid($var, $vars, $val, $message)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        if (empty($value) && ((string)(int)$value !== $value)) {
+            if ($var->required) {
+                $message = _("This field is required.");
+                return false;
+            } else {
+                return true;
+            }
+        }
+
+        if (count($this->_values) == 0 || isset($this->_values[$value])) {
+            return true;
+        }
+
+        $message = _("Invalid data.");
+        return false;
+    }
+
+}
+
+class Horde_Form_Type_keyval_multienum extends Horde_Form_Type_multienum {
+
+    function getInfo($vars, $var, &$info)
+    {
+        $value = $vars->get($var->name);
+        $info = array();
+        foreach ($value as $key) {
+            $info[$key] = $this->_values[$key];
+        }
+    }
+
+}
diff --git a/framework/Form/lib/Horde/Form/Type/String.php b/framework/Form/lib/Horde/Form/Type/String.php
new file mode 100644 (file)
index 0000000..276f541
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+/**
+ * String
+ */
+class Horde_Form_Type_String extends Horde_Form_Type {
+
+    /**
+     * Validation regex
+     *
+     * @type string
+     * @var string
+     */
+    protected $_regex;
+
+    /**
+     * Maximum length
+     *
+     * @type int
+     * @var integer
+     */
+    protected $_maxlength;
+
+    public function isValid($var, $vars, $value, &$message)
+    {
+        $valid = true;
+
+        if (!empty($this->_maxlength) && String::length($value) > $this->_maxlength) {
+            $valid = false;
+            $message = sprintf(_("Value is over the maximum length of %s."), $this->_maxlength);
+        } elseif ($var->required && empty($this->_regex)) {
+            if (!($valid = strlen(trim($value)) > 0)) {
+                $message = _("This field is required.");
+            }
+        } elseif (strlen($this->_regex)) {
+            if (!($valid = preg_match($this->_regex, $value))) {
+                $message = _("You must enter a valid value.");
+            }
+        }
+
+        return $valid;
+    }
+
+}
diff --git a/framework/Form/lib/Horde/Form/Type/Time.php b/framework/Form/lib/Horde/Form/Type/Time.php
new file mode 100644 (file)
index 0000000..3b26133
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+/**
+ * Time
+ */
+class Horde_Form_Type_Time extends Horde_Form_Type {
+
+    public function isValid($var, $vars, $value, &$message)
+    {
+        if ($var->required && empty($value) && ((string)(double)$value !== $value)) {
+            $message = _("This field is required.");
+            return false;
+        }
+
+        if (empty($value) || preg_match('/^[0-2]?[0-9]:[0-5][0-9]$/', $value)) {
+            return true;
+        }
+
+        $message = _("This field may only contain numbers and the colon.");
+        return false;
+    }
+
+}
diff --git a/framework/Form/lib/Horde/Form/VarRenderer.php b/framework/Form/lib/Horde/Form/VarRenderer.php
new file mode 100644 (file)
index 0000000..b9d3761
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+/**
+ * The Horde_Form_VarRenderer:: class provides base functionality for
+ * other Horde_Form elements.
+ *
+ * $Horde: incubator/Horde_Form/Horde/Form/VarRenderer.php,v 1.8 2008/01/02 11:12:48 jan Exp $
+ *
+ * Copyright 2003-2008 The Horde Project (http://www.horde.org/)
+ * Copyright 2005-2007 Matt Warden <mwarden@gmail.com>
+ *
+ * See the enclosed file LICENSE for license information (LGPL).
+ *
+ * @author  Jason M. Felice <jason.m.felice@gmail.com>
+ * @package Horde_Form
+ */
+class Horde_Form_VarRenderer {
+
+    /**
+     * Renders a variable.
+     *
+     * @param Horde_Form $form            Reference to a Horde_Form instance,
+     *                                    or null if none is available.
+     * @param Horde_Form_Variable $var    Reference to a Horde_Form_Variable.
+     * @param Variables $vars             A Variables instance.
+     * @param boolean $isInput            Whether this is an input field.
+     */
+    public function render($form, $var, $vars, $isInput = false)
+    {
+        if ($isInput) {
+            $state = 'Input';
+        } else {
+            $state = 'Display';
+        }
+        $method = "_renderVar${state}_" . str_replace('Horde_Form_Type_', '', get_class($var->type));
+        if (!method_exists($this, $method)) {
+            $method = "_renderVar${state}Default";
+        }
+        return $this->$method($form, $var, $vars);
+    }
+
+    /**
+     * Finishes rendering after all fields are output.
+     */
+    public function renderEnd()
+    {
+        return '';
+    }
+
+}
diff --git a/framework/Form/lib/Horde/Form/VarRenderer/Xhtml.php b/framework/Form/lib/Horde/Form/VarRenderer/Xhtml.php
new file mode 100644 (file)
index 0000000..5dfbc51
--- /dev/null
@@ -0,0 +1,1541 @@
+<?php
+/**
+ * The Horde_Form_VarRenderer_Xhtml:: class renders variables as Xhtml.
+ *
+ * $Horde: incubator/Horde_Form/Horde/Form/VarRenderer/Xhtml.php,v 1.14 2008/01/02 11:12:48 jan Exp $
+ *
+ * Copyright 2003-2008 The Horde Project (http://www.horde.org/)
+ * Copyright 2005 Matt Warden <mwarden@gmail.com>
+ *
+ * See the enclosed file LICENSE for license information (LGPL).
+ *
+ * @author  Jason M. Felice <jason.m.felice@gmail.com>
+ * @package Horde_Form
+ */
+class Horde_Form_VarRenderer_Xhtml extends Horde_Form_VarRenderer {
+
+    protected $_onLoadJS = array();
+
+    /**
+     * Handles the end of rendering of variables; writes onload JavaScript.
+     *
+     * @access public
+     * @author ?
+     * @return string the javascript to execute, and its container script tags,
+     *            or and empty string if there is nothing to execute onload
+     */
+    public function renderEnd()
+    {
+        if (count($this->_onLoadJS)) {
+            return "<script type=\"text/javascript\">" .
+                "<!--\n" .  implode("\n", $this->_onLoadJS) . "\n// -->\n" .
+                "</script>";
+        } else {
+            return '';
+        }
+    }
+
+    function _renderVarInputDefault($form, $var, $vars)
+    {
+        throw new Horde_Form_Exception('Unknown variable type:' . get_class($var->type));
+    }
+
+    function _renderVarInput_number($form, $var, $vars)
+    {
+        $value = $var->getValue($vars);
+        if ($var->type->fraction) {
+            $value = sprintf('%01.' . $var->type->fraction . 'f', $value);
+        }
+        $linfo = NLS::getLocaleInfo();
+        /* Only if there is a mon_decimal_point do the
+         * substitution. */
+        if (!empty($linfo['mon_decimal_point'])) {
+            $value = str_replace('.', $linfo['mon_decimal_point'], $value);
+        }
+        return sprintf('    <input type="text" class="form-input-number" name="%1$s" id="%1$s" value="%2$s"%3$s />',
+                       $var->getVarName(),
+                       $value,
+                       $this->_getActionScripts($form, $var)
+               );
+    }
+
+    function _renderVarInput_int($form, $var, $vars)
+    {
+        return sprintf('    <input type="text" class="form-input-int" name="%1$s" id="%1$s" value="%2$s"%3$s />',
+                       $var->getVarName(),
+                       $value = $var->getValue($vars),
+                       $this->_getActionScripts($form, $var)
+               );
+    }
+
+    function _renderVarInput_octal($form, $var, $vars)
+    {
+        return sprintf('<input type="text" class="form-input-octal" name="%1$s" id="%1$s" value="%2$s"%3$s />',
+                       $var->getVarName(),
+                       sprintf('0%o', octdec($var->getValue($vars))),
+                       $this->_getActionScripts($form, $var)
+               );
+    }
+
+    function _renderVarInput_intlist($form, $var, $vars)
+    {
+        return sprintf('<input type="text" class="form-input-intlist" name="%1$s" id="%1$s" value="%2$s"%3$s />',
+                       $var->getVarName(),
+                       $value = $var->getValue($vars),
+                       $this->_getActionScripts($form, $var)
+               );
+    }
+
+    function _renderVarInput_text($form, $var, $vars)
+    {
+        return sprintf(
+            '<input type="text" class="form-input-text%1$s" name="%2$s" '
+            . 'id="%2$s" value="%3$s"%4$s%5$s%6$s />',
+            ($var->isDisabled() ? ' form-input-disabled" ' : ''),
+            $var->getVarName(),
+            htmlspecialchars($var->getValue($vars), ENT_QUOTES, NLS::getCharset()),
+            ($var->isDisabled() ? ' disabled="disabled" ' : ''),
+            ($var->type->maxlength ? ' maxlength="' . $var->type->maxlength . '"' : ''),
+            $this->_getActionScripts($form, $var)
+        );
+    }
+
+    function _renderVarInput_stringlist($form, $var, $vars)
+    {
+        return sprintf(
+            '<input type="text" class="form-input-stringlist" name="%s" value="%s"%s />',
+            $var->getVarName(),
+            $value = $var->getValue($vars),
+            $this->_getActionScripts($form, $var)
+        );
+    }
+
+    function _renderVarInput_phone($form, $var, $vars)
+    {
+        return sprintf(
+            '<input type="text" class="form-input-phone" name="%1$s" id="%1$s" value="%2$s" %3$s%4$s />',
+            $var->getVarName(),
+            htmlspecialchars($var->getValue($vars), ENT_QUOTES, NLS::getCharset()),
+            ($var->isDisabled() ? ' disabled="disabled" ' : ''),
+            $this->_getActionScripts($form, $var)
+        );
+    }
+
+    function _renderVarInput_cellphone($form, $var, $vars)
+    {
+        return $this->_renderVarInput_phone($form, $var, $vars);
+    }
+
+    function _renderVarInput_ipaddress($form, $var, $vars)
+    {
+        return sprintf('    <input type="text" class="form-input-ipaddress" name="%1$s" id="%1$s" value="%2$s" %3$s%4$s />',
+                       $var->getVarName(),
+                       htmlspecialchars($var->getValue($vars), ENT_QUOTES, NLS::getCharset()),
+                       $var->isDisabled() ? ' disabled="disabled" ' : '',
+                       $this->_getActionScripts($form, $var)
+               );
+    }
+
+    function _renderVarInput_file($form, $var, $vars)
+    {
+        $file = $var->getValue($vars);
+        return sprintf('    <input type="file" class="form-input-file" name="%1$s" id="%1$s"%2$s />',
+                       $var->getVarName(),
+                       $this->_getActionScripts($form, $var));
+    }
+
+    /**
+     * @todo Show image dimensions in the width/height boxes.
+     */
+    function _renderVarInput_image($form, $var, $vars)
+    {
+        $varname = $var->getVarName();
+        $image = $var->getValue($vars);
+
+        /* Check if existing image data is being loaded. */
+        $var->type->loadImageData($image);
+
+        Horde::addScriptFile('image.js', 'horde', true);
+        $graphics_dir = $GLOBALS['registry']->getImageDir('horde');
+        $img_dir = $graphics_dir . '/image';
+
+        $html = '';
+
+        /* Check if there is existing img information stored. */
+        if (isset($image['img'])) {
+            /* Hidden tag to store the preview image filename. */
+            $html = sprintf('    <input type="hidden" name="%1$s" id="%1$s" value="%2$s" />',
+                   $varname . '[img]',
+                   htmlspecialchars($image['img'], ENT_QUOTES, NLS::getCharset()));
+
+            /* Unserialize the img information to get the full array. */
+            $image['img'] = @unserialize($image['img']);
+        }
+
+        /* Output the input tag. */
+        if (empty($image['img'])) {
+            $js = "
+var p = document.getElementById('" . $varname . "[preview]');
+o = '\\\\'; a = '/';
+tmp = '' + document.getElementById('" . $varname . "[new]').value;
+if (tmp) {
+    while (tmp.indexOf(o) > -1) {
+        pos = tmp.indexOf(o);
+        tmp = '' + (tmp.substring(0, pos) + a + tmp.substring((pos + o.length), tmp.length));
+    }
+    p.src = 'file:///' + tmp;
+    p.alt = '" . addslashes(_("If you see this message but no image, the image you want to upload can't be displayed by your browser.")) . "';
+}";
+            $browser = Browser::singleton();
+            if ($browser->isBrowser('msie')) {
+                $html .= sprintf('    <input type="file" class="form-input-file" name="%1$s" id="%1$s" onchange="%2$s" />',
+                             $varname . '[new]',
+                             $js);
+            } else {
+                $html .= sprintf('    <input type="file" class="form-input-file" name="%1$s" id="%1$s"
+                        onclick="window.setTimeout(\'document.getElementById(\\\'%1$s\\\').blur();\', 5);"
+                        onblur="%2$s" />',
+                             $varname . '[new]',
+                             $js);
+            }
+        } else {
+            $html .= sprintf('    <input type="file" class="form-input-file" name="%1$s" id="%1$s" />',
+                             $varname . '[new]');
+        }
+
+        /* Output the button to upload/reset the image. */
+        if ($var->type->show_upload) {
+            $html .= '&nbsp;';
+            $html .= sprintf('    <input class="form-button-upload" name="%1$s" id="%1$s" type="submit" value="%2$s" /> ',
+                         '_do_' . $varname,
+                         _("Upload"));
+        }
+
+        if (empty($image['img'])) {
+            /* No image information stored yet, show a blank
+             * preview. */
+            $html .= Horde::img('tree/blank.png', _("Preview"),
+                    'class="form-image-preview-blank" id="' . $varname . '[preview]"',
+                    $graphics_dir);
+        } else {
+            /* Image information stored, show preview, add buttons for
+             * image manipulation. */
+            $html .= '<br />';
+            $img = Horde::url($GLOBALS['registry']->get('webroot', 'horde') . '/services/images/view.php');
+            if (isset($image['img']['vfs_id'])) {
+                /* Calling an image from VFS. */
+                $img = Util::addParameter($img, array('f' => $image['img']['vfs_id'],
+                                                      's' => 'vfs',
+                                                      'p' => $image['img']['vfs_path']));
+            } else {
+                /* Calling an image from a tmp directory (uploads). */
+                $img = Util::addParameter($img, 'f', $image['img']['file']);
+            }
+
+            // TODO: possible to change to unobtrusive JS?
+            /* Rotate 270. */
+            $html .= Horde::link('#', '', '', '', 'showImage(\'' . Util::addParameter($img, array('a' => 'rotate', 'v' => '270')) . '\', \'_p_' . $varname . '\', true);') . Horde::img('rotate-270.png', _("Rotate Left"), '', $img_dir) . '</a>';
+
+            /* Rotate 180. */
+            $html .= Horde::link('#', '', '', '', 'showImage(\'' . Util::addParameter($img, array('a' => 'rotate', 'v' => '180')) . '\', \'_p_' . $varname . '\', true);') . Horde::img('rotate-180.png', _("Rotate 180"), '', $img_dir) . '</a>';
+
+            /* Rotate 90. */
+            $html .= Horde::link('#', '', '', '', 'showImage(\'' . Util::addParameter($img, array('a' => 'rotate', 'v' => '90')) . '\', \'_p_' . $varname . '\', true);') . Horde::img('rotate-90.png', _("Rotate Right"), '', $img_dir) . '</a>';
+
+            /* Flip image. */
+            $html .= Horde::link('#', '', '', '', 'showImage(\'' . Util::addParameter($img, 'a', 'flip') . '\', \'_p_' . $varname . '\', true);') . Horde::img('flip.png', _("Flip"), '', $img_dir) . '</a>';
+
+            /* Mirror image. */
+            $html .= Horde::link('#', '', '', '', 'showImage(\'' . Util::addParameter($img, 'a', 'mirror') . '\', \'_p_' . $varname . '\', true);') . Horde::img('mirror.png', _("Mirror"), '', $img_dir) . '</a>';
+
+            /* Apply grayscale. */
+            $html .= Horde::link('#', '', '', '', 'showImage(\'' . Util::addParameter($img, 'a', 'grayscale') . '\', \'_p_' . $varname . '\', true);') . Horde::img('grayscale.png', _("Grayscale"), '', $img_dir) . '</a>';
+
+            /* Resize width. */
+            $html .= sprintf('%s    <input type="text" class="form-input-resize" onchange="src=getResizeSrc(\'%s\', \'%s\');showImage(src, \'_p_%s\', true);" %s />',
+                   _("w:"),
+                   Util::addParameter($img, 'a', 'resize'),
+                   $varname,
+                   $varname,
+                   '_w_'. $varname);
+
+            /* Resize height. */
+            $html .= sprintf('%s    <input type="text" class="form-input-resize" onchange="src=getResizeSrc(\'%s\', \'%s\');showImage(src, \'_p_%s\', true);" %s />',
+                   _("h:"),
+                   Util::addParameter($img, 'a', 'resize'),
+                   $varname,
+                   $varname,
+                   '_h_'. $varname);
+
+            /* Apply fixed ratio resize. */
+            $html .= Horde::link('#', '', '', '', 'src=getResizeSrc(\'' . Util::addParameter($img, 'a', 'resize') . '\', \'' . $varname . '\', \'1\');showImage(src, \'_p_' . $varname . '\', true);') . Horde::img('ratio.png', _("Fix ratio"), '', $img_dir) . '</a>';
+
+            /* Keep also original if it has been requested. */
+            if ($var->type->show_keeporig) {
+                $html .= sprintf('    <input type="checkbox" class="form-input-checkbox" name="%s"%s />%s' . "\n",
+                       $varname . '[keep_orig]',
+                       !empty($image['keep_orig']) ? ' checked="checked"' : '',
+                       _("Keep original?"));
+            }
+
+            /* The preview image element. */
+            $html .= '<br /><img src="' . $img . '" id="_p_' . $varname .'" />'."\n";
+        }
+
+        return $html;
+    }
+
+    function _renderVarInput_longtext($form, $var, $vars)
+    {
+        global $browser;
+
+        $html = sprintf('<textarea class="form-input-longtext" id="%1$s" name="%1$s" '
+                            .'cols="%2$s" rows="%3$s"%4$s%5$s>%6$s</textarea>',
+                        $var->getVarName(),
+                        $var->type->cols,
+                        $var->type->rows,
+                        $this->_getActionScripts($form, $var),
+                        $var->isDisabled() ? ' disabled="disabled"' : '',
+                        htmlspecialchars($var->getValue($vars)));
+
+        if ($var->type->hasHelper('rte') && $browser->hasFeature('rte')) {
+            $editor = Horde_Editor::factory('xinha', array('id' => $var->getVarName()));
+        }
+
+        if ($var->type->hasHelper() && $browser->hasFeature('javascript')) {
+            $html .= '<div class="form-html-helper">';
+            Horde::addScriptFile('open_html_helper.js', 'horde');
+            $imgId = $var->getVarName() . 'ehelper';
+            if ($var->type->hasHelper('emoticons')) {
+                $html .= Horde::link('#', _("Emoticons"), '', '', 'openHtmlHelper(\'emoticons\', \'' . $var->getVarName() . '\'); return false;')
+                    . Horde::img('smile.png', _("Emoticons"), 'id="' . $imgId . '" align="middle"', $GLOBALS['registry']->getImageDir('horde') . '/emoticons')
+                    . '</a>'."\n";
+            }
+            $html .= '</div><div id="htmlhelper_' . $var->getVarName()
+                    . '" class="form-control"></div>'."\n";
+        }
+
+        return $html;
+    }
+
+    function _renderVarInput_countedtext($form, $var, $vars)
+    {
+        return sprintf('<textarea class="form-input-countedtext" id="%1$s" name="%1$s" '
+                        .'cols="%2$s" rows="%3$s"%4$s%5$s>%6$s</textarea>',
+                       $var->getVarName(),
+                       $var->type->cols,
+                       $var->type->rows,
+                       $this->_getActionScripts($form, $var),
+                       $var->isDisabled() ? ' disabled="disabled"' : '',
+                       $var->getValue($vars));
+    }
+
+    function _renderVarInput_address($form, $var, $vars)
+    {
+        return sprintf('<textarea class="form-input-address" id="%1$s" name="%1$s" '
+                        .'cols="%2$s" rows="%3$s"%4$s%5$s>%6$s</textarea>',
+                       $var->getVarName(),
+                       $var->type->cols,
+                       $var->type->rows,
+                       $this->_getActionScripts($form, $var),
+                       $var->isDisabled() ? ' disabled="disabled"' : '',
+                       $var->getValue($vars));
+    }
+
+    function _renderVarInput_date($form, $var, $vars)
+    {
+        return sprintf('    <input type="text" class="form-input-date" name="%1$s" id="%1$s" '
+                            .'value="%2$s"%3$s />',
+                        $var->getVarName(),
+                        $value = $var->getValue($vars),
+                        $this->_getActionScripts($form, $var));
+    }
+
+    function _renderVarInput_time($form, $var, $vars)
+    {
+        return sprintf('    <input type="text" class="form-input-time" name="%1$s" id="%1$s" '
+                            .'value="%2$s"%3$s />',
+                       $var->getVarName(),
+                       $value = $var->getValue($vars),
+                       $this->_getActionScripts($form, $var));
+    }
+
+    function _renderVarInput_hourminutesecond($form, $var, $vars)
+    {
+        $varname = $var->getVarName();
+        $time = $var->type->getTimeParts($var->getValue($vars));
+
+        /* Output hours. */
+        $hours = array('' => _("hh"));
+        for ($i = 0; $i <= 23; $i++) {
+            $hours[sprintf('%02d', $i)] = $i;
+        }
+        $html = sprintf('<select name="%1$s[hour]" id="%1$s[hour]"%2$s>%3$s    </select>',
+                        $varname,
+                        $this->_selectOptions($hours, $time['hour']),
+                        $this->_getActionScripts($form, $var));
+
+        /* Output minutes. */
+        $minutes = array('' => _("mm"));
+        for ($i = 0; $i <= 59; $i++) {
+            $minutes[sprintf('%02d', $i)] = $i;
+        }
+        $html .= sprintf('<select name="%1$s[minute]" id="%1$s[minute]"%2$s>%3$s    </select>',
+                         $varname,
+                         $this->_selectOptions($minutes, $time['minute']),
+                         $this->_getActionScripts($form, $var));
+
+        /* Return if seconds are not required. */
+        if ($var->type->show_seconds) {
+            /* Output seconds. */
+            $seconds = array('' => _("ss"));
+            for ($i = 0; $i <= 59; $i++) {
+                $seconds[sprintf('%02d', $i)] = $i;
+            }
+            $html .= sprintf('<select name="%1$s[second]" id="%1$s[second]"%2$s>%3$s    </select>',
+                            $varname,
+                            $this->_getActionScripts($form, $var),
+                            $this->_selectOptions($seconds, $time['second']));
+        }
+
+        return $html;
+    }
+
+    function _renderVarInput_monthyear($form, $var, $vars)
+    {
+        $dates = array();
+        $dates['month'] = array('' => _("MM"),
+                                1 => _("January"),
+                                2 => _("February"),
+                                3 => _("March"),
+                                4 => _("April"),
+                                5 => _("May"),
+                                6 => _("June"),
+                                7 => _("July"),
+                                8 => _("August"),
+                                9 => _("September"),
+                                10 => _("October"),
+                                11 => _("November"),
+                                12 => _("December"));
+        $dates['year'] = array('' => _("YYYY"));
+        if ($var->type->start_year > $var->type->end_year) {
+            for ($i = $var->type->start_year; $i >= $var->type->end_year; $i--) {
+                $dates['year'][$i] = $i;
+            }
+        } else {
+            for ($i = $var->type->start_year; $i <= $var->type->end_year; $i++) {
+                $dates['year'][$i] = $i;
+            }
+        }
+        $html = sprintf('<select name="%1$s" id="%1$s"%2$s>%3$s    </select>',
+               $var->type->getMonthVar($var),
+               $this->_getActionScripts($form, $var),
+               $this->_selectOptions($dates['month'], $vars->get($var->type->getMonthVar($var))));
+
+        $html .= sprintf('<select name="%1$s" id="%1$s"%2$s>%3$s    </select>',
+               $var->type->getYearVar($var),
+               $this->_getActionScripts($form, $var),
+               $this->_selectOptions($dates['year'], $vars->get($var->type->getYearVar($var))));
+
+        return $html;
+    }
+
+    function _renderVarInput_monthdayyear($form, $var, $vars)
+    {
+        $dates = array();
+        $dates['month'] = array(''   => _("MM"),
+                                '1'  => _("January"),
+                                '2'  => _("February"),
+                                '3'  => _("March"),
+                                '4'  => _("April"),
+                                '5'  => _("May"),
+                                '6'  => _("June"),
+                                '7'  => _("July"),
+                                '8'  => _("August"),
+                                '9'  => _("September"),
+                                '10' => _("October"),
+                                '11' => _("November"),
+                                '12' => _("December"));
+        $dates['day'] = array('' => _("DD"));
+        for ($i = 1; $i <= 31; $i++) {
+            $dates['day'][$i] = $i;
+        }
+        $dates['year'] = array('' => _("YYYY"));
+        if ($var->type->start_year > $var->type->end_year) {
+            for ($i = $var->type->start_year; $i >= $var->type->end_year; $i--) {
+                $dates['year'][$i] = $i;
+            }
+        } else {
+            for ($i = $var->type->start_year; $i <= $var->type->end_year; $i++) {
+                $dates['year'][$i] = $i;
+            }
+        }
+        $date = $var->type->getDateParts($var->getValue($vars));
+
+        // TODO: use NLS to get the order right for the Rest Of The
+        // World.
+        $html = '';
+        $date_parts = array('month', 'day', 'year');
+        foreach ($date_parts as $part) {
+            $varname = $var->getVarName() . '[' . $part . ']';
+            $html .= sprintf('<select name="%1$s" id="%1$s"%2$s>%3$s    </select>',
+                             $varname,
+                             $this->_getActionScripts($form, $var),
+                             $this->_selectOptions($dates[$part], $date[$part]));
+        }
+
+        if ($var->type->picker && $GLOBALS['browser']->hasFeature('javascript')) {
+            Horde::addScriptFile('open_calendar.js', 'horde');
+            $imgId = $var->getVarName() .'goto';
+            $html .= '<div id="goto"></div>';
+            $html .= Horde::link('#', _("Select a date"), '', '', 'openCalendar(\'' . $imgId . '\', \'' . $var->getVarName() . '\'); return false;')
+                . Horde::img('calendar.png', _("Calendar"), 'id="' . $imgId . '" ', $GLOBALS['registry']->getImageDir('horde'))
+                . '</a>';
+        }
+
+        return $html;
+    }
+
+    function _renderVarInput_datetime($form, $var, $vars)
+    {
+        return parent::_renderVarInput_monthdayyear($form, $var, $vars) .
+            parent::_renderVarInput_hourminutesecond($form, $var, $vars);
+    }
+
+    function _renderVarInput_colorpicker($form, $var, $vars)
+    {
+        $html = '<div class="form-colorpicker">'
+            . '<input type="text" maxlength="7" name="'
+            . $var->getVarName() . '" id="' . $var->getVarName()
+            . '" value="' . $var->getValue($vars) . '" />';
+
+        if ($GLOBALS['browser']->hasFeature('javascript')) {
+            Horde::addScriptFile('open_colorpicker.js', 'horde', true);
+            $html .= Horde::img('blank.gif', '', array('class' => 'form-colorpicker-preview',
+                                                       'id' => 'colordemo_' . $var->getVarName(),
+                                                       'style' => 'background:' . $var->getValue($vars)), $GLOBALS['registry']->getImageDir('horde'))
+                . Horde::link('#', _("Color Picker"), '', '', 'openColorPicker(\''. $var->getVarName() .'\'); return false;')
+                . Horde::img('colorpicker.png', _("Color Picker"), '', $GLOBALS['registry']->getImageDir('horde')) . '</a>'
+                . '<div id="colorpicker_' . $var->getVarName() . '" class="form-colorpicker-palette"></div>';
+        }
+
+        return $html . '</div>';
+    }
+
+    function _renderVarInput_sorter($form, $var, $vars)
+    {
+        global $registry;
+
+        $varname = $var->getVarName();
+        $instance = $var->type->instance;
+
+        Horde::addScriptFile('sorter.js', 'horde', true);
+
+        return '    <input type="hidden" name="'. $varname
+            . '[array]" value="" id="'. $varname .'-array-" />'."\n"
+            . '    <select class="leftFloat" multiple="multiple" size="'
+            . $var->type->size . '" name="' . $varname
+            . '[list]" onchange="' . $instance . '.deselectHeader();" '
+            . ' id="'. $varname . '-list-">'
+            . $var->type->getOptions($var->getValue($vars)) . '    </select><div class="leftFloat">'
+            . Horde::link('#', _("Move up"), '', '', $instance . '.moveColumnUp(); return false;')
+                . Horde::img('nav/up.png', _("Move up"), '', $registry->getImageDir('horde'))
+                . '</a><br />'
+            . Horde::link('#', _("Move up"), '', '', $instance . '.moveColumnDown(); return false;')
+                . Horde::img('nav/down.png', _("Move down"), '', $registry->getImageDir('horde'))
+                . '</a></div>'
+            . '<script type="text/javascript">' . "\n"
+            . sprintf('%1$s = new Horde_Form_Sorter(\'%1$s\', \'%2$s\', \'%3$s\');' . "\n",
+                    $instance, $varname, $var->type->header)
+            . sprintf("%s.setHidden();\n</script>\n", $instance);
+    }
+
+    function _renderVarInput_assign($form, $var, $vars)
+    {
+        global $registry;
+
+        Horde::addScriptFile('form_assign.js', 'horde', true);
+
+        $name = $var->getVarName();
+        $fname = $form->getName() . '.' . $name;
+        $width = $var->type->width;
+        $lhdr = (bool)$var->type->getHeader(0);
+        $rhdr = (bool)$var->type->getHeader(1);
+        $this->_onLoadJS[] = 'Horde_Form_Assign.setField(\'' . $fname . '\');';
+
+        $html = '<div class="form-input-assign">'
+             . '    <input type="hidden" name="' . $name . '__values" id="' . $name . '__values" />'
+             . sprintf('    <select name="%1$s__left" id="%1$s__left" multiple="multiple" '
+                         .'size="%2$d" style="width:%3$s"%4$s>',
+                     $name, $var->type->size, $width,
+                     $lhdr ? ' onchange="Horde_Form_Assign.deselectHeaders(\'' . $fname . '\', 0);"' : '')
+             . $var->type->getOptions(0, $fname)
+             . '    </select>'
+             . '<div><a href="" onclick="Horde_Form_Assign.move(\''. $fname .'\', 0); return false;">'
+             . Horde::img('rhand.png', _("Add column"), null, $registry->getImageDir('horde'))
+             . '</a><br /><a href="" onclick="Horde_Form_Assign.move(\''
+             . $fname . '\', 1); return false;">'
+             . Horde::img('lhand.png', _("Remove column"), null, $registry->getImageDir('horde'))
+             . '</a></div>'
+             . sprintf('    <select name="%s__right" multiple="multiple" size="%d" style="width:%s"%s>',
+                     $name, $size, $width,
+                     $rhdr ? ' onchange="Horde_Form_Assign.deselectHeaders(\'' . $fname . '\', 1);"' : '')
+             . $var->type->getOptions(1, $fname)
+             . '    </select></div>';
+
+        return $html;
+    }
+
+    function _renderVarInput_invalid($form, $var, $vars)
+    {
+        return $this->_renderVarDisplay_invalid($form, $var, $vars);
+    }
+
+    function _renderVarInput_enum($form, $var, $vars)
+    {
+        $values = $var->getValues();
+        $prompt = $var->type->prompt;
+        $htmlchars = $var->getOption('htmlchars');
+        if ($prompt) {
+            $prompt = '<option value="">' . ($htmlchars ? htmlspecialchars($prompt, ENT_QUOTES, NLS::getCharset()) : $prompt) . '</option>';
+        }
+        return sprintf('    <select name="%1$s" id="%1$s"%2$s>%3$s%4$s    </select>',
+               $var->getVarName(),
+               $this->_getActionScripts($form, $var),
+               $prompt,
+               $this->_selectOptions($values, $var->getValue($vars), $htmlchars));
+    }
+
+    function _renderVarInput_mlenum($form, $var, $vars)
+    {
+        $varname = $var->getVarName();
+        $values = $var->getValues();
+        $prompts = $var->type->prompts;
+        $selected = $var->getValue($vars);
+
+        /* If passing a non-array value need to get the keys. */
+        if (!is_array($selected)) {
+            foreach ($values as $key_1 => $values_2) {
+                if (isset($values_2[$selected])) {
+                    $selected = array('1' => $key_1, '2' => $selected);
+                    break;
+                }
+            }
+        }
+
+        /* Hidden tag to store the current first level. */
+        $html = sprintf('    <input type="hidden" name="%1$s[old]" id="%1$s[old]" value="%2$s" />',
+                        $varname,
+                        htmlspecialchars($selected['1'], ENT_QUOTES, NLS::getCharset()));
+
+        /* First level. */
+        $values_1 = Horde_Array::valuesToKeys(array_keys($values));
+        $html .= sprintf('    <select id="%1$s[1]" name="%1$s[1]" onchange="%2$s"%3$s>',
+                         $varname,
+                         'if (this.value) { document.' . $form->getName() . '.formname.value=\'\';' . 'document.' . $form->getName() . '.submit() }',
+                         ($var->hasAction() ? ' ' . $this->_genActionScript($form, $var->_action, $varname) : ''));
+        if (!empty($prompts)) {
+            $html .= '<option value="">' . htmlspecialchars($prompts[0], ENT_QUOTES, NLS::getCharset()) . '</option>';
+        }
+        $html .= $this->_selectOptions($values_1, $selected['1']);
+        $html .= '    </select>';
+
+        /* Second level. */
+        $html .= sprintf('    <select id="%1$s[2]" name="%1$s[2]"%2$s>',
+                         $varname,
+                         ($var->hasAction() ? ' ' . $this->_genActionScript($form, $var->_action, $varname) : ''));
+        if (!empty($prompts)) {
+            $html .= '<option value="">' . htmlspecialchars($prompts[1], ENT_QUOTES, NLS::getCharset()) . '</option>';
+        }
+        $values_2 = array();
+        if (!empty($selected['1'])) {
+            $values_2 = $values[$selected['1']];
+        }
+        return $html . $this->_selectOptions($values_2, $selected['2']) . '    </select>';
+    }
+
+    function _renderVarInput_multienum($form, $var, $vars)
+    {
+        $values = $var->getValues();
+        $selected = $vars->getExists($var->getVarName(), $wasset);
+        if (!$wasset) {
+            $selected = $var->getDefault();
+        }
+        $html = sprintf('    <select multiple="multiple" size="%1$s" name="%2$s[]" id="%2$s[]" %3$s>%4$s    </select>',
+                        $var->type->size,
+                        $var->getVarName(),
+                        $this->_getActionScripts($form, $var),
+                        $this->_multiSelectOptions($values, $selected));
+        return $html . '<p class="form-hint">'
+            . _("To select multiple items, hold down the Control (PC) or Command (Mac) key while clicking.")
+            . "</p>\n";
+    }
+
+    function _renderVarInput_keyval_multienum($form, $var, $vars)
+    {
+        return $this->_renderVarInput_multienum($form, $var, $vars);
+    }
+
+    function _renderVarInput_radio($form, $var, $vars)
+    {
+        return $this->_radioButtons($var->getVarName(),
+                                    $var->getValues(),
+                                    $var->getValue($vars),
+                                    $this->_getActionScripts($form, $var));
+    }
+
+    function _renderVarInput_set($form, $var, $vars)
+    {
+        $html = $this->_checkBoxes($var->getVarName(),
+                                   $var->getValues(),
+                                   $var->getValue($vars),
+                                   $this->_getActionScripts($form, $var));
+
+        if ($var->type->checkAll) {
+            $form_name = $form->getName();
+            $var_name = $var->getVarName() . '[]';
+            $function_name = 'select'  . $form_name . $var->getVarName();
+            $enable = _("Select all");
+            $disable = _("Select none");
+            $invert = _("Invert selection");
+            $html .= <<<EOT
+<script type="text/javascript">
+function $function_name()
+{
+    for (var i = 0; i < document.$form_name.elements.length; i++) {
+        f = document.$form_name.elements[i];
+        if (f.name != '$var_name') {
+            continue;
+        }
+        if (arguments.length) {
+            f.checked = arguments[0];
+        } else {
+            f.checked = !f.checked;
+        }
+    }
+}
+</script>
+<a href="#" onclick="$function_name(true); return false;">$enable</a>,
+<a href="#" onclick="$function_name(false); return false;">$disable</a>,
+<a href="#" onclick="$function_name(); return false;">$invert</a>
+EOT;
+        }
+
+        return $html;
+    }
+
+    function _renderVarInput_link($form, $var, $vars)
+    {
+        return $this->_renderVarDisplay_link($form, $var, $vars);
+    }
+
+    function _renderVarInput_html($form, $var, $vars)
+    {
+        return $this->_renderVarDisplay_html($form, $var, $vars);
+    }
+
+    function _renderVarInput_email($form, $var, $vars)
+    {
+        return sprintf('    <input type="text" id="%1$s" name="%1$s" value="%2$s"%3$s />',
+               $var->getVarName(),
+               $value = $var->getValue($vars),
+               $this->_getActionScripts($form, $var));
+    }
+
+    function _renderVarInput_matrix($form, $var, $vars)
+    {
+        $varname   = $var->getVarName();
+        $var_array = $var->getValue($vars);
+        $cols      = $var->type->cols;
+        $rows      = $var->type->rows;
+        $matrix    = $var->type->matrix;
+        $new_input = $var->type->new_input;
+
+        $html = '<table cellspacing="0"><tr>';
+
+        $html .= '<td align="right" width="20%"></td>';
+        foreach ($cols as $col_title) {
+            $html .= sprintf('<td align="center" width="1%%">%s</td>', $col_title);
+        }
+        $html .= '<td align="right" width="60%"></td></tr>';
+
+        /* Offer a new row of data to be added to the matrix? */
+        if ($new_input) {
+            $html .= '<tr><td>'."\n";
+            if (is_array($new_input)) {
+                $html .= sprintf('    <select%s name="%s[n][r]"><option value="">%s</option>%s    </select><br />'."\n",
+                       ' id="'. $varname .'-n--r-"',
+                       $varname,
+                       _("-- select --"),
+                       $this->_selectOptions($new_input, $var_array['n']['r']));
+            } elseif ($new_input == true) {
+                $html .= sprintf('    <input%s type="text" name="%s[n][r]" value="%s" />',
+                       ' id="'. $varname .'-n--r-',
+                       $varname,
+                       $var_array['n']['r']);
+            }
+            $html .= ' </td>';
+            foreach ($cols as $col_id => $col_title) {
+                $html .= sprintf('<td align="center"><input type="checkbox" class="checkbox" name="%s[n][v][%s]" /></td>', $varname, $col_id);
+            }
+            $html .= '<td>&nbsp;</td></tr>'."\n";
+        }
+
+        /* Loop through the rows and create checkboxes for each column. */
+        foreach ($rows as $row_id => $row_title) {
+            $html .= sprintf('<tr><td>%s</td>', $row_title);
+            foreach ($cols as $col_id => $col_title) {
+                $html .= sprintf('<td align="center"><input type="checkbox" class="checkbox" name="%s[r][%s][%s]"%s /></td>', $varname, $row_id, $col_id, (!empty($matrix[$row_id][$col_id]) ? ' checked="checked"' : ''));
+            }
+            $html .= '<td>&nbsp;</td></tr>'."\n";
+        }
+
+        $html .= '</table>'."\n";
+        return $html;
+    }
+
+    function _renderVarInput_password($form, $var, $vars)
+    {
+        return sprintf('<input type="password" id="%1$s" name="%1$s" value="%2$s"%3$s />',
+               $var->getVarName(),
+               $value = $var->getValue($vars),
+               $this->_getActionScripts($form, $var));
+    }
+
+    function _renderVarInput_emailconfirm($form, $var, $vars)
+    {
+        $email = $var->getValue($vars);
+        return '<ul><li>' . sprintf('<input type="text" class="form-input-emailconfirm"' .
+                                    ' id="%1$s" name="%1$s[original]" value="%2$s"%3$s />',
+                                    $var->getVarName(),
+                                    $value = $email['original'],
+                                    $this->_getActionScripts($form, $var)) . '</li><li>' .
+            sprintf('<input type="text" class="form-input-emailconfirm"' .
+                    ' id="%1$s-confirm-" name="%1$s[confirm]" value="%2$s"%3$s />',
+                    $var->getVarName(),
+                    $value = $email['confirm'],
+                    $this->_getActionScripts($form, $var)) . '</li></ul>';
+    }
+
+    function _renderVarInput_passwordconfirm($form, $var, $vars)
+    {
+        $password = $var->getValue($vars);
+        return '<ul><li>' . sprintf('<input type="password" class="form-input-passwordconfirm"'
+                                    .' id="%1$s" name="%1$s[original]" value="%2$s"%3$s />',
+                                    $var->getVarName(),
+                                    $value = $password['original'],
+                                    $this->_getActionScripts($form, $var)) . '</li><li>' .
+            sprintf('<input type="password" class="form-input-passwordconfirm"'
+                    .' id="%1$s-confirm-" name="%1$s[confirm]" value="%2$s"%3$s />',
+                    $var->getVarName(),
+                    $value = $password['confirm'],
+                    $this->_getActionScripts($form, $var)) . '</li></ul>';
+    }
+
+    function _renderVarInput_boolean($form, $var, $vars)
+    {
+        $varName = $var->getVarName();
+
+        $html = '    <input type="checkbox" class="form-input-checkbox" id="' .  $varName . '"'
+            .  ' name="' .  $varName . '"'
+            . ($var->getValue($vars) ? ' checked="checked"' : '');
+        if ($var->hasAction()) {
+            $html .= $this->_genActionScript($form, $var->_action,
+                                             $var->getVarName());
+        }
+        $html .= ' />';
+        return $html;
+    }
+
+    function _renderVarInput_creditcard($form, $var, $vars)
+    {
+        $varName = $var->getVarName();
+
+        $html = '    <input type="text" class="form-input-creditcard" id="' .  $varName . '"'
+            .  ' name="' .  $varName . '"'
+            .$var->getValue($vars);
+        if ($var->hasAction()) {
+            $html .= $this->_genActionScript($form, $var->_action,
+                                             $var->getVarName());
+        }
+
+        return $html . ' />';
+    }
+
+    function _renderVarInput_obrowser($form, $var, $vars)
+    {
+        $varname = $var->getVarName();
+        $varvalue = $vars->get($varname);
+        $fieldId = 'obrowser_' . md5(uniqid(rand(), true));
+        $html = '
+            <script type="text/javascript">
+            var obrowserWindowName;
+            function obrowserCallback(name, oid)
+            {
+                if (name == obrowserWindowName) {
+                    document.getElementById(\'' . $fieldId . '\').value = oid;
+                    return false;
+                } else {
+                    return "Invalid window name supplied";
+                }
+            }
+            </script>
+            ';
+        $html .= sprintf('<input type="hidden" name="%s" id="%s"%s value="%s" />',
+                         $varname,
+                         $fieldId,
+                         $this->_getActionScripts($form, $var),
+                         $varvalue);
+        if (!empty($varvalue)) {
+            $html .= $varvalue;
+        }
+
+        if ($GLOBALS['browser']->hasFeature('javascript')) {
+            Horde::addScriptFile('popup.js', 'horde', true);
+            $imgId = $varname .'goto';
+            $html .= '<div id="goto" class="headerbox"
+                    style="position:absolute;visibility:hidden;padding:0"></div>';
+            $html .= Horde::link('#', _("Select an object"), '', '', 'obrowserWindow = popup(\'' . $GLOBALS['registry']->get('webroot', 'horde') . '/services/obrowser/' . '\'); obrowserWindowName = obrowserWindow.name; return false;') . Horde::img('tree/leaf.png', _("Object"), 'id="' . $imgId . '" align="middle"', $GLOBALS['registry']->getImageDir('horde')) . "</a>\n";
+        }
+
+        return $html;
+    }
+
+    function _renderVarInput_dblookup($form, $var, $vars)
+    {
+        return $this->_renderVarInput_enum($form, $var, $vars);
+    }
+
+    function _renderVarInput_figlet($form, $var, $vars)
+    {
+        return sprintf('    <input type="text" class="form-input-figlet" id="%1$s" name="%1$s" size="%2$s" value="%3$s" />',
+                       $var->getVarName(),
+                       strlen($var->type->text),
+                       htmlspecialchars($var->getValue($vars))) .
+            '<p class="form-input-figlet">' . _("Enter the letters below:") . '</p>' .
+            $this->_renderVarDisplay_figlet($form, $var, $vars);
+    }
+
+    function _renderVarDisplayDefault($form, $var, $vars)
+    {
+        return nl2br(htmlspecialchars($var->getValue($vars), ENT_QUOTES,
+            NLS::getCharset()));
+    }
+
+    function _renderVarDisplay_html($form, $var, $vars)
+    {
+        return $var->getValue($vars);
+    }
+
+    function _renderVarDisplay_email($form, $var, $vars)
+    {
+        $display_email = $email = $var->getValue($vars);
+
+        if ($var->type->strip_domain && strpos($email, '@') !== false) {
+            $display_email = str_replace(array('@', '.'),
+                                         array(' (at) ', ' (dot) '),
+                                         $email);
+        }
+
+        if ($var->type->link_compose) {
+            $email_val = trim($email);
+
+            // Format the address according to RFC822.
+            $mailbox_host = explode('@', $email_val);
+            if (!isset($mailbox_host[1])) {
+                $mailbox_host[1] = '';
+            }
+
+            $name = $var->type->link_name;
+
+            require_once 'Horde/MIME.php';
+            $address = MIME::rfc822WriteAddress($mailbox_host[0], $mailbox_host[1], $name);
+
+            // Get rid of the trailing @ (when no host is included in
+            // the email address).
+            $address = str_replace('@>', '>', $address);
+            $mail_link = $GLOBALS['registry']->call('mail/compose', array(array('to' => addslashes($address))));
+            if (is_a($mail_link, 'PEAR_Error')) {
+                $mail_link = 'mailto:' . urlencode($address);
+            }
+
+            return Horde::link($mail_link, $email_val)
+                . htmlspecialchars($display_email) . '</a>';
+        } else {
+            return nl2br(htmlspecialchars($display_email, ENT_QUOTES, NLS::getCharset()));
+        }
+    }
+
+    function _renderVarDisplay_password($form, $var, $vars)
+    {
+        return '********';
+    }
+
+    function _renderVarDisplay_passwordconfirm($form, $var, $vars)
+    {
+        return '********';
+    }
+
+    function _renderVarDisplay_octal($form, $var, $vars)
+    {
+        return sprintf('0%o', octdec($var->getValue($vars)));
+    }
+
+    function _renderVarDisplay_boolean($form, $var, $vars)
+    {
+        return $var->getValue($vars) ? _("Yes") : _("No");
+    }
+
+    function _renderVarDisplay_enum($form, $var, $vars)
+    {
+        $values = $var->getValues();
+        $value = $var->getValue($vars);
+        if (count($values) == 0) {
+            return _("No values");
+        } elseif (isset($values[$value]) && $value != '') {
+            return htmlspecialchars($values[$value], ENT_QUOTES, NLS::getCharset());
+        }
+    }
+
+    function _renderVarDisplay_radio($form, $var, $vars)
+    {
+        $values = $var->getValues();
+        if (count($values) == 0) {
+            return _("No values");
+        } elseif (isset($values[$var->getValue($vars)])) {
+            return htmlspecialchars($values[$var->getValue($vars)], ENT_QUOTES, NLS::getCharset());
+        }
+    }
+
+    function _renderVarDisplay_multienum($form, $var, $vars)
+    {
+        $values = $var->getValues();
+        $on = $var->getValue($vars);
+        if (!count($values) || !count($on)) {
+            return _("No values");
+        } else {
+            $display = array();
+            foreach ($values as $value => $name) {
+                if (in_array($value, $on)) {
+                    $display[] = $name;
+                }
+            }
+            return htmlspecialchars(implode(', ', $display), ENT_QUOTES, NLS::getCharset());
+        }
+    }
+
+    function _renderVarDisplay_set($form, $var, $vars)
+    {
+        $values = $var->getValues();
+        $on = $var->getValue($vars);
+        if (!count($values) || !count($on)) {
+            return _("No values");
+        } else {
+            $display = array();
+            foreach ($values as $value => $name) {
+                if (in_array($value, $on)) {
+                    $display[] = $name;
+                }
+            }
+            return htmlspecialchars(implode(', ', $display), ENT_QUOTES, NLS::getCharset());
+        }
+    }
+
+    function _renderVarDisplay_image($form, $var, $vars)
+    {
+        $img_params = $var->getValue($vars);
+        $img_url = Horde::url($GLOBALS['registry']->get('webroot', 'horde') . '/services/images/view.php');
+        $img_url = Util::addParameter($img_url, $img_params);
+
+        return Horde::img($img_url, isset($img_params['f']) ? $img_params['f'] : '', '', '');
+    }
+
+    function _renderVarDisplay_phone($form, &$var, &$vars)
+    {
+        global $registry;
+
+        $number = $var->getValue($vars);
+        $html = htmlspecialchars($number, ENT_QUOTES, $this->_charset);
+
+        if ($number && $registry->hasMethod('telephony/dial')) {
+            $url = $registry->call('telephony/dial', array($number));
+            $label = sprintf(_("Dial %s"), $number);
+            $html .= ' ' . Horde::link($url, $label) . Horde::img('phone.png', $label, '', $registry->getImageDir('horde')) . '</a>';
+        }
+
+        return $html;
+    }
+
+    function _renderVarDisplay_cellphone($form, &$var, &$vars)
+    {
+        global $registry;
+
+        $html = $this->_renderVarDisplay_phone($form, $var, $vars);
+
+        $number = $var->getValue($vars);
+        if ($number && $registry->hasMethod('sms/compose')) {
+            $url = $registry->link('sms/compose', array('to' => $number));
+            $html .= ' ' . Horde::link($url, _("Send SMS")) . Horde::img('mobile.png', _("Send SMS"), '', $registry->getImageDir('horde')) . '</a>';
+        }
+
+        return $html;
+    }
+
+    function _renderVarDisplay_address($form, $var, $vars)
+    {
+        global $registry;
+
+        $address = $var->getValue($vars);
+
+        if (preg_match('/((?:A[BL]|B[ABDHLNRST]?|C[ABFHMORTVW]|D[ADEGHLNTY]|E[CHNX]?|F[KY]|G[LUY]?|H[ADGPRSUX]|I[GMPV]|JE|K[ATWY]|L[ADELNSU]?|M[EKL]?|N[EGNPRW]?|O[LX]|P[AEHLOR]|R[GHM]|S[AEGKLMNOPRSTWY]?|T[ADFNQRSW]|UB|W[ACDFNRSV]?|YO|ZE)\d(?:\d|[A-Z])? \d[A-Z]{2})/', $address, $postcode)) {
+            /* UK postcode detected. */
+            /* Multimap.co.uk generated map */
+            $mapurl = 'http://www.multimap.com/map/browse.cgi?pc=' . urlencode($postcode[1]);
+            $desc = _("Multimap UK map");
+            $icon = 'map.png';
+        } elseif (preg_match('/ACT|NSW|NT|QLD|SA|TAS|VIC|WA/', $address)) {
+            /* Australian state detected. */
+            /* Whereis.com.au generated map */
+            $mapurl = 'http://www.whereis.com.au/whereis/mapping/geocodeAddress.do?';
+            $desc = _("Whereis Australia map");
+            $icon = 'map.png';
+            /* Split out the address, line-by-line. */
+            $addressLines = explode("\n", $address);
+            for ($i = 0; $i < count($addressLines); $i++) {
+                /* See if it's the street number & name. */
+                if (preg_match('/(\d+\s*\/\s*)?(\d+|\d+[a-zA-Z])\s+([a-zA-Z ]*)/', $addressLines[$i], $lineParts)) {
+                    $mapurl .= '&streetNumber=' . urlencode($lineParts[2]);
+                    $mapurl .= '&streetName=' . urlencode($lineParts[3]);
+                }
+                /* Look for "Suburb, State". */
+                if (preg_match('/([a-zA-Z ]*),?\s+' . $aus_state_regexp . '/', $addressLines[$i], $lineParts)) {
+                    $mapurl .= '&suburb=' . urlencode($lineParts[1]);
+                }
+                /* Look for "State <4 digit postcode>". */
+                if (preg_match('/(' . $aus_state_regexp . ')\s+(\d{4})/', $addressLines[$i], $lineParts)) {
+                    $mapurl .= '&state=' . urlencode($lineParts[1]);
+                }
+            }
+        } elseif (preg_match('/(.*)\n(.*)\s*,\s*(\w+)\.?\s+(\d+|[a-zA-Z]\d[a-zA-Z]\s?\d[a-zA-Z]\d)/', $address, $addressParts)) {
+            /* American/Canadian address style. */
+            /* Mapquest generated map */
+            $mapurl = 'http://www.mapquest.com/maps/map.adp?size=big&zoom=7';
+            $desc = _("MapQuest map");
+            $icon = 'map.png';
+            $country = null;
+            if (!empty($addressParts[4]) && preg_match('|[a-zA-Z]\d[a-zA-Z]\s?\d[a-zA-Z]\d|', $addressParts[4])) {
+                $country = 'CA';
+            }
+            if (!empty($addressParts[1])) {
+                $mapurl .= '&address=' . urlencode($addressParts[1]);
+            }
+            if (!empty($addressParts[2])) {
+                $mapurl .= '&city=' . urlencode($addressParts[2]);
+            }
+            if (!empty($addressParts[3])) {
+                $mapurl .= '&state=' . urlencode($addressParts[3]);
+            }
+            if (!empty($addressParts[4])) {
+                if ($country == 'CA') {
+                    $mapurl .= '&country=CA';
+                }
+                $mapurl .= '&zipcode=' . urlencode($addressParts[4]);
+            }
+
+            /* Yahoo! generated map. */
+            $mapurl2 = 'http://us.rd.yahoo.com/maps/home/submit_a/*-http://maps.yahoo.com/maps?srchtype=a&getmap=Get+Map&';
+            $desc2 = _("Yahoo! map");
+            $icon2 = 'map.png';
+            if (!empty($addressParts[1])) {
+                $mapurl2 .= '&addr=' . urlencode($addressParts[1]);
+            }
+            /* Give precedence to zipcode over city/state */
+            if (empty($addressParts[4]) && !empty($addressParts[2]) && !empty($addressParts[3])) {
+                $mapurl2 .= '&csz=' . urlencode($addressParts[2] . ' ' . $addressParts[3]);
+            }
+            if (!empty($addressParts[4])) {
+                if (preg_match('|([a-zA-Z]\d[a-zA-Z])\s?(\d[a-zA-Z]\d)|', $addressParts[4], $pcParts)) {
+                    $mapurl2 .= '&country=ca';
+                    /* make sure the postal-code has a space */
+                    $addressParts[4] = $pcParts[1] . ' ' . $pcParts[2];
+                }
+                $mapurl2 .= '&csz=' . urlencode($addressParts[4]);
+            }
+
+            /* Google generated map. */
+            $mapurl3 = 'http://maps.google.com/maps?q=' . urlencode($addressParts[0]) . '&hl=en';
+            $desc3 = _("Google Maps");
+            $icon3 = 'map.png';
+
+        } elseif (preg_match('/(.*?)\r?\n([A-Z]{1,3})-(\d{5})\s+(.*)/i', $address, $addressParts)) {
+            /* European address style. */
+            include 'Horde/NLS/carsigns.php';
+            $country = array_search(String::upper($addressParts[2]), $carsigns);
+
+            /* Map24 generated map. */
+            if (in_array($country, array('al', 'ad', 'am', 'az', 'be', 'ba',
+                                         'bg', 'de', 'dk', 'ee', 'fo', 'fi',
+                                         'fr', 'ge', 'gr', 'gb', 'ie', 'is',
+                                         'it', 'hr', 'lv', 'li', 'lt', 'lu',
+                                         'mt', 'mk', 'md', 'mc', 'nl', 'no',
+                                         'pl', 'pt', 'ro', 'ru', 'se', 'ch',
+                                         'cs', 'sk', 'si', 'es', 'cz', 'tr',
+                                         'ua', 'hu', 'by', 'cy', 'at'))) {
+                if (in_array($country, array('at', 'be', 'ch', 'de', 'dk',
+                                             'es', 'fi', 'fr', 'it', 'nl',
+                                             'no', 'se'))) {
+                    $mirror = $country;
+                } else {
+                    $mirror = 'uk';
+                }
+                $mapurl = 'http://www.' . $mirror . '.map24.com/source/address/v2.0.0/cnt_nav_maplet.php?cid=validateaddr&country=' . $country;
+                $desc = _("Map24 map");
+                $icon = 'map_eu.png';
+                if (!empty($addressParts[1])) {
+                    $mapurl .= '&street=' . urlencode($addressParts[1]);
+                }
+                if (!empty($addressParts[3])) {
+                    $mapurl .= '&zip=' . urlencode($addressParts[3]);
+                }
+                if (!empty($addressParts[4])) {
+                    $mapurl .= '&city=' . urlencode($addressParts[4]);
+                }
+            }
+
+            /* Mapquest generated map. */
+            $mapurl2 = 'http://www.mapquest.com/maps/map.adp?country=' . String::upper($country);
+            $desc2 = _("MapQuest map");
+            $icon2 = 'map_eu.png';
+            if (!empty($addressParts[1])) {
+                $mapurl2 .= '&address=' . urlencode($addressParts[1]);
+            }
+            if (!empty($addressParts[3])) {
+                $mapurl2 .= '&zipcode=' . urlencode($addressParts[3]);
+            }
+            if (!empty($addressParts[4])) {
+                $mapurl2 .= '&city=' . urlencode($addressParts[4]);
+            }
+        }
+
+        $html = nl2br(htmlspecialchars($var->getValue($vars), ENT_QUOTES, NLS::getCharset()));
+        if (!empty($mapurl)) {
+            $html .= '&nbsp;&nbsp;' . Horde::link(Horde::externalUrl($mapurl), $desc, null, '_blank') . Horde::img($icon, $desc, '', $registry->getImageDir('horde')) . '</a>';
+        }
+        if (!empty($mapurl2)) {
+            $html .= '&nbsp;' . Horde::link(Horde::externalUrl($mapurl2), $desc2, null, '_blank') . Horde::img($icon2, $desc2, '', $registry->getImageDir('horde')) . '</a>';
+        }
+        if (!empty($mapurl3)) {
+            $html .= '&nbsp;' . Horde::link(Horde::externalUrl($mapurl3), $desc3, null, '_blank') . Horde::img($icon3, $desc3, '', $registry->getImageDir('horde')) . '</a>';
+        }
+
+        return $html;
+    }
+
+    function _renderVarDisplay_date($form, $var, $vars)
+    {
+        return $var->type->getFormattedTime($var->getValue($vars));
+    }
+
+    function _renderVarDisplay_monthyear($form, $var, $vars)
+    {
+        return $vars->get($var->getVarName() . '[month]') . ', ' . $vars->get($var->getVarName() . '[year]');
+    }
+
+    function _renderVarDisplay_monthdayyear($form, $var, $vars)
+    {
+        $date = $var->getValue($vars);
+        if ((is_array($date) && !empty($date['year']) &&
+             !empty($date['month']) && !empty($date['day']))
+            || (!is_array($date) && !empty($date))) {
+            return $var->type->formatDate($date);
+        }
+        return '';
+    }
+
+    function _renderVarDisplay_invalid($form, $var, $vars)
+    {
+        return '<p class="form-error form-inline">'
+                . htmlspecialchars($var->type->message, ENT_QUOTES, NLS::getCharset())
+                . '</p>';
+    }
+
+    function _renderVarDisplay_link($form, $var, $vars)
+    {
+        $values = $var->getValues();
+        if (!isset($values[0])) {
+            $values = array($values);
+        }
+
+
+        $count = count($values);
+        $html = '';
+        for ($i = 0; $i < $count; $i++) {
+            if (empty($values[$i]['url']) || empty($values[$i]['text'])) {
+                continue;
+            }
+            if (!isset($values[$i]['target'])) {
+                $values[$i]['target'] = '';
+            }
+            if (!isset($values[$i]['onclick'])) {
+                $values[$i]['onclick'] = '';
+            }
+            if (!isset($values[$i]['title'])) {
+                $values[$i]['title'] = '';
+            }
+            if (!isset($values[$i]['accesskey'])) {
+                $values[$i]['accesskey'] = '';
+            }
+            if ($i > 0) {
+                $html .= ' | ';
+            }
+            $html .= Horde::link($values[$i]['url'], $values[$i]['text'],
+                        'widget', $values[$i]['target'], $values[$i]['onclick'],
+                        $values[$i]['title'], $values[$i]['accesskey'])
+                    . $values[$i]['text'] . '</a>';
+        }
+
+        return $html;
+    }
+
+    function _renderVarDisplay_dblookup($form, $var, $vars)
+    {
+        return $this->_renderVarDisplay_enum($form, $var, $vars);
+    }
+
+    function _renderVarDisplay_figlet($form, $var, $vars)
+    {
+        $figlet = new Text_Figlet();
+        $result = $figlet->loadFont($var->type->font);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result->getMessage();
+        }
+
+        return '<pre>' . $figlet->lineEcho($var->type->text) . '</pre>';
+    }
+
+    function _renderVarInput_selectFiles($form, $var, $vars)
+    {
+        /* Needed for gollem js calls */
+        $html = sprintf('<input type="hidden" id="%1$s" name="%1$s" value="%2$s" />',
+                        'selectlist_selectid',
+                        $var->type->selectid)
+            . sprintf('<input type="hidden" id="%1$s" name="%1$s" />', 'actionID');
+
+        /* Form field. */
+        $html .= sprintf('<input type="hidden" id="%1$s" name="%1$s" value="%2$s" />',
+                         $var->getVarName(),
+                         $var->type->selectid);
+
+        /* Open window link. */
+        $param = array($var->type->link_text,
+                       $var->type->link_style,
+                       $form->getName(),
+                       $var->type->icon,
+                       $var->type->selectid);
+        $html .= "<p>\n" . $GLOBALS['registry']->call('files/selectlistLink', $param) . "</p>\n";
+
+        if ($var->type->selectid) {
+            $param = array($var->type->selectid);
+            $files = $GLOBALS['registry']->call('files/selectlistResults', $param);
+            if ($files) {
+                $html .= '<ol>';
+                foreach ($files as $id => $file) {
+                    $dir = key($file);
+                    $filename = current($file);
+                    if ($GLOBALS['registry']->hasMethod('files/getViewLink')) {
+                        $filename = basename($filename);
+                        $url = $GLOBALS['registry']->call('files/getViewLink', array($dir, $filename));
+                        $filename = Horde::link($url, _("Preview"), null, 'form_file_view') . htmlspecialchars(Util::realPath($dir . '/' . $filename), ENT_QUOTES, $this->_charset) . '</a>';
+                    } else {
+                        if (!empty($dir) && ($dir != '.')) {
+                            $filename = $dir . '/' . $filename;
+                        }
+                        $filename = htmlspecialchars($filename, ENT_QUOTES, $this->_charset);
+                    }
+                    $html .= '<li>' . $filename . "</li>\n";
+                }
+                $html .= '</ol>';
+            }
+        }
+
+        return $html;
+    }
+
+    function _selectOptions($values, $selectedValue = false, $htmlchars = true)
+    {
+        $result = '';
+        $sel = false;
+        foreach ($values as $value => $display) {
+            if (!is_null($selectedValue) && !$sel && $value == $selectedValue
+                && strlen($value) == strlen($selectedValue)) {
+                $selected = ' selected="selected"';
+                $sel = true;
+            } else {
+                $selected = '';
+            }
+            $result .= '        <option value="';
+            $result .= ($htmlchars) ? htmlspecialchars($value, ENT_QUOTES, NLS::getCharset()) : $value;
+            $result .= '"' . $selected . '>';
+            $result .= ($htmlchars) ? htmlspecialchars($display) : $display;
+            $result .= "</option>\n";
+        }
+
+        return $result;
+    }
+
+    function _multiSelectOptions($values, $selectedValues)
+    {
+        $result = '';
+        $sel = false;
+        foreach ($values as $value => $display) {
+            if (@in_array($value, $selectedValues)) {
+                $selected = ' selected="selected"';
+            } else {
+                $selected = '';
+            }
+            $result .= " <option value=\""
+                . htmlspecialchars($value, ENT_QUOTES, NLS::getCharset())
+                . "\"$selected>" . htmlspecialchars($display) . "</option>\n";
+        }
+
+        return $result;
+    }
+
+    function _checkBoxes($name, $values, $checkedValues, $actions = '')
+    {
+        $result = '';
+        if (!is_array($checkedValues)) {
+            $checkedValues = array();
+        }
+
+        if (count($values) > 0) {
+            $result .= "    <ul>\n";
+        }
+
+        $i = 0;
+        foreach ($values as $value => $display) {
+            $checked = in_array($value, $checkedValues) ? ' checked="checked"' : '';
+            $result .= sprintf('        <li>'
+                                .'<input id="%1$s%2$s" type="checkbox"'
+                                    .' class="form-input-checkbox" name="%1$s[]"'
+                                    .' value="%3$s"%4$s%5$s />'
+                                .'&nbsp;<label class="form-inline" for="%1$s%2$s">'
+                                    .'%6$s</label></li>'."\n",
+                            $name,
+                            $i,
+                            $value,
+                            $checked,
+                            $actions,
+                            $display);
+            $i++;
+        }
+
+        if (count($values) > 0) {
+            $result .= "    </ul>";
+        }
+
+
+        return $result;
+    }
+
+    function _radioButtons($name, $values, $checkedValue = null, $actions = '')
+    {
+        $result = '';
+
+        if (count($values) > 0) {
+            $result .= "    <ul>\n";
+        }
+
+        $i = 0;
+        foreach ($values as $value => $display) {
+            $checked = (!is_null($checkedValue) && $value == $checkedValue) ? ' checked="checked"' : '';
+            $result .= sprintf('        <li>'
+                                .'<input id="%1$s%2$s" type="radio"'
+                                    .' class="form-input-checkbox" name="%1$s"'
+                                    .' value="%3$s"%4$s%5$s />'
+                                .'&nbsp;<label class="form-inline" for="%1$s%2$s">'
+                                    .'%6$s</label></li>'."\n",
+                            $name,
+                            $i,
+                            $value,
+                            $checked,
+                            $actions,
+                            $display);
+            $i++;
+        }
+
+        if (count($values) > 0) {
+            $result .= "    </ul>";
+        }
+
+        return $result;
+    }
+
+    /**
+     *
+     * @access private
+     * @author ?
+     * @deprecated
+     */
+    function _genID($name, $fulltag = true)
+    {
+        return $fulltag ? 'id="' . htmlspecialchars($name) . '"' : $name;
+    }
+
+    /**
+     * Returns script for an rendered variable. TODO: make this unobtrusive.
+     *
+     * @access private
+     * @author ?
+     * @return string html representing an attribute with action script as value,
+     *         or and empty string, if the action is to happen window.onload
+     */
+    function _genActionScript($form, $action, $varname)
+    {
+        $html = '';
+        $triggers = $action->getTrigger();
+        if (!is_array($triggers)) {
+            $triggers = array($triggers);
+        }
+        $js = $action->getActionScript($form, $this, $varname);
+        foreach ($triggers as $trigger) {
+            if ($trigger == 'onload') {
+                $this->_onLoadJS[] = $js;
+            } else {
+                $html .= ' ' . $trigger . '="' . $js . '"';
+            }
+        }
+        return $html;
+    }
+
+    /**
+     * Returns scripts for an rendered variable. TODO: make this unobtrusive.
+     *
+     * @access private
+     * @author ?
+     * @return string html representing attributes with action script as values,
+     *         or and empty string, if the actions are all to happen window.onload
+     */
+    function _getActionScripts($form, $var)
+    {
+        $actions = '';
+        if ($var->hasAction()) {
+            $varname = $var->getVarName();
+            $action = &$var->_action;
+            $triggers = $action->getTrigger();
+            if (!is_array($triggers)) {
+                $triggers = array($triggers);
+            }
+            $js = $action->getActionScript($form, $this, $varname);
+            foreach ($triggers as $trigger) {
+                if ($trigger == 'onload') {
+                    $this->_onLoadJS[] = $js;
+                } else {
+                    $actions .= ' ' . $trigger . '="' . $js . '"';
+                }
+            }
+        }
+        return $actions;
+    }
+
+}
diff --git a/framework/Form/www/js/maxlength.js b/framework/Form/www/js/maxlength.js
new file mode 100644 (file)
index 0000000..200ab8e
--- /dev/null
@@ -0,0 +1,39 @@
+/* http://www.quirksmode.org/dom/maxlength.html */
+
+/*
+<textarea id="text" name="text" maxlength="1500"></textarea>
+*/
+
+
+function setMaxLength()
+{
+       var x = document.getElementsByTagName('textarea');
+       var counter = document.createElement('div');
+       counter.className = 'counter';
+       for (var i=0;i<x.length;i++)
+       {
+               if (x[i].getAttribute('maxlength'))
+               {
+                       var counterClone = counter.cloneNode(true);
+                       counterClone.relatedElement = x[i];
+                       counterClone.innerHTML = '<span>0</span>/'+x[i].getAttribute('maxlength');
+                       x[i].parentNode.insertBefore(counterClone,x[i].nextSibling);
+                       x[i].relatedElement = counterClone.getElementsByTagName('span')[0];
+
+                       x[i].onkeyup = x[i].onchange = checkMaxLength;
+                       x[i].onkeyup();
+               }
+       }
+}
+
+function checkMaxLength()
+{
+       var maxLength = this.getAttribute('maxlength');
+       var currentLength = this.value.length;
+       if (currentLength > maxLength)
+               this.relatedElement.className = 'toomuch';
+       else
+               this.relatedElement.className = '';
+       this.relatedElement.firstChild.nodeValue = currentLength;
+       // not innerHTML
+}
diff --git a/framework/Form/www/test.php b/framework/Form/www/test.php
new file mode 100644 (file)
index 0000000..8267f25
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+/**
+ * Incubator Horde_Form rewrite example page.
+ *
+ * The initial Horde_Form xhtml rewrite was supported by Google SoC
+ * 2005.
+ *
+ * $Horde: incubator/Horde_Form/test.php,v 1.25 2008/09/02 17:43:09 chuck Exp $
+ */
+
+@define('HORDE_BASE', dirname(__FILE__) . '/../..');
+@define('INCUBATOR_BASE', dirname(__FILE__));
+
+require_once HORDE_BASE . '/lib/core.php';
+require_once 'Horde/Variables.php';
+require_once 'Horde/Autoloader.php';
+Horde_Autoloader::addClassPath(dirname(__FILE__));
+$registry = Registry::singleton();
+$vars = Variables::getDefaultVariables();
+
+$vars->set('example_bar', 'text with a beginning and an end');
+$form = new Horde_Form($vars, 'Horde_Form Test');
+
+$choices = array('big' => 'BIG',
+                 'small' => 'small',
+                 'other' => 'Other');
+$form->add('condchoices', 'Enum', _("Select something"), '', true, false, array($choices, true));
+
+$o = $form->add('other_text', 'String', _("If other, please describe"), '', false);
+$params = array('target' => 'condchoices',
+                'enabled' => true,
+                'values' => array('other'));
+$o->setAction(new Horde_Form_Action_ConditionalEnable($params));
+
+$form->add('color', 'Color', _("Color"), null, false);
+
+$vars->set('form', 'add');
+$enum = array('' => _("Select:"),
+              1 => _("Yes"),
+              0 => _("No"));
+$form->add('opciones', 'Enum', _("Simple description"), '', true, false, array($enum));
+$form->add('bool', 'Boolean', _("Boolean"));
+$form->add('number', 'Int', _("Integer"));
+$form->add('mybday', 'date', _("A Date"), '', false);
+$form->addHidden('form', 'String', true);
+$unamevar = $form->add('user_name', 'String', _("Username"));
+$form->add('password', 'password', _("Password"));
+$form->addHidden('example_hidden', 'int', false);
+$form->add('some_text', 'String', _("Insert some text"), _("Insert some text in this box"), false);
+$choices = array('big' => 'BIG',
+                 'small' => 'small',
+                 'mixed' => 'mIxED');
+$form->add('choices', 'enum', _("Select something2"), 'Use the selection box to make your choice', true, false, array($choices, true));
+$form->add('email_address', 'email', _("Email"));
+$form->add('email_address2', 'emailconfirm', _("Email2"));
+$form->add('a_creditcard', 'creditcard', _("Credit Card"));
+$form->add('a_password', 'password', _("Password"));
+$form->add('a_password2', 'passwordconfirm', _("Password with confirmation"), _("type the password twice to confirm"));
+$form->add('a_octal', 'Octal', _("Octal"), false);
+$form->add('a_radiogroup', 'set', _("Radio Group"), '', true, false, array($choices));
+
+$t = $form->add('example_bar', 'String', _("Bar field"), _("You have to fill in some long text here"), true, false, array(4, 40));
+$t->setAction(new Horde_Form_Action_setcursorpos(array(4)));
+
+$form->add('a_checkboxgroup', 'set', _("Checkbox Group"), '', false, false, array($choices));
+//$form->add('a_obrowser', 'obrowser', _("obrowser"));
+
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html>
+<head>
+<title>Incubator Horde_Form Test</title>
+<link rel="stylesheet" type="text/css" href="themes/form.css" />
+<script type="text/javascript" src="<?=$registry->get('jsuri', 'horde')?>/horde.js"></script>
+<script type="text/javascript" src="<?=$registry->get('jsuri', 'horde')?>/form_helpers.js"></script>
+</head>
+<body>
+<?php
+if ($form->validate()) {
+    $form->getInfo($info);
+    echo 'You have submitted:<br /><pre>';
+    var_dump($info);
+    echo '</pre>';
+}
+
+/* Render the form. */
+$renderer = new Horde_Form_Renderer_Xhtml;
+$renderer->setButtons(_("Add user"), _("Reset"));
+$renderer->renderActive($form, 'test.php', 'post');
+
+?>
+</body>
+</html>
diff --git a/framework/Form/www/themes/form.css b/framework/Form/www/themes/form.css
new file mode 100644 (file)
index 0000000..0c3ae39
--- /dev/null
@@ -0,0 +1,262 @@
+form.horde-form {
+    font-size: 100%;
+    font-weight: normal;
+}
+
+form.horde-form input[type="text"], form.horde-form input[type="password"],
+form.horde-form select, form.horde-form textarea {
+    width: 130px;
+    margin: .2em 0 .1em .1em;
+}
+
+form.horde-form input[type="text"], form.horde-form input[type="password"], form.horde-form textarea {
+    padding: .1em;
+}
+
+.clear {
+    clear: both;
+}
+
+.horde-form fieldset {
+    padding: .5em;
+    margin: 0;
+    border: 1px solid #ccc;
+}
+
+.horde-form fieldset.form-buttons {
+    color: #000;
+    background: #ccc;
+    border: 1px solid #aaa;
+}
+
+fieldset legend {
+    color: #000;
+    background: #ccc;
+    padding: .5em;
+    margin-bottom: -1em;
+    border: 1px solid #999;
+    border-bottom: none;
+    font-weight: bold;
+}
+
+fieldset.form-section {
+    padding: .5em;
+    margin: .5em 0;
+}
+
+.form-button, .form-button-upload {
+    font-size: 90%;
+}
+
+.form-colorpicker {
+    float: left;
+}
+.form-colorpicker img {
+    border: 0;
+    padding: 0;
+    margin: 2px;
+    height: 16px;
+    width: 16px;
+}
+.form-colorpicker input {
+    width: 100px;
+    clear: none;
+}
+.form-colorpicker-palette {
+    display: none;
+    margin: 2px;
+}
+
+.form-description {
+    padding: 8px;
+}
+
+.form-error, .form-error-example {
+    color: #f00;
+}
+
+p.form-error {
+    background-color: #f00;
+    color: #fff;
+    padding: 3px;
+    border: 1px solid #000;
+    margin: auto 70px;
+}
+
+div.form-error {
+    background-color: #ffffe1;
+    background-image: url("graphics/required.png");
+    background-repeat: no-repeat;
+    background-position: top left;
+    color: #000;
+}
+
+.form-hint {
+    display: block;
+    margin: 0 0 5px 142px;
+    padding: 1px 3px;
+    font-size: 88%;
+}
+
+.form-header {
+    vertical-align: bottom;
+    font-weight: bold;
+}
+
+.form-image-preview-blank {
+    width: 50px;
+    height: 40px;
+    vertical-align: top;
+}
+
+.form-inline {
+    display: inline;
+}
+
+.form-input {
+    vertical-align: top;
+    clear: both;
+    padding: 4px;
+    margin: 2px 0;
+}
+
+.form-input label {
+    vertical-align: top;
+    text-align: right;
+    float: left;
+    clear: left;
+    display: block;
+    width: 10em;
+    padding: 0.3em;
+}
+
+.form-input label.form-inline {
+    float: none;
+    width: auto;
+    display: inline;
+}
+
+.form-input ul, .form-input ol {
+    list-style-type: none;
+    display: block;
+    float: left;
+}
+
+.form-input ul, .form-input ol, .form-input li {
+    margin-top: 0;
+    margin-left: 0;
+    padding-top: 0;
+    padding-left: 0;
+}
+.form-input ul {
+    margin-bottom: .5em;
+}
+
+.form-input-address {
+}
+
+.form-input-assign {
+}
+
+.form-input-assign select, .form-input-assign div {
+    display: inline;
+    clear: none;
+}
+
+.form-input-checkbox {
+    border: 0;
+    height: 14px;
+    width: 14px;
+    background: transparent;
+}
+
+.form-input-text, .form-input-intlist, .form-input-octal, .form-input-int,
+.form-input-number, .form-input-phone, .form-input-file {
+    width: 100px;
+}
+
+.form-input-resize {
+    width: 25px;
+}
+
+.form-input-stringlist {
+    width: 150px;
+}
+
+.form-input-time {
+    width: 30px;
+}
+
+.form-note {
+    clear: left;
+    width: 130px;
+    height: auto;
+    padding: .3em;
+    margin: 0 0 0 10.7em;
+    border: 1px solid #666;
+    background-color: #ffc;
+}
+.form-note p {
+    margin: 0;
+    padding: 0;
+    font-style: italic;
+    font-size: 70%;
+}
+
+.form-required {
+    background: url("graphics/required.png") .5em .5em no-repeat;
+}
+
+.form-required label {
+    font-weight: bold;
+}
+
+.form-sectionhidden {
+    position: absolute;
+    left: 0;
+    top: -500px;
+    width: 1px;
+    height: 1px;
+    overflow: hidden;
+    display: block;
+}
+
+.form-sectionshown {
+    display: block;
+}
+
+.form-spacer {
+    padding: .9em;
+}
+
+.rowEven {
+    background-color: #eef;
+}
+.rowOdd {
+    background-color: #fff;
+}
+
+/* Form styles. */
+.htmlarea .statusBar .statusBarTree a {
+    font: inherit;
+}
+.htmlarea table {
+    width: auto;
+}
+input, select {
+}
+input {
+    padding: 1px;
+}
+option {
+    padding: 0 5px 0 3px;
+}
+
+.button {
+    font-size: 90%;
+}
+a.button {
+    padding: 2px;
+    font-weight: normal;
+    text-decoration: none;
+}
diff --git a/framework/Form/www/themes/graphics/required.png b/framework/Form/www/themes/graphics/required.png
new file mode 100644 (file)
index 0000000..1756362
Binary files /dev/null and b/framework/Form/www/themes/graphics/required.png differ
diff --git a/framework/Horde_Date_Parser/chronic/History.txt b/framework/Horde_Date_Parser/chronic/History.txt
deleted file mode 100644 (file)
index 0f23646..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-= 0.2.3
-
-* fixed 12am/12pm (by Nicholas Schlueter)
-
-= 0.2.2
-
-* added missing files (damn you manifest)
-
-= 0.2.1
-
-* fixed time overflow issue
-* implemented "next" for minute repeater
-* generalized time dealiasing to dealias regardless of day portion and time position
-* added additional token match for cases like "friday evening at 7" and "tomorrow evening at 7"
-* added support for Time#to_s output format: "Mon Apr 02 17:00:00 PDT 2007"
-
-= 0.2.0 2007-03-20
-
-* implemented numerizer, allowing the use of number words (e.g. five weeks ago) (by shalev)
-
-= 0.1.6 2006-01-15
-
-* added 'weekend' support (by eventualbuddha)
-
-= 0.1.5 2006-12-20
-
-* fixed 'aug 20' returning next year if current month is august
-* modified behavior of 'from now'
-* added support for seconds on times, and thus db timestamp format: "2006-12-20 18:04:23"
-* made Hoe compliant
-
-= 0.1.4
-
-* removed verbose error checking code. oops. :-/
-
-= 0.1.3
-
-* improved regexes for word variations (by Josh Goebel)
-* fixed a bug that caused "today at 3am" to return nil if current time is after 3am
-
-= 0.1.2
-
-* removed Date dependency (now works on windows properly without fiddling)
-
-= 0.1.1
-
-* run to_s on incoming object
-* fixed loop loading of repeaters files (out of order on some machines)
-* fixed find_within to use this instead of next (was breaking "today at 6pm")
-
-= 0.1.0
-
-* initial release
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/README.txt b/framework/Horde_Date_Parser/chronic/README.txt
deleted file mode 100644 (file)
index 2e4f179..0000000
+++ /dev/null
@@ -1,149 +0,0 @@
-Chronic
-       http://chronic.rubyforge.org/
-       by Tom Preston-Werner
-
-== DESCRIPTION:
-
-Chronic is a natural language date/time parser written in pure Ruby. See below for the wide variety of formats Chronic will parse.
-
-== INSTALLATION:
-
-Chronic can be installed via RubyGems:
-
-  $ sudo gem install chronic
-
-== USAGE:
-
-You can parse strings containing a natural language date using the Chronic.parse method.
-
-  require 'rubygems'
-  require 'chronic'
-
-  Time.now   #=> Sun Aug 27 23:18:25 PDT 2006
-
-  #---
-
-  Chronic.parse('tomorrow')       
-    #=> Mon Aug 28 12:00:00 PDT 2006
-
-  Chronic.parse('monday', :context => :past)
-    #=> Mon Aug 21 12:00:00 PDT 2006
-
-  Chronic.parse('this tuesday 5:00')
-    #=> Tue Aug 29 17:00:00 PDT 2006
-
-  Chronic.parse('this tuesday 5:00', :ambiguous_time_range => :none)
-    #=> Tue Aug 29 05:00:00 PDT 2006
-
-  Chronic.parse('may 27th', :now => Time.local(2000, 1, 1))
-    #=> Sat May 27 12:00:00 PDT 2000
-
-  Chronic.parse('may 27th', :guess => false)
-    #=> Sun May 27 00:00:00 PDT 2007..Mon May 28 00:00:00 PDT 2007
-
-See Chronic.parse for detailed usage instructions.
-
-== EXAMPLES:
-
-Chronic can parse a huge variety of date and time formats. Following is a small sample of strings that will be properly parsed. Parsing is case insensitive and will handle common abbreviations and misspellings.
-
-Simple
-
-  thursday
-  november
-  summer
-  friday 13:00
-  mon 2:35
-  4pm
-  6 in the morning
-  friday 1pm
-  sat 7 in the evening
-  yesterday
-  today
-  tomorrow
-  this tuesday
-  next month
-  last winter
-  this morning
-  last night
-  this second
-  yesterday at 4:00
-  last friday at 20:00
-  last week tuesday
-  tomorrow at 6:45pm
-  afternoon yesterday
-  thursday last week
-
-Complex
-
-  3 years ago
-  5 months before now
-  7 hours ago
-  7 days from now
-  1 week hence
-  in 3 hours
-  1 year ago tomorrow
-  3 months ago saturday at 5:00 pm
-  7 hours before tomorrow at noon
-  3rd wednesday in november
-  3rd month next year
-  3rd thursday this september
-  4th day last week
-
-Specific Dates
-
-  January 5
-  dec 25
-  may 27th
-  October 2006
-  oct 06
-  jan 3 2010
-  february 14, 2004
-  3 jan 2000
-  17 april 85
-  5/27/1979
-  27/5/1979
-  05/06
-  1979-05-27
-  Friday
-  5
-  4:00
-  17:00
-  0800
-
-Specific Times (many of the above with an added time)
-
-  January 5 at 7pm
-  1979-05-27 05:00:00
-  etc
-
-== LIMITATIONS:
-  
-Chronic uses Ruby's built in Time class for all time storage and computation. Because of this, only times that the Time class can handle will be properly parsed. Parsing for times outside of this range will simply return nil. Support for a wider range of times is planned for a future release.
-
-Time zones other than the local one are not currently supported. Support for other time zones is planned for a future release.
-
-== LICENSE:
-
-(The MIT License)
-
-Copyright (c) 2006 Ryan Davis, Zen Spider Software
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/framework/Horde_Date_Parser/chronic/lib/chronic.rb b/framework/Horde_Date_Parser/chronic/lib/chronic.rb
deleted file mode 100644 (file)
index 6d0e7ba..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-#=============================================================================
-#
-#  Name:       Chronic
-#  Author:     Tom Preston-Werner
-#  Purpose:    Parse natural language dates and times into Time or
-#              Chronic::Span objects
-#
-#=============================================================================
-
-$:.unshift File.dirname(__FILE__)     # For use/testing when no gem is installed
-
-require 'chronic/chronic'
-require 'chronic/handlers'
-
-require 'chronic/repeater'
-require 'chronic/repeaters/repeater_year'
-require 'chronic/repeaters/repeater_season'
-require 'chronic/repeaters/repeater_season_name'
-require 'chronic/repeaters/repeater_month'
-require 'chronic/repeaters/repeater_month_name'
-require 'chronic/repeaters/repeater_fortnight'
-require 'chronic/repeaters/repeater_week'
-require 'chronic/repeaters/repeater_weekend'
-require 'chronic/repeaters/repeater_day'
-require 'chronic/repeaters/repeater_day_name'
-require 'chronic/repeaters/repeater_day_portion'
-require 'chronic/repeaters/repeater_hour'
-require 'chronic/repeaters/repeater_minute'
-require 'chronic/repeaters/repeater_second'
-require 'chronic/repeaters/repeater_time'
-
-require 'chronic/grabber'
-require 'chronic/pointer'
-require 'chronic/scalar'
-require 'chronic/ordinal'
-require 'chronic/separator'
-require 'chronic/time_zone'
-
-require 'numerizer/numerizer'
-
-module Chronic
-  VERSION = "0.2.3"
-  
-  def self.debug; false; end
-end
-
-alias p_orig p
-
-def p(val)
-  p_orig val
-  puts
-end
-
-# class Time
-#   def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
-#     # extra_seconds = second > 60 ? second - 60 : 0
-#     # extra_minutes = minute > 59 ? minute - 59 : 0
-#     # extra_hours = hour > 23 ? hour - 23 : 0
-#     # extra_days = day > 
-#     
-#     if month > 12
-#       if month % 12 == 0
-#         year += (month - 12) / 12
-#         month = 12
-#       else
-#         year += month / 12
-#         month = month % 12
-#       end
-#     end
-#     
-#     base = Time.local(year, month)
-#     puts base
-#     offset = ((day - 1) * 24 * 60 * 60) + (hour * 60 * 60) + (minute * 60) + second
-#     puts offset.to_s
-#     date = base + offset
-#     puts date
-#     date
-#   end
-# end
-
-class Time
-  def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
-    if second >= 60
-      minute += second / 60
-      second = second % 60
-    end
-    
-    if minute >= 60
-      hour += minute / 60
-      minute = minute % 60
-    end
-    
-    if hour >= 24
-      day += hour / 24
-      hour = hour % 24
-    end
-    
-    # determine if there is a day overflow. this is complicated by our crappy calendar
-    # system (non-constant number of days per month)
-    day <= 56 || raise("day must be no more than 56 (makes month resolution easier)")
-    if day > 28
-      # no month ever has fewer than 28 days, so only do this if necessary
-      leap_year = (year % 4 == 0) && !(year % 100 == 0)
-      leap_year_month_days = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
-      common_year_month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
-      days_this_month = leap_year ? leap_year_month_days[month - 1] : common_year_month_days[month - 1]
-      if day > days_this_month
-        month += day / days_this_month
-        day = day % days_this_month
-      end
-    end
-    
-    if month > 12
-      if month % 12 == 0
-        year += (month - 12) / 12
-        month = 12
-      else
-        year += month / 12
-        month = month % 12
-      end
-    end
-    
-    Time.local(year, month, day, hour, minute, second)
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/lib/chronic/chronic.rb b/framework/Horde_Date_Parser/chronic/lib/chronic/chronic.rb
deleted file mode 100644 (file)
index 5e7779f..0000000
+++ /dev/null
@@ -1,239 +0,0 @@
-module Chronic
-  class << self
-    
-    # Parses a string containing a natural language date or time. If the parser
-    # can find a date or time, either a Time or Chronic::Span will be returned 
-    # (depending on the value of <tt>:guess</tt>). If no date or time can be found,
-    # +nil+ will be returned.
-    #
-    # Options are:
-    #
-    # [<tt>:context</tt>]
-    #     <tt>:past</tt> or <tt>:future</tt> (defaults to <tt>:future</tt>)
-    #
-    #     If your string represents a birthday, you can set <tt>:context</tt> to <tt>:past</tt> 
-    #     and if an ambiguous string is given, it will assume it is in the 
-    #     past. Specify <tt>:future</tt> or omit to set a future context.
-    #
-    # [<tt>:now</tt>]
-    #     Time (defaults to Time.now)
-    #
-    #     By setting <tt>:now</tt> to a Time, all computations will be based off
-    #     of that time instead of Time.now
-    #
-    # [<tt>:guess</tt>]
-    #     +true+ or +false+ (defaults to +true+)
-    #
-    #     By default, the parser will guess a single point in time for the
-    #     given date or time. If you'd rather have the entire time span returned,
-    #     set <tt>:guess</tt> to +false+ and a Chronic::Span will be returned.
-    #     
-    # [<tt>:ambiguous_time_range</tt>]
-    #     Integer or <tt>:none</tt> (defaults to <tt>6</tt> (6am-6pm))
-    #
-    #     If an Integer is given, ambiguous times (like 5:00) will be 
-    #     assumed to be within the range of that time in the AM to that time
-    #     in the PM. For example, if you set it to <tt>7</tt>, then the parser will
-    #     look for the time between 7am and 7pm. In the case of 5:00, it would
-    #     assume that means 5:00pm. If <tt>:none</tt> is given, no assumption
-    #     will be made, and the first matching instance of that time will 
-    #     be used.
-    def parse(text, specified_options = {})
-      # get options and set defaults if necessary
-      default_options = {:context => :future,
-                         :now => Time.now,
-                         :guess => true,
-                         :ambiguous_time_range => 6}
-      options = default_options.merge specified_options
-            
-      # ensure the specified options are valid
-      specified_options.keys.each do |key|
-        default_options.keys.include?(key) || raise(InvalidArgumentException, "#{key} is not a valid option key.")
-      end
-      [:past, :future, :none].include?(options[:context]) || raise(InvalidArgumentException, "Invalid value ':#{options[:context]}' for :context specified. Valid values are :past and :future.")
-      
-      # store now for later =)
-      @now = options[:now]
-      
-      # put the text into a normal format to ease scanning
-      text = self.pre_normalize(text)
-          
-      # get base tokens for each word
-      @tokens = self.base_tokenize(text)
-    
-      # scan the tokens with each token scanner
-      [Repeater].each do |tokenizer|
-        @tokens = tokenizer.scan(@tokens, options)
-      end
-      
-      [Grabber, Pointer, Scalar, Ordinal, Separator, TimeZone].each do |tokenizer|
-        @tokens = tokenizer.scan(@tokens)
-      end
-      
-      # strip any non-tagged tokens
-      @tokens = @tokens.select { |token| token.tagged? }
-      
-      if Chronic.debug
-        puts "+---------------------------------------------------"
-        puts "| " + @tokens.to_s
-        puts "+---------------------------------------------------"
-      end
-      
-      # do the heavy lifting
-      begin
-        span = self.tokens_to_span(@tokens, options)
-      rescue
-        raise
-        return nil
-      end
-      
-      # guess a time within a span if required
-      if options[:guess]
-        return self.guess(span)
-      else
-        return span
-      end
-    end
-    
-    # Clean up the specified input text by stripping unwanted characters,
-    # converting idioms to their canonical form, converting number words
-    # to numbers (three => 3), and converting ordinal words to numeric
-    # ordinals (third => 3rd)
-    def pre_normalize(text) #:nodoc:
-      normalized_text = text.to_s.downcase
-      normalized_text = numericize_numbers(normalized_text)
-      normalized_text.gsub!(/['"\.]/, '')
-      normalized_text.gsub!(/([\/\-\,\@])/) { ' ' + $1 + ' ' }
-      normalized_text.gsub!(/\btoday\b/, 'this day')
-      normalized_text.gsub!(/\btomm?orr?ow\b/, 'next day')
-      normalized_text.gsub!(/\byesterday\b/, 'last day')
-      normalized_text.gsub!(/\bnoon\b/, '12:00')
-      normalized_text.gsub!(/\bmidnight\b/, '24:00')
-      normalized_text.gsub!(/\bbefore now\b/, 'past')
-      normalized_text.gsub!(/\bnow\b/, 'this second')
-      normalized_text.gsub!(/\b(ago|before)\b/, 'past')
-      normalized_text.gsub!(/\bthis past\b/, 'last')
-      normalized_text.gsub!(/\bthis last\b/, 'last')
-      normalized_text.gsub!(/\b(?:in|during) the (morning)\b/, '\1')
-      normalized_text.gsub!(/\b(?:in the|during the|at) (afternoon|evening|night)\b/, '\1')
-      normalized_text.gsub!(/\btonight\b/, 'this night')
-      normalized_text.gsub!(/(?=\w)([ap]m|oclock)\b/, ' \1')
-      normalized_text.gsub!(/\b(hence|after|from)\b/, 'future')
-      normalized_text = numericize_ordinals(normalized_text)
-    end
-  
-    # Convert number words to numbers (three => 3)
-    def numericize_numbers(text) #:nodoc:
-      Numerizer.numerize(text)
-    end
-  
-    # Convert ordinal words to numeric ordinals (third => 3rd)
-    def numericize_ordinals(text) #:nodoc:
-      text
-    end
-  
-    # Split the text on spaces and convert each word into
-    # a Token
-    def base_tokenize(text) #:nodoc:
-      text.split(' ').map { |word| Token.new(word) }
-    end
-    
-    # Guess a specific time within the given span
-    def guess(span) #:nodoc:
-      return nil if span.nil?
-      if span.width > 1
-        span.begin + (span.width / 2)
-      else
-        span.begin
-      end
-    end
-  end
-  
-  class Token #:nodoc:
-    attr_accessor :word, :tags
-    
-    def initialize(word)
-      @word = word
-      @tags = []
-    end
-    
-    # Tag this token with the specified tag
-    def tag(new_tag)
-      @tags << new_tag
-    end
-    
-    # Remove all tags of the given class
-    def untag(tag_class)
-      @tags = @tags.select { |m| !m.kind_of? tag_class }
-    end
-    
-    # Return true if this token has any tags
-    def tagged?
-      @tags.size > 0
-    end
-    
-    # Return the Tag that matches the given class
-    def get_tag(tag_class)
-      matches = @tags.select { |m| m.kind_of? tag_class }
-      #matches.size < 2 || raise("Multiple identical tags found")
-      return matches.first
-    end
-    
-    # Print this Token in a pretty way
-    def to_s
-      @word << '(' << @tags.join(', ') << ') '
-    end
-  end
-  
-  # A Span represents a range of time. Since this class extends
-  # Range, you can use #begin and #end to get the beginning and
-  # ending times of the span (they will be of class Time)
-  class Span < Range   
-    # Returns the width of this span in seconds   
-    def width
-      (self.end - self.begin).to_i
-    end
-    
-    # Add a number of seconds to this span, returning the 
-    # resulting Span
-    def +(seconds)
-      Span.new(self.begin + seconds, self.end + seconds)
-    end
-    
-    # Subtract a number of seconds to this span, returning the 
-    # resulting Span
-    def -(seconds)
-      self + -seconds
-    end
-    
-    # Prints this span in a nice fashion
-    def to_s
-      '(' << self.begin.to_s << '..' << self.end.to_s << ')'
-    end
-  end
-
-  # Tokens are tagged with subclassed instances of this class when
-  # they match specific criteria
-  class Tag #:nodoc:
-    attr_accessor :type
-    
-    def initialize(type)
-      @type = type
-    end
-    
-    def start=(s)
-      @now = s
-    end
-  end
-  
-  # Internal exception
-  class ChronicPain < Exception #:nodoc:
-    
-  end
-  
-  # This exception is raised if an invalid argument is provided to
-  # any of Chronic's methods
-  class InvalidArgumentException < Exception
-    
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/lib/chronic/grabber.rb b/framework/Horde_Date_Parser/chronic/lib/chronic/grabber.rb
deleted file mode 100644 (file)
index 4162a26..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-#module Chronic
-
-  class Chronic::Grabber < Chronic::Tag #:nodoc:
-    def self.scan(tokens)
-      tokens.each_index do |i|
-        if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t); next end
-      end
-      tokens
-    end
-    
-    def self.scan_for_all(token)
-      scanner = {/last/ => :last,
-                 /this/ => :this,
-                 /next/ => :next}
-      scanner.keys.each do |scanner_item|
-        return self.new(scanner[scanner_item]) if scanner_item =~ token.word
-      end
-      return nil
-    end
-    
-    def to_s
-      'grabber-' << @type.to_s
-    end
-  end
-
-#end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/lib/chronic/handlers.rb b/framework/Horde_Date_Parser/chronic/lib/chronic/handlers.rb
deleted file mode 100644 (file)
index 551d632..0000000
+++ /dev/null
@@ -1,469 +0,0 @@
-module Chronic
-
-       class << self
-         
-         def definitions #:nodoc:
-           @definitions ||= 
-      {:time => [Handler.new([:repeater_time, :repeater_day_portion?], nil)],
-        
-       :date => [Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :repeater_time, :time_zone, :scalar_year], :handle_rdn_rmn_sd_t_tz_sy),
-                 Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),
-                 Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),
-                 Handler.new([:repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),
-                 Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),
-                 Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),
-                 Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),
-                 Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),
-                 Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy),
-                 Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),
-                 Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy)],
-                 
-       # tonight at 7pm
-       :anchor => [Handler.new([:grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
-                   Handler.new([:grabber?, :repeater, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
-                   Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)],
-                   
-       # 3 weeks from now, in 2 months
-       :arrow => [Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p),
-                  Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r),
-                  Handler.new([:scalar, :repeater, :pointer, 'anchor'], :handle_s_r_p_a)],
-                  
-       # 3rd week in march
-       :narrow => [Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r),
-                   Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r)]
-      }
-    end
-    
-    def tokens_to_span(tokens, options) #:nodoc:                   
-      # maybe it's a specific date
-      
-      self.definitions[:date].each do |handler|
-        if handler.match(tokens, self.definitions)
-          puts "-date" if Chronic.debug
-          good_tokens = tokens.select { |o| !o.get_tag Separator }
-          return self.send(handler.handler_method, good_tokens, options)
-        end
-      end
-            
-      # I guess it's not a specific date, maybe it's just an anchor
-            
-      self.definitions[:anchor].each do |handler|
-        if handler.match(tokens, self.definitions)
-          puts "-anchor" if Chronic.debug
-          good_tokens = tokens.select { |o| !o.get_tag Separator }
-          return self.send(handler.handler_method, good_tokens, options)
-        end
-      end
-            
-      # not an anchor, perhaps it's an arrow
-      
-      self.definitions[:arrow].each do |handler|
-        if handler.match(tokens, self.definitions)
-          puts "-arrow" if Chronic.debug
-          good_tokens = tokens.reject { |o| o.get_tag(SeparatorAt) || o.get_tag(SeparatorSlashOrDash) || o.get_tag(SeparatorComma) }
-          return self.send(handler.handler_method, good_tokens, options)
-        end
-      end
-      
-      # not an arrow, let's hope it's a narrow
-      
-      self.definitions[:narrow].each do |handler|
-        if handler.match(tokens, self.definitions)
-          puts "-narrow" if Chronic.debug
-          #good_tokens = tokens.select { |o| !o.get_tag Separator }
-          return self.send(handler.handler_method, tokens, options)
-        end
-      end
-      
-      # I guess you're out of luck!
-      puts "-none" if Chronic.debug
-      return nil
-    end
-    
-    #--------------
-    
-    def day_or_time(day_start, time_tokens, options)
-      outer_span = Span.new(day_start, day_start + (24 * 60 * 60))
-      
-      if !time_tokens.empty?
-        @now = outer_span.begin
-        time = get_anchor(dealias_and_disambiguate_times(time_tokens, options), options)
-        return time
-      else
-        return outer_span
-      end
-    end
-    
-    #--------------
-    
-    def handle_m_d(month, day, time_tokens, options) #:nodoc:
-      month.start = @now
-      span = month.this(options[:context])
-      
-      day_start = Time.local(span.begin.year, span.begin.month, day)
-      
-      day_or_time(day_start, time_tokens, options)
-    end
-    
-    def handle_rmn_sd(tokens, options) #:nodoc:
-      handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(ScalarDay).type, tokens[2..tokens.size], options)
-    end
-    
-    def handle_rmn_od(tokens, options) #:nodoc:
-      handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(OrdinalDay).type, tokens[2..tokens.size], options)
-    end
-    
-    def handle_rmn_sy(tokens, options) #:nodoc:
-      month = tokens[0].get_tag(RepeaterMonthName).index
-      year = tokens[1].get_tag(ScalarYear).type
-      
-      if month == 12
-        next_month_year = year + 1
-        next_month_month = 1
-      else
-        next_month_year = year
-        next_month_month = month + 1
-      end
-      
-      begin
-        Span.new(Time.local(year, month), Time.local(next_month_year, next_month_month))
-      rescue ArgumentError
-        nil
-      end
-    end
-    
-    def handle_rdn_rmn_sd_t_tz_sy(tokens, options) #:nodoc:
-      month = tokens[1].get_tag(RepeaterMonthName).index
-      day = tokens[2].get_tag(ScalarDay).type
-      year = tokens[5].get_tag(ScalarYear).type
-      
-      begin
-        day_start = Time.local(year, month, day)
-        day_or_time(day_start, [tokens[3]], options)
-      rescue ArgumentError
-        nil
-      end
-    end
-    
-    def handle_rmn_sd_sy(tokens, options) #:nodoc:
-      month = tokens[0].get_tag(RepeaterMonthName).index
-      day = tokens[1].get_tag(ScalarDay).type
-      year = tokens[2].get_tag(ScalarYear).type
-      
-      time_tokens = tokens.last(tokens.size - 3)
-      
-      begin
-        day_start = Time.local(year, month, day)
-        day_or_time(day_start, time_tokens, options)
-      rescue ArgumentError
-        nil
-      end
-    end
-    
-    def handle_sd_rmn_sy(tokens, options) #:nodoc:
-      new_tokens = [tokens[1], tokens[0], tokens[2]]
-      time_tokens = tokens.last(tokens.size - 3)
-      self.handle_rmn_sd_sy(new_tokens + time_tokens, options)
-    end
-    
-    def handle_sm_sd_sy(tokens, options) #:nodoc:
-      month = tokens[0].get_tag(ScalarMonth).type
-      day = tokens[1].get_tag(ScalarDay).type
-      year = tokens[2].get_tag(ScalarYear).type
-      
-      time_tokens = tokens.last(tokens.size - 3)
-      
-      begin
-        day_start = Time.local(year, month, day) #:nodoc:
-        day_or_time(day_start, time_tokens, options)
-      rescue ArgumentError
-        nil
-      end
-    end
-    
-    def handle_sd_sm_sy(tokens, options) #:nodoc:
-      new_tokens = [tokens[1], tokens[0], tokens[2]]
-      time_tokens = tokens.last(tokens.size - 3)
-      self.handle_sm_sd_sy(new_tokens + time_tokens, options)
-    end
-    
-    def handle_sy_sm_sd(tokens, options) #:nodoc:
-      new_tokens = [tokens[1], tokens[2], tokens[0]]
-      time_tokens = tokens.last(tokens.size - 3)
-      self.handle_sm_sd_sy(new_tokens + time_tokens, options)
-    end
-    
-    def handle_sm_sy(tokens, options) #:nodoc:
-      month = tokens[0].get_tag(ScalarMonth).type
-      year = tokens[1].get_tag(ScalarYear).type
-      
-      if month == 12
-        next_month_year = year + 1
-        next_month_month = 1
-      else
-        next_month_year = year
-        next_month_month = month + 1
-      end
-      
-      begin
-        Span.new(Time.local(year, month), Time.local(next_month_year, next_month_month))
-      rescue ArgumentError
-        nil
-      end
-    end
-    
-    # anchors
-    
-    def handle_r(tokens, options) #:nodoc:
-      dd_tokens = dealias_and_disambiguate_times(tokens, options)
-      self.get_anchor(dd_tokens, options)
-    end
-    
-    def handle_r_g_r(tokens, options) #:nodoc:
-      new_tokens = [tokens[1], tokens[0], tokens[2]]
-      self.handle_r(new_tokens, options)
-    end
-    
-    # arrows
-    
-    def handle_srp(tokens, span, options) #:nodoc:
-      distance = tokens[0].get_tag(Scalar).type
-      repeater = tokens[1].get_tag(Repeater)
-      pointer = tokens[2].get_tag(Pointer).type
-      
-      repeater.offset(span, distance, pointer)
-    end
-    
-    def handle_s_r_p(tokens, options) #:nodoc:
-      repeater = tokens[1].get_tag(Repeater)
-            
-      # span = 
-      # case true
-      # when [RepeaterYear, RepeaterSeason, RepeaterSeasonName, RepeaterMonth, RepeaterMonthName, RepeaterFortnight, RepeaterWeek].include?(repeater.class)
-      #   self.parse("this hour", :guess => false, :now => @now)
-      # when [RepeaterWeekend, RepeaterDay, RepeaterDayName, RepeaterDayPortion, RepeaterHour].include?(repeater.class)
-      #   self.parse("this minute", :guess => false, :now => @now)
-      # when [RepeaterMinute, RepeaterSecond].include?(repeater.class)
-      #   self.parse("this second", :guess => false, :now => @now)
-      # else
-      #   raise(ChronicPain, "Invalid repeater: #{repeater.class}")
-      # end
-      
-      span = self.parse("this second", :guess => false, :now => @now)
-      
-      self.handle_srp(tokens, span, options)
-    end
-    
-    def handle_p_s_r(tokens, options) #:nodoc:
-      new_tokens = [tokens[1], tokens[2], tokens[0]]
-      self.handle_s_r_p(new_tokens, options)
-    end
-    
-    def handle_s_r_p_a(tokens, options) #:nodoc:
-      anchor_span = get_anchor(tokens[3..tokens.size - 1], options)
-      self.handle_srp(tokens, anchor_span, options)
-    end
-    
-    # narrows
-    
-    def handle_orr(tokens, outer_span, options) #:nodoc:
-      repeater = tokens[1].get_tag(Repeater)
-      repeater.start = outer_span.begin - 1
-      ordinal = tokens[0].get_tag(Ordinal).type
-      span = nil
-      ordinal.times do
-        span = repeater.next(:future)
-        if span.begin > outer_span.end
-          span = nil
-          break
-        end
-      end
-      span
-    end
-    
-    def handle_o_r_s_r(tokens, options) #:nodoc:
-      outer_span = get_anchor([tokens[3]], options)
-      handle_orr(tokens[0..1], outer_span, options)
-    end
-    
-    def handle_o_r_g_r(tokens, options) #:nodoc:
-      outer_span = get_anchor(tokens[2..3], options)
-      handle_orr(tokens[0..1], outer_span, options)
-    end
-    
-    # support methods
-    
-    def get_anchor(tokens, options) #:nodoc:
-      grabber = Grabber.new(:this)
-      pointer = :future
-      
-      repeaters = self.get_repeaters(tokens)
-      repeaters.size.times { tokens.pop }
-                    
-      if tokens.first && tokens.first.get_tag(Grabber)
-        grabber = tokens.first.get_tag(Grabber)
-        tokens.pop
-      end
-      
-      head = repeaters.shift
-      head.start = @now
-                  
-      case grabber.type
-        when :last
-          outer_span = head.next(:past)
-        when :this
-          if repeaters.size > 0
-            outer_span = head.this(:none)
-          else
-            outer_span = head.this(options[:context])
-          end
-        when :next
-          outer_span = head.next(:future)
-        else raise(ChronicPain, "Invalid grabber")
-      end
-      
-      puts "--#{outer_span}" if Chronic.debug
-      anchor = find_within(repeaters, outer_span, pointer)
-    end
-    
-    def get_repeaters(tokens) #:nodoc:
-      repeaters = []
-                 tokens.each do |token|
-                   if t = token.get_tag(Repeater)
-          repeaters << t
-        end
-      end
-      repeaters.sort.reverse
-    end
-    
-    # Recursively finds repeaters within other repeaters.
-    # Returns a Span representing the innermost time span
-    # or nil if no repeater union could be found
-    def find_within(tags, span, pointer) #:nodoc:
-      puts "--#{span}" if Chronic.debug
-      return span if tags.empty?
-      
-      head, *rest = tags
-      head.start = pointer == :future ? span.begin : span.end
-      h = head.this(:none)
-            
-      if span.include?(h.begin) || span.include?(h.end)
-        return find_within(rest, h, pointer)
-      else
-        return nil
-      end
-    end
-    
-    def dealias_and_disambiguate_times(tokens, options) #:nodoc:
-      # handle aliases of am/pm
-      # 5:00 in the morning -> 5:00 am
-      # 7:00 in the evening -> 7:00 pm
-      
-      day_portion_index = nil
-      tokens.each_with_index do |t, i|
-        if t.get_tag(RepeaterDayPortion)
-          day_portion_index = i
-          break
-        end
-      end
-       
-      time_index = nil
-      tokens.each_with_index do |t, i|
-        if t.get_tag(RepeaterTime)
-          time_index = i
-          break
-        end
-      end
-      
-      if (day_portion_index && time_index)
-        t1 = tokens[day_portion_index]
-        t1tag = t1.get_tag(RepeaterDayPortion)
-      
-        if [:morning].include?(t1tag.type)
-          puts '--morning->am' if Chronic.debug
-          t1.untag(RepeaterDayPortion)
-          t1.tag(RepeaterDayPortion.new(:am))
-        elsif [:afternoon, :evening, :night].include?(t1tag.type)
-          puts "--#{t1tag.type}->pm" if Chronic.debug
-          t1.untag(RepeaterDayPortion)
-          t1.tag(RepeaterDayPortion.new(:pm))
-        end
-      end
-      
-      # tokens.each_with_index do |t0, i|
-      #   t1 = tokens[i + 1]
-      #   if t1 && (t1tag = t1.get_tag(RepeaterDayPortion)) && t0.get_tag(RepeaterTime)
-      #     if [:morning].include?(t1tag.type)
-      #       puts '--morning->am' if Chronic.debug
-      #       t1.untag(RepeaterDayPortion)
-      #       t1.tag(RepeaterDayPortion.new(:am))
-      #     elsif [:afternoon, :evening, :night].include?(t1tag.type)
-      #       puts "--#{t1tag.type}->pm" if Chronic.debug
-      #       t1.untag(RepeaterDayPortion)
-      #       t1.tag(RepeaterDayPortion.new(:pm))
-      #     end
-      #   end
-      # end
-            
-      # handle ambiguous times if :ambiguous_time_range is specified
-      if options[:ambiguous_time_range] != :none
-        ttokens = []
-        tokens.each_with_index do |t0, i|
-          ttokens << t0
-          t1 = tokens[i + 1]
-          if t0.get_tag(RepeaterTime) && t0.get_tag(RepeaterTime).type.ambiguous? && (!t1 || !t1.get_tag(RepeaterDayPortion))
-            distoken = Token.new('disambiguator')
-            distoken.tag(RepeaterDayPortion.new(options[:ambiguous_time_range]))
-            ttokens << distoken
-          end
-        end
-        tokens = ttokens
-      end
-      
-      tokens
-    end
-    
-  end
-  
-  class Handler #:nodoc:
-    attr_accessor :pattern, :handler_method
-    
-    def initialize(pattern, handler_method)
-      @pattern = pattern
-      @handler_method = handler_method
-    end
-    
-    def constantize(name)
-      camel = name.to_s.gsub(/(^|_)(.)/) { $2.upcase }
-      ::Chronic.module_eval(camel, __FILE__, __LINE__)
-    end
-    
-    def match(tokens, definitions)
-      token_index = 0
-      @pattern.each do |element|
-        name = element.to_s
-        optional = name.reverse[0..0] == '?'
-        name = name.chop if optional
-        if element.instance_of? Symbol
-          klass = constantize(name)
-          match = tokens[token_index] && !tokens[token_index].tags.select { |o| o.kind_of?(klass) }.empty?
-          return false if !match && !optional
-          (token_index += 1; next) if match
-          next if !match && optional
-        elsif element.instance_of? String
-          return true if optional && token_index == tokens.size
-          sub_handlers = definitions[name.intern] || raise(ChronicPain, "Invalid subset #{name} specified")
-          sub_handlers.each do |sub_handler|
-            return true if sub_handler.match(tokens[token_index..tokens.size], definitions)
-          end
-          return false
-        else
-          raise(ChronicPain, "Invalid match type: #{element.class}")
-        end
-      end
-      return false if token_index != tokens.size
-      return true
-    end
-  end
-  
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/lib/chronic/ordinal.rb b/framework/Horde_Date_Parser/chronic/lib/chronic/ordinal.rb
deleted file mode 100644 (file)
index 45b8148..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-module Chronic
-
-  class Ordinal < Tag #:nodoc:
-    def self.scan(tokens)
-      # for each token
-      tokens.each_index do |i|
-        if t = self.scan_for_ordinals(tokens[i]) then tokens[i].tag(t) end
-        if t = self.scan_for_days(tokens[i]) then tokens[i].tag(t) end
-      end
-      tokens
-    end
-  
-    def self.scan_for_ordinals(token)
-      if token.word =~ /^(\d*)(st|nd|rd|th)$/
-        return Ordinal.new($1.to_i)
-      end
-      return nil
-    end
-    
-    def self.scan_for_days(token)
-      if token.word =~ /^(\d*)(st|nd|rd|th)$/
-        unless $1.to_i > 31
-          return OrdinalDay.new(token.word.to_i)
-        end
-      end
-      return nil
-    end
-    
-    def to_s
-      'ordinal'
-    end
-  end
-  
-  class OrdinalDay < Ordinal #:nodoc:
-    def to_s
-      super << '-day-' << @type.to_s
-    end
-  end
-
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/lib/chronic/pointer.rb b/framework/Horde_Date_Parser/chronic/lib/chronic/pointer.rb
deleted file mode 100644 (file)
index 224efaf..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-module Chronic
-
-  class Pointer < Tag #:nodoc:
-    def self.scan(tokens)
-      # for each token
-      tokens.each_index do |i|
-        if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t) end
-      end
-      tokens
-    end
-  
-    def self.scan_for_all(token)
-      scanner = {/\bpast\b/ => :past,
-                 /\bfuture\b/ => :future,
-                 /\bin\b/ => :future}
-      scanner.keys.each do |scanner_item|
-        return self.new(scanner[scanner_item]) if scanner_item =~ token.word
-      end
-      return nil
-    end
-    
-    def to_s
-      'pointer-' << @type.to_s
-    end
-  end
-
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/lib/chronic/repeater.rb b/framework/Horde_Date_Parser/chronic/lib/chronic/repeater.rb
deleted file mode 100644 (file)
index 9f80daf..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-class Chronic::Repeater < Chronic::Tag #:nodoc:
-  def self.scan(tokens, options)
-    # for each token
-    tokens.each_index do |i|
-      if t = self.scan_for_month_names(tokens[i]) then tokens[i].tag(t); next end
-      if t = self.scan_for_day_names(tokens[i]) then tokens[i].tag(t); next end
-      if t = self.scan_for_day_portions(tokens[i]) then tokens[i].tag(t); next end
-      if t = self.scan_for_times(tokens[i], options) then tokens[i].tag(t); next end
-      if t = self.scan_for_units(tokens[i]) then tokens[i].tag(t); next end
-    end
-    tokens
-  end
-  
-  def self.scan_for_month_names(token)
-    scanner = {/^jan\.?(uary)?$/ => :january,
-               /^feb\.?(ruary)?$/ => :february,
-               /^mar\.?(ch)?$/ => :march,
-               /^apr\.?(il)?$/ => :april,
-               /^may$/ => :may,
-               /^jun\.?e?$/ => :june,
-               /^jul\.?y?$/ => :july,
-               /^aug\.?(ust)?$/ => :august,
-               /^sep\.?(t\.?|tember)?$/ => :september,
-               /^oct\.?(ober)?$/ => :october,
-               /^nov\.?(ember)?$/ => :november,
-               /^dec\.?(ember)?$/ => :december}
-    scanner.keys.each do |scanner_item|
-      return Chronic::RepeaterMonthName.new(scanner[scanner_item]) if scanner_item =~ token.word
-    end
-    return nil
-  end
-  
-  def self.scan_for_day_names(token)
-    scanner = {/^m[ou]n(day)?$/ => :monday,
-               /^t(ue|eu|oo|u|)s(day)?$/ => :tuesday,
-               /^tue$/ => :tuesday,
-               /^we(dnes|nds|nns)day$/ => :wednesday,
-               /^wed$/ => :wednesday,
-               /^th(urs|ers)day$/ => :thursday,
-               /^thu$/ => :thursday,
-               /^fr[iy](day)?$/ => :friday,
-               /^sat(t?[ue]rday)?$/ => :saturday,
-               /^su[nm](day)?$/ => :sunday}
-    scanner.keys.each do |scanner_item|
-      return Chronic::RepeaterDayName.new(scanner[scanner_item]) if scanner_item =~ token.word
-    end
-    return nil
-  end
-  
-  def self.scan_for_day_portions(token)
-    scanner = {/^ams?$/ => :am,
-               /^pms?$/ => :pm,
-               /^mornings?$/ => :morning,
-               /^afternoons?$/ => :afternoon,
-               /^evenings?$/ => :evening,
-               /^(night|nite)s?$/ => :night}
-    scanner.keys.each do |scanner_item|
-      return Chronic::RepeaterDayPortion.new(scanner[scanner_item]) if scanner_item =~ token.word
-    end
-    return nil
-  end
-  
-  def self.scan_for_times(token, options)
-    if token.word =~ /^\d{1,2}(:?\d{2})?([\.:]?\d{2})?$/
-      return Chronic::RepeaterTime.new(token.word, options)
-    end
-    return nil
-  end
-  
-  def self.scan_for_units(token)
-    scanner = {/^years?$/ => :year,
-               /^seasons?$/ => :season,
-               /^months?$/ => :month,
-               /^fortnights?$/ => :fortnight,
-               /^weeks?$/ => :week,
-               /^weekends?$/ => :weekend,
-               /^days?$/ => :day,
-               /^hours?$/ => :hour,
-               /^minutes?$/ => :minute,
-               /^seconds?$/ => :second}
-    scanner.keys.each do |scanner_item|
-      if scanner_item =~ token.word
-        klass_name = 'Chronic::Repeater' + scanner[scanner_item].to_s.capitalize
-        klass = eval(klass_name)
-        return klass.new(scanner[scanner_item]) 
-      end
-    end
-    return nil
-  end
-  
-  def <=>(other)
-    width <=> other.width
-  end
-  
-  # returns the width (in seconds or months) of this repeatable.
-  def width
-    raise("Repeatable#width must be overridden in subclasses")
-  end
-  
-  # returns the next occurance of this repeatable.
-  def next(pointer)
-    !@now.nil? || raise("Start point must be set before calling #next")
-    [:future, :none, :past].include?(pointer) || raise("First argument 'pointer' must be one of :past or :future")
-    #raise("Repeatable#next must be overridden in subclasses")
-  end
-  
-  def this(pointer)
-    !@now.nil? || raise("Start point must be set before calling #this")
-    [:future, :past, :none].include?(pointer) || raise("First argument 'pointer' must be one of :past, :future, :none")
-  end
-  
-  def to_s
-    'repeater'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_day.rb b/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_day.rb
deleted file mode 100644 (file)
index a92d83f..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-class Chronic::RepeaterDay < Chronic::Repeater #:nodoc:
-  DAY_SECONDS = 86_400 # (24 * 60 * 60)
-  
-  def next(pointer)
-    super
-    
-    if !@current_day_start
-      @current_day_start = Time.local(@now.year, @now.month, @now.day)
-    end
-    
-    direction = pointer == :future ? 1 : -1
-    @current_day_start += direction * DAY_SECONDS
-    
-    Chronic::Span.new(@current_day_start, @current_day_start + DAY_SECONDS)
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    case pointer
-    when :future
-      day_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
-      day_end = Time.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
-    when :past
-      day_begin = Time.construct(@now.year, @now.month, @now.day)
-      day_end = Time.construct(@now.year, @now.month, @now.day, @now.hour)
-    when :none
-      day_begin = Time.construct(@now.year, @now.month, @now.day)
-      day_end = Time.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
-    end
-    
-    Chronic::Span.new(day_begin, day_end)
-  end
-  
-  def offset(span, amount, pointer)
-    direction = pointer == :future ? 1 : -1
-    span + direction * amount * DAY_SECONDS
-  end
-  
-  def width
-    DAY_SECONDS
-  end
-  
-  def to_s
-    super << '-day'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_day_name.rb b/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_day_name.rb
deleted file mode 100644 (file)
index 0486a4d..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-class Chronic::RepeaterDayName < Chronic::Repeater #:nodoc:
-  DAY_SECONDS = 86400 # (24 * 60 * 60)
-  
-  def next(pointer)
-    super
-    
-    direction = pointer == :future ? 1 : -1
-    
-    if !@current_day_start
-      @current_day_start = Time.construct(@now.year, @now.month, @now.day)
-      @current_day_start += direction * DAY_SECONDS
-
-      day_num = symbol_to_number(@type)
-      
-      while @current_day_start.wday != day_num
-        @current_day_start += direction * DAY_SECONDS
-      end
-    else
-      @current_day_start += direction * 7 * DAY_SECONDS
-    end
-    
-    Chronic::Span.new(@current_day_start, @current_day_start + DAY_SECONDS)
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    pointer = :future if pointer == :none
-    self.next(pointer)
-  end
-  
-  def width
-    DAY_SECONDS
-  end
-  
-  def to_s
-    super << '-dayname-' << @type.to_s
-  end
-  
-  private
-  
-  def symbol_to_number(sym)
-    lookup = {:sunday => 0, :monday => 1, :tuesday => 2, :wednesday => 3, :thursday => 4, :friday => 5, :saturday => 6}
-    lookup[sym] || raise("Invalid symbol specified")
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_day_portion.rb b/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_day_portion.rb
deleted file mode 100644 (file)
index c854933..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-class Chronic::RepeaterDayPortion < Chronic::Repeater #:nodoc:
-  @@morning = (6 * 60 * 60)..(12 * 60 * 60) # 6am-12am
-  @@afternoon = (13 * 60 * 60)..(17 * 60 * 60) # 1pm-5pm
-  @@evening = (17 * 60 * 60)..(20 * 60 * 60) # 5pm-8pm
-  @@night = (20 * 60 * 60)..(24 * 60 * 60) # 8pm-12pm
-  
-  def initialize(type)
-    super
-    
-    if type.kind_of? Integer
-      @range = (@type * 60 * 60)..((@type + 12) * 60 * 60)
-    else
-      lookup = {:am => 0..(12 * 60 * 60 - 1),
-                :pm => (12 * 60 * 60)..(24 * 60 * 60 - 1),
-                :morning => @@morning,
-                :afternoon => @@afternoon,
-                :evening => @@evening,
-                :night => @@night}
-      @range = lookup[type]
-      lookup[type] || raise("Invalid type '#{type}' for RepeaterDayPortion")
-    end
-    @range || raise("Range should have been set by now")
-  end
-  
-  def next(pointer)
-    super
-    
-    full_day = 60 * 60 * 24
-    
-    if !@current_span
-      now_seconds = @now - Time.construct(@now.year, @now.month, @now.day)
-      if now_seconds < @range.begin
-        case pointer
-        when :future
-          range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
-        when :past
-          range_start = Time.construct(@now.year, @now.month, @now.day) - full_day + @range.begin
-        end
-      elsif now_seconds > @range.end
-        case pointer
-        when :future
-          range_start = Time.construct(@now.year, @now.month, @now.day) + full_day + @range.begin
-        when :past
-          range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
-        end
-      else
-        case pointer
-        when :future
-          range_start = Time.construct(@now.year, @now.month, @now.day) + full_day + @range.begin
-        when :past
-          range_start = Time.construct(@now.year, @now.month, @now.day) - full_day + @range.begin
-        end
-      end
-      
-      @current_span = Chronic::Span.new(range_start, range_start + (@range.end - @range.begin))
-    else
-      case pointer
-      when :future
-        @current_span += full_day
-      when :past
-        @current_span -= full_day
-      end
-    end
-  end
-  
-  def this(context = :future)
-    super
-    
-    range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
-    @current_span = Chronic::Span.new(range_start, range_start + (@range.end - @range.begin))
-  end
-  
-  def offset(span, amount, pointer)
-    @now = span.begin
-    portion_span = self.next(pointer)
-    direction = pointer == :future ? 1 : -1
-    portion_span + (direction * (amount - 1) * Chronic::RepeaterDay::DAY_SECONDS)
-  end
-  
-  def width
-    @range || raise("Range has not been set")
-    return @current_span.width if @current_span
-    if @type.kind_of? Integer
-      return (12 * 60 * 60)
-    else
-      @range.end - @range.begin
-    end
-  end
-  
-  def to_s
-    super << '-dayportion-' << @type.to_s
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_fortnight.rb b/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_fortnight.rb
deleted file mode 100644 (file)
index 058fbb9..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-class Chronic::RepeaterFortnight < Chronic::Repeater #:nodoc:
-  FORTNIGHT_SECONDS = 1_209_600 # (14 * 24 * 60 * 60)
-  
-  def next(pointer)
-    super
-    
-    if !@current_fortnight_start
-      case pointer
-      when :future
-        sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
-        sunday_repeater.start = @now
-        next_sunday_span = sunday_repeater.next(:future)
-        @current_fortnight_start = next_sunday_span.begin
-      when :past
-        sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
-        sunday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
-        2.times { sunday_repeater.next(:past) }
-        last_sunday_span = sunday_repeater.next(:past)
-        @current_fortnight_start = last_sunday_span.begin
-      end
-    else
-      direction = pointer == :future ? 1 : -1
-      @current_fortnight_start += direction * FORTNIGHT_SECONDS
-    end
-    
-    Chronic::Span.new(@current_fortnight_start, @current_fortnight_start + FORTNIGHT_SECONDS)
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    pointer = :future if pointer == :none
-    
-    case pointer
-    when :future
-      this_fortnight_start = Time.construct(@now.year, @now.month, @now.day, @now.hour) + Chronic::RepeaterHour::HOUR_SECONDS
-      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
-      sunday_repeater.start = @now
-      sunday_repeater.this(:future)
-      this_sunday_span = sunday_repeater.this(:future)
-      this_fortnight_end = this_sunday_span.begin
-      Chronic::Span.new(this_fortnight_start, this_fortnight_end)
-    when :past
-      this_fortnight_end = Time.construct(@now.year, @now.month, @now.day, @now.hour)
-      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
-      sunday_repeater.start = @now
-      last_sunday_span = sunday_repeater.next(:past)
-      this_fortnight_start = last_sunday_span.begin
-      Chronic::Span.new(this_fortnight_start, this_fortnight_end)
-    end
-  end
-  
-  def offset(span, amount, pointer)
-    direction = pointer == :future ? 1 : -1
-    span + direction * amount * FORTNIGHT_SECONDS
-  end
-
-  def width
-    FORTNIGHT_SECONDS
-  end
-  
-  def to_s
-    super << '-fortnight'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_hour.rb b/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_hour.rb
deleted file mode 100644 (file)
index f38a3f8..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-class Chronic::RepeaterHour < Chronic::Repeater #:nodoc:
-  HOUR_SECONDS = 3600 # 60 * 60
-  
-  def next(pointer)
-    super
-    
-    if !@current_hour_start
-      case pointer
-      when :future
-        @current_hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
-      when :past
-        @current_hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour - 1)
-      end
-    else
-      direction = pointer == :future ? 1 : -1
-      @current_hour_start += direction * HOUR_SECONDS
-    end
-    
-    Chronic::Span.new(@current_hour_start, @current_hour_start + HOUR_SECONDS)
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    case pointer
-    when :future
-      hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min + 1)
-      hour_end = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
-    when :past
-      hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour)
-      hour_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
-    when :none
-      hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour)
-      hour_end = hour_begin + HOUR_SECONDS
-    end
-    
-    Chronic::Span.new(hour_start, hour_end)
-  end
-  
-  def offset(span, amount, pointer)
-    direction = pointer == :future ? 1 : -1
-    span + direction * amount * HOUR_SECONDS
-  end
-  
-  def width
-    HOUR_SECONDS
-  end
-  
-  def to_s
-    super << '-hour'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_minute.rb b/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_minute.rb
deleted file mode 100644 (file)
index 342d3cd..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-class Chronic::RepeaterMinute < Chronic::Repeater #:nodoc:
-  MINUTE_SECONDS = 60
-  
-  def next(pointer = :future)
-    super
-    
-    if !@current_minute_start
-      case pointer
-      when :future
-        @current_minute_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min + 1)
-      when :past
-        @current_minute_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min - 1)
-      end
-    else
-      direction = pointer == :future ? 1 : -1
-      @current_minute_start += direction * MINUTE_SECONDS
-    end
-    
-    Chronic::Span.new(@current_minute_start, @current_minute_start + MINUTE_SECONDS)
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    case pointer
-    when :future
-      minute_begin = @now
-      minute_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
-    when :past
-      minute_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
-      minute_end = @now
-    when :none
-      minute_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
-      minute_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min) + MINUTE_SECONDS
-    end
-    
-    Chronic::Span.new(minute_begin, minute_end)
-  end
-  
-  def offset(span, amount, pointer)
-    direction = pointer == :future ? 1 : -1
-    span + direction * amount * MINUTE_SECONDS
-  end
-  
-  def width
-    MINUTE_SECONDS
-  end
-  
-  def to_s
-    super << '-minute'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_month.rb b/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_month.rb
deleted file mode 100644 (file)
index edd89ee..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-class Chronic::RepeaterMonth < Chronic::Repeater #:nodoc:
-  MONTH_SECONDS = 2_592_000 # 30 * 24 * 60 * 60
-  YEAR_MONTHS = 12
-  
-  def next(pointer)
-    super
-    
-    if !@current_month_start
-      @current_month_start = offset_by(Time.construct(@now.year, @now.month), 1, pointer)
-    else
-      @current_month_start = offset_by(Time.construct(@current_month_start.year, @current_month_start.month), 1, pointer)
-    end
-    
-    Chronic::Span.new(@current_month_start, Time.construct(@current_month_start.year, @current_month_start.month + 1))
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    case pointer
-    when :future
-      month_start = Time.construct(@now.year, @now.month, @now.day + 1)
-      month_end = self.offset_by(Time.construct(@now.year, @now.month), 1, :future)
-    when :past
-      month_start = Time.construct(@now.year, @now.month)
-      month_end = Time.construct(@now.year, @now.month, @now.day)
-    when :none
-      month_start = Time.construct(@now.year, @now.month)
-      month_end = self.offset_by(Time.construct(@now.year, @now.month), 1, :future)
-    end
-    
-    Chronic::Span.new(month_start, month_end)
-  end
-  
-  def offset(span, amount, pointer)      
-    Chronic::Span.new(offset_by(span.begin, amount, pointer), offset_by(span.end, amount, pointer))
-  end
-  
-  def offset_by(time, amount, pointer) 
-    direction = pointer == :future ? 1 : -1
-    
-    amount_years = direction * amount / YEAR_MONTHS
-    amount_months = direction * amount % YEAR_MONTHS
-    
-    new_year = time.year + amount_years
-    new_month = time.month + amount_months
-    if new_month > YEAR_MONTHS
-      new_year += 1
-      new_month -= YEAR_MONTHS
-    end
-    Time.construct(new_year, new_month, time.day, time.hour, time.min, time.sec)
-  end
-  
-  def width
-    MONTH_SECONDS
-  end
-  
-  def to_s
-    super << '-month'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_month_name.rb b/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_month_name.rb
deleted file mode 100644 (file)
index 1f8b748..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-class Chronic::RepeaterMonthName < Chronic::Repeater #:nodoc:
-  MONTH_SECONDS = 2_592_000 # 30 * 24 * 60 * 60
-  
-  def next(pointer)
-    super
-    
-    if !@current_month_begin
-      target_month = symbol_to_number(@type)
-      case pointer
-      when :future
-        if @now.month < target_month
-          @current_month_begin = Time.construct(@now.year, target_month)
-        else @now.month > target_month
-          @current_month_begin = Time.construct(@now.year + 1, target_month)
-        end
-      when :none
-        if @now.month <= target_month
-          @current_month_begin = Time.construct(@now.year, target_month)
-        else @now.month > target_month
-          @current_month_begin = Time.construct(@now.year + 1, target_month)
-        end
-      when :past
-        if @now.month > target_month
-          @current_month_begin = Time.construct(@now.year, target_month)
-        else @now.month < target_month
-          @current_month_begin = Time.construct(@now.year - 1, target_month)
-        end
-      end
-      @current_month_begin || raise("Current month should be set by now")
-    else
-      case pointer
-      when :future
-        @current_month_begin = Time.construct(@current_month_begin.year + 1, @current_month_begin.month)
-      when :past
-        @current_month_begin = Time.construct(@current_month_begin.year - 1, @current_month_begin.month)
-      end
-    end
-    
-    cur_month_year = @current_month_begin.year
-    cur_month_month = @current_month_begin.month
-    
-    if cur_month_month == 12
-      next_month_year = cur_month_year + 1
-      next_month_month = 1
-    else
-      next_month_year = cur_month_year
-      next_month_month = cur_month_month + 1
-    end
-      
-    Chronic::Span.new(@current_month_begin, Time.construct(next_month_year, next_month_month))
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    case pointer
-    when :past
-      self.next(pointer)
-    when :future, :none
-      self.next(:none)
-    end
-  end
-  
-  def width
-    MONTH_SECONDS
-  end
-  
-  def index
-    symbol_to_number(@type)
-  end
-  
-  def to_s
-    super << '-monthname-' << @type.to_s
-  end
-  
-  private
-  
-  def symbol_to_number(sym)
-    lookup = {:january => 1,
-              :february => 2,
-              :march => 3,
-              :april => 4,
-              :may => 5,
-              :june => 6,
-              :july => 7,
-              :august => 8,
-              :september => 9,
-              :october => 10,
-              :november => 11,
-              :december => 12}
-    lookup[sym] || raise("Invalid symbol specified")
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_season.rb b/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_season.rb
deleted file mode 100644 (file)
index a255865..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-class Chronic::RepeaterSeason < Chronic::Repeater #:nodoc:
-  SEASON_SECONDS = 7_862_400 # 91 * 24 * 60 * 60
-  
-  def next(pointer)
-    super
-    
-    raise 'Not implemented'
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    raise 'Not implemented'
-  end
-  
-  def width
-    SEASON_SECONDS
-  end
-  
-  def to_s
-    super << '-season'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_season_name.rb b/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_season_name.rb
deleted file mode 100644 (file)
index adfd1f2..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-class Chronic::RepeaterSeasonName < Chronic::RepeaterSeason #:nodoc:
-  @summer = ['jul 21', 'sep 22']
-  @autumn = ['sep 23', 'dec 21']
-  @winter = ['dec 22', 'mar 19']
-  @spring = ['mar 20', 'jul 20']
-  
-  def next(pointer)
-    super
-    raise 'Not implemented'
-  end
-  
-  def this(pointer = :future)
-    super
-    raise 'Not implemented'
-  end
-  
-  def width
-    (91 * 24 * 60 * 60)
-  end
-  
-  def to_s
-    super << '-season-' << @type.to_s
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_second.rb b/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_second.rb
deleted file mode 100644 (file)
index 6d05545..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-class Chronic::RepeaterSecond < Chronic::Repeater #:nodoc:
-  SECOND_SECONDS = 1 # haha, awesome
-  
-  def next(pointer = :future)
-    super
-    
-    direction = pointer == :future ? 1 : -1
-    
-    if !@second_start
-      @second_start = @now + (direction * SECOND_SECONDS)
-    else
-      @second_start += SECOND_SECONDS * direction
-    end
-    
-    Chronic::Span.new(@second_start, @second_start + SECOND_SECONDS)
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    Chronic::Span.new(@now, @now + 1)
-  end
-  
-  def offset(span, amount, pointer)
-    direction = pointer == :future ? 1 : -1
-    span + direction * amount * SECOND_SECONDS
-  end
-  
-  def width
-    SECOND_SECONDS
-  end
-  
-  def to_s
-    super << '-second'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_time.rb b/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_time.rb
deleted file mode 100644 (file)
index f856014..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-class Chronic::RepeaterTime < Chronic::Repeater #:nodoc:
-  class Tick #:nodoc:
-    attr_accessor :time
-    
-    def initialize(time, ambiguous = false)
-      @time = time
-      @ambiguous = ambiguous
-    end
-    
-    def ambiguous?
-      @ambiguous
-    end
-    
-    def *(other)
-      Tick.new(@time * other, @ambiguous)
-    end
-    
-    def to_f
-      @time.to_f
-    end
-    
-    def to_s
-      @time.to_s + (@ambiguous ? '?' : '')
-    end
-  end
-  
-  def initialize(time, options = {})
-    t = time.gsub(/\:/, '')
-    @type = 
-    if (1..2) === t.size
-      hours = t.to_i
-      hours == 12 ? Tick.new(0 * 60 * 60, true) : Tick.new(hours * 60 * 60, true)
-    elsif t.size == 3
-      Tick.new((t[0..0].to_i * 60 * 60) + (t[1..2].to_i * 60), true)
-    elsif t.size == 4
-      ambiguous = time =~ /:/ && t[0..0].to_i != 0 && t[0..1].to_i <= 12
-      hours = t[0..1].to_i
-      hours == 12 ? Tick.new(0 * 60 * 60 + t[2..3].to_i * 60, ambiguous) : Tick.new(hours * 60 * 60 + t[2..3].to_i * 60, ambiguous)
-    elsif t.size == 5
-      Tick.new(t[0..0].to_i * 60 * 60 + t[1..2].to_i * 60 + t[3..4].to_i, true)
-    elsif t.size == 6
-      ambiguous = time =~ /:/ && t[0..0].to_i != 0 && t[0..1].to_i <= 12
-      hours = t[0..1].to_i
-      hours == 12 ? Tick.new(0 * 60 * 60 + t[2..3].to_i * 60 + t[4..5].to_i, ambiguous) : Tick.new(hours * 60 * 60 + t[2..3].to_i * 60 + t[4..5].to_i, ambiguous)
-    else
-      raise("Time cannot exceed six digits")
-    end
-  end
-  
-  # Return the next past or future Span for the time that this Repeater represents
-  #   pointer - Symbol representing which temporal direction to fetch the next day
-  #             must be either :past or :future
-  def next(pointer)
-    super
-    
-    half_day = 60 * 60 * 12
-    full_day = 60 * 60 * 24
-    
-    first = false
-    
-    unless @current_time
-      first = true
-      midnight = Time.local(@now.year, @now.month, @now.day)
-      yesterday_midnight = midnight - full_day
-      tomorrow_midnight = midnight + full_day
-
-      catch :done do
-        if pointer == :future
-          if @type.ambiguous?
-            [midnight + @type, midnight + half_day + @type, tomorrow_midnight + @type].each do |t|
-              (@current_time = t; throw :done) if t >= @now
-            end
-          else
-            [midnight + @type, tomorrow_midnight + @type].each do |t|
-              (@current_time = t; throw :done) if t >= @now
-            end
-          end
-        else # pointer == :past
-          if @type.ambiguous?
-            [midnight + half_day + @type, midnight + @type, yesterday_midnight + @type * 2].each do |t|
-              (@current_time = t; throw :done) if t <= @now
-            end
-          else
-            [midnight + @type, yesterday_midnight + @type].each do |t|
-              (@current_time = t; throw :done) if t <= @now
-            end
-          end
-        end
-      end
-      
-      @current_time || raise("Current time cannot be nil at this point")
-    end
-    
-    unless first
-      increment = @type.ambiguous? ? half_day : full_day
-      @current_time += pointer == :future ? increment : -increment
-    end
-    
-    Chronic::Span.new(@current_time, @current_time + width)
-  end
-  
-  def this(context = :future)
-    super
-    
-    context = :future if context == :none
-    
-    self.next(context)
-  end
-  
-  def width
-    1
-  end
-  
-  def to_s
-    super << '-time-' << @type.to_s
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_week.rb b/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_week.rb
deleted file mode 100644 (file)
index ec88ff1..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-class Chronic::RepeaterWeek < Chronic::Repeater #:nodoc:
-  WEEK_SECONDS = 604800 # (7 * 24 * 60 * 60)
-  
-  def next(pointer)
-    super
-    
-    if !@current_week_start
-      case pointer
-      when :future
-        sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
-        sunday_repeater.start = @now
-        next_sunday_span = sunday_repeater.next(:future)
-        @current_week_start = next_sunday_span.begin
-      when :past
-        sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
-        sunday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
-        sunday_repeater.next(:past)
-        last_sunday_span = sunday_repeater.next(:past)
-        @current_week_start = last_sunday_span.begin
-      end
-    else
-      direction = pointer == :future ? 1 : -1
-      @current_week_start += direction * WEEK_SECONDS
-    end
-    
-    Chronic::Span.new(@current_week_start, @current_week_start + WEEK_SECONDS)
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    case pointer
-    when :future
-      this_week_start = Time.local(@now.year, @now.month, @now.day, @now.hour) + Chronic::RepeaterHour::HOUR_SECONDS
-      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
-      sunday_repeater.start = @now
-      this_sunday_span = sunday_repeater.this(:future)
-      this_week_end = this_sunday_span.begin
-      Chronic::Span.new(this_week_start, this_week_end)
-    when :past
-      this_week_end = Time.local(@now.year, @now.month, @now.day, @now.hour)
-      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
-      sunday_repeater.start = @now
-      last_sunday_span = sunday_repeater.next(:past)
-      this_week_start = last_sunday_span.begin
-      Chronic::Span.new(this_week_start, this_week_end)
-    when :none
-      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
-      sunday_repeater.start = @now
-      last_sunday_span = sunday_repeater.next(:past)
-      this_week_start = last_sunday_span.begin
-      Chronic::Span.new(this_week_start, this_week_start + WEEK_SECONDS)
-    end
-  end
-  
-  def offset(span, amount, pointer)
-    direction = pointer == :future ? 1 : -1
-    span + direction * amount * WEEK_SECONDS
-  end
-  
-  def width
-    WEEK_SECONDS
-  end
-  
-  def to_s
-    super << '-week'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_weekend.rb b/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_weekend.rb
deleted file mode 100644 (file)
index f012267..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-class Chronic::RepeaterWeekend < Chronic::Repeater #:nodoc:
-  WEEKEND_SECONDS = 172_800 # (2 * 24 * 60 * 60)
-  
-  def next(pointer)
-    super
-    
-    if !@current_week_start
-      case pointer
-      when :future
-        saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
-        saturday_repeater.start = @now
-        next_saturday_span = saturday_repeater.next(:future)
-        @current_week_start = next_saturday_span.begin
-      when :past
-        saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
-        saturday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
-        last_saturday_span = saturday_repeater.next(:past)
-        @current_week_start = last_saturday_span.begin
-      end
-    else
-      direction = pointer == :future ? 1 : -1
-      @current_week_start += direction * Chronic::RepeaterWeek::WEEK_SECONDS
-    end
-    
-    Chronic::Span.new(@current_week_start, @current_week_start + WEEKEND_SECONDS)
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    case pointer
-    when :future, :none
-      saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
-      saturday_repeater.start = @now
-      this_saturday_span = saturday_repeater.this(:future)
-      Chronic::Span.new(this_saturday_span.begin, this_saturday_span.begin + WEEKEND_SECONDS)
-    when :past
-      saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
-      saturday_repeater.start = @now
-      last_saturday_span = saturday_repeater.this(:past)
-      Chronic::Span.new(last_saturday_span.begin, last_saturday_span.begin + WEEKEND_SECONDS)
-    end
-  end
-  
-  def offset(span, amount, pointer)
-    direction = pointer == :future ? 1 : -1
-    weekend = Chronic::RepeaterWeekend.new(:weekend)
-    weekend.start = span.begin
-    start = weekend.next(pointer).begin + (amount - 1) * direction * Chronic::RepeaterWeek::WEEK_SECONDS
-    Chronic::Span.new(start, start + (span.end - span.begin))
-  end
-  
-  def width
-    WEEKEND_SECONDS
-  end
-  
-  def to_s
-    super << '-weekend'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_year.rb b/framework/Horde_Date_Parser/chronic/lib/chronic/repeaters/repeater_year.rb
deleted file mode 100644 (file)
index 426371f..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-class Chronic::RepeaterYear < Chronic::Repeater #:nodoc:
-  
-  def next(pointer)
-    super
-    
-    if !@current_year_start
-      case pointer
-      when :future
-        @current_year_start = Time.construct(@now.year + 1)
-      when :past
-        @current_year_start = Time.construct(@now.year - 1)
-      end
-    else
-      diff = pointer == :future ? 1 : -1
-      @current_year_start = Time.construct(@current_year_start.year + diff)
-    end
-    
-    Chronic::Span.new(@current_year_start, Time.construct(@current_year_start.year + 1))
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    case pointer
-    when :future
-      this_year_start = Time.construct(@now.year, @now.month, @now.day) + Chronic::RepeaterDay::DAY_SECONDS
-      this_year_end = Time.construct(@now.year + 1, 1, 1)
-    when :past
-      this_year_start = Time.construct(@now.year, 1, 1)
-      this_year_end = Time.construct(@now.year, @now.month, @now.day)
-    when :none
-      this_year_start = Time.construct(@now.year, 1, 1)
-      this_year_end = Time.construct(@now.year + 1, 1, 1)
-    end
-    
-    Chronic::Span.new(this_year_start, this_year_end)
-  end
-  
-  def offset(span, amount, pointer)
-    direction = pointer == :future ? 1 : -1
-    
-    sb = span.begin
-    new_begin = Time.construct(sb.year + (amount * direction), sb.month, sb.day, sb.hour, sb.min, sb.sec)
-    
-    se = span.end
-    new_end = Time.construct(se.year + (amount * direction), se.month, se.day, se.hour, se.min, se.sec)
-    
-    Chronic::Span.new(new_begin, new_end)
-  end
-  
-  def width
-    (365 * 24 * 60 * 60)
-  end
-  
-  def to_s
-    super << '-year'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/lib/chronic/scalar.rb b/framework/Horde_Date_Parser/chronic/lib/chronic/scalar.rb
deleted file mode 100644 (file)
index b08cfee..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-module Chronic
-
-  class Scalar < Tag #:nodoc:
-    def self.scan(tokens)
-      # for each token
-      tokens.each_index do |i|
-        if t = self.scan_for_scalars(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
-        if t = self.scan_for_days(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
-        if t = self.scan_for_months(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
-        if t = self.scan_for_years(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
-      end
-      tokens
-    end
-  
-    def self.scan_for_scalars(token, post_token)
-      if token.word =~ /^\d*$/
-        unless post_token && %w{am pm morning afternoon evening night}.include?(post_token)
-          return Scalar.new(token.word.to_i)
-        end
-      end
-      return nil
-    end
-    
-    def self.scan_for_days(token, post_token)
-      if token.word =~ /^\d\d?$/
-        unless token.word.to_i > 31 || (post_token && %w{am pm morning afternoon evening night}.include?(post_token))
-          return ScalarDay.new(token.word.to_i)
-        end
-      end
-      return nil
-    end
-    
-    def self.scan_for_months(token, post_token)
-      if token.word =~ /^\d\d?$/
-        unless token.word.to_i > 12 || (post_token && %w{am pm morning afternoon evening night}.include?(post_token))
-          return ScalarMonth.new(token.word.to_i)
-        end
-      end
-      return nil
-    end
-    
-    def self.scan_for_years(token, post_token)
-      if token.word =~ /^([1-9]\d)?\d\d?$/
-        unless post_token && %w{am pm morning afternoon evening night}.include?(post_token)
-          return ScalarYear.new(token.word.to_i)
-        end
-      end
-      return nil
-    end
-    
-    def to_s
-      'scalar'
-    end
-  end
-  
-  class ScalarDay < Scalar #:nodoc:
-    def to_s
-      super << '-day-' << @type.to_s
-    end
-  end
-  
-  class ScalarMonth < Scalar #:nodoc:
-    def to_s
-      super << '-month-' << @type.to_s
-    end
-  end
-  
-  class ScalarYear < Scalar #:nodoc:
-    def to_s
-      super << '-year-' << @type.to_s
-    end
-  end
-
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/lib/chronic/separator.rb b/framework/Horde_Date_Parser/chronic/lib/chronic/separator.rb
deleted file mode 100644 (file)
index 86c56e3..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-module Chronic
-
-  class Separator < Tag #:nodoc:
-    def self.scan(tokens)
-      tokens.each_index do |i|
-        if t = self.scan_for_commas(tokens[i]) then tokens[i].tag(t); next end
-        if t = self.scan_for_slash_or_dash(tokens[i]) then tokens[i].tag(t); next end
-        if t = self.scan_for_at(tokens[i]) then tokens[i].tag(t); next end
-        if t = self.scan_for_in(tokens[i]) then tokens[i].tag(t); next end
-      end
-      tokens
-    end
-    
-    def self.scan_for_commas(token)
-      scanner = {/^,$/ => :comma}
-      scanner.keys.each do |scanner_item|
-        return SeparatorComma.new(scanner[scanner_item]) if scanner_item =~ token.word
-      end
-      return nil
-    end
-    
-    def self.scan_for_slash_or_dash(token)
-      scanner = {/^-$/ => :dash,
-                 /^\/$/ => :slash}
-      scanner.keys.each do |scanner_item|
-        return SeparatorSlashOrDash.new(scanner[scanner_item]) if scanner_item =~ token.word
-      end
-      return nil
-    end
-    
-    def self.scan_for_at(token)
-      scanner = {/^(at|@)$/ => :at}
-      scanner.keys.each do |scanner_item|
-        return SeparatorAt.new(scanner[scanner_item]) if scanner_item =~ token.word
-      end
-      return nil
-    end
-    
-    def self.scan_for_in(token)
-      scanner = {/^in$/ => :in}
-      scanner.keys.each do |scanner_item|
-        return SeparatorIn.new(scanner[scanner_item]) if scanner_item =~ token.word
-      end
-      return nil
-    end
-    
-    def to_s
-      'separator'
-    end
-  end
-  
-  class SeparatorComma < Separator #:nodoc:
-    def to_s
-      super << '-comma'
-    end
-  end
-  
-  class SeparatorSlashOrDash < Separator #:nodoc:
-    def to_s
-      super << '-slashordash-' << @type.to_s
-    end
-  end
-  
-  class SeparatorAt < Separator #:nodoc:
-    def to_s
-      super << '-at'
-    end
-  end
-  
-  class SeparatorIn < Separator #:nodoc:
-    def to_s
-      super << '-in'
-    end
-  end
-
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/lib/chronic/time_zone.rb b/framework/Horde_Date_Parser/chronic/lib/chronic/time_zone.rb
deleted file mode 100644 (file)
index 41041ef..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-module Chronic
-  class TimeZone < Tag #:nodoc:    
-    def self.scan(tokens)
-      tokens.each_index do |i|
-        if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t); next end
-      end
-      tokens
-    end
-
-    def self.scan_for_all(token)
-      scanner = {/[PMCE][DS]T/i => :tz}
-      scanner.keys.each do |scanner_item|
-        return self.new(scanner[scanner_item]) if scanner_item =~ token.word
-      end
-      return nil
-    end
-
-    def to_s
-      'timezone'
-    end
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/test/suite.rb b/framework/Horde_Date_Parser/chronic/test/suite.rb
deleted file mode 100644 (file)
index fa8bdaa..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-require 'test/unit'
-
-tests = Dir["#{File.dirname(__FILE__)}/test_*.rb"]
-tests.delete_if { |o| o =~ /test_parsing/ }
-tests.each do |file|
-  require file
-end
-
-require File.dirname(__FILE__) + '/test_parsing.rb'
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/test/test_Chronic.rb b/framework/Horde_Date_Parser/chronic/test/test_Chronic.rb
deleted file mode 100644 (file)
index 04fedb5..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-require 'chronic'
-require 'test/unit'
-
-class TestChronic < Test::Unit::TestCase
-  
-  def setup
-    # Wed Aug 16 14:00:00 UTC 2006
-    @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
-  end
-  
-  def test_post_normalize_am_pm_aliases
-    # affect wanted patterns
-    
-    tokens = [Chronic::Token.new("5:00"), Chronic::Token.new("morning")]
-    tokens[0].tag(Chronic::RepeaterTime.new("5:00"))
-    tokens[1].tag(Chronic::RepeaterDayPortion.new(:morning))
-    
-    assert_equal :morning, tokens[1].tags[0].type
-    
-    tokens = Chronic.dealias_and_disambiguate_times(tokens, {})
-    
-    assert_equal :am, tokens[1].tags[0].type
-    assert_equal 2, tokens.size
-    
-    # don't affect unwanted patterns
-    
-    tokens = [Chronic::Token.new("friday"), Chronic::Token.new("morning")]
-    tokens[0].tag(Chronic::RepeaterDayName.new(:friday))
-    tokens[1].tag(Chronic::RepeaterDayPortion.new(:morning))
-    
-    assert_equal :morning, tokens[1].tags[0].type
-    
-    tokens = Chronic.dealias_and_disambiguate_times(tokens, {})
-    
-    assert_equal :morning, tokens[1].tags[0].type
-    assert_equal 2, tokens.size
-  end
-  
-  def test_guess
-    span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0))
-    assert_equal Time.local(2006, 8, 16, 12), Chronic.guess(span)
-    
-    span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0, 0, 1))
-    assert_equal Time.local(2006, 8, 16, 12), Chronic.guess(span)
-    
-    span = Chronic::Span.new(Time.local(2006, 11), Time.local(2006, 12))
-    assert_equal Time.local(2006, 11, 16), Chronic.guess(span)
-  end
-  
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/test/test_Handler.rb b/framework/Horde_Date_Parser/chronic/test/test_Handler.rb
deleted file mode 100644 (file)
index 4e36dfe..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-require 'chronic'
-require 'test/unit'
-
-class TestHandler < Test::Unit::TestCase
-  
-  def setup
-    # Wed Aug 16 14:00:00 UTC 2006
-    @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
-  end
-
-  def test_handler_class_1
-    handler = Chronic::Handler.new([:repeater], :handler)
-    
-    tokens = [Chronic::Token.new('friday')]
-    tokens[0].tag(Chronic::RepeaterDayName.new(:friday))
-    
-    assert handler.match(tokens, Chronic.definitions)
-    
-    tokens << Chronic::Token.new('afternoon')
-    tokens[1].tag(Chronic::RepeaterDayPortion.new(:afternoon))
-    
-    assert !handler.match(tokens, Chronic.definitions)
-  end
-  
-  def test_handler_class_2
-    handler = Chronic::Handler.new([:repeater, :repeater?], :handler)
-    
-    tokens = [Chronic::Token.new('friday')]
-    tokens[0].tag(Chronic::RepeaterDayName.new(:friday))
-    
-    assert handler.match(tokens, Chronic.definitions)
-    
-    tokens << Chronic::Token.new('afternoon')
-    tokens[1].tag(Chronic::RepeaterDayPortion.new(:afternoon))
-    
-    assert handler.match(tokens, Chronic.definitions)
-    
-    tokens << Chronic::Token.new('afternoon')
-    tokens[2].tag(Chronic::RepeaterDayPortion.new(:afternoon))
-    
-    assert !handler.match(tokens, Chronic.definitions)
-  end
-  
-  def test_handler_class_3
-    handler = Chronic::Handler.new([:repeater, 'time?'], :handler)
-    
-    tokens = [Chronic::Token.new('friday')]
-    tokens[0].tag(Chronic::RepeaterDayName.new(:friday))
-    
-    assert handler.match(tokens, Chronic.definitions)
-    
-    tokens << Chronic::Token.new('afternoon')
-    tokens[1].tag(Chronic::RepeaterDayPortion.new(:afternoon))
-    
-    assert !handler.match(tokens, Chronic.definitions)
-  end
-  
-  def test_handler_class_4
-    handler = Chronic::Handler.new([:repeater_month_name, :scalar_day, 'time?'], :handler)
-    
-    tokens = [Chronic::Token.new('may')]
-    tokens[0].tag(Chronic::RepeaterMonthName.new(:may))
-    
-    assert !handler.match(tokens, Chronic.definitions)
-    
-    tokens << Chronic::Token.new('27')
-    tokens[1].tag(Chronic::ScalarDay.new(27))
-    
-    assert handler.match(tokens, Chronic.definitions)
-  end
-  
-  def test_handler_class_5
-    handler = Chronic::Handler.new([:repeater, 'time?'], :handler)
-    
-    tokens = [Chronic::Token.new('friday')]
-    tokens[0].tag(Chronic::RepeaterDayName.new(:friday))
-    
-    assert handler.match(tokens, Chronic.definitions)
-    
-    tokens << Chronic::Token.new('5:00')
-    tokens[1].tag(Chronic::RepeaterTime.new('5:00'))
-    
-    assert handler.match(tokens, Chronic.definitions)
-    
-    tokens << Chronic::Token.new('pm')
-    tokens[2].tag(Chronic::RepeaterDayPortion.new(:pm))
-    
-    assert handler.match(tokens, Chronic.definitions)
-  end
-  
-  def test_handler_class_6
-    handler = Chronic::Handler.new([:scalar, :repeater, :pointer], :handler)
-    
-    tokens = [Chronic::Token.new('3'),
-              Chronic::Token.new('years'),
-              Chronic::Token.new('past')]
-              
-    tokens[0].tag(Chronic::Scalar.new(3))
-    tokens[1].tag(Chronic::RepeaterYear.new(:year))
-    tokens[2].tag(Chronic::Pointer.new(:past))
-    
-    assert handler.match(tokens, Chronic.definitions)
-  end
-  
-  def test_constantize
-    handler = Chronic::Handler.new([], :handler)
-    assert_equal Chronic::RepeaterTime, handler.constantize(:repeater_time)
-  end
-  
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/test/test_RepeaterDayName.rb b/framework/Horde_Date_Parser/chronic/test/test_RepeaterDayName.rb
deleted file mode 100644 (file)
index 8e119db..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-require 'chronic'
-require 'test/unit'
-
-class TestRepeaterDayName < Test::Unit::TestCase
-  
-  def setup
-    @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
-  end
-  
-  def test_match
-    token = Chronic::Token.new('saturday')
-    repeater = Chronic::Repeater.scan_for_day_names(token)
-    assert_equal Chronic::RepeaterDayName, repeater.class
-    assert_equal :saturday, repeater.type
-    
-    token = Chronic::Token.new('sunday')
-    repeater = Chronic::Repeater.scan_for_day_names(token)
-    assert_equal Chronic::RepeaterDayName, repeater.class
-    assert_equal :sunday, repeater.type
-  end
-
-  def test_next_future
-    mondays = Chronic::RepeaterDayName.new(:monday)
-    mondays.start = @now
-    
-    span = mondays.next(:future)
-    
-    assert_equal Time.local(2006, 8, 21), span.begin
-    assert_equal Time.local(2006, 8, 22), span.end 
-
-    span = mondays.next(:future)
-    
-    assert_equal Time.local(2006, 8, 28), span.begin
-    assert_equal Time.local(2006, 8, 29), span.end
-  end
-  
-  def test_next_past
-    mondays = Chronic::RepeaterDayName.new(:monday)
-    mondays.start = @now
-    
-    span = mondays.next(:past)
-    
-    assert_equal Time.local(2006, 8, 14), span.begin
-    assert_equal Time.local(2006, 8, 15), span.end 
-
-    span = mondays.next(:past)
-    
-    assert_equal Time.local(2006, 8, 7), span.begin
-    assert_equal Time.local(2006, 8, 8), span.end
-  end
-  
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/test/test_RepeaterFortnight.rb b/framework/Horde_Date_Parser/chronic/test/test_RepeaterFortnight.rb
deleted file mode 100644 (file)
index cb80c43..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-require 'chronic'
-require 'test/unit'
-
-class TestRepeaterFortnight < Test::Unit::TestCase
-  
-  def setup
-    @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
-  end
-
-  def test_next_future
-    fortnights = Chronic::RepeaterFortnight.new(:fortnight)
-    fortnights.start = @now
-    
-    next_fortnight = fortnights.next(:future)
-    assert_equal Time.local(2006, 8, 20), next_fortnight.begin
-    assert_equal Time.local(2006, 9, 3), next_fortnight.end
-    
-    next_next_fortnight = fortnights.next(:future)
-    assert_equal Time.local(2006, 9, 3), next_next_fortnight.begin
-    assert_equal Time.local(2006, 9, 17), next_next_fortnight.end
-  end
-  
-  def test_next_past
-    fortnights = Chronic::RepeaterFortnight.new(:fortnight)
-    fortnights.start = @now
-    
-    last_fortnight = fortnights.next(:past)
-    assert_equal Time.local(2006, 7, 30), last_fortnight.begin
-    assert_equal Time.local(2006, 8, 13), last_fortnight.end
-    
-    last_last_fortnight = fortnights.next(:past)
-    assert_equal Time.local(2006, 7, 16), last_last_fortnight.begin
-    assert_equal Time.local(2006, 7, 30), last_last_fortnight.end
-  end
-  
-  def test_this_future
-    fortnights = Chronic::RepeaterFortnight.new(:fortnight)
-    fortnights.start = @now
-    
-    this_fortnight = fortnights.this(:future)
-    assert_equal Time.local(2006, 8, 16, 15), this_fortnight.begin
-    assert_equal Time.local(2006, 8, 27), this_fortnight.end
-  end
-  
-  def test_this_past
-    fortnights = Chronic::RepeaterFortnight.new(:fortnight)
-    fortnights.start = @now
-    
-    this_fortnight = fortnights.this(:past)
-    assert_equal Time.local(2006, 8, 13, 0), this_fortnight.begin
-    assert_equal Time.local(2006, 8, 16, 14), this_fortnight.end
-  end
-  
-  def test_offset
-    span = Chronic::Span.new(@now, @now + 1)
-    
-    offset_span = Chronic::RepeaterWeek.new(:week).offset(span, 3, :future)
-    
-    assert_equal Time.local(2006, 9, 6, 14), offset_span.begin
-    assert_equal Time.local(2006, 9, 6, 14, 0, 1), offset_span.end
-  end
-  
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/test/test_RepeaterHour.rb b/framework/Horde_Date_Parser/chronic/test/test_RepeaterHour.rb
deleted file mode 100644 (file)
index 48f37c4..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-require 'chronic'
-require 'test/unit'
-
-class TestRepeaterHour < Test::Unit::TestCase
-  
-  def setup
-    @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
-  end
-
-  def test_next_future
-    hours = Chronic::RepeaterHour.new(:hour)
-    hours.start = @now
-    
-    next_hour = hours.next(:future)
-    assert_equal Time.local(2006, 8, 16, 15), next_hour.begin
-    assert_equal Time.local(2006, 8, 16, 16), next_hour.end
-    
-    next_next_hour = hours.next(:future)
-    assert_equal Time.local(2006, 8, 16, 16), next_next_hour.begin
-    assert_equal Time.local(2006, 8, 16, 17), next_next_hour.end
-  end
-  
-  def test_next_past
-    hours = Chronic::RepeaterHour.new(:hour)
-    hours.start = @now
-    
-    past_hour = hours.next(:past)
-    assert_equal Time.local(2006, 8, 16, 13), past_hour.begin
-    assert_equal Time.local(2006, 8, 16, 14), past_hour.end
-    
-    past_past_hour = hours.next(:past)
-    assert_equal Time.local(2006, 8, 16, 12), past_past_hour.begin
-    assert_equal Time.local(2006, 8, 16, 13), past_past_hour.end
-  end
-  
-  def test_this
-    @now = Time.local(2006, 8, 16, 14, 30)
-    
-    hours = Chronic::RepeaterHour.new(:hour)
-    hours.start = @now
-    
-    this_hour = hours.this(:future)
-    assert_equal Time.local(2006, 8, 16, 14, 31), this_hour.begin
-    assert_equal Time.local(2006, 8, 16, 15), this_hour.end
-    
-    this_hour = hours.this(:past)
-    assert_equal Time.local(2006, 8, 16, 14), this_hour.begin
-    assert_equal Time.local(2006, 8, 16, 14, 30), this_hour.end
-  end
-  
-  def test_offset
-    span = Chronic::Span.new(@now, @now + 1)
-    
-    offset_span = Chronic::RepeaterHour.new(:hour).offset(span, 3, :future)
-    
-    assert_equal Time.local(2006, 8, 16, 17), offset_span.begin
-    assert_equal Time.local(2006, 8, 16, 17, 0, 1), offset_span.end
-    
-    offset_span = Chronic::RepeaterHour.new(:hour).offset(span, 24, :past)
-    
-    assert_equal Time.local(2006, 8, 15, 14), offset_span.begin
-    assert_equal Time.local(2006, 8, 15, 14, 0, 1), offset_span.end
-  end
-  
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/test/test_RepeaterMonth.rb b/framework/Horde_Date_Parser/chronic/test/test_RepeaterMonth.rb
deleted file mode 100644 (file)
index d0609c5..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-require 'chronic'
-require 'test/unit'
-
-class TestRepeaterMonth < Test::Unit::TestCase
-  
-  def setup
-    # Wed Aug 16 14:00:00 2006
-    @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
-  end
-  
-  def test_offset_by
-    # future
-    
-    time = Chronic::RepeaterMonth.new(:month).offset_by(@now, 1, :future)
-    assert_equal Time.local(2006, 9, 16, 14), time
-    
-    time = Chronic::RepeaterMonth.new(:month).offset_by(@now, 5, :future)
-    assert_equal Time.local(2007, 1, 16, 14), time
-    
-    # past
-    
-    time = Chronic::RepeaterMonth.new(:month).offset_by(@now, 1, :past)
-    assert_equal Time.local(2006, 7, 16, 14), time
-    
-    time = Chronic::RepeaterMonth.new(:month).offset_by(@now, 10, :past)
-    assert_equal Time.local(2005, 10, 16, 14), time
-  end
-  
-  def test_offset
-    # future
-    
-    span = Chronic::Span.new(@now, @now + 60)
-    offset_span = Chronic::RepeaterMonth.new(:month).offset(span, 1, :future)
-    
-    assert_equal Time.local(2006, 9, 16, 14), offset_span.begin
-    assert_equal Time.local(2006, 9, 16, 14, 1), offset_span.end
-    
-   # past
-   
-   span = Chronic::Span.new(@now, @now + 60)
-   offset_span = Chronic::RepeaterMonth.new(:month).offset(span, 1, :past)
-   
-   assert_equal Time.local(2006, 7, 16, 14), offset_span.begin
-   assert_equal Time.local(2006, 7, 16, 14, 1), offset_span.end
-  end
-  
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/test/test_RepeaterMonthName.rb b/framework/Horde_Date_Parser/chronic/test/test_RepeaterMonthName.rb
deleted file mode 100644 (file)
index 6326a45..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-require 'chronic'
-require 'test/unit'
-
-class TestRepeaterMonthName < Test::Unit::TestCase
-  
-  def setup
-    # Wed Aug 16 14:00:00 2006
-    @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
-  end
-  
-  def test_next
-    # future
-    
-    mays = Chronic::RepeaterMonthName.new(:may)
-    mays.start = @now
-    
-    next_may = mays.next(:future)
-    assert_equal Time.local(2007, 5), next_may.begin
-    assert_equal Time.local(2007, 6), next_may.end
-    
-    next_next_may = mays.next(:future)
-    assert_equal Time.local(2008, 5), next_next_may.begin
-    assert_equal Time.local(2008, 6), next_next_may.end
-    
-    decembers = Chronic::RepeaterMonthName.new(:december)
-    decembers.start = @now
-    
-    next_december = decembers.next(:future)
-    assert_equal Time.local(2006, 12), next_december.begin
-    assert_equal Time.local(2007, 1), next_december.end
-    
-    # past
-    
-    mays = Chronic::RepeaterMonthName.new(:may)
-    mays.start = @now
-    
-    assert_equal Time.local(2006, 5), mays.next(:past).begin
-    assert_equal Time.local(2005, 5), mays.next(:past).begin
-  end
-  
-  def test_this
-    octobers = Chronic::RepeaterMonthName.new(:october)
-    octobers.start = @now
-    
-    this_october = octobers.this(:future)
-    assert_equal Time.local(2006, 10, 1), this_october.begin
-    assert_equal Time.local(2006, 11, 1), this_october.end
-    
-    aprils = Chronic::RepeaterMonthName.new(:april)
-    aprils.start = @now
-    
-    this_april = aprils.this(:past)
-    assert_equal Time.local(2006, 4, 1), this_april.begin
-    assert_equal Time.local(2006, 5, 1), this_april.end
-  end
-  
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/test/test_RepeaterTime.rb b/framework/Horde_Date_Parser/chronic/test/test_RepeaterTime.rb
deleted file mode 100644 (file)
index bb27735..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-require 'chronic'
-require 'test/unit'
-
-class TestRepeaterTime < Test::Unit::TestCase
-  
-  def setup
-    # Wed Aug 16 14:00:00 2006
-    @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
-  end
-  
-  def test_next_future
-    t = Chronic::RepeaterTime.new('4:00')
-    t.start = @now
-    
-    assert_equal Time.local(2006, 8, 16, 16), t.next(:future).begin
-    assert_equal Time.local(2006, 8, 17, 4), t.next(:future).begin
-    
-    t = Chronic::RepeaterTime.new('13:00')
-    t.start = @now
-    
-    assert_equal Time.local(2006, 8, 17, 13), t.next(:future).begin
-    assert_equal Time.local(2006, 8, 18, 13), t.next(:future).begin
-    
-    t = Chronic::RepeaterTime.new('0400')
-    t.start = @now
-    
-    assert_equal Time.local(2006, 8, 17, 4), t.next(:future).begin
-    assert_equal Time.local(2006, 8, 18, 4), t.next(:future).begin
-  end
-  
-  def test_next_past
-    t = Chronic::RepeaterTime.new('4:00')
-    t.start = @now
-    
-    assert_equal Time.local(2006, 8, 16, 4), t.next(:past).begin
-    assert_equal Time.local(2006, 8, 15, 16), t.next(:past).begin
-    
-    t = Chronic::RepeaterTime.new('13:00')
-    t.start = @now
-    
-    assert_equal Time.local(2006, 8, 16, 13), t.next(:past).begin
-    assert_equal Time.local(2006, 8, 15, 13), t.next(:past).begin
-  end
-
-  def test_type
-    t1 = Chronic::RepeaterTime.new('4')
-    assert_equal 14_400, t1.type.time
-    
-    t1 = Chronic::RepeaterTime.new('14')
-    assert_equal 50_400, t1.type.time
-    
-    t1 = Chronic::RepeaterTime.new('4:00')
-    assert_equal 14_400, t1.type.time
-    
-    t1 = Chronic::RepeaterTime.new('4:30')
-    assert_equal 16_200, t1.type.time
-    
-    t1 = Chronic::RepeaterTime.new('1400')
-    assert_equal 50_400, t1.type.time
-    
-    t1 = Chronic::RepeaterTime.new('0400')
-    assert_equal 14_400, t1.type.time
-    
-    t1 = Chronic::RepeaterTime.new('04')
-    assert_equal 14_400, t1.type.time
-    
-    t1 = Chronic::RepeaterTime.new('400')
-    assert_equal 14_400, t1.type.time
-  end
-
-  
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/test/test_RepeaterWeek.rb b/framework/Horde_Date_Parser/chronic/test/test_RepeaterWeek.rb
deleted file mode 100644 (file)
index 084ef4e..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-require 'chronic'
-require 'test/unit'
-
-class TestRepeaterWeek < Test::Unit::TestCase
-  
-  def setup
-    @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
-  end
-
-  def test_next_future
-    weeks = Chronic::RepeaterWeek.new(:week)
-    weeks.start = @now
-    
-    next_week = weeks.next(:future)
-    assert_equal Time.local(2006, 8, 20), next_week.begin
-    assert_equal Time.local(2006, 8, 27), next_week.end
-    
-    next_next_week = weeks.next(:future)
-    assert_equal Time.local(2006, 8, 27), next_next_week.begin
-    assert_equal Time.local(2006, 9, 3), next_next_week.end
-  end
-  
-  def test_next_past
-    weeks = Chronic::RepeaterWeek.new(:week)
-    weeks.start = @now
-    
-    last_week = weeks.next(:past)
-    assert_equal Time.local(2006, 8, 6), last_week.begin
-    assert_equal Time.local(2006, 8, 13), last_week.end
-    
-    last_last_week = weeks.next(:past)
-    assert_equal Time.local(2006, 7, 30), last_last_week.begin
-    assert_equal Time.local(2006, 8, 6), last_last_week.end
-  end
-  
-  def test_this_future
-    weeks = Chronic::RepeaterWeek.new(:week)
-    weeks.start = @now
-    
-    this_week = weeks.this(:future)
-    assert_equal Time.local(2006, 8, 16, 15), this_week.begin
-    assert_equal Time.local(2006, 8, 20), this_week.end
-  end
-  
-  def test_this_past
-    weeks = Chronic::RepeaterWeek.new(:week)
-    weeks.start = @now
-    
-    this_week = weeks.this(:past)
-    assert_equal Time.local(2006, 8, 13, 0), this_week.begin
-    assert_equal Time.local(2006, 8, 16, 14), this_week.end
-  end
-  
-  def test_offset
-    span = Chronic::Span.new(@now, @now + 1)
-    
-    offset_span = Chronic::RepeaterWeek.new(:week).offset(span, 3, :future)
-    
-    assert_equal Time.local(2006, 9, 6, 14), offset_span.begin
-    assert_equal Time.local(2006, 9, 6, 14, 0, 1), offset_span.end
-  end
-  
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/test/test_RepeaterWeekend.rb b/framework/Horde_Date_Parser/chronic/test/test_RepeaterWeekend.rb
deleted file mode 100644 (file)
index 44dc087..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-require 'chronic'
-require 'test/unit'
-
-class TestRepeaterWeekend < Test::Unit::TestCase
-  
-  def setup
-    # Wed Aug 16 14:00:00 2006
-    @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
-  end
-  
-  def test_next_future
-    weekend = Chronic::RepeaterWeekend.new(:weekend)
-    weekend.start = @now
-    
-    next_weekend = weekend.next(:future)
-    assert_equal Time.local(2006, 8, 19), next_weekend.begin
-    assert_equal Time.local(2006, 8, 21), next_weekend.end
-  end
-  
-  def test_next_past
-    weekend = Chronic::RepeaterWeekend.new(:weekend)
-    weekend.start = @now
-    
-    next_weekend = weekend.next(:past)
-    assert_equal Time.local(2006, 8, 12), next_weekend.begin
-    assert_equal Time.local(2006, 8, 14), next_weekend.end
-  end
-  
-  def test_this_future
-    weekend = Chronic::RepeaterWeekend.new(:weekend)
-    weekend.start = @now
-    
-    next_weekend = weekend.this(:future)
-    assert_equal Time.local(2006, 8, 19), next_weekend.begin
-    assert_equal Time.local(2006, 8, 21), next_weekend.end
-  end
-  
-  def test_this_past
-    weekend = Chronic::RepeaterWeekend.new(:weekend)
-    weekend.start = @now
-    
-    next_weekend = weekend.this(:past)
-    assert_equal Time.local(2006, 8, 12), next_weekend.begin
-    assert_equal Time.local(2006, 8, 14), next_weekend.end
-  end
-  
-  def test_this_none
-    weekend = Chronic::RepeaterWeekend.new(:weekend)
-    weekend.start = @now
-    
-    next_weekend = weekend.this(:future)
-    assert_equal Time.local(2006, 8, 19), next_weekend.begin
-    assert_equal Time.local(2006, 8, 21), next_weekend.end
-  end
-  
-  def test_offset
-    span = Chronic::Span.new(@now, @now + 1)
-    
-    offset_span = Chronic::RepeaterWeekend.new(:weekend).offset(span, 3, :future)
-    
-    assert_equal Time.local(2006, 9, 2), offset_span.begin
-    assert_equal Time.local(2006, 9, 2, 0, 0, 1), offset_span.end
-    
-    offset_span = Chronic::RepeaterWeekend.new(:weekend).offset(span, 1, :past)
-    
-    assert_equal Time.local(2006, 8, 12), offset_span.begin
-    assert_equal Time.local(2006, 8, 12, 0, 0, 1), offset_span.end
-    
-    offset_span = Chronic::RepeaterWeekend.new(:weekend).offset(span, 0, :future)
-    
-    assert_equal Time.local(2006, 8, 12), offset_span.begin
-    assert_equal Time.local(2006, 8, 12, 0, 0, 1), offset_span.end
-  end
-  
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/test/test_RepeaterYear.rb b/framework/Horde_Date_Parser/chronic/test/test_RepeaterYear.rb
deleted file mode 100644 (file)
index eaebe25..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-require 'chronic'
-require 'test/unit'
-
-class TestRepeaterYear < Test::Unit::TestCase
-  
-  def setup
-    @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
-  end
-
-  def test_next_future
-    years = Chronic::RepeaterYear.new(:year)
-    years.start = @now
-    
-    next_year = years.next(:future)
-    assert_equal Time.local(2007, 1, 1), next_year.begin
-    assert_equal Time.local(2008, 1, 1), next_year.end
-    
-    next_next_year = years.next(:future)
-    assert_equal Time.local(2008, 1, 1), next_next_year.begin
-    assert_equal Time.local(2009, 1, 1), next_next_year.end
-  end
-  
-  def test_next_past
-    years = Chronic::RepeaterYear.new(:year)
-    years.start = @now
-    
-    last_year = years.next(:past)
-    assert_equal Time.local(2005, 1, 1), last_year.begin
-    assert_equal Time.local(2006, 1, 1), last_year.end
-    
-    last_last_year = years.next(:past)
-    assert_equal Time.local(2004, 1, 1), last_last_year.begin
-    assert_equal Time.local(2005, 1, 1), last_last_year.end
-  end
-  
-  def test_this
-    years = Chronic::RepeaterYear.new(:year)
-    years.start = @now
-    
-    this_year = years.this(:future)
-    assert_equal Time.local(2006, 8, 17), this_year.begin
-    assert_equal Time.local(2007, 1, 1), this_year.end
-    
-    this_year = years.this(:past)
-    assert_equal Time.local(2006, 1, 1), this_year.begin
-    assert_equal Time.local(2006, 8, 16), this_year.end
-  end
-  
-  def test_offset
-    span = Chronic::Span.new(@now, @now + 1)
-    
-    offset_span = Chronic::RepeaterYear.new(:year).offset(span, 3, :future)
-    
-    assert_equal Time.local(2009, 8, 16, 14), offset_span.begin
-    assert_equal Time.local(2009, 8, 16, 14, 0, 1), offset_span.end
-    
-    offset_span = Chronic::RepeaterYear.new(:year).offset(span, 10, :past)
-    
-    assert_equal Time.local(1996, 8, 16, 14), offset_span.begin
-    assert_equal Time.local(1996, 8, 16, 14, 0, 1), offset_span.end
-  end
-  
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/test/test_Span.rb b/framework/Horde_Date_Parser/chronic/test/test_Span.rb
deleted file mode 100644 (file)
index 099455a..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-require 'chronic'
-require 'test/unit'
-
-class TestSpan < Test::Unit::TestCase
-  
-  def setup
-    # Wed Aug 16 14:00:00 UTC 2006
-    @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
-  end
-
-  def test_span_width
-    span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0))
-    assert_equal (60 * 60 * 24), span.width
-  end
-  
-  def test_span_math
-    s = Chronic::Span.new(1, 2)
-    assert_equal 2, (s + 1).begin
-    assert_equal 3, (s + 1).end
-    assert_equal 0, (s - 1).begin
-    assert_equal 1, (s - 1).end
-  end
-  
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/test/test_Time.rb b/framework/Horde_Date_Parser/chronic/test/test_Time.rb
deleted file mode 100644 (file)
index 3ffc9c0..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-require 'chronic'
-require 'test/unit'
-
-class TestTime < Test::Unit::TestCase
-  
-  def setup
-  end
-  
-  def test_normal
-    assert_equal Time.local(2006, 1, 2, 0, 0, 0), Time.construct(2006, 1, 2, 0, 0, 0)
-    assert_equal Time.local(2006, 1, 2, 3, 0, 0), Time.construct(2006, 1, 2, 3, 0, 0)
-    assert_equal Time.local(2006, 1, 2, 3, 4, 0), Time.construct(2006, 1, 2, 3, 4, 0)
-    assert_equal Time.local(2006, 1, 2, 3, 4, 5), Time.construct(2006, 1, 2, 3, 4, 5)
-  end
-  
-  def test_second_overflow
-    assert_equal Time.local(2006, 1, 1, 0, 1, 30), Time.construct(2006, 1, 1, 0, 0, 90)
-    assert_equal Time.local(2006, 1, 1, 0, 5, 0), Time.construct(2006, 1, 1, 0, 0, 300)
-  end
-  
-  def test_minute_overflow
-    assert_equal Time.local(2006, 1, 1, 1, 30), Time.construct(2006, 1, 1, 0, 90)
-    assert_equal Time.local(2006, 1, 1, 5), Time.construct(2006, 1, 1, 0, 300)
-  end
-  
-  def test_hour_overflow
-    assert_equal Time.local(2006, 1, 2, 12), Time.construct(2006, 1, 1, 36)
-    assert_equal Time.local(2006, 1, 7), Time.construct(2006, 1, 1, 144)
-  end
-  
-  def test_day_overflow
-    assert_equal Time.local(2006, 2, 1), Time.construct(2006, 1, 32)
-    assert_equal Time.local(2006, 3, 5), Time.construct(2006, 2, 33)
-    assert_equal Time.local(2004, 3, 4), Time.construct(2004, 2, 33)
-    assert_equal Time.local(2000, 3, 5), Time.construct(2000, 2, 33)
-    
-    assert_nothing_raised do
-      Time.construct(2006, 1, 56)
-    end
-    
-    assert_raise(RuntimeError) do
-      Time.construct(2006, 1, 57)
-    end
-  end
-  
-  def test_month_overflow
-    assert_equal Time.local(2006, 1), Time.construct(2005, 13)
-    assert_equal Time.local(2005, 12), Time.construct(2000, 72)
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/test/test_Token.rb b/framework/Horde_Date_Parser/chronic/test/test_Token.rb
deleted file mode 100644 (file)
index 80463d1..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-require 'chronic'
-require 'test/unit'
-
-class TestToken < Test::Unit::TestCase
-  
-  def setup
-    # Wed Aug 16 14:00:00 UTC 2006
-    @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
-  end
-  
-  def test_token
-    token = Chronic::Token.new('foo')
-    assert_equal 0, token.tags.size
-    assert !token.tagged?
-    token.tag("mytag")
-    assert_equal 1, token.tags.size
-    assert token.tagged?
-    assert_equal String, token.get_tag(String).class
-    token.tag(5)
-    assert_equal 2, token.tags.size
-    token.untag(String)
-    assert_equal 1, token.tags.size
-    assert_equal 'foo', token.word
-  end
-  
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/chronic/test/test_parsing.rb b/framework/Horde_Date_Parser/chronic/test/test_parsing.rb
deleted file mode 100644 (file)
index d2216d6..0000000
+++ /dev/null
@@ -1,614 +0,0 @@
-require 'chronic'
-require 'time'
-require 'test/unit'
-
-class TestParsing < Test::Unit::TestCase
-  # Wed Aug 16 14:00:00 UTC 2006
-  TIME_2006_08_16_14_00_00 = Time.local(2006, 8, 16, 14, 0, 0, 0)
-  
-  def setup
-    @time_2006_08_16_14_00_00 = TIME_2006_08_16_14_00_00
-  end
-  
-  def test_parse_guess_dates
-    # rm_sd
-
-    time = parse_now("may 27")
-    assert_equal Time.local(2007, 5, 27, 12), time
-    
-    time = parse_now("may 28", :context => :past)
-    assert_equal Time.local(2006, 5, 28, 12), time
-    
-    time = parse_now("may 28 5pm", :context => :past)
-    assert_equal Time.local(2006, 5, 28, 17), time
-    
-    time = parse_now("may 28 at 5pm", :context => :past)
-    assert_equal Time.local(2006, 5, 28, 17), time
-    
-    time = parse_now("may 28 at 5:32.19pm", :context => :past)
-    assert_equal Time.local(2006, 5, 28, 17, 32, 19), time
-    
-    # rm_od
-    
-    time = parse_now("may 27th")
-    assert_equal Time.local(2007, 5, 27, 12), time
-    
-    time = parse_now("may 27th", :context => :past)
-    assert_equal Time.local(2006, 5, 27, 12), time
-    
-    time = parse_now("may 27th 5:00 pm", :context => :past)
-    assert_equal Time.local(2006, 5, 27, 17), time
-    
-    time = parse_now("may 27th at 5pm", :context => :past)
-    assert_equal Time.local(2006, 5, 27, 17), time
-    
-    time = parse_now("may 27th at 5", :ambiguous_time_range => :none)
-    assert_equal Time.local(2007, 5, 27, 5), time
-    
-    # rm_sy
-    
-    time = parse_now("June 1979")
-    assert_equal Time.local(1979, 6, 16, 0), time
-    
-    time = parse_now("dec 79")
-    assert_equal Time.local(1979, 12, 16, 12), time
-    
-    # rm_sd_sy
-    
-    time = parse_now("jan 3 2010")
-    assert_equal Time.local(2010, 1, 3, 12), time
-    
-    time = parse_now("jan 3 2010 midnight")
-    assert_equal Time.local(2010, 1, 4, 0), time
-    
-    time = parse_now("jan 3 2010 at midnight")
-    assert_equal Time.local(2010, 1, 4, 0), time
-    
-    time = parse_now("jan 3 2010 at 4", :ambiguous_time_range => :none)
-    assert_equal Time.local(2010, 1, 3, 4), time
-    
-    #time = parse_now("January 12, '00")
-    #assert_equal Time.local(2000, 1, 12, 12), time
-    
-    time = parse_now("may 27 79")
-    assert_equal Time.local(1979, 5, 27, 12), time
-    
-    time = parse_now("may 27 79 4:30")
-    assert_equal Time.local(1979, 5, 27, 16, 30), time
-    
-    time = parse_now("may 27 79 at 4:30", :ambiguous_time_range => :none)
-    assert_equal Time.local(1979, 5, 27, 4, 30), time
-    
-    # sd_rm_sy
-
-    time = parse_now("3 jan 2010")
-    assert_equal Time.local(2010, 1, 3, 12), time
-    
-    time = parse_now("3 jan 2010 4pm")
-    assert_equal Time.local(2010, 1, 3, 16), time
-    
-    # sm_sd_sy
-    
-    time = parse_now("5/27/1979")
-    assert_equal Time.local(1979, 5, 27, 12), time
-    
-    time = parse_now("5/27/1979 4am")
-    assert_equal Time.local(1979, 5, 27, 4), time
-    
-    # sd_sm_sy
-    
-    time = parse_now("27/5/1979")
-    assert_equal Time.local(1979, 5, 27, 12), time
-    
-    time = parse_now("27/5/1979 @ 0700")
-    assert_equal Time.local(1979, 5, 27, 7), time
-    
-    # sm_sy
-    
-    time = parse_now("05/06")
-    assert_equal Time.local(2006, 5, 16, 12), time
-    
-    time = parse_now("12/06")
-    assert_equal Time.local(2006, 12, 16, 12), time
-    
-    time = parse_now("13/06")
-    assert_equal nil, time
-    
-    # sy_sm_sd
-    
-    time = parse_now("2000-1-1")
-    assert_equal Time.local(2000, 1, 1, 12), time
-    
-    time = parse_now("2006-08-20")
-    assert_equal Time.local(2006, 8, 20, 12), time
-    
-    time = parse_now("2006-08-20 7pm")
-    assert_equal Time.local(2006, 8, 20, 19), time
-    
-    time = parse_now("2006-08-20 03:00")
-    assert_equal Time.local(2006, 8, 20, 3), time
-    
-    time = parse_now("2006-08-20 03:30:30")
-    assert_equal Time.local(2006, 8, 20, 3, 30, 30), time
-    
-    time = parse_now("2006-08-20 15:30:30")
-    assert_equal Time.local(2006, 8, 20, 15, 30, 30), time
-    
-    time = parse_now("2006-08-20 15:30.30")
-    assert_equal Time.local(2006, 8, 20, 15, 30, 30), time
-    
-    # rdn_rm_rd_rt_rtz_ry
-    
-    time = parse_now("Mon Apr 02 17:00:00 PDT 2007")
-    assert_equal Time.local(2007, 4, 2, 17), time
-    
-    now = Time.now
-    time = parse_now(now.to_s)
-    assert_equal now.to_s, time.to_s
-    
-    # rm_sd_rt
-    
-    #time = parse_now("jan 5 13:00")
-    #assert_equal Time.local(2007, 1, 5, 13), time
-    
-    # due to limitations of the Time class, these don't work
-    
-    time = parse_now("may 40")
-    assert_equal nil, time
-    
-    time = parse_now("may 27 40")
-    assert_equal nil, time
-    
-    time = parse_now("1800-08-20")
-    assert_equal nil, time
-  end
-  
-  def test_foo
-    Chronic.parse('two months ago this friday')
-  end
-
-  def test_parse_guess_r
-    time = parse_now("friday")
-    assert_equal Time.local(2006, 8, 18, 12), time
-    
-    time = parse_now("tue")
-    assert_equal Time.local(2006, 8, 22, 12), time
-    
-    time = parse_now("5")
-    assert_equal Time.local(2006, 8, 16, 17), time
-    
-    time = Chronic.parse("5", :now => Time.local(2006, 8, 16, 3, 0, 0, 0), :ambiguous_time_range => :none)
-    assert_equal Time.local(2006, 8, 16, 5), time
-    
-    time = parse_now("13:00")
-    assert_equal Time.local(2006, 8, 17, 13), time
-    
-    time = parse_now("13:45")
-    assert_equal Time.local(2006, 8, 17, 13, 45), time
-    
-    time = parse_now("november")
-    assert_equal Time.local(2006, 11, 16), time
-  end
-  
-  def test_parse_guess_rr
-    time = parse_now("friday 13:00")
-    assert_equal Time.local(2006, 8, 18, 13), time
-    
-    time = parse_now("monday 4:00")
-    assert_equal Time.local(2006, 8, 21, 16), time
-    
-    time = parse_now("sat 4:00", :ambiguous_time_range => :none)
-    assert_equal Time.local(2006, 8, 19, 4), time
-    
-    time = parse_now("sunday 4:20", :ambiguous_time_range => :none)
-    assert_equal Time.local(2006, 8, 20, 4, 20), time
-    
-    time = parse_now("4 pm")
-    assert_equal Time.local(2006, 8, 16, 16), time
-    
-    time = parse_now("4 am", :ambiguous_time_range => :none)
-    assert_equal Time.local(2006, 8, 16, 4), time
-    
-    time = parse_now("12 pm")
-    assert_equal Time.local(2006, 8, 16, 12), time
-    
-    time = parse_now("12:01 pm")
-    assert_equal Time.local(2006, 8, 16, 12, 1), time
-    
-    time = parse_now("12:01 am")
-    assert_equal Time.local(2006, 8, 16, 0, 1), time
-    
-    time = parse_now("12 am")
-    assert_equal Time.local(2006, 8, 16), time
-    
-    time = parse_now("4:00 in the morning")
-    assert_equal Time.local(2006, 8, 16, 4), time
-    
-    time = parse_now("november 4")
-    assert_equal Time.local(2006, 11, 4, 12), time
-    
-    time = parse_now("aug 24")
-    assert_equal Time.local(2006, 8, 24, 12), time
-  end
-  
-  def test_parse_guess_rrr
-    time = parse_now("friday 1 pm")
-    assert_equal Time.local(2006, 8, 18, 13), time
-    
-    time = parse_now("friday 11 at night")
-    assert_equal Time.local(2006, 8, 18, 23), time
-    
-    time = parse_now("friday 11 in the evening")
-    assert_equal Time.local(2006, 8, 18, 23), time
-    
-    time = parse_now("sunday 6am")
-    assert_equal Time.local(2006, 8, 20, 6), time
-    
-    time = parse_now("friday evening at 7")
-    assert_equal Time.local(2006, 8, 18, 19), time
-  end
-  
-  def test_parse_guess_gr
-    # year
-    
-    time = parse_now("this year")
-    assert_equal Time.local(2006, 10, 24, 12, 30), time
-    
-    time = parse_now("this year", :context => :past)
-    assert_equal Time.local(2006, 4, 24, 12, 30), time
-    
-    # month
-    
-    time = parse_now("this month")
-    assert_equal Time.local(2006, 8, 24, 12), time
-    
-    time = parse_now("this month", :context => :past)
-    assert_equal Time.local(2006, 8, 8, 12), time
-    
-    time = Chronic.parse("next month", :now => Time.local(2006, 11, 15))
-    assert_equal Time.local(2006, 12, 16, 12), time
-    
-    # month name
-    
-    time = parse_now("last november")
-    assert_equal Time.local(2005, 11, 16), time
-    
-    # fortnight
-    
-    time = parse_now("this fortnight")
-    assert_equal Time.local(2006, 8, 21, 19, 30), time
-    
-    time = parse_now("this fortnight", :context => :past)
-    assert_equal Time.local(2006, 8, 14, 19), time
-    
-    # week
-    
-    time = parse_now("this week")
-    assert_equal Time.local(2006, 8, 18, 7, 30), time
-    
-    time = parse_now("this week", :context => :past)
-    assert_equal Time.local(2006, 8, 14, 19), time
-    
-    # weekend
-    
-    time = parse_now("this weekend")
-    assert_equal Time.local(2006, 8, 20), time
-    
-    time = parse_now("this weekend", :context => :past)
-    assert_equal Time.local(2006, 8, 13), time
-    
-    time = parse_now("last weekend")
-    assert_equal Time.local(2006, 8, 13), time
-    
-    # day
-    
-    time = parse_now("this day")
-    assert_equal Time.local(2006, 8, 16, 19, 30), time
-    
-    time = parse_now("this day", :context => :past)
-    assert_equal Time.local(2006, 8, 16, 7), time
-    
-    time = parse_now("today")
-    assert_equal Time.local(2006, 8, 16, 19, 30), time
-    
-    time = parse_now("yesterday")
-    assert_equal Time.local(2006, 8, 15, 12), time
-    
-    time = parse_now("tomorrow")
-    assert_equal Time.local(2006, 8, 17, 12), time
-    
-    # day name
-    
-    time = parse_now("this tuesday")
-    assert_equal Time.local(2006, 8, 22, 12), time
-    
-    time = parse_now("next tuesday")
-    assert_equal Time.local(2006, 8, 22, 12), time
-    
-    time = parse_now("last tuesday")
-    assert_equal Time.local(2006, 8, 15, 12), time
-    
-    time = parse_now("this wed")
-    assert_equal Time.local(2006, 8, 23, 12), time
-    
-    time = parse_now("next wed")
-    assert_equal Time.local(2006, 8, 23, 12), time
-    
-    time = parse_now("last wed")
-    assert_equal Time.local(2006, 8, 9, 12), time
-    
-    # day portion
-    
-    time = parse_now("this morning")
-    assert_equal Time.local(2006, 8, 16, 9), time
-    
-    time = parse_now("tonight")
-    assert_equal Time.local(2006, 8, 16, 22), time
-    
-    # minute
-    
-    time = parse_now("next minute")
-    assert_equal Time.local(2006, 8, 16, 14, 1, 30), time
-    
-    # second
-    
-    time = parse_now("this second")
-    assert_equal Time.local(2006, 8, 16, 14), time
-    
-    time = parse_now("this second", :context => :past)
-    assert_equal Time.local(2006, 8, 16, 14), time
-    
-    time = parse_now("next second")
-    assert_equal Time.local(2006, 8, 16, 14, 0, 1), time
-    
-    time = parse_now("last second")
-    assert_equal Time.local(2006, 8, 16, 13, 59, 59), time
-  end
-  
-  def test_parse_guess_grr    
-    time = parse_now("yesterday at 4:00")
-    assert_equal Time.local(2006, 8, 15, 16), time
-    
-    time = parse_now("today at 9:00")
-    assert_equal Time.local(2006, 8, 16, 9), time
-    
-    time = parse_now("today at 2100")
-    assert_equal Time.local(2006, 8, 16, 21), time
-    
-    time = parse_now("this day at 0900")
-    assert_equal Time.local(2006, 8, 16, 9), time
-    
-    time = parse_now("tomorrow at 0900")
-    assert_equal Time.local(2006, 8, 17, 9), time
-    
-    time = parse_now("yesterday at 4:00", :ambiguous_time_range => :none)
-    assert_equal Time.local(2006, 8, 15, 4), time
-    
-    time = parse_now("last friday at 4:00")
-    assert_equal Time.local(2006, 8, 11, 16), time
-    
-    time = parse_now("next wed 4:00")
-    assert_equal Time.local(2006, 8, 23, 16), time
-    
-    time = parse_now("yesterday afternoon")
-    assert_equal Time.local(2006, 8, 15, 15), time
-    
-    time = parse_now("last week tuesday")
-    assert_equal Time.local(2006, 8, 8, 12), time
-    
-    time = parse_now("tonight at 7")
-    assert_equal Time.local(2006, 8, 16, 19), time
-    
-    time = parse_now("tonight 7")
-    assert_equal Time.local(2006, 8, 16, 19), time
-    
-    time = parse_now("7 tonight")
-    assert_equal Time.local(2006, 8, 16, 19), time
-  end
-    
-  def test_parse_guess_grrr
-    time = parse_now("today at 6:00pm")
-    assert_equal Time.local(2006, 8, 16, 18), time
-    
-    time = parse_now("today at 6:00am")
-    assert_equal Time.local(2006, 8, 16, 6), time
-    
-    time = parse_now("this day 1800")
-    assert_equal Time.local(2006, 8, 16, 18), time
-    
-    time = parse_now("yesterday at 4:00pm")
-    assert_equal Time.local(2006, 8, 15, 16), time
-    
-    time = parse_now("tomorrow evening at 7")
-    assert_equal Time.local(2006, 8, 17, 19), time
-    
-    time = parse_now("tomorrow morning at 5:30")
-    assert_equal Time.local(2006, 8, 17, 5, 30), time
-    
-    time = parse_now("next monday at 12:01 am")
-    assert_equal Time.local(2006, 8, 21, 00, 1), time
-    
-    time = parse_now("next monday at 12:01 pm")
-    assert_equal Time.local(2006, 8, 21, 12, 1), time
-  end
-  
-  def test_parse_guess_rgr
-    time = parse_now("afternoon yesterday")
-    assert_equal Time.local(2006, 8, 15, 15), time
-    
-    time = parse_now("tuesday last week")
-    assert_equal Time.local(2006, 8, 8, 12), time
-  end
-  
-  def test_parse_guess_s_r_p
-    # past
-    
-    time = parse_now("3 years ago")
-    assert_equal Time.local(2003, 8, 16, 14), time
-    
-    time = parse_now("1 month ago")
-    assert_equal Time.local(2006, 7, 16, 14), time
-    
-    time = parse_now("1 fortnight ago")
-    assert_equal Time.local(2006, 8, 2, 14), time
-    
-    time = parse_now("2 fortnights ago")
-    assert_equal Time.local(2006, 7, 19, 14), time
-    
-    time = parse_now("3 weeks ago")
-    assert_equal Time.local(2006, 7, 26, 14), time
-    
-    time = parse_now("2 weekends ago")
-    assert_equal Time.local(2006, 8, 5), time
-    
-    time = parse_now("3 days ago")
-    assert_equal Time.local(2006, 8, 13, 14), time
-    
-    #time = parse_now("1 monday ago")
-    #assert_equal Time.local(2006, 8, 14, 12), time
-    
-    time = parse_now("5 mornings ago")
-    assert_equal Time.local(2006, 8, 12, 9), time
-    
-    time = parse_now("7 hours ago")
-    assert_equal Time.local(2006, 8, 16, 7), time
-    
-    time = parse_now("3 minutes ago")
-    assert_equal Time.local(2006, 8, 16, 13, 57), time
-    
-    time = parse_now("20 seconds before now")
-    assert_equal Time.local(2006, 8, 16, 13, 59, 40), time
-
-    # future
-    
-    time = parse_now("3 years from now")
-    assert_equal Time.local(2009, 8, 16, 14, 0, 0), time
-    
-    time = parse_now("6 months hence")
-    assert_equal Time.local(2007, 2, 16, 14), time
-    
-    time = parse_now("3 fortnights hence")
-    assert_equal Time.local(2006, 9, 27, 14), time
-    
-    time = parse_now("1 week from now")
-    assert_equal Time.local(2006, 8, 23, 14, 0, 0), time
-    
-    time = parse_now("1 weekend from now")
-    assert_equal Time.local(2006, 8, 19), time
-    
-    time = parse_now("2 weekends from now")
-    assert_equal Time.local(2006, 8, 26), time
-    
-    time = parse_now("1 day hence")
-    assert_equal Time.local(2006, 8, 17, 14), time
-    
-    time = parse_now("5 mornings hence")
-    assert_equal Time.local(2006, 8, 21, 9), time
-    
-    time = parse_now("1 hour from now")
-    assert_equal Time.local(2006, 8, 16, 15), time
-    
-    time = parse_now("20 minutes hence")
-    assert_equal Time.local(2006, 8, 16, 14, 20), time
-    
-    time = parse_now("20 seconds from now")
-    assert_equal Time.local(2006, 8, 16, 14, 0, 20), time
-    
-    time = Chronic.parse("2 months ago", :now => Time.parse("2007-03-07 23:30"))
-    assert_equal Time.local(2007, 1, 7, 23, 30), time
-  end
-  
-  def test_parse_guess_p_s_r
-    time = parse_now("in 3 hours")
-    assert_equal Time.local(2006, 8, 16, 17), time
-  end
-  
-  def test_parse_guess_s_r_p_a
-    # past
-    
-    time = parse_now("3 years ago tomorrow")
-    assert_equal Time.local(2003, 8, 17, 12), time
-    
-    time = parse_now("3 years ago this friday")
-    assert_equal Time.local(2003, 8, 18, 12), time
-    
-    time = parse_now("3 months ago saturday at 5:00 pm")
-    assert_equal Time.local(2006, 5, 19, 17), time
-    
-    time = parse_now("2 days from this second")
-    assert_equal Time.local(2006, 8, 18, 14), time
-    
-    time = parse_now("7 hours before tomorrow at midnight")
-    assert_equal Time.local(2006, 8, 17, 17), time
-    
-    # future
-  end
-  
-  def test_parse_guess_o_r_s_r
-    time = parse_now("3rd wednesday in november")
-    assert_equal Time.local(2006, 11, 15, 12), time
-    
-    time = parse_now("10th wednesday in november")
-    assert_equal nil, time
-    
-    # time = parse_now("3rd wednesday in 2007")
-    # assert_equal Time.local(2007, 1, 20, 12), time
-  end
-  
-  def test_parse_guess_o_r_g_r
-    time = parse_now("3rd month next year")
-    assert_equal Time.local(2007, 3, 16, 12, 30), time
-    
-    time = parse_now("3rd thursday this september")
-    assert_equal Time.local(2006, 9, 21, 12), time
-    
-    time = parse_now("4th day last week")
-    assert_equal Time.local(2006, 8, 9, 12), time
-  end
-  
-  def test_parse_guess_nonsense
-    time = parse_now("some stupid nonsense")
-    assert_equal nil, time
-  end
-  
-  def test_parse_span
-    span = parse_now("friday", :guess => false)
-    assert_equal Time.local(2006, 8, 18), span.begin
-    assert_equal Time.local(2006, 8, 19), span.end
-    
-    span = parse_now("november", :guess => false)
-    assert_equal Time.local(2006, 11), span.begin
-    assert_equal Time.local(2006, 12), span.end
-    
-    span = Chronic.parse("weekend" , :now => @time_2006_08_16_14_00_00, :guess => false)
-    assert_equal Time.local(2006, 8, 19), span.begin
-    assert_equal Time.local(2006, 8, 21), span.end
-  end
-  
-  def test_parse_words
-    assert_equal parse_now("33 days from now"), parse_now("thirty-three days from now")
-    assert_equal parse_now("2867532 seconds from now"), parse_now("two million eight hundred and sixty seven thousand five hundred and thirty two seconds from now")
-    assert_equal parse_now("may 10th"), parse_now("may tenth")
-  end
-  
-  def test_parse_only_complete_pointers
-    assert_equal parse_now("eat pasty buns today at 2pm"), @time_2006_08_16_14_00_00
-    assert_equal parse_now("futuristically speaking today at 2pm"), @time_2006_08_16_14_00_00
-    assert_equal parse_now("meeting today at 2pm"), @time_2006_08_16_14_00_00
-  end
-  
-  def test_argument_validation
-    assert_raise(Chronic::InvalidArgumentException) do
-      time = Chronic.parse("may 27", :foo => :bar)
-    end
-    
-    assert_raise(Chronic::InvalidArgumentException) do
-      time = Chronic.parse("may 27", :context => :bar)
-    end
-  end
-  
-  private
-  def parse_now(string, options={})
-    Chronic.parse(string, {:now => TIME_2006_08_16_14_00_00 }.merge(options))
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser.php
deleted file mode 100644 (file)
index 6406ce5..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-<?php
-/**
- *
- */
-class Horde_Date_Parser
-{
-    public static function factory($args = array())
-    {
-        $locale = isset($args['locale']) ? $args['locale'] : null;
-        if ($locale && strtolower($locale) != 'base') {
-            $locale = str_replace(' ', '_', ucwords(str_replace('_', ' ', strtolower($locale))));
-            $class = 'Horde_Date_Parser_Locale_' . $locale;
-            if (class_exists($class)) {
-                return new $class($args);
-            }
-
-            $language = array_shift(explode('_', $locale));
-            if ($language != $locale) {
-                $class = 'Horde_Date_Parser_Locale_' . $language;
-                if (class_exists($class)) {
-                    return new $class($args);
-                }
-            }
-        }
-
-        return new Horde_Date_Parser_Locale_Base($args);
-    }
-
-    public static function componentFactory($component, $args = array())
-    {
-        $locale = isset($args['locale']) ? $args['locale'] : null;
-        if ($locale && strtolower($locale) != 'base') {
-            $locale = str_replace(' ', '_', ucwords(str_replace('_', ' ', strtolower($locale))));
-            $class = 'Horde_Date_Parser_Locale_' . $locale . '_' . $component;
-            if (class_exists($class)) {
-                return new $class($args);
-            }
-
-            $language = array_shift(explode('_', $locale));
-            if ($language != $locale) {
-                $class = 'Horde_Date_Parser_Locale_' . $language . '_' . $component;
-                if (class_exists($class)) {
-                    return new $class($args);
-                }
-            }
-        }
-
-        $class = 'Horde_Date_Parser_Locale_Base_' . $component;
-        return new $class($args);
-    }
-
-}
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Exception.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Exception.php
deleted file mode 100644 (file)
index c6a8ac0..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-<?php
-class Horde_Date_Parser_Exception extends Exception
-{
-}
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base.php
deleted file mode 100644 (file)
index 436250c..0000000
+++ /dev/null
@@ -1,152 +0,0 @@
-module Chronic
-  class << self
-
-    # Parses a string containing a natural language date or time. If the parser
-    # can find a date or time, either a Time or Chronic::Span will be returned
-    # (depending on the value of <tt>:guess</tt>). If no date or time can be found,
-    # +nil+ will be returned.
-    #
-    # Options are:
-    #
-    # [<tt>:context</tt>]
-    #     <tt>:past</tt> or <tt>:future</tt> (defaults to <tt>:future</tt>)
-    #
-    #     If your string represents a birthday, you can set <tt>:context</tt> to <tt>:past</tt>
-    #     and if an ambiguous string is given, it will assume it is in the
-    #     past. Specify <tt>:future</tt> or omit to set a future context.
-    #
-    # [<tt>:now</tt>]
-    #     Time (defaults to Time.now)
-    #
-    #     By setting <tt>:now</tt> to a Time, all computations will be based off
-    #     of that time instead of Time.now
-    #
-    # [<tt>:guess</tt>]
-    #     +true+ or +false+ (defaults to +true+)
-    #
-    #     By default, the parser will guess a single point in time for the
-    #     given date or time. If you'd rather have the entire time span returned,
-    #     set <tt>:guess</tt> to +false+ and a Chronic::Span will be returned.
-    #
-    # [<tt>:ambiguous_time_range</tt>]
-    #     Integer or <tt>:none</tt> (defaults to <tt>6</tt> (6am-6pm))
-    #
-    #     If an Integer is given, ambiguous times (like 5:00) will be
-    #     assumed to be within the range of that time in the AM to that time
-    #     in the PM. For example, if you set it to <tt>7</tt>, then the parser will
-    #     look for the time between 7am and 7pm. In the case of 5:00, it would
-    #     assume that means 5:00pm. If <tt>:none</tt> is given, no assumption
-    #     will be made, and the first matching instance of that time will
-    #     be used.
-    def parse(text, specified_options = {})
-      # get options and set defaults if necessary
-      default_options = {:context => :future,
-                         :now => Time.now,
-                         :guess => true,
-                         :ambiguous_time_range => 6}
-      options = default_options.merge specified_options
-
-      # ensure the specified options are valid
-      specified_options.keys.each do |key|
-        default_options.keys.include?(key) || raise(InvalidArgumentException, "#{key} is not a valid option key.")
-      end
-      [:past, :future, :none].include?(options[:context]) || raise(InvalidArgumentException, "Invalid value ':#{options[:context]}' for :context specified. Valid values are :past and :future.")
-
-      # store now for later =)
-      @now = options[:now]
-
-      # put the text into a normal format to ease scanning
-      text = self.pre_normalize(text)
-
-      # get base tokens for each word
-      @tokens = self.base_tokenize(text)
-
-      # scan the tokens with each token scanner
-      [Repeater].each do |tokenizer|
-        @tokens = tokenizer.scan(@tokens, options)
-      end
-
-      [Grabber, Pointer, Scalar, Ordinal, Separator, TimeZone].each do |tokenizer|
-        @tokens = tokenizer.scan(@tokens)
-      end
-
-      # strip any non-tagged tokens
-      @tokens = @tokens.select { |token| token.tagged? }
-
-      if Chronic.debug
-        puts "+---------------------------------------------------"
-        puts "| " + @tokens.to_s
-        puts "+---------------------------------------------------"
-      end
-
-      # do the heavy lifting
-      begin
-        span = self.tokens_to_span(@tokens, options)
-      rescue
-        raise
-        return nil
-      end
-
-      # guess a time within a span if required
-      if options[:guess]
-        return self.guess(span)
-      else
-        return span
-      end
-    end
-
-    # Clean up the specified input text by stripping unwanted characters,
-    # converting idioms to their canonical form, converting number words
-    # to numbers (three => 3), and converting ordinal words to numeric
-    # ordinals (third => 3rd)
-    def pre_normalize(text) #:nodoc:
-      normalized_text = text.to_s.downcase
-      normalized_text = numericize_numbers(normalized_text)
-      normalized_text.gsub!(/['"\.]/, '')
-      normalized_text.gsub!(/([\/\-\,\@])/) { ' ' + $1 + ' ' }
-      normalized_text.gsub!(/\btoday\b/, 'this day')
-      normalized_text.gsub!(/\btomm?orr?ow\b/, 'next day')
-      normalized_text.gsub!(/\byesterday\b/, 'last day')
-      normalized_text.gsub!(/\bnoon\b/, '12:00')
-      normalized_text.gsub!(/\bmidnight\b/, '24:00')
-      normalized_text.gsub!(/\bbefore now\b/, 'past')
-      normalized_text.gsub!(/\bnow\b/, 'this second')
-      normalized_text.gsub!(/\b(ago|before)\b/, 'past')
-      normalized_text.gsub!(/\bthis past\b/, 'last')
-      normalized_text.gsub!(/\bthis last\b/, 'last')
-      normalized_text.gsub!(/\b(?:in|during) the (morning)\b/, '\1')
-      normalized_text.gsub!(/\b(?:in the|during the|at) (afternoon|evening|night)\b/, '\1')
-      normalized_text.gsub!(/\btonight\b/, 'this night')
-      normalized_text.gsub!(/(?=\w)([ap]m|oclock)\b/, ' \1')
-      normalized_text.gsub!(/\b(hence|after|from)\b/, 'future')
-      normalized_text = numericize_ordinals(normalized_text)
-    end
-
-    # Convert number words to numbers (three => 3)
-    def numericize_numbers(text) #:nodoc:
-      Numerizer.numerize(text)
-    end
-
-    # Convert ordinal words to numeric ordinals (third => 3rd)
-    def numericize_ordinals(text) #:nodoc:
-      text
-    end
-
-    # Split the text on spaces and convert each word into
-    # a Token
-    def base_tokenize(text) #:nodoc:
-      text.split(' ').map { |word| Token.new(word) }
-    end
-
-    # Guess a specific time within the given span
-    def guess(span) #:nodoc:
-      return nil if span.nil?
-      if span.width > 1
-        span.begin + (span.width / 2)
-      else
-        span.begin
-      end
-    end
-  end
-
-end
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Grabber.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Grabber.php
deleted file mode 100644 (file)
index 4162a26..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-#module Chronic
-
-  class Chronic::Grabber < Chronic::Tag #:nodoc:
-    def self.scan(tokens)
-      tokens.each_index do |i|
-        if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t); next end
-      end
-      tokens
-    end
-    
-    def self.scan_for_all(token)
-      scanner = {/last/ => :last,
-                 /this/ => :this,
-                 /next/ => :next}
-      scanner.keys.each do |scanner_item|
-        return self.new(scanner[scanner_item]) if scanner_item =~ token.word
-      end
-      return nil
-    end
-    
-    def to_s
-      'grabber-' << @type.to_s
-    end
-  end
-
-#end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Handlers.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Handlers.php
deleted file mode 100644 (file)
index 551d632..0000000
+++ /dev/null
@@ -1,469 +0,0 @@
-module Chronic
-
-       class << self
-         
-         def definitions #:nodoc:
-           @definitions ||= 
-      {:time => [Handler.new([:repeater_time, :repeater_day_portion?], nil)],
-        
-       :date => [Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :repeater_time, :time_zone, :scalar_year], :handle_rdn_rmn_sd_t_tz_sy),
-                 Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),
-                 Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),
-                 Handler.new([:repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),
-                 Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),
-                 Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),
-                 Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),
-                 Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),
-                 Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy),
-                 Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),
-                 Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy)],
-                 
-       # tonight at 7pm
-       :anchor => [Handler.new([:grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
-                   Handler.new([:grabber?, :repeater, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
-                   Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)],
-                   
-       # 3 weeks from now, in 2 months
-       :arrow => [Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p),
-                  Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r),
-                  Handler.new([:scalar, :repeater, :pointer, 'anchor'], :handle_s_r_p_a)],
-                  
-       # 3rd week in march
-       :narrow => [Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r),
-                   Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r)]
-      }
-    end
-    
-    def tokens_to_span(tokens, options) #:nodoc:                   
-      # maybe it's a specific date
-      
-      self.definitions[:date].each do |handler|
-        if handler.match(tokens, self.definitions)
-          puts "-date" if Chronic.debug
-          good_tokens = tokens.select { |o| !o.get_tag Separator }
-          return self.send(handler.handler_method, good_tokens, options)
-        end
-      end
-            
-      # I guess it's not a specific date, maybe it's just an anchor
-            
-      self.definitions[:anchor].each do |handler|
-        if handler.match(tokens, self.definitions)
-          puts "-anchor" if Chronic.debug
-          good_tokens = tokens.select { |o| !o.get_tag Separator }
-          return self.send(handler.handler_method, good_tokens, options)
-        end
-      end
-            
-      # not an anchor, perhaps it's an arrow
-      
-      self.definitions[:arrow].each do |handler|
-        if handler.match(tokens, self.definitions)
-          puts "-arrow" if Chronic.debug
-          good_tokens = tokens.reject { |o| o.get_tag(SeparatorAt) || o.get_tag(SeparatorSlashOrDash) || o.get_tag(SeparatorComma) }
-          return self.send(handler.handler_method, good_tokens, options)
-        end
-      end
-      
-      # not an arrow, let's hope it's a narrow
-      
-      self.definitions[:narrow].each do |handler|
-        if handler.match(tokens, self.definitions)
-          puts "-narrow" if Chronic.debug
-          #good_tokens = tokens.select { |o| !o.get_tag Separator }
-          return self.send(handler.handler_method, tokens, options)
-        end
-      end
-      
-      # I guess you're out of luck!
-      puts "-none" if Chronic.debug
-      return nil
-    end
-    
-    #--------------
-    
-    def day_or_time(day_start, time_tokens, options)
-      outer_span = Span.new(day_start, day_start + (24 * 60 * 60))
-      
-      if !time_tokens.empty?
-        @now = outer_span.begin
-        time = get_anchor(dealias_and_disambiguate_times(time_tokens, options), options)
-        return time
-      else
-        return outer_span
-      end
-    end
-    
-    #--------------
-    
-    def handle_m_d(month, day, time_tokens, options) #:nodoc:
-      month.start = @now
-      span = month.this(options[:context])
-      
-      day_start = Time.local(span.begin.year, span.begin.month, day)
-      
-      day_or_time(day_start, time_tokens, options)
-    end
-    
-    def handle_rmn_sd(tokens, options) #:nodoc:
-      handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(ScalarDay).type, tokens[2..tokens.size], options)
-    end
-    
-    def handle_rmn_od(tokens, options) #:nodoc:
-      handle_m_d(tokens[0].get_tag(RepeaterMonthName), tokens[1].get_tag(OrdinalDay).type, tokens[2..tokens.size], options)
-    end
-    
-    def handle_rmn_sy(tokens, options) #:nodoc:
-      month = tokens[0].get_tag(RepeaterMonthName).index
-      year = tokens[1].get_tag(ScalarYear).type
-      
-      if month == 12
-        next_month_year = year + 1
-        next_month_month = 1
-      else
-        next_month_year = year
-        next_month_month = month + 1
-      end
-      
-      begin
-        Span.new(Time.local(year, month), Time.local(next_month_year, next_month_month))
-      rescue ArgumentError
-        nil
-      end
-    end
-    
-    def handle_rdn_rmn_sd_t_tz_sy(tokens, options) #:nodoc:
-      month = tokens[1].get_tag(RepeaterMonthName).index
-      day = tokens[2].get_tag(ScalarDay).type
-      year = tokens[5].get_tag(ScalarYear).type
-      
-      begin
-        day_start = Time.local(year, month, day)
-        day_or_time(day_start, [tokens[3]], options)
-      rescue ArgumentError
-        nil
-      end
-    end
-    
-    def handle_rmn_sd_sy(tokens, options) #:nodoc:
-      month = tokens[0].get_tag(RepeaterMonthName).index
-      day = tokens[1].get_tag(ScalarDay).type
-      year = tokens[2].get_tag(ScalarYear).type
-      
-      time_tokens = tokens.last(tokens.size - 3)
-      
-      begin
-        day_start = Time.local(year, month, day)
-        day_or_time(day_start, time_tokens, options)
-      rescue ArgumentError
-        nil
-      end
-    end
-    
-    def handle_sd_rmn_sy(tokens, options) #:nodoc:
-      new_tokens = [tokens[1], tokens[0], tokens[2]]
-      time_tokens = tokens.last(tokens.size - 3)
-      self.handle_rmn_sd_sy(new_tokens + time_tokens, options)
-    end
-    
-    def handle_sm_sd_sy(tokens, options) #:nodoc:
-      month = tokens[0].get_tag(ScalarMonth).type
-      day = tokens[1].get_tag(ScalarDay).type
-      year = tokens[2].get_tag(ScalarYear).type
-      
-      time_tokens = tokens.last(tokens.size - 3)
-      
-      begin
-        day_start = Time.local(year, month, day) #:nodoc:
-        day_or_time(day_start, time_tokens, options)
-      rescue ArgumentError
-        nil
-      end
-    end
-    
-    def handle_sd_sm_sy(tokens, options) #:nodoc:
-      new_tokens = [tokens[1], tokens[0], tokens[2]]
-      time_tokens = tokens.last(tokens.size - 3)
-      self.handle_sm_sd_sy(new_tokens + time_tokens, options)
-    end
-    
-    def handle_sy_sm_sd(tokens, options) #:nodoc:
-      new_tokens = [tokens[1], tokens[2], tokens[0]]
-      time_tokens = tokens.last(tokens.size - 3)
-      self.handle_sm_sd_sy(new_tokens + time_tokens, options)
-    end
-    
-    def handle_sm_sy(tokens, options) #:nodoc:
-      month = tokens[0].get_tag(ScalarMonth).type
-      year = tokens[1].get_tag(ScalarYear).type
-      
-      if month == 12
-        next_month_year = year + 1
-        next_month_month = 1
-      else
-        next_month_year = year
-        next_month_month = month + 1
-      end
-      
-      begin
-        Span.new(Time.local(year, month), Time.local(next_month_year, next_month_month))
-      rescue ArgumentError
-        nil
-      end
-    end
-    
-    # anchors
-    
-    def handle_r(tokens, options) #:nodoc:
-      dd_tokens = dealias_and_disambiguate_times(tokens, options)
-      self.get_anchor(dd_tokens, options)
-    end
-    
-    def handle_r_g_r(tokens, options) #:nodoc:
-      new_tokens = [tokens[1], tokens[0], tokens[2]]
-      self.handle_r(new_tokens, options)
-    end
-    
-    # arrows
-    
-    def handle_srp(tokens, span, options) #:nodoc:
-      distance = tokens[0].get_tag(Scalar).type
-      repeater = tokens[1].get_tag(Repeater)
-      pointer = tokens[2].get_tag(Pointer).type
-      
-      repeater.offset(span, distance, pointer)
-    end
-    
-    def handle_s_r_p(tokens, options) #:nodoc:
-      repeater = tokens[1].get_tag(Repeater)
-            
-      # span = 
-      # case true
-      # when [RepeaterYear, RepeaterSeason, RepeaterSeasonName, RepeaterMonth, RepeaterMonthName, RepeaterFortnight, RepeaterWeek].include?(repeater.class)
-      #   self.parse("this hour", :guess => false, :now => @now)
-      # when [RepeaterWeekend, RepeaterDay, RepeaterDayName, RepeaterDayPortion, RepeaterHour].include?(repeater.class)
-      #   self.parse("this minute", :guess => false, :now => @now)
-      # when [RepeaterMinute, RepeaterSecond].include?(repeater.class)
-      #   self.parse("this second", :guess => false, :now => @now)
-      # else
-      #   raise(ChronicPain, "Invalid repeater: #{repeater.class}")
-      # end
-      
-      span = self.parse("this second", :guess => false, :now => @now)
-      
-      self.handle_srp(tokens, span, options)
-    end
-    
-    def handle_p_s_r(tokens, options) #:nodoc:
-      new_tokens = [tokens[1], tokens[2], tokens[0]]
-      self.handle_s_r_p(new_tokens, options)
-    end
-    
-    def handle_s_r_p_a(tokens, options) #:nodoc:
-      anchor_span = get_anchor(tokens[3..tokens.size - 1], options)
-      self.handle_srp(tokens, anchor_span, options)
-    end
-    
-    # narrows
-    
-    def handle_orr(tokens, outer_span, options) #:nodoc:
-      repeater = tokens[1].get_tag(Repeater)
-      repeater.start = outer_span.begin - 1
-      ordinal = tokens[0].get_tag(Ordinal).type
-      span = nil
-      ordinal.times do
-        span = repeater.next(:future)
-        if span.begin > outer_span.end
-          span = nil
-          break
-        end
-      end
-      span
-    end
-    
-    def handle_o_r_s_r(tokens, options) #:nodoc:
-      outer_span = get_anchor([tokens[3]], options)
-      handle_orr(tokens[0..1], outer_span, options)
-    end
-    
-    def handle_o_r_g_r(tokens, options) #:nodoc:
-      outer_span = get_anchor(tokens[2..3], options)
-      handle_orr(tokens[0..1], outer_span, options)
-    end
-    
-    # support methods
-    
-    def get_anchor(tokens, options) #:nodoc:
-      grabber = Grabber.new(:this)
-      pointer = :future
-      
-      repeaters = self.get_repeaters(tokens)
-      repeaters.size.times { tokens.pop }
-                    
-      if tokens.first && tokens.first.get_tag(Grabber)
-        grabber = tokens.first.get_tag(Grabber)
-        tokens.pop
-      end
-      
-      head = repeaters.shift
-      head.start = @now
-                  
-      case grabber.type
-        when :last
-          outer_span = head.next(:past)
-        when :this
-          if repeaters.size > 0
-            outer_span = head.this(:none)
-          else
-            outer_span = head.this(options[:context])
-          end
-        when :next
-          outer_span = head.next(:future)
-        else raise(ChronicPain, "Invalid grabber")
-      end
-      
-      puts "--#{outer_span}" if Chronic.debug
-      anchor = find_within(repeaters, outer_span, pointer)
-    end
-    
-    def get_repeaters(tokens) #:nodoc:
-      repeaters = []
-                 tokens.each do |token|
-                   if t = token.get_tag(Repeater)
-          repeaters << t
-        end
-      end
-      repeaters.sort.reverse
-    end
-    
-    # Recursively finds repeaters within other repeaters.
-    # Returns a Span representing the innermost time span
-    # or nil if no repeater union could be found
-    def find_within(tags, span, pointer) #:nodoc:
-      puts "--#{span}" if Chronic.debug
-      return span if tags.empty?
-      
-      head, *rest = tags
-      head.start = pointer == :future ? span.begin : span.end
-      h = head.this(:none)
-            
-      if span.include?(h.begin) || span.include?(h.end)
-        return find_within(rest, h, pointer)
-      else
-        return nil
-      end
-    end
-    
-    def dealias_and_disambiguate_times(tokens, options) #:nodoc:
-      # handle aliases of am/pm
-      # 5:00 in the morning -> 5:00 am
-      # 7:00 in the evening -> 7:00 pm
-      
-      day_portion_index = nil
-      tokens.each_with_index do |t, i|
-        if t.get_tag(RepeaterDayPortion)
-          day_portion_index = i
-          break
-        end
-      end
-       
-      time_index = nil
-      tokens.each_with_index do |t, i|
-        if t.get_tag(RepeaterTime)
-          time_index = i
-          break
-        end
-      end
-      
-      if (day_portion_index && time_index)
-        t1 = tokens[day_portion_index]
-        t1tag = t1.get_tag(RepeaterDayPortion)
-      
-        if [:morning].include?(t1tag.type)
-          puts '--morning->am' if Chronic.debug
-          t1.untag(RepeaterDayPortion)
-          t1.tag(RepeaterDayPortion.new(:am))
-        elsif [:afternoon, :evening, :night].include?(t1tag.type)
-          puts "--#{t1tag.type}->pm" if Chronic.debug
-          t1.untag(RepeaterDayPortion)
-          t1.tag(RepeaterDayPortion.new(:pm))
-        end
-      end
-      
-      # tokens.each_with_index do |t0, i|
-      #   t1 = tokens[i + 1]
-      #   if t1 && (t1tag = t1.get_tag(RepeaterDayPortion)) && t0.get_tag(RepeaterTime)
-      #     if [:morning].include?(t1tag.type)
-      #       puts '--morning->am' if Chronic.debug
-      #       t1.untag(RepeaterDayPortion)
-      #       t1.tag(RepeaterDayPortion.new(:am))
-      #     elsif [:afternoon, :evening, :night].include?(t1tag.type)
-      #       puts "--#{t1tag.type}->pm" if Chronic.debug
-      #       t1.untag(RepeaterDayPortion)
-      #       t1.tag(RepeaterDayPortion.new(:pm))
-      #     end
-      #   end
-      # end
-            
-      # handle ambiguous times if :ambiguous_time_range is specified
-      if options[:ambiguous_time_range] != :none
-        ttokens = []
-        tokens.each_with_index do |t0, i|
-          ttokens << t0
-          t1 = tokens[i + 1]
-          if t0.get_tag(RepeaterTime) && t0.get_tag(RepeaterTime).type.ambiguous? && (!t1 || !t1.get_tag(RepeaterDayPortion))
-            distoken = Token.new('disambiguator')
-            distoken.tag(RepeaterDayPortion.new(options[:ambiguous_time_range]))
-            ttokens << distoken
-          end
-        end
-        tokens = ttokens
-      end
-      
-      tokens
-    end
-    
-  end
-  
-  class Handler #:nodoc:
-    attr_accessor :pattern, :handler_method
-    
-    def initialize(pattern, handler_method)
-      @pattern = pattern
-      @handler_method = handler_method
-    end
-    
-    def constantize(name)
-      camel = name.to_s.gsub(/(^|_)(.)/) { $2.upcase }
-      ::Chronic.module_eval(camel, __FILE__, __LINE__)
-    end
-    
-    def match(tokens, definitions)
-      token_index = 0
-      @pattern.each do |element|
-        name = element.to_s
-        optional = name.reverse[0..0] == '?'
-        name = name.chop if optional
-        if element.instance_of? Symbol
-          klass = constantize(name)
-          match = tokens[token_index] && !tokens[token_index].tags.select { |o| o.kind_of?(klass) }.empty?
-          return false if !match && !optional
-          (token_index += 1; next) if match
-          next if !match && optional
-        elsif element.instance_of? String
-          return true if optional && token_index == tokens.size
-          sub_handlers = definitions[name.intern] || raise(ChronicPain, "Invalid subset #{name} specified")
-          sub_handlers.each do |sub_handler|
-            return true if sub_handler.match(tokens[token_index..tokens.size], definitions)
-          end
-          return false
-        else
-          raise(ChronicPain, "Invalid match type: #{element.class}")
-        end
-      end
-      return false if token_index != tokens.size
-      return true
-    end
-  end
-  
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Ordinal.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Ordinal.php
deleted file mode 100644 (file)
index 45b8148..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-module Chronic
-
-  class Ordinal < Tag #:nodoc:
-    def self.scan(tokens)
-      # for each token
-      tokens.each_index do |i|
-        if t = self.scan_for_ordinals(tokens[i]) then tokens[i].tag(t) end
-        if t = self.scan_for_days(tokens[i]) then tokens[i].tag(t) end
-      end
-      tokens
-    end
-  
-    def self.scan_for_ordinals(token)
-      if token.word =~ /^(\d*)(st|nd|rd|th)$/
-        return Ordinal.new($1.to_i)
-      end
-      return nil
-    end
-    
-    def self.scan_for_days(token)
-      if token.word =~ /^(\d*)(st|nd|rd|th)$/
-        unless $1.to_i > 31
-          return OrdinalDay.new(token.word.to_i)
-        end
-      end
-      return nil
-    end
-    
-    def to_s
-      'ordinal'
-    end
-  end
-  
-  class OrdinalDay < Ordinal #:nodoc:
-    def to_s
-      super << '-day-' << @type.to_s
-    end
-  end
-
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Pointer.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Pointer.php
deleted file mode 100644 (file)
index 224efaf..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-module Chronic
-
-  class Pointer < Tag #:nodoc:
-    def self.scan(tokens)
-      # for each token
-      tokens.each_index do |i|
-        if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t) end
-      end
-      tokens
-    end
-  
-    def self.scan_for_all(token)
-      scanner = {/\bpast\b/ => :past,
-                 /\bfuture\b/ => :future,
-                 /\bin\b/ => :future}
-      scanner.keys.each do |scanner_item|
-        return self.new(scanner[scanner_item]) if scanner_item =~ token.word
-      end
-      return nil
-    end
-    
-    def to_s
-      'pointer-' << @type.to_s
-    end
-  end
-
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeater.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeater.php
deleted file mode 100644 (file)
index 9f80daf..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-class Chronic::Repeater < Chronic::Tag #:nodoc:
-  def self.scan(tokens, options)
-    # for each token
-    tokens.each_index do |i|
-      if t = self.scan_for_month_names(tokens[i]) then tokens[i].tag(t); next end
-      if t = self.scan_for_day_names(tokens[i]) then tokens[i].tag(t); next end
-      if t = self.scan_for_day_portions(tokens[i]) then tokens[i].tag(t); next end
-      if t = self.scan_for_times(tokens[i], options) then tokens[i].tag(t); next end
-      if t = self.scan_for_units(tokens[i]) then tokens[i].tag(t); next end
-    end
-    tokens
-  end
-  
-  def self.scan_for_month_names(token)
-    scanner = {/^jan\.?(uary)?$/ => :january,
-               /^feb\.?(ruary)?$/ => :february,
-               /^mar\.?(ch)?$/ => :march,
-               /^apr\.?(il)?$/ => :april,
-               /^may$/ => :may,
-               /^jun\.?e?$/ => :june,
-               /^jul\.?y?$/ => :july,
-               /^aug\.?(ust)?$/ => :august,
-               /^sep\.?(t\.?|tember)?$/ => :september,
-               /^oct\.?(ober)?$/ => :october,
-               /^nov\.?(ember)?$/ => :november,
-               /^dec\.?(ember)?$/ => :december}
-    scanner.keys.each do |scanner_item|
-      return Chronic::RepeaterMonthName.new(scanner[scanner_item]) if scanner_item =~ token.word
-    end
-    return nil
-  end
-  
-  def self.scan_for_day_names(token)
-    scanner = {/^m[ou]n(day)?$/ => :monday,
-               /^t(ue|eu|oo|u|)s(day)?$/ => :tuesday,
-               /^tue$/ => :tuesday,
-               /^we(dnes|nds|nns)day$/ => :wednesday,
-               /^wed$/ => :wednesday,
-               /^th(urs|ers)day$/ => :thursday,
-               /^thu$/ => :thursday,
-               /^fr[iy](day)?$/ => :friday,
-               /^sat(t?[ue]rday)?$/ => :saturday,
-               /^su[nm](day)?$/ => :sunday}
-    scanner.keys.each do |scanner_item|
-      return Chronic::RepeaterDayName.new(scanner[scanner_item]) if scanner_item =~ token.word
-    end
-    return nil
-  end
-  
-  def self.scan_for_day_portions(token)
-    scanner = {/^ams?$/ => :am,
-               /^pms?$/ => :pm,
-               /^mornings?$/ => :morning,
-               /^afternoons?$/ => :afternoon,
-               /^evenings?$/ => :evening,
-               /^(night|nite)s?$/ => :night}
-    scanner.keys.each do |scanner_item|
-      return Chronic::RepeaterDayPortion.new(scanner[scanner_item]) if scanner_item =~ token.word
-    end
-    return nil
-  end
-  
-  def self.scan_for_times(token, options)
-    if token.word =~ /^\d{1,2}(:?\d{2})?([\.:]?\d{2})?$/
-      return Chronic::RepeaterTime.new(token.word, options)
-    end
-    return nil
-  end
-  
-  def self.scan_for_units(token)
-    scanner = {/^years?$/ => :year,
-               /^seasons?$/ => :season,
-               /^months?$/ => :month,
-               /^fortnights?$/ => :fortnight,
-               /^weeks?$/ => :week,
-               /^weekends?$/ => :weekend,
-               /^days?$/ => :day,
-               /^hours?$/ => :hour,
-               /^minutes?$/ => :minute,
-               /^seconds?$/ => :second}
-    scanner.keys.each do |scanner_item|
-      if scanner_item =~ token.word
-        klass_name = 'Chronic::Repeater' + scanner[scanner_item].to_s.capitalize
-        klass = eval(klass_name)
-        return klass.new(scanner[scanner_item]) 
-      end
-    end
-    return nil
-  end
-  
-  def <=>(other)
-    width <=> other.width
-  end
-  
-  # returns the width (in seconds or months) of this repeatable.
-  def width
-    raise("Repeatable#width must be overridden in subclasses")
-  end
-  
-  # returns the next occurance of this repeatable.
-  def next(pointer)
-    !@now.nil? || raise("Start point must be set before calling #next")
-    [:future, :none, :past].include?(pointer) || raise("First argument 'pointer' must be one of :past or :future")
-    #raise("Repeatable#next must be overridden in subclasses")
-  end
-  
-  def this(pointer)
-    !@now.nil? || raise("Start point must be set before calling #this")
-    [:future, :past, :none].include?(pointer) || raise("First argument 'pointer' must be one of :past, :future, :none")
-  end
-  
-  def to_s
-    'repeater'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Day.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Day.php
deleted file mode 100644 (file)
index a92d83f..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-class Chronic::RepeaterDay < Chronic::Repeater #:nodoc:
-  DAY_SECONDS = 86_400 # (24 * 60 * 60)
-  
-  def next(pointer)
-    super
-    
-    if !@current_day_start
-      @current_day_start = Time.local(@now.year, @now.month, @now.day)
-    end
-    
-    direction = pointer == :future ? 1 : -1
-    @current_day_start += direction * DAY_SECONDS
-    
-    Chronic::Span.new(@current_day_start, @current_day_start + DAY_SECONDS)
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    case pointer
-    when :future
-      day_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
-      day_end = Time.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
-    when :past
-      day_begin = Time.construct(@now.year, @now.month, @now.day)
-      day_end = Time.construct(@now.year, @now.month, @now.day, @now.hour)
-    when :none
-      day_begin = Time.construct(@now.year, @now.month, @now.day)
-      day_end = Time.construct(@now.year, @now.month, @now.day) + DAY_SECONDS
-    end
-    
-    Chronic::Span.new(day_begin, day_end)
-  end
-  
-  def offset(span, amount, pointer)
-    direction = pointer == :future ? 1 : -1
-    span + direction * amount * DAY_SECONDS
-  end
-  
-  def width
-    DAY_SECONDS
-  end
-  
-  def to_s
-    super << '-day'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/DayName.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/DayName.php
deleted file mode 100644 (file)
index 0486a4d..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-class Chronic::RepeaterDayName < Chronic::Repeater #:nodoc:
-  DAY_SECONDS = 86400 # (24 * 60 * 60)
-  
-  def next(pointer)
-    super
-    
-    direction = pointer == :future ? 1 : -1
-    
-    if !@current_day_start
-      @current_day_start = Time.construct(@now.year, @now.month, @now.day)
-      @current_day_start += direction * DAY_SECONDS
-
-      day_num = symbol_to_number(@type)
-      
-      while @current_day_start.wday != day_num
-        @current_day_start += direction * DAY_SECONDS
-      end
-    else
-      @current_day_start += direction * 7 * DAY_SECONDS
-    end
-    
-    Chronic::Span.new(@current_day_start, @current_day_start + DAY_SECONDS)
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    pointer = :future if pointer == :none
-    self.next(pointer)
-  end
-  
-  def width
-    DAY_SECONDS
-  end
-  
-  def to_s
-    super << '-dayname-' << @type.to_s
-  end
-  
-  private
-  
-  def symbol_to_number(sym)
-    lookup = {:sunday => 0, :monday => 1, :tuesday => 2, :wednesday => 3, :thursday => 4, :friday => 5, :saturday => 6}
-    lookup[sym] || raise("Invalid symbol specified")
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/DayPortion.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/DayPortion.php
deleted file mode 100644 (file)
index c854933..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-class Chronic::RepeaterDayPortion < Chronic::Repeater #:nodoc:
-  @@morning = (6 * 60 * 60)..(12 * 60 * 60) # 6am-12am
-  @@afternoon = (13 * 60 * 60)..(17 * 60 * 60) # 1pm-5pm
-  @@evening = (17 * 60 * 60)..(20 * 60 * 60) # 5pm-8pm
-  @@night = (20 * 60 * 60)..(24 * 60 * 60) # 8pm-12pm
-  
-  def initialize(type)
-    super
-    
-    if type.kind_of? Integer
-      @range = (@type * 60 * 60)..((@type + 12) * 60 * 60)
-    else
-      lookup = {:am => 0..(12 * 60 * 60 - 1),
-                :pm => (12 * 60 * 60)..(24 * 60 * 60 - 1),
-                :morning => @@morning,
-                :afternoon => @@afternoon,
-                :evening => @@evening,
-                :night => @@night}
-      @range = lookup[type]
-      lookup[type] || raise("Invalid type '#{type}' for RepeaterDayPortion")
-    end
-    @range || raise("Range should have been set by now")
-  end
-  
-  def next(pointer)
-    super
-    
-    full_day = 60 * 60 * 24
-    
-    if !@current_span
-      now_seconds = @now - Time.construct(@now.year, @now.month, @now.day)
-      if now_seconds < @range.begin
-        case pointer
-        when :future
-          range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
-        when :past
-          range_start = Time.construct(@now.year, @now.month, @now.day) - full_day + @range.begin
-        end
-      elsif now_seconds > @range.end
-        case pointer
-        when :future
-          range_start = Time.construct(@now.year, @now.month, @now.day) + full_day + @range.begin
-        when :past
-          range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
-        end
-      else
-        case pointer
-        when :future
-          range_start = Time.construct(@now.year, @now.month, @now.day) + full_day + @range.begin
-        when :past
-          range_start = Time.construct(@now.year, @now.month, @now.day) - full_day + @range.begin
-        end
-      end
-      
-      @current_span = Chronic::Span.new(range_start, range_start + (@range.end - @range.begin))
-    else
-      case pointer
-      when :future
-        @current_span += full_day
-      when :past
-        @current_span -= full_day
-      end
-    end
-  end
-  
-  def this(context = :future)
-    super
-    
-    range_start = Time.construct(@now.year, @now.month, @now.day) + @range.begin
-    @current_span = Chronic::Span.new(range_start, range_start + (@range.end - @range.begin))
-  end
-  
-  def offset(span, amount, pointer)
-    @now = span.begin
-    portion_span = self.next(pointer)
-    direction = pointer == :future ? 1 : -1
-    portion_span + (direction * (amount - 1) * Chronic::RepeaterDay::DAY_SECONDS)
-  end
-  
-  def width
-    @range || raise("Range has not been set")
-    return @current_span.width if @current_span
-    if @type.kind_of? Integer
-      return (12 * 60 * 60)
-    else
-      @range.end - @range.begin
-    end
-  end
-  
-  def to_s
-    super << '-dayportion-' << @type.to_s
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Fortnight.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Fortnight.php
deleted file mode 100644 (file)
index 058fbb9..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-class Chronic::RepeaterFortnight < Chronic::Repeater #:nodoc:
-  FORTNIGHT_SECONDS = 1_209_600 # (14 * 24 * 60 * 60)
-  
-  def next(pointer)
-    super
-    
-    if !@current_fortnight_start
-      case pointer
-      when :future
-        sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
-        sunday_repeater.start = @now
-        next_sunday_span = sunday_repeater.next(:future)
-        @current_fortnight_start = next_sunday_span.begin
-      when :past
-        sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
-        sunday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
-        2.times { sunday_repeater.next(:past) }
-        last_sunday_span = sunday_repeater.next(:past)
-        @current_fortnight_start = last_sunday_span.begin
-      end
-    else
-      direction = pointer == :future ? 1 : -1
-      @current_fortnight_start += direction * FORTNIGHT_SECONDS
-    end
-    
-    Chronic::Span.new(@current_fortnight_start, @current_fortnight_start + FORTNIGHT_SECONDS)
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    pointer = :future if pointer == :none
-    
-    case pointer
-    when :future
-      this_fortnight_start = Time.construct(@now.year, @now.month, @now.day, @now.hour) + Chronic::RepeaterHour::HOUR_SECONDS
-      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
-      sunday_repeater.start = @now
-      sunday_repeater.this(:future)
-      this_sunday_span = sunday_repeater.this(:future)
-      this_fortnight_end = this_sunday_span.begin
-      Chronic::Span.new(this_fortnight_start, this_fortnight_end)
-    when :past
-      this_fortnight_end = Time.construct(@now.year, @now.month, @now.day, @now.hour)
-      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
-      sunday_repeater.start = @now
-      last_sunday_span = sunday_repeater.next(:past)
-      this_fortnight_start = last_sunday_span.begin
-      Chronic::Span.new(this_fortnight_start, this_fortnight_end)
-    end
-  end
-  
-  def offset(span, amount, pointer)
-    direction = pointer == :future ? 1 : -1
-    span + direction * amount * FORTNIGHT_SECONDS
-  end
-
-  def width
-    FORTNIGHT_SECONDS
-  end
-  
-  def to_s
-    super << '-fortnight'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Hour.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Hour.php
deleted file mode 100644 (file)
index f38a3f8..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-class Chronic::RepeaterHour < Chronic::Repeater #:nodoc:
-  HOUR_SECONDS = 3600 # 60 * 60
-  
-  def next(pointer)
-    super
-    
-    if !@current_hour_start
-      case pointer
-      when :future
-        @current_hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
-      when :past
-        @current_hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour - 1)
-      end
-    else
-      direction = pointer == :future ? 1 : -1
-      @current_hour_start += direction * HOUR_SECONDS
-    end
-    
-    Chronic::Span.new(@current_hour_start, @current_hour_start + HOUR_SECONDS)
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    case pointer
-    when :future
-      hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min + 1)
-      hour_end = Time.construct(@now.year, @now.month, @now.day, @now.hour + 1)
-    when :past
-      hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour)
-      hour_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
-    when :none
-      hour_start = Time.construct(@now.year, @now.month, @now.day, @now.hour)
-      hour_end = hour_begin + HOUR_SECONDS
-    end
-    
-    Chronic::Span.new(hour_start, hour_end)
-  end
-  
-  def offset(span, amount, pointer)
-    direction = pointer == :future ? 1 : -1
-    span + direction * amount * HOUR_SECONDS
-  end
-  
-  def width
-    HOUR_SECONDS
-  end
-  
-  def to_s
-    super << '-hour'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Minute.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Minute.php
deleted file mode 100644 (file)
index 342d3cd..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-class Chronic::RepeaterMinute < Chronic::Repeater #:nodoc:
-  MINUTE_SECONDS = 60
-  
-  def next(pointer = :future)
-    super
-    
-    if !@current_minute_start
-      case pointer
-      when :future
-        @current_minute_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min + 1)
-      when :past
-        @current_minute_start = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min - 1)
-      end
-    else
-      direction = pointer == :future ? 1 : -1
-      @current_minute_start += direction * MINUTE_SECONDS
-    end
-    
-    Chronic::Span.new(@current_minute_start, @current_minute_start + MINUTE_SECONDS)
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    case pointer
-    when :future
-      minute_begin = @now
-      minute_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
-    when :past
-      minute_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
-      minute_end = @now
-    when :none
-      minute_begin = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min)
-      minute_end = Time.construct(@now.year, @now.month, @now.day, @now.hour, @now.min) + MINUTE_SECONDS
-    end
-    
-    Chronic::Span.new(minute_begin, minute_end)
-  end
-  
-  def offset(span, amount, pointer)
-    direction = pointer == :future ? 1 : -1
-    span + direction * amount * MINUTE_SECONDS
-  end
-  
-  def width
-    MINUTE_SECONDS
-  end
-  
-  def to_s
-    super << '-minute'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Month.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Month.php
deleted file mode 100644 (file)
index edd89ee..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-class Chronic::RepeaterMonth < Chronic::Repeater #:nodoc:
-  MONTH_SECONDS = 2_592_000 # 30 * 24 * 60 * 60
-  YEAR_MONTHS = 12
-  
-  def next(pointer)
-    super
-    
-    if !@current_month_start
-      @current_month_start = offset_by(Time.construct(@now.year, @now.month), 1, pointer)
-    else
-      @current_month_start = offset_by(Time.construct(@current_month_start.year, @current_month_start.month), 1, pointer)
-    end
-    
-    Chronic::Span.new(@current_month_start, Time.construct(@current_month_start.year, @current_month_start.month + 1))
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    case pointer
-    when :future
-      month_start = Time.construct(@now.year, @now.month, @now.day + 1)
-      month_end = self.offset_by(Time.construct(@now.year, @now.month), 1, :future)
-    when :past
-      month_start = Time.construct(@now.year, @now.month)
-      month_end = Time.construct(@now.year, @now.month, @now.day)
-    when :none
-      month_start = Time.construct(@now.year, @now.month)
-      month_end = self.offset_by(Time.construct(@now.year, @now.month), 1, :future)
-    end
-    
-    Chronic::Span.new(month_start, month_end)
-  end
-  
-  def offset(span, amount, pointer)      
-    Chronic::Span.new(offset_by(span.begin, amount, pointer), offset_by(span.end, amount, pointer))
-  end
-  
-  def offset_by(time, amount, pointer) 
-    direction = pointer == :future ? 1 : -1
-    
-    amount_years = direction * amount / YEAR_MONTHS
-    amount_months = direction * amount % YEAR_MONTHS
-    
-    new_year = time.year + amount_years
-    new_month = time.month + amount_months
-    if new_month > YEAR_MONTHS
-      new_year += 1
-      new_month -= YEAR_MONTHS
-    end
-    Time.construct(new_year, new_month, time.day, time.hour, time.min, time.sec)
-  end
-  
-  def width
-    MONTH_SECONDS
-  end
-  
-  def to_s
-    super << '-month'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/MonthName.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/MonthName.php
deleted file mode 100644 (file)
index 1f8b748..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-class Chronic::RepeaterMonthName < Chronic::Repeater #:nodoc:
-  MONTH_SECONDS = 2_592_000 # 30 * 24 * 60 * 60
-  
-  def next(pointer)
-    super
-    
-    if !@current_month_begin
-      target_month = symbol_to_number(@type)
-      case pointer
-      when :future
-        if @now.month < target_month
-          @current_month_begin = Time.construct(@now.year, target_month)
-        else @now.month > target_month
-          @current_month_begin = Time.construct(@now.year + 1, target_month)
-        end
-      when :none
-        if @now.month <= target_month
-          @current_month_begin = Time.construct(@now.year, target_month)
-        else @now.month > target_month
-          @current_month_begin = Time.construct(@now.year + 1, target_month)
-        end
-      when :past
-        if @now.month > target_month
-          @current_month_begin = Time.construct(@now.year, target_month)
-        else @now.month < target_month
-          @current_month_begin = Time.construct(@now.year - 1, target_month)
-        end
-      end
-      @current_month_begin || raise("Current month should be set by now")
-    else
-      case pointer
-      when :future
-        @current_month_begin = Time.construct(@current_month_begin.year + 1, @current_month_begin.month)
-      when :past
-        @current_month_begin = Time.construct(@current_month_begin.year - 1, @current_month_begin.month)
-      end
-    end
-    
-    cur_month_year = @current_month_begin.year
-    cur_month_month = @current_month_begin.month
-    
-    if cur_month_month == 12
-      next_month_year = cur_month_year + 1
-      next_month_month = 1
-    else
-      next_month_year = cur_month_year
-      next_month_month = cur_month_month + 1
-    end
-      
-    Chronic::Span.new(@current_month_begin, Time.construct(next_month_year, next_month_month))
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    case pointer
-    when :past
-      self.next(pointer)
-    when :future, :none
-      self.next(:none)
-    end
-  end
-  
-  def width
-    MONTH_SECONDS
-  end
-  
-  def index
-    symbol_to_number(@type)
-  end
-  
-  def to_s
-    super << '-monthname-' << @type.to_s
-  end
-  
-  private
-  
-  def symbol_to_number(sym)
-    lookup = {:january => 1,
-              :february => 2,
-              :march => 3,
-              :april => 4,
-              :may => 5,
-              :june => 6,
-              :july => 7,
-              :august => 8,
-              :september => 9,
-              :october => 10,
-              :november => 11,
-              :december => 12}
-    lookup[sym] || raise("Invalid symbol specified")
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Season.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Season.php
deleted file mode 100644 (file)
index a255865..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-class Chronic::RepeaterSeason < Chronic::Repeater #:nodoc:
-  SEASON_SECONDS = 7_862_400 # 91 * 24 * 60 * 60
-  
-  def next(pointer)
-    super
-    
-    raise 'Not implemented'
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    raise 'Not implemented'
-  end
-  
-  def width
-    SEASON_SECONDS
-  end
-  
-  def to_s
-    super << '-season'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/SeasonName.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/SeasonName.php
deleted file mode 100644 (file)
index adfd1f2..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-class Chronic::RepeaterSeasonName < Chronic::RepeaterSeason #:nodoc:
-  @summer = ['jul 21', 'sep 22']
-  @autumn = ['sep 23', 'dec 21']
-  @winter = ['dec 22', 'mar 19']
-  @spring = ['mar 20', 'jul 20']
-  
-  def next(pointer)
-    super
-    raise 'Not implemented'
-  end
-  
-  def this(pointer = :future)
-    super
-    raise 'Not implemented'
-  end
-  
-  def width
-    (91 * 24 * 60 * 60)
-  end
-  
-  def to_s
-    super << '-season-' << @type.to_s
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Second.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Second.php
deleted file mode 100644 (file)
index 6d05545..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-class Chronic::RepeaterSecond < Chronic::Repeater #:nodoc:
-  SECOND_SECONDS = 1 # haha, awesome
-  
-  def next(pointer = :future)
-    super
-    
-    direction = pointer == :future ? 1 : -1
-    
-    if !@second_start
-      @second_start = @now + (direction * SECOND_SECONDS)
-    else
-      @second_start += SECOND_SECONDS * direction
-    end
-    
-    Chronic::Span.new(@second_start, @second_start + SECOND_SECONDS)
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    Chronic::Span.new(@now, @now + 1)
-  end
-  
-  def offset(span, amount, pointer)
-    direction = pointer == :future ? 1 : -1
-    span + direction * amount * SECOND_SECONDS
-  end
-  
-  def width
-    SECOND_SECONDS
-  end
-  
-  def to_s
-    super << '-second'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Time.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Time.php
deleted file mode 100644 (file)
index f856014..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-class Chronic::RepeaterTime < Chronic::Repeater #:nodoc:
-  class Tick #:nodoc:
-    attr_accessor :time
-    
-    def initialize(time, ambiguous = false)
-      @time = time
-      @ambiguous = ambiguous
-    end
-    
-    def ambiguous?
-      @ambiguous
-    end
-    
-    def *(other)
-      Tick.new(@time * other, @ambiguous)
-    end
-    
-    def to_f
-      @time.to_f
-    end
-    
-    def to_s
-      @time.to_s + (@ambiguous ? '?' : '')
-    end
-  end
-  
-  def initialize(time, options = {})
-    t = time.gsub(/\:/, '')
-    @type = 
-    if (1..2) === t.size
-      hours = t.to_i
-      hours == 12 ? Tick.new(0 * 60 * 60, true) : Tick.new(hours * 60 * 60, true)
-    elsif t.size == 3
-      Tick.new((t[0..0].to_i * 60 * 60) + (t[1..2].to_i * 60), true)
-    elsif t.size == 4
-      ambiguous = time =~ /:/ && t[0..0].to_i != 0 && t[0..1].to_i <= 12
-      hours = t[0..1].to_i
-      hours == 12 ? Tick.new(0 * 60 * 60 + t[2..3].to_i * 60, ambiguous) : Tick.new(hours * 60 * 60 + t[2..3].to_i * 60, ambiguous)
-    elsif t.size == 5
-      Tick.new(t[0..0].to_i * 60 * 60 + t[1..2].to_i * 60 + t[3..4].to_i, true)
-    elsif t.size == 6
-      ambiguous = time =~ /:/ && t[0..0].to_i != 0 && t[0..1].to_i <= 12
-      hours = t[0..1].to_i
-      hours == 12 ? Tick.new(0 * 60 * 60 + t[2..3].to_i * 60 + t[4..5].to_i, ambiguous) : Tick.new(hours * 60 * 60 + t[2..3].to_i * 60 + t[4..5].to_i, ambiguous)
-    else
-      raise("Time cannot exceed six digits")
-    end
-  end
-  
-  # Return the next past or future Span for the time that this Repeater represents
-  #   pointer - Symbol representing which temporal direction to fetch the next day
-  #             must be either :past or :future
-  def next(pointer)
-    super
-    
-    half_day = 60 * 60 * 12
-    full_day = 60 * 60 * 24
-    
-    first = false
-    
-    unless @current_time
-      first = true
-      midnight = Time.local(@now.year, @now.month, @now.day)
-      yesterday_midnight = midnight - full_day
-      tomorrow_midnight = midnight + full_day
-
-      catch :done do
-        if pointer == :future
-          if @type.ambiguous?
-            [midnight + @type, midnight + half_day + @type, tomorrow_midnight + @type].each do |t|
-              (@current_time = t; throw :done) if t >= @now
-            end
-          else
-            [midnight + @type, tomorrow_midnight + @type].each do |t|
-              (@current_time = t; throw :done) if t >= @now
-            end
-          end
-        else # pointer == :past
-          if @type.ambiguous?
-            [midnight + half_day + @type, midnight + @type, yesterday_midnight + @type * 2].each do |t|
-              (@current_time = t; throw :done) if t <= @now
-            end
-          else
-            [midnight + @type, yesterday_midnight + @type].each do |t|
-              (@current_time = t; throw :done) if t <= @now
-            end
-          end
-        end
-      end
-      
-      @current_time || raise("Current time cannot be nil at this point")
-    end
-    
-    unless first
-      increment = @type.ambiguous? ? half_day : full_day
-      @current_time += pointer == :future ? increment : -increment
-    end
-    
-    Chronic::Span.new(@current_time, @current_time + width)
-  end
-  
-  def this(context = :future)
-    super
-    
-    context = :future if context == :none
-    
-    self.next(context)
-  end
-  
-  def width
-    1
-  end
-  
-  def to_s
-    super << '-time-' << @type.to_s
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Week.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Week.php
deleted file mode 100644 (file)
index ec88ff1..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-class Chronic::RepeaterWeek < Chronic::Repeater #:nodoc:
-  WEEK_SECONDS = 604800 # (7 * 24 * 60 * 60)
-  
-  def next(pointer)
-    super
-    
-    if !@current_week_start
-      case pointer
-      when :future
-        sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
-        sunday_repeater.start = @now
-        next_sunday_span = sunday_repeater.next(:future)
-        @current_week_start = next_sunday_span.begin
-      when :past
-        sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
-        sunday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
-        sunday_repeater.next(:past)
-        last_sunday_span = sunday_repeater.next(:past)
-        @current_week_start = last_sunday_span.begin
-      end
-    else
-      direction = pointer == :future ? 1 : -1
-      @current_week_start += direction * WEEK_SECONDS
-    end
-    
-    Chronic::Span.new(@current_week_start, @current_week_start + WEEK_SECONDS)
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    case pointer
-    when :future
-      this_week_start = Time.local(@now.year, @now.month, @now.day, @now.hour) + Chronic::RepeaterHour::HOUR_SECONDS
-      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
-      sunday_repeater.start = @now
-      this_sunday_span = sunday_repeater.this(:future)
-      this_week_end = this_sunday_span.begin
-      Chronic::Span.new(this_week_start, this_week_end)
-    when :past
-      this_week_end = Time.local(@now.year, @now.month, @now.day, @now.hour)
-      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
-      sunday_repeater.start = @now
-      last_sunday_span = sunday_repeater.next(:past)
-      this_week_start = last_sunday_span.begin
-      Chronic::Span.new(this_week_start, this_week_end)
-    when :none
-      sunday_repeater = Chronic::RepeaterDayName.new(:sunday)
-      sunday_repeater.start = @now
-      last_sunday_span = sunday_repeater.next(:past)
-      this_week_start = last_sunday_span.begin
-      Chronic::Span.new(this_week_start, this_week_start + WEEK_SECONDS)
-    end
-  end
-  
-  def offset(span, amount, pointer)
-    direction = pointer == :future ? 1 : -1
-    span + direction * amount * WEEK_SECONDS
-  end
-  
-  def width
-    WEEK_SECONDS
-  end
-  
-  def to_s
-    super << '-week'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Weekend.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Weekend.php
deleted file mode 100644 (file)
index f012267..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-class Chronic::RepeaterWeekend < Chronic::Repeater #:nodoc:
-  WEEKEND_SECONDS = 172_800 # (2 * 24 * 60 * 60)
-  
-  def next(pointer)
-    super
-    
-    if !@current_week_start
-      case pointer
-      when :future
-        saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
-        saturday_repeater.start = @now
-        next_saturday_span = saturday_repeater.next(:future)
-        @current_week_start = next_saturday_span.begin
-      when :past
-        saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
-        saturday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
-        last_saturday_span = saturday_repeater.next(:past)
-        @current_week_start = last_saturday_span.begin
-      end
-    else
-      direction = pointer == :future ? 1 : -1
-      @current_week_start += direction * Chronic::RepeaterWeek::WEEK_SECONDS
-    end
-    
-    Chronic::Span.new(@current_week_start, @current_week_start + WEEKEND_SECONDS)
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    case pointer
-    when :future, :none
-      saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
-      saturday_repeater.start = @now
-      this_saturday_span = saturday_repeater.this(:future)
-      Chronic::Span.new(this_saturday_span.begin, this_saturday_span.begin + WEEKEND_SECONDS)
-    when :past
-      saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
-      saturday_repeater.start = @now
-      last_saturday_span = saturday_repeater.this(:past)
-      Chronic::Span.new(last_saturday_span.begin, last_saturday_span.begin + WEEKEND_SECONDS)
-    end
-  end
-  
-  def offset(span, amount, pointer)
-    direction = pointer == :future ? 1 : -1
-    weekend = Chronic::RepeaterWeekend.new(:weekend)
-    weekend.start = span.begin
-    start = weekend.next(pointer).begin + (amount - 1) * direction * Chronic::RepeaterWeek::WEEK_SECONDS
-    Chronic::Span.new(start, start + (span.end - span.begin))
-  end
-  
-  def width
-    WEEKEND_SECONDS
-  end
-  
-  def to_s
-    super << '-weekend'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Year.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Repeaters/Year.php
deleted file mode 100644 (file)
index 426371f..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-class Chronic::RepeaterYear < Chronic::Repeater #:nodoc:
-  
-  def next(pointer)
-    super
-    
-    if !@current_year_start
-      case pointer
-      when :future
-        @current_year_start = Time.construct(@now.year + 1)
-      when :past
-        @current_year_start = Time.construct(@now.year - 1)
-      end
-    else
-      diff = pointer == :future ? 1 : -1
-      @current_year_start = Time.construct(@current_year_start.year + diff)
-    end
-    
-    Chronic::Span.new(@current_year_start, Time.construct(@current_year_start.year + 1))
-  end
-  
-  def this(pointer = :future)
-    super
-    
-    case pointer
-    when :future
-      this_year_start = Time.construct(@now.year, @now.month, @now.day) + Chronic::RepeaterDay::DAY_SECONDS
-      this_year_end = Time.construct(@now.year + 1, 1, 1)
-    when :past
-      this_year_start = Time.construct(@now.year, 1, 1)
-      this_year_end = Time.construct(@now.year, @now.month, @now.day)
-    when :none
-      this_year_start = Time.construct(@now.year, 1, 1)
-      this_year_end = Time.construct(@now.year + 1, 1, 1)
-    end
-    
-    Chronic::Span.new(this_year_start, this_year_end)
-  end
-  
-  def offset(span, amount, pointer)
-    direction = pointer == :future ? 1 : -1
-    
-    sb = span.begin
-    new_begin = Time.construct(sb.year + (amount * direction), sb.month, sb.day, sb.hour, sb.min, sb.sec)
-    
-    se = span.end
-    new_end = Time.construct(se.year + (amount * direction), se.month, se.day, se.hour, se.min, se.sec)
-    
-    Chronic::Span.new(new_begin, new_end)
-  end
-  
-  def width
-    (365 * 24 * 60 * 60)
-  end
-  
-  def to_s
-    super << '-year'
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Scalar.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Scalar.php
deleted file mode 100644 (file)
index b08cfee..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-module Chronic
-
-  class Scalar < Tag #:nodoc:
-    def self.scan(tokens)
-      # for each token
-      tokens.each_index do |i|
-        if t = self.scan_for_scalars(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
-        if t = self.scan_for_days(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
-        if t = self.scan_for_months(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
-        if t = self.scan_for_years(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
-      end
-      tokens
-    end
-  
-    def self.scan_for_scalars(token, post_token)
-      if token.word =~ /^\d*$/
-        unless post_token && %w{am pm morning afternoon evening night}.include?(post_token)
-          return Scalar.new(token.word.to_i)
-        end
-      end
-      return nil
-    end
-    
-    def self.scan_for_days(token, post_token)
-      if token.word =~ /^\d\d?$/
-        unless token.word.to_i > 31 || (post_token && %w{am pm morning afternoon evening night}.include?(post_token))
-          return ScalarDay.new(token.word.to_i)
-        end
-      end
-      return nil
-    end
-    
-    def self.scan_for_months(token, post_token)
-      if token.word =~ /^\d\d?$/
-        unless token.word.to_i > 12 || (post_token && %w{am pm morning afternoon evening night}.include?(post_token))
-          return ScalarMonth.new(token.word.to_i)
-        end
-      end
-      return nil
-    end
-    
-    def self.scan_for_years(token, post_token)
-      if token.word =~ /^([1-9]\d)?\d\d?$/
-        unless post_token && %w{am pm morning afternoon evening night}.include?(post_token)
-          return ScalarYear.new(token.word.to_i)
-        end
-      end
-      return nil
-    end
-    
-    def to_s
-      'scalar'
-    end
-  end
-  
-  class ScalarDay < Scalar #:nodoc:
-    def to_s
-      super << '-day-' << @type.to_s
-    end
-  end
-  
-  class ScalarMonth < Scalar #:nodoc:
-    def to_s
-      super << '-month-' << @type.to_s
-    end
-  end
-  
-  class ScalarYear < Scalar #:nodoc:
-    def to_s
-      super << '-year-' << @type.to_s
-    end
-  end
-
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Separator.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Separator.php
deleted file mode 100644 (file)
index 86c56e3..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-module Chronic
-
-  class Separator < Tag #:nodoc:
-    def self.scan(tokens)
-      tokens.each_index do |i|
-        if t = self.scan_for_commas(tokens[i]) then tokens[i].tag(t); next end
-        if t = self.scan_for_slash_or_dash(tokens[i]) then tokens[i].tag(t); next end
-        if t = self.scan_for_at(tokens[i]) then tokens[i].tag(t); next end
-        if t = self.scan_for_in(tokens[i]) then tokens[i].tag(t); next end
-      end
-      tokens
-    end
-    
-    def self.scan_for_commas(token)
-      scanner = {/^,$/ => :comma}
-      scanner.keys.each do |scanner_item|
-        return SeparatorComma.new(scanner[scanner_item]) if scanner_item =~ token.word
-      end
-      return nil
-    end
-    
-    def self.scan_for_slash_or_dash(token)
-      scanner = {/^-$/ => :dash,
-                 /^\/$/ => :slash}
-      scanner.keys.each do |scanner_item|
-        return SeparatorSlashOrDash.new(scanner[scanner_item]) if scanner_item =~ token.word
-      end
-      return nil
-    end
-    
-    def self.scan_for_at(token)
-      scanner = {/^(at|@)$/ => :at}
-      scanner.keys.each do |scanner_item|
-        return SeparatorAt.new(scanner[scanner_item]) if scanner_item =~ token.word
-      end
-      return nil
-    end
-    
-    def self.scan_for_in(token)
-      scanner = {/^in$/ => :in}
-      scanner.keys.each do |scanner_item|
-        return SeparatorIn.new(scanner[scanner_item]) if scanner_item =~ token.word
-      end
-      return nil
-    end
-    
-    def to_s
-      'separator'
-    end
-  end
-  
-  class SeparatorComma < Separator #:nodoc:
-    def to_s
-      super << '-comma'
-    end
-  end
-  
-  class SeparatorSlashOrDash < Separator #:nodoc:
-    def to_s
-      super << '-slashordash-' << @type.to_s
-    end
-  end
-  
-  class SeparatorAt < Separator #:nodoc:
-    def to_s
-      super << '-at'
-    end
-  end
-  
-  class SeparatorIn < Separator #:nodoc:
-    def to_s
-      super << '-in'
-    end
-  end
-
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Timezone.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Locale/Base/Timezone.php
deleted file mode 100644 (file)
index 41041ef..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-module Chronic
-  class TimeZone < Tag #:nodoc:    
-    def self.scan(tokens)
-      tokens.each_index do |i|
-        if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t); next end
-      end
-      tokens
-    end
-
-    def self.scan_for_all(token)
-      scanner = {/[PMCE][DS]T/i => :tz}
-      scanner.keys.each do |scanner_item|
-        return self.new(scanner[scanner_item]) if scanner_item =~ token.word
-      end
-      return nil
-    end
-
-    def to_s
-      'timezone'
-    end
-  end
-end
\ No newline at end of file
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Tag.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Tag.php
deleted file mode 100644 (file)
index a079640..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-<?php
-/**
- * Tokens are tagged with subclassed instances of this class when
- * they match specific criteria
- */
-class Horde_Date_Parser_Tag
-{
-    public $type;
-    public $now;
-
-    public function __construct($type)
-    {
-        $this->type = $type;
-    }
-
-    public function start($s)
-    {
-        $this->now = $s;
-    }
-
-}
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Token.php b/framework/Horde_Date_Parser/lib/Horde/Date/Parser/Token.php
deleted file mode 100644 (file)
index 16b3b70..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-<?php
-class Horde_Date_Parser_Token
-{
-    public $word;
-    public $tags;
-
-    public function __construct($word)
-    {
-        $this->word = $word;
-        $this->tags = array();
-    }
-
-    /**
-     * Tag this token with the specified tag
-     */
-    public function tag($new_tag)
-    {
-        $this->tags[] = $new_tag;
-    }
-
-    /**
-     * Remove all tags of the given class
-     */
-    public function untag($tag_class)
-    {
-        $this->tags = array_filter($this->tags, create_function('$t', 'return $t instanceof ' . $tag_class));
-    }
-
-    /**
-     * Return true if this token has any tags
-     */
-    public function tagged()
-    {
-        return count($this->tags) > 0;
-    }
-
-    /**
-     * Return the Tag that matches the given class
-     */
-    public function getTag($tag_class)
-    {
-        $matches = array_filter($this->tags, create_function('$t', 'return $t instanceof ' . $tag_class));
-        return array_shift($matches);
-    }
-
-    /**
-     * Print this Token in a pretty way
-     */
-    public function __toString()
-    {
-        return '(' . implode(', ', $this->tags) . ') ';
-    }
-
-}
diff --git a/framework/Horde_Date_Parser/lib/Horde/Date/Span.php b/framework/Horde_Date_Parser/lib/Horde/Date/Span.php
deleted file mode 100644 (file)
index d61c24c..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-  # A Span represents a range of time. Since this class extends
-  # Range, you can use #begin and #end to get the beginning and
-  # ending times of the span (they will be of class Time)
-  class Span < Range
-    # Returns the width of this span in seconds
-    def width
-      (self.end - self.begin).to_i
-    end
-
-    # Add a number of seconds to this span, returning the
-    # resulting Span
-    def +(seconds)
-      Span.new(self.begin + seconds, self.end + seconds)
-    end
-
-    # Subtract a number of seconds to this span, returning the
-    # resulting Span
-    def -(seconds)
-      self + -seconds
-    end
-
-    # Prints this span in a nice fashion
-    def to_s
-      '(' << self.begin.to_s << '..' << self.end.to_s << ')'
-    end
-  end
-
diff --git a/framework/Horde_Date_Parser/lib/Horde/Support/Numerizer.php b/framework/Horde_Date_Parser/lib/Horde/Support/Numerizer.php
deleted file mode 100644 (file)
index 062f704..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-<?php
-/**
- *
- */
-class Horde_Support_Numerizer
-{
-    public static function factory($args = array())
-    {
-        $locale = isset($args['locale']) ? $args['locale'] : null;
-        if ($locale && strtolower($locale) != 'base') {
-            $locale = str_replace(' ', '_', ucwords(str_replace('_', ' ', strtolower($locale))));
-            $class = 'Horde_Support_Numerizer_Locale_' . $locale;
-            if (class_exists($class)) {
-                return new $class($args);
-            }
-
-            $language = array_shift(explode('_', $locale));
-            if ($language != $locale) {
-                $class = 'Horde_Support_Numerizer_Locale_' . $language;
-                if (class_exists($class)) {
-                    return new $class($args);
-                }
-            }
-        }
-
-        return new Horde_Support_Numerizer_Locale_Base($args);
-    }
-
-}
diff --git a/framework/Horde_Date_Parser/lib/Horde/Support/Numerizer/Locale/Base.php b/framework/Horde_Date_Parser/lib/Horde/Support/Numerizer/Locale/Base.php
deleted file mode 100644 (file)
index b6cc380..0000000
+++ /dev/null
@@ -1,149 +0,0 @@
-<?php
-class Horde_Support_Numerizer_Locale_Base
-{
-    public $DIRECT_NUMS = array(
-        'eleven' => '11',
-        'twelve' => '12',
-        'thirteen' => '13',
-        'fourteen' => '14',
-        'fifteen' => '15',
-        'sixteen' => '16',
-        'seventeen' => '17',
-        'eighteen' => '18',
-        'nineteen' => '19',
-        'ninteen' => '19',      // Common mis-spelling
-        'zero' => '0',
-        'one' => '1',
-        'two' => '2',
-        'three' => '3',
-        'four(\W|$)' => '4$1',  // The weird regex is so that it matches four but not fourty
-        'five' => '5',
-        'six(\W|$)' => '6$1',
-        'seven(\W|$)' => '7$1',
-        'eight(\W|$)' => '8$1',
-        'nine(\W|$)' => '9$1',
-        'ten' => '10',
-        '\ba[\b^$]' => '1',     // doesn't make sense for an 'a' at the end to be a 1
-    );
-
-    public $TEN_PREFIXES = array(
-        'twenty' => 20,
-        'thirty' => 30,
-        'fourty' => 40,
-        'fifty' => 50,
-        'sixty' => 60,
-        'seventy' => 70,
-        'eighty' => 80,
-        'ninety' => 90,
-    );
-
-    public $BIG_PREFIXES = array(
-        'hundred' => 100,
-        'thousand' => 1000,
-        'million' => 1000000,
-        'billion' => 1000000000,
-        'trillion' => 1000000000000,
-    );
-
-    public function numerize($string)
-    {
-        // preprocess
-        $string = $this->_splitHyphenatedWords($string);
-        $string = $this->_hideAHalf($string);
-
-        $string = $this->_directReplacements($string);
-        $string = $this->_replaceTenPrefixes($string);
-        $string = $this->_replaceBigPrefixes($string);
-        $string = $this->_fractionalAddition($string);
-
-        return $string;
-    }
-
-    /**
-     * will mutilate hyphenated-words but shouldn't matter for date extraction
-     */
-    protected function _splitHyphenatedWords($string)
-    {
-        return preg_replace('/ +|([^\d])-([^d])/', '$1 $2', $string);
-    }
-
-    /**
-     * take the 'a' out so it doesn't turn into a 1, save the half for the end
-     */
-    protected function _hideAHalf($string)
-    {
-        return str_replace('a half', 'haAlf', $string);
-    }
-
-    /**
-     * easy/direct replacements
-     */
-    protected function _directReplacements($string)
-    {
-        foreach ($this->DIRECT_NUMS as $dn => $dn_replacement) {
-            $string = preg_replace("/$dn/i", $dn_replacement, $string);
-        }
-        return $string;
-    }
-
-    /**
-     * ten, twenty, etc.
-     */
-    protected function _replaceTenPrefixes($string)
-    {
-        foreach ($this->TEN_PREFIXES as $tp => $tp_replacement) {
-            $string = preg_replace_callback(
-                "/(?:$tp)( *\d(?=[^\d]|\$))*/i",
-                create_function(
-                    '$m',
-                    'return ' . $tp_replacement . ' + (isset($m[1]) ? (int)$m[1] : 0);'
-                ),
-                $string);
-        }
-        return $string;
-    }
-
-    /**
-     * hundreds, thousands, millions, etc.
-     */
-    protected function _replaceBigPrefixes($string)
-    {
-        foreach ($this->BIG_PREFIXES as $bp => $bp_replacement) {
-            $string = preg_replace_callback(
-                '/(\d*) *' . $bp . '/i',
-                create_function(
-                    '$m',
-                    'return ' . $bp_replacement . ' * (int)$m[1];'
-                ),
-                $string);
-            $string = $this->_andition($string);
-        }
-        return $string;
-    }
-
-    protected function _andition($string)
-    {
-        while (true) {
-            if (preg_match('/(\d+)( | and )(\d+)(?=[^\w]|$)/i', $string, $sc, PREG_OFFSET_CAPTURE)) {
-                if (preg_match('/and/', $sc[2][0]) || $sc[1][0] > $sc[3][0]) {
-                    $string = substr($string, 0, $sc[1][1]) . ((int)$sc[1][0] + (int)$sc[3][0]) . substr($string, $sc[3][1] + strlen($sc[3][0]));
-                    continue;
-                }
-            }
-            break;
-        }
-        return $string;
-    }
-
-    protected function _fractionalAddition($string)
-    {
-        return preg_replace_callback(
-            '/(\d+)(?: | and |-)*haAlf/i',
-            create_function(
-                '$m',
-                'return (string)((float)$m[1] + 0.5);'
-            ),
-            $string);
-    }
-
-}
diff --git a/framework/Horde_Date_Parser/lib/Horde/Support/Numerizer/Locale/De.php b/framework/Horde_Date_Parser/lib/Horde/Support/Numerizer/Locale/De.php
deleted file mode 100644 (file)
index 2e13fae..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-<?php
-class Horde_Support_Numerizer_Locale_De extends Horde_Support_Numerizer_Locale_Base
-{
-    public $DIRECT_NUMS = array(
-        'dreizehn' => 13,
-        'vierzehn' => 14,
-        'fünfzehn' => 15,
-        'sechzehn' => 16,
-        'siebzehn' => 17,
-        'achtzehn' => 18,
-        'neunzehn' => 19,
-        'eins' => 1,
-        'zwei' => 2,
-        'zwo' => 2,
-        'drei' => 3,
-        'vier' => 4,
-        'fünf' => 5,
-        'sechs' => 6,
-        'sieben' => 7,
-        'acht' => 8,
-        'neun' => 9,
-        'zehn' => 10,
-        'elf' => 11,
-        'zwölf' => 12,
-        'eine?' => 1,
-    );
-
-    public $TEN_PREFIXES = array(
-        'zwanzig' => 20,
-        'dreißig' => 30,
-        'vierzig' => 40,
-        'fünfzig' => 50,
-        'sechzig' => 60,
-        'siebzig' => 70,
-        'achtzig' => 80,
-        'neunzig' => 90,
-    );
-
-    public $BIG_PREFIXES = array(
-        'hundert' => 100,
-        'tausend' => 1000,
-        'million' => 1000000,
-        'milliarde' => 1000000000,
-        'billion' => 1000000000000,
-    );
-
-    /**
-     * Rules:
-     *
-     * - there are irregular word for 11 and 12 like in English
-     * - numbers below one million are written together (1 M = "eine Million", 100 = "einhundert")
-     * - "a" is declinable (see above, "one" = "eins", "a" = "ein/eine")
-     * - numbers below 100 are flipped compared to english, and have an "and = "und" (21 = "twenty-one" = "einundzwanzig")
-     */
-    public function numerize($string)
-    {
-        // preprocess?
-
-        $string = $this->_replaceTenPrefixes($string);
-        $string = $this->_directReplacements($string);
-        $string = $this->_replaceBigPrefixes($string);
-        $string = $this->_fractionalAddition($string);
-        $string = $this->_andition($string);
-
-        return $string;
-    }
-
-    /**
-     * ten, twenty, etc.
-     */
-    protected function _replaceTenPrefixes($string)
-    {
-        foreach ($this->TEN_PREFIXES as $tp => $tp_replacement) {
-            $string = preg_replace_callback(
-                "/(?:$tp)( *\d(?=[^\d]|\$))*/i",
-                create_function(
-                    '$m',
-                    'return ' . $tp_replacement . ' + (isset($m[1]) ? (int)$m[1] : 0);'
-                ),
-                $string);
-        }
-        return $string;
-    }
-
-    /**
-     * hundreds, thousands, millions, etc.
-     */
-    protected function _replaceBigPrefixes($string)
-    {
-        foreach ($this->BIG_PREFIXES as $bp => $bp_replacement) {
-            $string = preg_replace_callback(
-                '/(\d*) *' . $bp . '/i',
-                create_function(
-                    '$m',
-                    '$factor = (int)$m[1]; if (!$factor) $factor = 1; return (' . $bp_replacement . ' * $factor) . "und";'
-                ),
-                $string);
-        }
-        return $string;
-    }
-
-    protected function _andition($string)
-    {
-        while (preg_match('/(\d+)((?:und)+)(\d*)(?=[^\w]|$)/i', $string, $sc, PREG_OFFSET_CAPTURE)) {
-            $string = substr($string, 0, $sc[1][1]) . ((int)$sc[1][0] + (int)$sc[3][0]) . substr($string, $sc[3][1] + strlen($sc[3][0]));
-        }
-        return $string;
-    }
-
-}
diff --git a/framework/Horde_Date_Parser/package.xml b/framework/Horde_Date_Parser/package.xml
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/framework/Horde_Date_Parser/test/Horde/Date/Parser/AllTests.php b/framework/Horde_Date_Parser/test/Horde/Date/Parser/AllTests.php
deleted file mode 100644 (file)
index 53d8bde..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-<?php
-/**
- * @category   Horde
- * @package    Date_Parser
- * @subpackage UnitTests
- * @copyright  2008 The Horde Project (http://www.horde.org/)
- * @license    http://opensource.org/licenses/bsd-license.php
- */
-
-if (!defined('PHPUnit_MAIN_METHOD')) {
-    define('PHPUnit_MAIN_METHOD', 'Horde_Date_Parser_AllTests::main');
-}
-
-require_once 'PHPUnit/Framework/TestSuite.php';
-require_once 'PHPUnit/TextUI/TestRunner.php';
-
-class Horde_Date_Parser_AllTests {
-
-    public static function main()
-    {
-        PHPUnit_TextUI_TestRunner::run(self::suite());
-    }
-
-    public static function suite()
-    {
-        set_include_path(dirname(__FILE__) . '/../../../lib' . PATH_SEPARATOR . get_include_path());
-        if (!spl_autoload_functions()) {
-            spl_autoload_register(create_function('$class', '$filename = str_replace(array(\'::\', \'_\'), \'/\', $class); include "$filename.php";'));
-        }
-
-        $suite = new PHPUnit_Framework_TestSuite('Horde Framework - Horde_Date_Parser');
-
-        $basedir = dirname(__FILE__);
-        $baseregexp = preg_quote($basedir . DIRECTORY_SEPARATOR, '/');
-
-        foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($basedir)) as $file) {
-            if ($file->isFile() && preg_match('/Test.php$/', $file->getFilename())) {
-                $pathname = $file->getPathname();
-                require $pathname;
-
-                $class = str_replace(DIRECTORY_SEPARATOR, '_',
-                                     preg_replace("/^$baseregexp(.*)\.php/", '\\1', $pathname));
-                $suite->addTestSuite('Horde_Date_Parser_' . $class);
-            }
-        }
-
-        return $suite;
-    }
-
-}
-
-if (PHPUnit_MAIN_METHOD == 'Horde_Date_Parser_AllTests::main') {
-    Horde_Date_Parser_AllTests::main();
-}
diff --git a/framework/Horde_Date_Parser/test/Horde/Support/AllTests.php b/framework/Horde_Date_Parser/test/Horde/Support/AllTests.php
deleted file mode 100644 (file)
index 7fac8b1..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-<?php
-/**
- * @category   Horde
- * @package    Support
- * @subpackage UnitTests
- * @copyright  2008 The Horde Project (http://www.horde.org/)
- * @license    http://opensource.org/licenses/bsd-license.php
- */
-
-if (!defined('PHPUnit_MAIN_METHOD')) {
-    define('PHPUnit_MAIN_METHOD', 'Horde_Support_AllTests::main');
-}
-
-require_once 'PHPUnit/Framework/TestSuite.php';
-require_once 'PHPUnit/TextUI/TestRunner.php';
-
-class Horde_Support_AllTests {
-
-    public static function main()
-    {
-        PHPUnit_TextUI_TestRunner::run(self::suite());
-    }
-
-    public static function suite()
-    {
-        set_include_path(dirname(__FILE__) . '/../../../lib' . PATH_SEPARATOR . get_include_path());
-        if (!spl_autoload_functions()) {
-            spl_autoload_register(create_function('$class', '$filename = str_replace(array(\'::\', \'_\'), \'/\', $class); include "$filename.php";'));
-        }
-
-        $suite = new PHPUnit_Framework_TestSuite('Horde Framework - Horde_Support');
-
-        $basedir = dirname(__FILE__);
-        $baseregexp = preg_quote($basedir . DIRECTORY_SEPARATOR, '/');
-
-        foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($basedir)) as $file) {
-            if ($file->isFile() && preg_match('/Test.php$/', $file->getFilename())) {
-                $pathname = $file->getPathname();
-                require $pathname;
-
-                $class = str_replace(DIRECTORY_SEPARATOR, '_',
-                                     preg_replace("/^$baseregexp(.*)\.php/", '\\1', $pathname));
-                $suite->addTestSuite('Horde_Support_' . $class);
-            }
-        }
-
-        return $suite;
-    }
-
-}
-
-if (PHPUnit_MAIN_METHOD == 'Horde_Support_AllTests::main') {
-    Horde_Support_AllTests::main();
-}
diff --git a/framework/Horde_Date_Parser/test/Horde/Support/Numerizer/Locale/BaseTest.php b/framework/Horde_Date_Parser/test/Horde/Support/Numerizer/Locale/BaseTest.php
deleted file mode 100644 (file)
index 0643e33..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-<?php
-/**
- * @category   Horde
- * @package    Horde_Support
- * @subpackage UnitTests
- */
-
-/**
- * @category   Horde
- * @package    Horde_Support
- * @subpackage UnitTests
- */
-class Horde_Support_Numerizer_Locale_BaseTest extends PHPUnit_Framework_TestCase
-{
-    public function testStraightParsing()
-    {
-        $numerizer = Horde_Support_Numerizer::factory();
-        $strings = array(
-            1 => 'one',
-            5 => 'five',
-            10 => 'ten',
-            11 => 'eleven',
-            12 => 'twelve',
-            13 => 'thirteen',
-            14 => 'fourteen',
-            15 => 'fifteen',
-            16 => 'sixteen',
-            17 => 'seventeen',
-            18 => 'eighteen',
-            19 => 'nineteen',
-            20 => 'twenty',
-            27 => 'twenty seven',
-            31 => 'thirty-one',
-            59 => 'fifty nine',
-            100 => 'a hundred',
-            100 => 'one hundred',
-            150 => 'one hundred and fifty',
-            // 150 => 'one fifty',
-            200 => 'two-hundred',
-            500 => '5 hundred',
-            999 => 'nine hundred and ninety nine',
-            1000 => 'one thousand',
-            1200 => 'twelve hundred',
-            1200 => 'one thousand two hundred',
-            17000 => 'seventeen thousand',
-            21473 => 'twentyone-thousand-four-hundred-and-seventy-three',
-            74002 => 'seventy four thousand and two',
-            99999 => 'ninety nine thousand nine hundred ninety nine',
-            100000 => '100 thousand',
-            250000 => 'two hundred fifty thousand',
-            1000000 => 'one million',
-            1250007 => 'one million two hundred fifty thousand and seven',
-            1000000000 => 'one billion',
-            1000000001 => 'one billion and one',
-        );
-
-        foreach ($strings as $key => $string) {
-            $this->assertEquals($key, (int)$numerizer->numerize($string));
-        }
-    }
-
-}
diff --git a/framework/Horde_Date_Parser/test/Horde/Support/Numerizer/Locale/DeTest.php b/framework/Horde_Date_Parser/test/Horde/Support/Numerizer/Locale/DeTest.php
deleted file mode 100644 (file)
index 806d9bb..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-<?php
-/**
- * @category   Horde
- * @package    Horde_Support
- * @subpackage UnitTests
- */
-
-/**
- * @category   Horde
- * @package    Horde_Support
- * @subpackage UnitTests
- */
-class Horde_Support_Numerizer_Locale_DeTest extends PHPUnit_Framework_TestCase
-{
-    public function testStraightParsing()
-    {
-        $numerizer = Horde_Support_Numerizer::factory(array('locale' => 'de'));
-        $strings = array(
-            array(1, 'eins'),
-            array(5, 'fünf'),
-            array(10, 'zehn'),
-            array(11, 'elf'),
-            array(12, 'zwölf'),
-            array(13, 'dreizehn'),
-            array(14, 'vierzehn'),
-            array(15, 'fünfzehn'),
-            array(16, 'sechzehn'),
-            array(17, 'siebzehn'),
-            array(18, 'achtzehn'),
-            array(19, 'neunzehn'),
-            array(20, 'zwanzig'),
-            array(27, 'siebenundzwanzig'),
-            array(31, 'einunddreißig'),
-            array(59, 'neunundfünfzig'),
-            array(100, 'einhundert'),
-            array(100, 'ein hundert'),
-            array(150, 'hundertundfünfzig'),
-            array(150, 'einhundertundfünfzig'),
-            array(200, 'zweihundert'),
-            array(500, 'fünfhundert'),
-            array(999, 'neunhundertneunundneunzig'),
-            array(1000, 'eintausend'),
-            array(1200, 'zwölfhundert'),
-            array(1200, 'eintausendzweihundert'),
-            array(17000, 'siebzehntausend'),
-            array(21473, 'einundzwanzigtausendvierhundertdreiundsiebzig'),
-            array(74002, 'vierundsiebzigtausendzwei'),
-            array(74002, 'vierundsiebzigtausendundzwei'),
-            array(99999, 'neunundneunzigtausendneunhundertneunundneunzig'),
-            array(100000, 'hunderttausend'),
-            array(100000, 'einhunderttausend'),
-            array(250000, 'zweihundertfünfzigtausend'),
-            array(1000000, 'eine million'),
-            array(1250007, 'eine million zweihundertfünfzigtausendundsieben'),
-            array(1000000000, 'eine milliarde'),
-            array(1000000001, 'eine milliarde und eins'),
-        );
-
-        foreach ($strings as $pair) {
-            $this->assertEquals((string)$pair[0], $numerizer->numerize($pair[1]));
-        }
-    }
-
-}
diff --git a/framework/Horde_Form/lib/Horde/Form.php b/framework/Horde_Form/lib/Horde/Form.php
deleted file mode 100644 (file)
index f995eea..0000000
+++ /dev/null
@@ -1,3946 +0,0 @@
-<?php
-/**
- * @package Horde_Form
- */
-
-/** String */
-include_once 'Horde/String.php';
-
-/**
- * Horde_Form Master Class.
- *
- * The Horde_Form:: package provides form rendering, validation, and
- * other functionality for the Horde Application Framework.
- *
- * $Horde: incubator/Horde_Form/Horde/Form.php,v 1.19 2008/08/26 15:32:55 selsky Exp $
- *
- * Copyright 2001-2007 Robert E. Coyle <robertecoyle@hotmail.com>
- * Copyright 2001-2008 The Horde Project (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (LGPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
- *
- * @author  Robert E. Coyle <robertecoyle@hotmail.com>
- * @author  Chuck Hagenbuch <chuck@horde.org>
- * @since   Horde 3.0
- * @package Horde_Form
- */
-class Horde_Form {
-
-    protected $_name = '';
-    protected $_title = '';
-    protected $_vars;
-    protected $_errors = array();
-    protected $_submitted = null;
-    protected $_sections = array();
-    protected $_open_section = null;
-    protected $_currentSection = array();
-    protected $_variables = array();
-    protected $_hiddenVariables = array();
-    protected $_useFormToken = true;
-    protected $_autofilled = false;
-    protected $_help = false;
-
-    public function __construct($vars, $title = '', $name = null)
-    {
-        if (is_null($name)) {
-            $name = String::lower(get_class($this));
-        }
-
-        $this->_vars = $vars;
-        $this->_title = $title;
-        $this->_name = $name;
-    }
-
-    public function setVars($vars)
-    {
-        $this->_vars = $vars;
-    }
-
-    public function getVars()
-    {
-        return $this->_vars;
-    }
-
-    public function getTitle()
-    {
-        return $this->_title;
-    }
-
-    public function setTitle($title)
-    {
-        $this->_title = $title;
-    }
-
-    public function getName()
-    {
-        return $this->_name;
-    }
-
-    /**
-     * Sets or gets whether the form should be verified by tokens.
-     * Tokens are used to verify that a form is only submitted once.
-     *
-     * @param boolean $token  If specified, sets whether to use form tokens.
-     *
-     * @return boolean  Whether form tokens are being used.
-     */
-    public function useToken($token = null)
-    {
-        if (!is_null($token)) {
-            $this->_useFormToken = $token;
-        }
-        return $this->_useFormToken;
-    }
-
-    /**
-     * Get the renderer for this form, either a custom renderer or the
-     * standard one.
-     *
-     * To use a custom form renderer, your form class needs to
-     * override this function:
-     * <code>
-     * function getRenderer()
-     * {
-     *     return new CustomFormRenderer();
-     * }
-     * </code>
-     *
-     * ... where CustomFormRenderer is the classname of the custom
-     * renderer class, which should extend Horde_Form_Renderer.
-     *
-     * @param array $params  A hash of renderer-specific parameters.
-     *
-     * @return object Horde_Form_Renderer  The form renderer.
-     */
-    function getRenderer($params = array())
-    {
-        return new Horde_Form_Renderer_Xhtml($params);
-    }
-
-    function getType($type, $params = array())
-    {
-        $type_class = 'Horde_Form_Type_' . $type;
-        if (!class_exists($type_class)) {
-            Horde::fatal(PEAR::raiseError(sprintf('Nonexistant class "%s" for field type "%s"', $type_class, $type)), __FILE__, __LINE__);
-        }
-        $type_ob = new $type_class();
-        call_user_func_array(array(&$type_ob, 'init'), $params);
-        return $type_ob;
-    }
-
-    public function setSection($section = '', $desc = '', $image = '', $expanded = true)
-    {
-        $this->_currentSection = $section;
-        if (!count($this->_sections) && !$this->getOpenSection()) {
-            $this->setOpenSection($section);
-        }
-        $this->_sections[$section]['desc'] = $desc;
-        $this->_sections[$section]['expanded'] = $expanded;
-        $this->_sections[$section]['image'] = $image;
-    }
-
-    public function getSections()
-    {
-        return $this->_sections;
-    }
-
-    public function getSectionDesc($section)
-    {
-        return $this->_sections[$section]['desc'];
-    }
-
-    public function getSectionImage($section)
-    {
-        return $this->_sections[$section]['image'];
-    }
-
-    public function setOpenSection($section)
-    {
-        $this->_vars->set('__formOpenSection', $section);
-    }
-
-    public function getOpenSection()
-    {
-        return $this->_vars->get('__formOpenSection');
-    }
-
-    public function getSectionExpandedState($section, $boolean = false)
-    {
-        if ($boolean) {
-            /* Only the boolean value is required. */
-            return $this->_sections[$section]['expanded'];
-        }
-
-        /* Need to return the values for use in styles. */
-        if ($this->_sections[$section]['expanded']) {
-            return 'block';
-        } else {
-            return 'none';
-        }
-    }
-
-    /**
-     * TODO
-     */
-    public function addVariable($humanName, $varName, $type, $required,
-                                $readonly = false, $description = null,
-                                $params = array())
-    {
-        return $this->insertVariableBefore(null, $humanName, $varName, $type,
-                                           $required, $readonly, $description,
-                                           $params);
-    }
-
-    /**
-     * TODO
-     */
-    public function insertVariableBefore($before, $humanName, $varName, $type,
-                                         $required, $readonly = false,
-                                         $description = null, $params = array())
-    {
-        $type = $this->getType($type, $params);
-        $var = new Horde_Form_Variable($humanName, $varName, $type,
-                                       $required, $readonly, $description);
-
-        /* Set the form object reference in the var. */
-        $var->setFormOb($this);
-
-        if ($var->getType() instanceof Horde_Form_Type_enum &&
-            count($var->getValues()) == 1) {
-            $vals = array_keys($var->getValues());
-            $this->_vars->add($var->varName, $vals[0]);
-            $var->_autofilled = true;
-        }
-        if (empty($this->_currentSection)) {
-            $this->_currentSection = '__base';
-        }
-
-        if (is_null($before)) {
-            $this->_variables[$this->_currentSection][] = &$var;
-        } else {
-            $num = 0;
-            while (isset($this->_variables[$this->_currentSection][$num]) &&
-                   $this->_variables[$this->_currentSection][$num]->getVarName() != $before) {
-                $num++;
-            }
-            if (!isset($this->_variables[$this->_currentSection][$num])) {
-                $this->_variables[$this->_currentSection][] = &$var;
-            } else {
-                $this->_variables[$this->_currentSection] = array_merge(
-                    array_slice($this->_variables[$this->_currentSection], 0, $num),
-                    array(&$var),
-                    array_slice($this->_variables[$this->_currentSection], $num));
-            }
-        }
-
-        return $var;
-    }
-
-    /**
-     * Removes a variable from the form.
-     *
-     * As only variables can be passed by reference, you need to call this
-     * method this way if want to pass a variable name:
-     * <code>
-     * $form->removeVariable($var = 'varname');
-     * </code>
-     *
-     * @param Horde_Form_Variable|string $var  Either the variable's name or
-     *                                         the variable to remove from the
-     *                                         form.
-     *
-     * @return boolean  True if the variable was found (and deleted).
-     */
-    public function removeVariable(&$var)
-    {
-        foreach (array_keys($this->_variables) as $section) {
-            foreach (array_keys($this->_variables[$section]) as $i) {
-                if ((is_a($var, 'Horde_Form_Variable') && $this->_variables[$section][$i] === $var) ||
-                    ($this->_variables[$section][$i]->getVarName() == $var)) {
-                    // Slice out the variable to be removed.
-                    $this->_variables[$this->_currentSection] = array_merge(
-                        array_slice($this->_variables[$this->_currentSection], 0, $i),
-                        array_slice($this->_variables[$this->_currentSection], $i + 1));
-
-                    return true;
-                }
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * TODO
-     */
-    public function addHidden($varName, $type, $required, $params = array())
-    {
-        $type = $this->getType($type, $params);
-        $var = new Horde_Form_Variable('', $varName, $type, $required);
-        $var->hide();
-        $this->_hiddenVariables[] = &$var;
-        return $var;
-    }
-
-    public function getVariables($flat = true, $withHidden = false)
-    {
-        if ($flat) {
-            $vars = array();
-            foreach ($this->_variables as $section) {
-                foreach ($section as $var) {
-                    $vars[] = $var;
-                }
-            }
-            if ($withHidden) {
-                foreach ($this->_hiddenVariables as $var) {
-                    $vars[] = $var;
-                }
-            }
-            return $vars;
-        } else {
-            return $this->_variables;
-        }
-    }
-
-    public function getHiddenVariables()
-    {
-        return $this->_hiddenVariables;
-    }
-
-    /**
-     * Preserve the variables/values from another Horde_Form object.
-     */
-    public function preserve(Horde_Form $form)
-    {
-        /* OLD IMPLEMENTATION
-        if ($this->_useFormToken) {
-            $this->_preserveVarByPost($this->_name . '_formToken', Horde_Token::generateId($this->_name));
-        }
-
-        $variables = $this->getVariables();
-        foreach ($variables as $var) {
-            $varname = $var->getVarName();
-
-            switch (get_class($var->getType()) {
-            case 'passwordconfirm':
-            case 'emailconfirm':
-                $this->preserveVarByPost($this->_vars, $varname . '[original]');
-                $this->preserveVarByPost($this->_vars, $varname . '[confirm]');
-                break;
-
-            case 'monthyear':
-                $this->preserveVarByPost($this->_vars, $varname . '[month]');
-                $this->preserveVarByPost($this->_vars, $varname . '[year]');
-                break;
-
-            case 'monthdayyear':
-                $this->preserveVarByPost($this->_vars, $varname . '[month]');
-                $this->preserveVarByPost($this->_vars, $varname . '[day]');
-                $this->preserveVarByPost($this->_vars, $varname . '[year]');
-                break;
-            }
-
-            $this->preserveVarByPost($this->_vars, $varname);
-        }
-        foreach ($this->_hiddenVariables as $var) {
-            $this->preserveVarByPost($this->_vars, $var->getVarName());
-        }
-        */
-    }
-
-    /**
-     * Does the action of validating the form, checking if it really has been
-     * submitted by calling isSubmitted() and if true does any onSubmit()
-     * calls for var types in the form. The _submitted var is then rechecked.
-     *
-     * @param boolean         $canAutofill  Can the form be valid without
-     *                                      being submitted?
-     *
-     * @return boolean  True if the form is valid.
-     */
-    public function validate($canAutoFill = false)
-    {
-        /* Get submitted status. */
-        if ($this->isSubmitted() || $canAutoFill) {
-            /* Form was submitted or can autofill; check for any variable
-             * types' onSubmit(). */
-            $this->onSubmit($this->_vars);
-
-            /* Recheck submitted status. */
-            if (!$this->isSubmitted() && !$canAutoFill) {
-                return false;
-            }
-        } else {
-            /* Form has not been submitted; return false. */
-            return false;
-        }
-
-        $message = '';
-        $this->_autofilled = true;
-
-        if ($this->_useFormToken) {
-            global $conf;
-            if (isset($conf['token'])) {
-                /* If there is a configured token system, set it up. */
-                $tokenSource = Horde_Token::factory($conf['token']['driver'], Horde::getDriverConfig('token', $conf['token']['driver']));
-            } else {
-                /* Default to the file system if no config. */
-                $tokenSource = Horde_Token::factory('file');
-            }
-            if (!$tokenSource->verify($this->_vars->get($this->_name . '_formToken'))) {
-                $this->_errors['_formToken'] = _("This form has already been processed.");
-            }
-        }
-
-        foreach ($this->getVariables() as $var) {
-            $this->_autofilled = $var->_autofilled && $this->_autofilled;
-            if (!$var->validate($this->_vars, $message)) {
-                $this->_errors[$var->getVarName()] = $message;
-            }
-        }
-
-        if ($this->_autofilled) {
-            unset($this->_errors['_formToken']);
-        }
-
-        foreach ($this->_hiddenVariables as $var) {
-            if (!$var->validate($this->_vars, $message)) {
-                $this->_errors[$var->getVarName()] = $message;
-            }
-        }
-
-        return $this->isValid();
-    }
-
-    public function clearValidation()
-    {
-        $this->_errors = array();
-    }
-
-    public function getError($var)
-    {
-        if (is_a($var, 'Horde_Form_Variable')) {
-            $name = $var->getVarName();
-        } else {
-            $name = $var;
-        }
-        return isset($this->_errors[$name]) ? $this->_errors[$name] : null;
-    }
-
-    public function setError($var, $message)
-    {
-        if (is_a($var, 'Horde_Form_Variable')) {
-            $name = $var->getVarName();
-        } else {
-            $name = $var;
-        }
-        $this->_errors[$name] = $message;
-    }
-
-    public function clearError($var)
-    {
-        if (is_a($var, 'Horde_Form_Variable')) {
-            $name = $var->getVarName();
-        } else {
-            $name = $var;
-        }
-        unset($this->_errors[$name]);
-    }
-
-    public function isValid()
-    {
-        return ($this->_autofilled || !count($this->_errors));
-    }
-
-    public function execute()
-    {
-        throw new Horde_Form_Exception('Subclass must overide execute()');
-    }
-
-    /**
-     * Fetch the field values of the submitted form.
-     *
-     * @param array $info      Array to be filled with the submitted field
-     *                         values.
-     */
-    public function getInfo(&$info)
-    {
-        $this->_getInfoFromVariables($this->getVariables(), $info);
-        $this->_getInfoFromVariables($this->_hiddenVariables, $info);
-    }
-
-    /**
-     * Fetch the field values from a given array of variables.
-     *
-     * @access private
-     *
-     * @param array  $variables  An array of Horde_Form_Variable objects to
-     *                           fetch from.
-     * @param array  $info       The array to be filled with the submitted
-     *                           field values.
-     */
-    protected function _getInfoFromVariables($variables, &$info)
-    {
-        foreach ($variables as $var) {
-            if ($var->isArrayVal()) {
-                $var->getInfo($this->_vars, $values);
-                if (is_array($values)) {
-                    $varName = str_replace('[]', '', $var->getVarName());
-                    foreach ($values as $i => $val) {
-                        $info[$i][$varName] = $val;
-                    }
-                }
-            } else {
-                if (Horde_Array::getArrayParts($var->getVarName(), $base, $keys)) {
-                    if (!isset($info[$base])) {
-                        $info[$base] = array();
-                    }
-                    $pointer = &$info[$base];
-                    while (count($keys)) {
-                        $key = array_shift($keys);
-                        if (!isset($pointer[$key])) {
-                            $pointer[$key] = array();
-                        }
-                        $pointer = &$pointer[$key];
-                    }
-                    $var->getInfo($this->_vars, $pointer);
-                } else {
-                    $var->getInfo($this->_vars, $info[$var->getVarName()]);
-                }
-            }
-        }
-    }
-
-    public function hasHelp()
-    {
-        return $this->_help;
-    }
-
-    /**
-     * Determines if this form has been submitted or not. If the class
-     * var _submitted is null then it will check for the presence of
-     * the formname in the form variables.
-     *
-     * Other events can explicitly set the _submitted variable to
-     * false to indicate a form submit but not for actual posting of
-     * data (eg. onChange events to update the display of fields).
-     *
-     * @return boolean  True or false indicating if the form has been
-     *                  submitted.
-     */
-    public function isSubmitted()
-    {
-        if (is_null($this->_submitted)) {
-            if ($this->_vars->get('formname') == $this->getName()) {
-                $this->_submitted = true;
-            } else {
-                $this->_submitted = false;
-            }
-        }
-
-        return $this->_submitted;
-    }
-
-    /**
-     * Checks if there is anything to do on the submission of the form by
-     * looping through each variable's onSubmit() function.
-     */
-    public function onSubmit()
-    {
-        /* Loop through all vars and check if there's anything to do on
-         * submit. */
-        $variables = $this->getVariables();
-        foreach ($variables as $var) {
-            $var->type->onSubmit($var, $this->_vars);
-            /* If changes to var being tracked don't register the form as
-             * submitted if old value and new value differ. */
-            if ($var->getOption('trackchange')) {
-                $varname = $var->getVarName();
-                if (!is_null($this->_vars->get('formname')) &&
-                    $this->_vars->get($varname) != $this->_vars->get('__old_' . $varname)) {
-                    $this->_submitted = false;
-                }
-            }
-        }
-    }
-
-    /**
-     * Explicitly sets the state of the form submit.
-     *
-     * An event can override the automatic determination of the submit state
-     * in the isSubmitted() function.
-     *
-     * @param boolean $state  Whether to set the state of the form as being
-     *                        submitted.
-     */
-    public function setSubmitted($state = true)
-    {
-        $this->_submitted = $state;
-    }
-
-}
-
-/**
- * Horde_Form_Type Class
- *
- * @author  Robert E. Coyle <robertecoyle@hotmail.com>
- * @package Horde_Form
- */
-class Horde_Form_Type {
-
-    protected function __get($property)
-    {
-        $prop = '_' . $property;
-        return isset($this->$prop) ? $this->$prop : null;
-    }
-
-    protected function __set($property, $value)
-    {
-        $prop = '_' . $property;
-        $this->$prop = $value;
-    }
-
-    protected function __isset($property)
-    {
-        $prop = '_' . $property;
-        return isset($this->$prop);
-    }
-
-    protected function __unset($property)
-    {
-        $prop = '_' . $property;
-        unset($this->$prop);
-    }
-
-    public function init()
-    {
-    }
-
-    public function onSubmit()
-    {
-    }
-
-    public function isValid($var, $vars, $value, &$message)
-    {
-        $message = '<strong>Error:</strong> Horde_Form_Type::isValid() called - should be overridden<br />';
-        return false;
-    }
-
-    function getInfo($vars, $var, &$info)
-    {
-        $info = $var->getValue($vars);
-    }
-
-}
-
-class Horde_Form_Type_number extends Horde_Form_Type {
-
-    var $_fraction;
-
-    function init($fraction = null)
-    {
-        $this->_fraction = $fraction;
-    }
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        if ($var->isRequired() && empty($value) && ((string)(double)$value !== $value)) {
-            $message = _("This field is required.");
-            return false;
-        } elseif (empty($value)) {
-            return true;
-        }
-
-        /* If matched, then this is a correct numeric value. */
-        if (preg_match($this->_getValidationPattern(), $value)) {
-            return true;
-        }
-
-        $message = _("This field must be a valid number.");
-        return false;
-    }
-
-    function _getValidationPattern()
-    {
-        static $pattern = '';
-        if (!empty($pattern)) {
-            return $pattern;
-        }
-
-        /* Get current locale information. */
-        $linfo = NLS::getLocaleInfo();
-
-        /* Build the pattern. */
-        $pattern = '(-)?';
-
-        /* Only check thousands separators if locale has any. */
-        if (!empty($linfo['mon_thousands_sep'])) {
-            /* Regex to check for correct thousands separators (if any). */
-            $pattern .= '((\d+)|((\d{0,3}?)([' . $linfo['mon_thousands_sep'] . ']\d{3})*?))';
-        } else {
-            /* No locale thousands separator, check for only digits. */
-            $pattern .= '(\d+)';
-        }
-        /* If no decimal point specified default to dot. */
-        if (empty($linfo['mon_decimal_point'])) {
-            $linfo['mon_decimal_point'] = '.';
-        }
-        /* Regex to check for correct decimals (if any). */
-        if (empty($this->_fraction)) {
-            $fraction = '*';
-        } else {
-            $fraction = '{0,' . $this->_fraction . '}';
-        }
-        $pattern .= '([' . $linfo['mon_decimal_point'] . '](\d' . $fraction . '))?';
-
-        /* Put together the whole regex pattern. */
-        $pattern = '/^' . $pattern . '$/';
-
-        return $pattern;
-    }
-
-    function getInfo($vars, $var, &$info)
-    {
-        $value = $vars->get($var->getVarName());
-        $linfo = NLS::getLocaleInfo();
-        $value = str_replace($linfo['mon_thousands_sep'], '', $value);
-        $info = str_replace($linfo['mon_decimal_point'], '.', $value);
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array('name' => _("Number"));
-    }
-
-}
-
-class Horde_Form_Type_int extends Horde_Form_Type {
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        if ($var->isRequired() && empty($value) && ((string)(int)$value !== $value)) {
-            $message = _("This field is required.");
-            return false;
-        }
-
-        if (empty($value) || preg_match('/^[0-9]+$/', $value)) {
-            return true;
-        }
-
-        $message = _("This field may only contain integers.");
-        return false;
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array('name' => _("Integer"));
-    }
-
-}
-
-class Horde_Form_Type_octal extends Horde_Form_Type {
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        if ($var->isRequired() && empty($value) && ((string)(int)$value !== $value)) {
-            $message = _("This field is required.");
-            return false;
-        }
-
-        if (empty($value) || preg_match('/^[0-7]+$/', $value)) {
-            return true;
-        }
-
-        $message = _("This field may only contain octal values.");
-        return false;
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array('name' => _("Octal"));
-    }
-
-}
-
-class Horde_Form_Type_intlist extends Horde_Form_Type {
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        if (empty($value) && $var->isRequired()) {
-            $message = _("This field is required.");
-            return false;
-        }
-
-        if (empty($value) || preg_match('/^[0-9 ,]+$/', $value)) {
-            return true;
-        }
-
-        $message = _("This field must be a comma or space separated list of integers");
-        return false;
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array('name' => _("Integer list"));
-    }
-
-}
-
-class Horde_Form_Type_text extends Horde_Form_Type {
-
-    var $_regex;
-    var $_size;
-    var $_maxlength;
-
-    /**
-     * The initialisation function for the text variable type.
-     *
-     * @access private
-     *
-     * @param string $regex       Any valid PHP PCRE pattern syntax that
-     *                            needs to be matched for the field to be
-     *                            considered valid. If left empty validity
-     *                            will be checked only for required fields
-     *                            whether they are empty or not.
-     *                            If using this regex test it is advisable
-     *                            to enter a description for this field to
-     *                            warn the user what is expected, as the
-     *                            generated error message is quite generic
-     *                            and will not give any indication where
-     *                            the regex failed.
-     * @param integer $size       The size of the input field.
-     * @param integer $maxlength  The max number of characters.
-     */
-    function init($regex = '', $size = 40, $maxlength = null)
-    {
-        $this->_regex     = $regex;
-        $this->_size      = $size;
-        $this->_maxlength = $maxlength;
-    }
-
-    public function isValid($var, $vars, $value, &$message)
-    {
-        $valid = true;
-
-        if (!empty($this->_maxlength) && String::length($value) > $this->_maxlength) {
-            $valid = false;
-            $message = sprintf(_("Value is over the maximum length of %s."), $this->_maxlength);
-        } elseif ($var->isRequired() && empty($this->_regex)) {
-            if (!($valid = strlen(trim($value)) > 0)) {
-                $message = _("This field is required.");
-            }
-        } elseif (strlen($this->_regex)) {
-            if (!($valid = preg_match($this->_regex, $value))) {
-                $message = _("You must enter a valid value.");
-            }
-        }
-
-        return $valid;
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array(
-            'name' => _("Text"),
-            'params' => array(
-                'regex'     => array('label' => _("Regex"),
-                                     'type'  => 'text'),
-                'size'      => array('label' => _("Size"),
-                                     'type'  => 'int'),
-                'maxlength' => array('label' => _("Maximum length"),
-                                     'type'  => 'int')));
-    }
-
-}
-
-class Horde_Form_Type_stringlist extends Horde_Form_Type_text {
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array(
-            'name' => _("String list"),
-            'params' => array(
-                'regex'     => array('label' => _("Regex"),
-                                     'type'  => 'text'),
-                'size'      => array('label' => _("Size"),
-                                     'type'  => 'int'),
-                'maxlength' => array('label' => _("Maximum length"),
-                                     'type'  => 'int')),
-        );
-    }
-
-}
-
-class Horde_Form_Type_phone extends Horde_Form_Type {
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        $valid = true;
-
-        if ($var->isRequired()) {
-            $valid = strlen(trim($value)) > 0;
-            if (!$valid) {
-                $message = _("This field is required.");
-            }
-        } else {
-            $valid = preg_match('/^\+?[\d()\-\/ ]*$/', $value);
-            if (!$valid) {
-                $message = _("You must enter a valid phone number, digits only with an optional '+' for the international dialing prefix.");
-            }
-        }
-
-        return $valid;
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array('name' => _("Phone number"));
-    }
-
-}
-
-class Horde_Form_Type_cellphone extends Horde_Form_Type_phone {
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array('name' => _("Mobile phone number"));
-    }
-
-}
-
-class Horde_Form_Type_ipaddress extends Horde_Form_Type_text {
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        $valid = true;
-
-        if (strlen(trim($value)) > 0) {
-            $ip = explode('.', $value);
-            $valid = (count($ip) == 4);
-            if ($valid) {
-                foreach ($ip as $part) {
-                    if (!is_numeric($part) ||
-                        $part > 255 ||
-                        $part < 0) {
-                        $valid = false;
-                        break;
-                    }
-                }
-            }
-
-            if (!$valid) {
-                $message = _("Please enter a valid IP address.");
-            }
-        } elseif ($var->isRequired()) {
-            $valid = false;
-            $message = _("This field is required.");
-        }
-
-        return $valid;
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array('name' => _("IP address"));
-    }
-
-}
-
-class Horde_Form_Type_longtext extends Horde_Form_Type_text {
-
-    var $_rows;
-    var $_cols;
-    var $_helper = array();
-
-    function init($rows = 8, $cols = 80, $helper = array())
-    {
-        if (!is_array($helper)) {
-            $helper = array($helper);
-        }
-
-        $this->_rows = $rows;
-        $this->_cols = $cols;
-        $this->_helper = $helper;
-    }
-
-    function hasHelper($option = '')
-    {
-        if (empty($option)) {
-            /* No option specified, check if any helpers have been
-             * activated. */
-            return !empty($this->_helper);
-        } elseif (empty($this->_helper)) {
-            /* No helpers activated at all, return false. */
-            return false;
-        } else {
-            /* Check if given helper has been activated. */
-            return in_array($option, $this->_helper);
-        }
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array(
-            'name' => _("Long text"),
-            'params' => array(
-                'rows'   => array('label' => _("Number of rows"),
-                                  'type'  => 'int'),
-                'cols'   => array('label' => _("Number of columns"),
-                                  'type'  => 'int'),
-                'helper' => array('label' => _("Helper?"),
-                                  'type'  => 'boolean')));
-    }
-
-}
-
-class Horde_Form_Type_countedtext extends Horde_Form_Type_longtext {
-
-    var $_chars;
-
-    function init($rows = null, $cols = null, $chars = 1000)
-    {
-        parent::init($rows, $cols);
-        $this->_chars = $chars;
-    }
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        $valid = true;
-
-        $length = String::length(trim($value));
-
-        if ($var->isRequired() && $length <= 0) {
-            $valid = false;
-            $message = _("This field is required.");
-        } elseif ($length > $this->_chars) {
-            $valid = false;
-            $message = sprintf(_("There are too many characters in this field. You have entered %s characters; you must enter less than %s."), String::length(trim($value)), $this->_chars);
-        }
-
-        return $valid;
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array(
-            'name' => _("Counted text"),
-            'params' => array(
-                'rows'  => array('label' => _("Number of rows"),
-                                 'type'  => 'int'),
-                'cols'  => array('label' => _("Number of columns"),
-                                 'type'  => 'int'),
-                'chars' => array('label' => _("Number of characters"),
-                                 'type'  => 'int')));
-    }
-
-}
-
-class Horde_Form_Type_address extends Horde_Form_Type_longtext {
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array(
-            'name' => _("Address"),
-            'params' => array(
-                'rows' => array('label' => _("Number of rows"),
-                                'type'  => 'int'),
-                'cols' => array('label' => _("Number of columns"),
-                                'type'  => 'int')));
-    }
-
-}
-
-class Horde_Form_Type_addresslink extends Horde_Form_Type {
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        return true;
-    }
-
-}
-
-class Horde_Form_Type_file extends Horde_Form_Type {
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        if ($var->isRequired()) {
-            $uploaded = Browser::wasFileUploaded($var->getVarName());
-            if (is_a($uploaded, 'PEAR_Error')) {
-                $message = $uploaded->getMessage();
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    function getInfo($vars, $var, &$info)
-    {
-        $name = $var->getVarName();
-        $uploaded = Browser::wasFileUploaded($name);
-        if ($uploaded === true) {
-            $info['name'] = $_FILES[$name]['name'];
-            $info['type'] = $_FILES[$name]['type'];
-            $info['tmp_name'] = $_FILES[$name]['tmp_name'];
-            $info['file'] = $_FILES[$name]['tmp_name'];
-            $info['error'] = $_FILES[$name]['error'];
-            $info['size'] = $_FILES[$name]['size'];
-        }
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array('name' => _("File upload"));
-    }
-
-}
-
-class Horde_Form_Type_image extends Horde_Form_Type {
-
-    /**
-     * Has a file been uploaded on this form submit?
-     *
-     * @var boolean
-     */
-    var $_uploaded = null;
-
-    /**
-     * Show the upload button?
-     *
-     * @var boolean
-     */
-    var $_show_upload = true;
-
-    /**
-     * Show the option to upload also original non-modified image?
-     *
-     * @var boolean
-     */
-    var $_show_keeporig = false;
-
-    /**
-     * Limit the file size?
-     *
-     * @var integer
-     */
-    var $_max_filesize = null;
-
-    /**
-     * Hash containing the previously uploaded image info.
-     *
-     * @var array
-     */
-    var $_img = array();
-
-    function init($show_upload = true, $show_keeporig = false, $max_filesize = null)
-    {
-        $this->_show_upload   = $show_upload;
-        $this->_show_keeporig = $show_keeporig;
-        $this->_max_filesize  = $max_filesize;
-    }
-
-    function onSubmit($var, $vars)
-    {
-        /* Get the upload. */
-        $this->_getUpload($vars, $var);
-
-        /* If this was done through the upload button override the submitted
-         * value of the form. */
-        if ($vars->get('_do_' . $var->getVarName())) {
-            $var->form->setSubmitted(false);
-        }
-    }
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        $field = $vars->get($var->getVarName());
-
-        /* Get the upload. */
-        $this->_getUpload($vars, $var);
-
-        /* The upload generated a PEAR Error. */
-        if (is_a($this->_uploaded, 'PEAR_Error')) {
-            /* Not required and no image upload attempted. */
-            if (!$var->isRequired() && empty($field['img']) &&
-                $this->_uploaded->getCode() == UPLOAD_ERR_NO_FILE) {
-                return true;
-            }
-
-            if (($this->_uploaded->getCode() == UPLOAD_ERR_NO_FILE) &&
-                empty($field['img'])) {
-                /* Nothing uploaded and no older upload. */
-                $message = _("This field is required.");
-                return false;
-            } elseif (!empty($field['img'])) {
-                /* Nothing uploaded but older upload present. */
-                return true;
-            } else {
-                /* Some other error message. */
-                $message = $this->_uploaded->getMessage();
-                return false;
-            }
-        } elseif ($this->_max_filesize &&
-                  $this->_img['size'] > $this->_max_filesize) {
-            $message = sprintf(_("The image file was larger than the maximum allowed size (%d bytes)."), $this->_max_filesize);
-            return false;
-        }
-
-        return true;
-    }
-
-    function getInfo($vars, $var, &$info)
-    {
-        /* Get the upload. */
-        $this->_getUpload($vars, $var);
-
-        /* Get image params stored in the hidden field. */
-        $value = $var->getValue($vars);
-        $info = $this->_img;
-        if (empty($info['file'])) {
-            unset($info['file']);
-            return;
-        }
-        if ($this->_show_keeporig) {
-            $info['keep_orig'] = !empty($value['keep_orig']);
-        }
-
-        /* Set the uploaded value (either true or PEAR_Error). */
-        $info['uploaded'] = &$this->_uploaded;
-
-        /* If a modified file exists move it over the original. */
-        if ($this->_show_keeporig && $info['keep_orig']) {
-            /* Requested the saving of original file also. */
-            $info['orig_file'] = Horde::getTempDir() . '/' . $info['file'];
-            $info['file'] = Horde::getTempDir() . '/mod_' . $info['file'];
-            /* Check if a modified file actually exists. */
-            if (!file_exists($info['file'])) {
-                $info['file'] = $info['orig_file'];
-                unset($info['orig_file']);
-            }
-        } else {
-            /* Saving of original not required. */
-            $mod_file = Horde::getTempDir() . '/mod_' . $info['file'];
-            $info['file'] = Horde::getTempDir() . '/' . $info['file'];
-
-            if (file_exists($mod_file)) {
-                /* Unlink first (has to be done on Windows machines?) */
-                unlink($info['file']);
-                rename($mod_file, $info['file']);
-            }
-        }
-    }
-
-    /**
-     * Gets the upload and sets up the upload data array. Either
-     * fetches an upload done with this submit or retries stored
-     * upload info.
-     */
-    function _getUpload($vars, $var)
-    {
-        /* Don't bother with this function if already called and set
-         * up vars. */
-        if (!empty($this->_img)) {
-            return true;
-        }
-
-        /* Check if file has been uploaded. */
-        $varname = $var->getVarName();
-        $this->_uploaded = Browser::wasFileUploaded($varname . '[new]');
-
-        if ($this->_uploaded === true) {
-            /* A file has been uploaded on this submit. Save to temp dir for
-             * preview work. */
-            $this->_img['type'] = $this->getUploadedFileType($varname . '[new]');
-
-            /* Get the other parts of the upload. */
-            Horde_Array::getArrayParts($varname . '[new]', $base, $keys);
-
-            /* Get the temporary file name. */
-            $keys_path = array_merge(array($base, 'tmp_name'), $keys);
-            $this->_img['file'] = Horde_Array::getElement($_FILES, $keys_path);
-
-            /* Get the actual file name. */
-            $keys_path= array_merge(array($base, 'name'), $keys);
-            $this->_img['name'] = Horde_Array::getElement($_FILES, $keys_path);
-
-            /* Get the file size. */
-            $keys_path= array_merge(array($base, 'size'), $keys);
-            $this->_img['size'] = Horde_Array::getElement($_FILES, $keys_path);
-
-            /* Get any existing values for the image upload field. */
-            $upload = $vars->get($var->getVarName());
-            $upload['img'] = @unserialize($upload['img']);
-
-            /* Get the temp file if already one uploaded, otherwise create a
-             * new temporary file. */
-            if (!empty($upload['img']['file'])) {
-                $tmp_file = Horde::getTempDir() . '/' . $upload['img']['file'];
-            } else {
-                $tmp_file = Horde::getTempFile('Horde', false);
-            }
-
-            /* Move the browser created temp file to the new temp file. */
-            move_uploaded_file($this->_img['file'], $tmp_file);
-            $this->_img['file'] = basename($tmp_file);
-
-            /* Store the uploaded image file data to the hidden field. */
-            $upload['img'] = serialize($this->_img);
-            $vars->set($var->getVarName(), $upload);
-        } elseif ($this->_uploaded) {
-            /* File has not been uploaded. */
-            $upload = $vars->get($var->getVarName());
-            if ($this->_uploaded->getCode() == 4 && !empty($upload['img'])) {
-                $this->_img = @unserialize($upload['img']);
-            }
-        }
-    }
-
-    function getUploadedFileType($field)
-    {
-        /* Get any index on the field name. */
-        $index = Horde_Array::getArrayParts($field, $base, $keys);
-
-        if ($index) {
-            /* Index present, fetch the mime type var to check. */
-            $keys_path = array_merge(array($base, 'type'), $keys);
-            $type = Horde_Array::getElement($_FILES, $keys_path);
-            $keys_path= array_merge(array($base, 'tmp_name'), $keys);
-            $tmp_name = Horde_Array::getElement($_FILES, $keys_path);
-        } else {
-            /* No index, simple set up of vars to check. */
-            $type = $_FILES[$field]['type'];
-            $tmp_name = $_FILES[$field]['tmp_name'];
-        }
-
-        if (empty($type) || ($type == 'application/octet-stream')) {
-            /* Type wasn't set on upload, try analising the upload. */
-            global $conf;
-            require_once 'Horde/MIME/Magic.php';
-            if (!($type = MIME_Magic::analyzeFile($tmp_name, isset($conf['mime']['magic_db']) ? $conf['mime']['magic_db'] : null))) {
-                if ($index) {
-                    /* Get the name value. */
-                    $keys_path = array_merge(array($base, 'name'), $keys);
-                    $name = Horde_Array::getElement($_FILES, $keys_path);
-
-                    /* Work out the type from the file name. */
-                    $type = MIME_Magic::filenameToMIME($name);
-
-                    /* Set the type. */
-                    $keys_path = array_merge(array($base, 'type'), $keys);
-                    Horde_Array::getElement($_FILES, $keys_path, $type);
-                } else {
-                    /* Work out the type from the file name. */
-                    $type = MIME_Magic::filenameToMIME($_FILES[$field]['name']);
-
-                    /* Set the type. */
-                    $_FILES[$field]['type'] = MIME_Magic::filenameToMIME($_FILES[$field]['name']);
-                }
-            }
-        }
-
-        return $type;
-    }
-
-    /**
-     * Loads any existing image data into the image field. Requires that the
-     * array $image passed to it contains the structure:
-     *   $image['load']['file'] - the filename of the image;
-     *   $image['load']['data'] - the raw image data.
-     *
-     * @param array $image  The image array.
-     */
-    function loadImageData(&$image)
-    {
-        /* No existing image data to load. */
-        if (!isset($image['load'])) {
-            return;
-        }
-
-        /* Save the data to the temp dir. */
-        $tmp_file = Horde::getTempDir() . '/' . $image['load']['file'];
-        if ($fd = fopen($tmp_file, 'w')) {
-            fwrite($fd, $image['load']['data']);
-            fclose($fd);
-        }
-
-        $image['img'] = serialize(array('file' => $image['load']['file']));
-        unset($image['load']);
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array(
-            'name' => _("Image upload"),
-            'params' => array(
-                'show_upload'   => array('label' => _("Show upload?"),
-                                         'type'  => 'boolean'),
-                'show_keeporig' => array('label' => _("Show option to keep original?"),
-                                         'type'  => 'boolean'),
-                'max_filesize'  => array('label' => _("Maximum file size in bytes"),
-                                         'type'  => 'int')));
-    }
-
-}
-
-class Horde_Form_Type_boolean extends Horde_Form_Type {
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        return true;
-    }
-
-    function getInfo($vars, $var, &$info)
-    {
-        $info = String::lower($vars->get($var->getVarName())) == 'on';
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array('name' => _("True or false"));
-    }
-
-}
-
-class Horde_Form_Type_link extends Horde_Form_Type {
-
-    /**
-     * List of hashes containing link parameters. Possible keys: 'url', 'text',
-     * 'target', 'onclick', 'title', 'accesskey'.
-     *
-     * @var array
-     */
-    var $values;
-
-    function init($values)
-    {
-        $this->values = $values;
-    }
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        return true;
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array(
-            'name' => _("Link"),
-            'params' => array(
-                'url' => array(
-                    'label' => _("Link URL"),
-                    'type' => 'text'),
-                'text' => array(
-                    'label' => _("Link text"),
-                    'type' => 'text'),
-                'target' => array(
-                    'label' => _("Link target"),
-                    'type' => 'text'),
-                'onclick' => array(
-                    'label' => _("Onclick event"),
-                    'type' => 'text'),
-                'title' => array(
-                    'label' => _("Link title attribute"),
-                    'type' => 'text'),
-                'accesskey' => array(
-                    'label' => _("Link access key"),
-                    'type' => 'text')));
-    }
-
-}
-
-class Horde_Form_Type_email extends Horde_Form_Type {
-
-    var $_allow_multi = false;
-    var $_strip_domain = false;
-    var $_link_compose = false;
-    var $_check_smtp = false;
-    var $_link_name;
-
-    /**
-     * A string containing valid delimiters (default is just comma).
-     *
-     * @var string
-     */
-    var $_delimiters = ',';
-
-    function init($allow_multi = false, $strip_domain = false,
-                  $link_compose = false, $link_name = null,
-                  $delimiters = ',')
-    {
-        $this->_allow_multi = $allow_multi;
-        $this->_strip_domain = $strip_domain;
-        $this->_link_compose = $link_compose;
-        $this->_link_name = $link_name;
-        $this->_delimiters = $delimiters;
-    }
-
-    /**
-     */
-    function isValid($var, $vars, $value, &$message)
-    {
-        // Split into individual addresses.
-        $emails = $this->splitEmailAddresses($value);
-
-        // Check for too many.
-        if (!$this->_allow_multi && count($emails) > 1) {
-            $message = _("Only one email address is allowed.");
-            return false;
-        }
-
-        // Check for all valid and at least one non-empty.
-        $nonEmpty = 0;
-        foreach ($emails as $email) {
-            if (!strlen($email)) {
-                continue;
-            }
-            if (!$this->validateEmailAddress($email)) {
-                $message = sprintf(_("\"%s\" is not a valid email address."), $email);
-                return false;
-            }
-            ++$nonEmpty;
-        }
-
-        if (!$nonEmpty && $var->isRequired()) {
-            if ($this->_allow_multi) {
-                $message = _("You must enter at least one email address.");
-            } else {
-                $message = _("You must enter an email address.");
-            }
-            return false;
-        }
-
-        return true;
-    }
-
-    /**
-     * Explodes an RFC 2822 string, ignoring a delimiter if preceded
-     * by a "\" character, or if the delimiter is inside single or
-     * double quotes.
-     *
-     * @param string $string     The RFC 822 string.
-     *
-     * @return array  The exploded string in an array.
-     */
-    function splitEmailAddresses($string)
-    {
-        $quotes = array('"', "'");
-        $emails = array();
-        $pos = 0;
-        $in_quote = null;
-        $in_group = false;
-        $prev = null;
-
-        if (!strlen($string)) {
-            return array();
-        }
-
-        $char = $string[0];
-        if (in_array($char, $quotes)) {
-            $in_quote = $char;
-        } elseif ($char == ':') {
-            $in_group = true;
-        } elseif (strpos($this->_delimiters, $char) !== false) {
-            $emails[] = '';
-            $pos = 1;
-        }
-
-        for ($i = 1, $iMax = strlen($string); $i < $iMax; ++$i) {
-            $char = $string[$i];
-            if (in_array($char, $quotes)) {
-                if ($prev !== '\\') {
-                    if ($in_quote === $char) {
-                        $in_quote = null;
-                    } elseif (is_null($in_quote)) {
-                        $in_quote = $char;
-                    }
-                }
-            } elseif ($in_group) {
-                if ($char == ';') {
-                    $emails[] = substr($string, $pos, $i - $pos + 1);
-                    $pos = $i + 1;
-                    $in_group = false;
-                }
-            } elseif ($char == ':') {
-                $in_group = true;
-            } elseif (strpos($this->_delimiters, $char) !== false &&
-                      $prev !== '\\' &&
-                      is_null($in_quote)) {
-                $emails[] = substr($string, $pos, $i - $pos);
-                $pos = $i + 1;
-            }
-            $prev = $char;
-        }
-
-        if ($pos != $i) {
-            /* The string ended without a delimiter. */
-            $emails[] = substr($string, $pos, $i - $pos);
-        }
-
-        return $emails;
-    }
-
-    /**
-     * RFC(2)822 Email Parser.
-     *
-     * By Cal Henderson <cal@iamcal.com>
-     * This code is licensed under a Creative Commons Attribution-ShareAlike 2.5 License
-     * http://creativecommons.org/licenses/by-sa/2.5/
-     *
-     * http://code.iamcal.com/php/rfc822/
-     *
-     * http://iamcal.com/publish/articles/php/parsing_email
-     *
-     * Revision 4
-     *
-     * @param string $email An individual email address to validate.
-     *
-     * @return boolean
-     */
-    function validateEmailAddress($email)
-    {
-        static $comment_regexp, $email_regexp;
-        if ($comment_regexp === null) {
-            $this->_defineValidationRegexps($comment_regexp, $email_regexp);
-        }
-
-        // We need to strip comments first (repeat until we can't find
-        // any more).
-        while (true) {
-            $new = preg_replace("!$comment_regexp!", '', $email);
-            if (strlen($new) == strlen($email)){
-                break;
-            }
-            $email = $new;
-        }
-
-        // Now match what's left.
-        $result = (bool)preg_match("!^$email_regexp$!", $email);
-        if ($result && $this->_check_smtp) {
-            $result = $this->validateEmailAddressSmtp($email);
-        }
-
-        return $result;
-    }
-
-    /**
-     * Attempt partial delivery of mail to an address to validate it.
-     *
-     * @param string $email An individual email address to validate.
-     *
-     * @return boolean
-     */
-    function validateEmailAddressSmtp($email)
-    {
-        list(, $maildomain) = explode('@', $email, 2);
-
-        // Try to get the real mailserver from MX records.
-        if (function_exists('getmxrr') &&
-            @getmxrr($maildomain, $mxhosts, $mxpriorities)) {
-            // MX record found.
-            array_multisort($mxpriorities, $mxhosts);
-            $mailhost = $mxhosts[0];
-        } else {
-            // No MX record found, try the root domain as the mail
-            // server.
-            $mailhost = $maildomain;
-        }
-
-        $fp = @fsockopen($mailhost, 25, $errno, $errstr, 5);
-        if (!$fp) {
-            return false;
-        }
-
-        // Read initial response.
-        fgets($fp, 4096);
-
-        // HELO
-        fputs($fp, "HELO $mailhost\r\n");
-        fgets($fp, 4096);
-
-        // MAIL FROM
-        fputs($fp, "MAIL FROM: <root@example.com>\r\n");
-        fgets($fp, 4096);
-
-        // RCPT TO - gets the result we want.
-        fputs($fp, "RCPT TO: <$email>\r\n");
-        $result = trim(fgets($fp, 4096));
-
-        // QUIT
-        fputs($fp, "QUIT\r\n");
-        fgets($fp, 4096);
-        fclose($fp);
-
-        return substr($result, 0, 1) == '2';
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array(
-            'name' => _("Email"),
-            'params' => array(
-                'allow_multi' => array('label' => _("Allow multiple addresses?"),
-                                       'type'  => 'boolean'),
-                'strip_domain' => array('label' => _("Protect address from spammers?"),
-                                        'type' => 'boolean'),
-                'link_compose' => array('label' => _("Link the email address to the compose page when displaying?"),
-                                        'type' => 'boolean'),
-                'link_name' => array('label' => _("The name to use when linking to the compose page"),
-                                     'type' => 'text'),
-                'delimiters' => array('label' => _("Character to split multiple addresses with"),
-                                      'type' => 'text'),
-            ),
-        );
-    }
-
-    /**
-     * RFC(2)822 Email Parser.
-     *
-     * By Cal Henderson <cal@iamcal.com>
-     * This code is licensed under a Creative Commons Attribution-ShareAlike 2.5 License
-     * http://creativecommons.org/licenses/by-sa/2.5/
-     *
-     * http://code.iamcal.com/php/rfc822/
-     *
-     * http://iamcal.com/publish/articles/php/parsing_email
-     *
-     * Revision 4
-     *
-     * @param string &$comment The regexp for comments.
-     * @param string &$addr_spec The regexp for email addresses.
-     */
-    function _defineValidationRegexps(&$comment, &$addr_spec)
-    {
-        /**
-         * NO-WS-CTL       =       %d1-8 /         ; US-ASCII control characters
-         *                         %d11 /          ;  that do not include the
-         *                         %d12 /          ;  carriage return, line feed,
-         *                         %d14-31 /       ;  and white space characters
-         *                         %d127
-         * ALPHA          =  %x41-5A / %x61-7A   ; A-Z / a-z
-         * DIGIT          =  %x30-39
-         */
-        $no_ws_ctl  = "[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]";
-        $alpha      = "[\\x41-\\x5a\\x61-\\x7a]";
-        $digit      = "[\\x30-\\x39]";
-        $cr         = "\\x0d";
-        $lf         = "\\x0a";
-        $crlf       = "($cr$lf)";
-
-        /**
-         * obs-char        =       %d0-9 / %d11 /          ; %d0-127 except CR and
-         *                         %d12 / %d14-127         ;  LF
-         * obs-text        =       *LF *CR *(obs-char *LF *CR)
-         * text            =       %d1-9 /         ; Characters excluding CR and LF
-         *                         %d11 /
-         *                         %d12 /
-         *                         %d14-127 /
-         *                         obs-text
-         * obs-qp          =       "\" (%d0-127)
-         * quoted-pair     =       ("\" text) / obs-qp
-         */
-        $obs_char       = "[\\x00-\\x09\\x0b\\x0c\\x0e-\\x7f]";
-        $obs_text       = "($lf*$cr*($obs_char$lf*$cr*)*)";
-        $text           = "([\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f]|$obs_text)";
-        $obs_qp         = "(\\x5c[\\x00-\\x7f])";
-        $quoted_pair    = "(\\x5c$text|$obs_qp)";
-
-        /**
-         * obs-FWS         =       1*WSP *(CRLF 1*WSP)
-         * FWS             =       ([*WSP CRLF] 1*WSP) /   ; Folding white space
-         *                         obs-FWS
-         * ctext           =       NO-WS-CTL /     ; Non white space controls
-         *                         %d33-39 /       ; The rest of the US-ASCII
-         *                         %d42-91 /       ;  characters not including "(",
-         *                         %d93-126        ;  ")", or "\"
-         * ccontent        =       ctext / quoted-pair / comment
-         * comment         =       "(" *([FWS] ccontent) [FWS] ")"
-         * CFWS            =       *([FWS] comment) (([FWS] comment) / FWS)
-         *
-         * @note: We translate ccontent only partially to avoid an
-         * infinite loop. Instead, we'll recursively strip comments
-         * before processing the input.
-         */
-        $wsp        = "[\\x20\\x09]";
-        $obs_fws    = "($wsp+($crlf$wsp+)*)";
-        $fws        = "((($wsp*$crlf)?$wsp+)|$obs_fws)";
-        $ctext      = "($no_ws_ctl|[\\x21-\\x27\\x2A-\\x5b\\x5d-\\x7e])";
-        $ccontent   = "($ctext|$quoted_pair)";
-        $comment    = "(\\x28($fws?$ccontent)*$fws?\\x29)";
-        $cfws       = "(($fws?$comment)*($fws?$comment|$fws))";
-        $cfws       = "$fws*";
-
-        /**
-         * atext           =       ALPHA / DIGIT / ; Any character except controls,
-         *                         "!" / "#" /     ;  SP, and specials.
-         *                         "$" / "%" /     ;  Used for atoms
-         *                         "&" / "'" /
-         *                         "*" / "+" /
-         *                         "-" / "/" /
-         *                         "=" / "?" /
-         *                         "^" / "_" /
-         *                         "`" / "{" /
-         *                         "|" / "}" /
-         *                         "~"
-         * atom            =       [CFWS] 1*atext [CFWS]
-         */
-        $atext      = "($alpha|$digit|[\\x21\\x23-\\x27\\x2a\\x2b\\x2d\\x2e\\x3d\\x3f\\x5e\\x5f\\x60\\x7b-\\x7e])";
-        $atom       = "($cfws?$atext+$cfws?)";
-
-        /**
-         * qtext           =       NO-WS-CTL /     ; Non white space controls
-         *                         %d33 /          ; The rest of the US-ASCII
-         *                         %d35-91 /       ;  characters not including "\"
-         *                         %d93-126        ;  or the quote character
-         * qcontent        =       qtext / quoted-pair
-         * quoted-string   =       [CFWS]
-         *                         DQUOTE *([FWS] qcontent) [FWS] DQUOTE
-         *                         [CFWS]
-         * word            =       atom / quoted-string
-         */
-        $qtext      = "($no_ws_ctl|[\\x21\\x23-\\x5b\\x5d-\\x7e])";
-        $qcontent   = "($qtext|$quoted_pair)";
-        $quoted_string  = "($cfws?\\x22($fws?$qcontent)*$fws?\\x22$cfws?)";
-        $word       = "($atom|$quoted_string)";
-
-        /**
-         * obs-local-part  =       word *("." word)
-         * obs-domain      =       atom *("." atom)
-         */
-        $obs_local_part = "($word(\\x2e$word)*)";
-        $obs_domain = "($atom(\\x2e$atom)*)";
-
-        /**
-         * dot-atom-text   =       1*atext *("." 1*atext)
-         * dot-atom        =       [CFWS] dot-atom-text [CFWS]
-         */
-        $dot_atom_text  = "($atext+(\\x2e$atext+)*)";
-        $dot_atom   = "($cfws?$dot_atom_text$cfws?)";
-
-        /**
-         * domain-literal  =       [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS]
-         * dcontent        =       dtext / quoted-pair
-         * dtext           =       NO-WS-CTL /     ; Non white space controls
-         *
-         *                         %d33-90 /       ; The rest of the US-ASCII
-         *                         %d94-126        ;  characters not including "[",
-         *                                         ;  "]", or "\"
-         */
-        $dtext      = "($no_ws_ctl|[\\x21-\\x5a\\x5e-\\x7e])";
-        $dcontent   = "($dtext|$quoted_pair)";
-        $domain_literal = "($cfws?\\x5b($fws?$dcontent)*$fws?\\x5d$cfws?)";
-
-        /**
-         * local-part      =       dot-atom / quoted-string / obs-local-part
-         * domain          =       dot-atom / domain-literal / obs-domain
-         * addr-spec       =       local-part "@" domain
-         */
-        $local_part = "($dot_atom|$quoted_string|$obs_local_part)";
-        $domain     = "($dot_atom|$domain_literal|$obs_domain)";
-        $addr_spec  = "($local_part\\x40$domain)";
-    }
-
-}
-
-class Horde_Form_Type_matrix extends Horde_Form_Type {
-
-    var $_cols;
-    var $_rows;
-    var $_matrix;
-    var $_new_input;
-
-    /**
-     * Initializes the variable.
-     *
-     * @example
-     * init(array('Column A', 'Column B'),
-     *      array(1 => 'Row One', 2 => 'Row 2', 3 => 'Row 3'),
-     *      array(array(true, true, false),
-     *            array(true, false, true),
-     *            array(fasle, true, false)),
-     *      array('Row 4', 'Row 5'));
-     *
-     * @param array $cols               A list of column headers.
-     * @param array $rows               A hash with row IDs as the keys and row
-     *                                  labels as the values.
-     * @param array $matrix             A two dimensional hash with the field
-     *                                  values.
-     * @param boolean|array $new_input  If true, a free text field to add a new
-     *                                  row is displayed on the top, a select
-     *                                  box if this parameter is a value.
-     */
-    function init($cols, $rows = array(), $matrix = array(), $new_input = false)
-    {
-        $this->_cols       = $cols;
-        $this->_rows       = $rows;
-        $this->_matrix     = $matrix;
-        $this->_new_input  = $new_input;
-    }
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        return true;
-    }
-
-    function getInfo($vars, $var, &$info)
-    {
-        $values = $vars->get($var->getVarName());
-        if (!empty($values['n']['r']) && isset($values['n']['v'])) {
-            $new_row = $values['n']['r'];
-            $values['r'][$new_row] = $values['n']['v'];
-            unset($values['n']);
-        }
-
-        $info = (isset($values['r']) ? $values['r'] : array());
-    }
-
-    function about()
-    {
-        return array(
-            'name' => _("Field matrix"),
-            'params' => array(
-                'cols' => array('label' => _("Column titles"),
-                                'type'  => 'stringlist')));
-    }
-
-}
-
-class Horde_Form_Type_emailConfirm extends Horde_Form_Type {
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        if ($var->isRequired() && empty($value['original'])) {
-            $message = _("This field is required.");
-            return false;
-        }
-
-        if ($value['original'] != $value['confirm']) {
-            $message = _("Email addresses must match.");
-            return false;
-        } else {
-            $parser = new Mail_RFC822();
-            $parsed_email = $parser->parseAddressList($value['original'], false, true);
-
-            if (count($parsed_email) > 1) {
-                $message = _("Only one email address allowed.");
-                return false;
-            }
-            if (empty($parsed_email[0]->mailbox)) {
-                $message = _("You did not enter a valid email address.");
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array('name' => _("Email with confirmation"));
-    }
-
-}
-
-class Horde_Form_Type_password extends Horde_Form_Type {
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        $valid = true;
-
-        if ($var->isRequired()) {
-            $valid = strlen(trim($value)) > 0;
-
-            if (!$valid) {
-                $message = _("This field is required.");
-            }
-        }
-
-        return $valid;
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array('name' => _("Password"));
-    }
-
-}
-
-class Horde_Form_Type_passwordconfirm extends Horde_Form_Type {
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        if ($var->isRequired() && empty($value['original'])) {
-            $message = _("This field is required.");
-            return false;
-        }
-
-        if ($value['original'] != $value['confirm']) {
-            $message = _("Passwords must match.");
-            return false;
-        }
-
-        return true;
-    }
-
-    function getInfo($vars, $var, &$info)
-    {
-        $value = $vars->get($var->getVarName());
-        $info = $value['original'];
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array('name' => _("Password with confirmation"));
-    }
-
-}
-
-class Horde_Form_Type_enum extends Horde_Form_Type {
-
-    var $_values;
-    var $_prompt;
-
-    function init($values, $prompt = null)
-    {
-        $this->_values = $values;
-
-        if ($prompt === true) {
-            $this->_prompt = _("-- select --");
-        } else {
-            $this->_prompt = $prompt;
-        }
-    }
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        if ($var->isRequired() && $value == '' && !isset($this->_values[$value])) {
-            $message = _("This field is required.");
-            return false;
-        }
-
-        if (count($this->_values) == 0 || isset($this->_values[$value]) ||
-            ($this->_prompt && empty($value))) {
-            return true;
-        }
-
-        $message = _("Invalid data.");
-        return false;
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array(
-            'name' => _("Drop down list"),
-            'params' => array(
-                'values' => array('label' => _("Values to select from"),
-                                  'type'  => 'stringlist'),
-                'prompt' => array('label' => _("Prompt text"),
-                                  'type'  => 'text')));
-    }
-
-}
-
-class Horde_Form_Type_mlenum extends Horde_Form_Type {
-
-    var $_values;
-    var $_prompts;
-
-    function init(&$values, $prompts = null)
-    {
-        $this->_values = &$values;
-
-        if ($prompts === true) {
-            $this->_prompts = array(_("-- select --"), _("-- select --"));
-        } elseif (!is_array($prompts)) {
-            $this->_prompts = array($prompts, $prompts);
-        } else {
-            $this->_prompts = $prompts;
-        }
-    }
-
-    function onSubmit($var, $vars)
-    {
-        $varname = $var->getVarName();
-        $value = $vars->get($varname);
-
-        if ($value['1'] != $value['old']) {
-            $var->form->setSubmitted(false);
-        }
-    }
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        if ($var->isRequired() && (empty($value['1']) || empty($value['2']))) {
-            $message = _("This field is required.");
-            return false;
-        }
-
-        if (!count($this->_values) || isset($this->_values[$value['1']]) ||
-            (!empty($this->_prompts) && empty($value['1']))) {
-            return true;
-        }
-
-        $message = _("Invalid data.");
-        return false;
-    }
-
-    function getInfo($vars, &$var, &$info)
-    {
-        $info = $vars->get($var->getVarName());
-        return $info['2'];
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array(
-            'name' => _("Multi-level drop down lists"),
-            'params' => array(
-                'values' => array('label' => _("Values to select from"),
-                                  'type'  => 'stringlist'),
-                'prompt' => array('label' => _("Prompt text"),
-                                  'type'  => 'text')));
-    }
-
-}
-
-class Horde_Form_Type_multienum extends Horde_Form_Type_enum {
-
-    var $size = 5;
-
-    function init($values, $size = null)
-    {
-        if (!is_null($size)) {
-            $this->size = (int)$size;
-        }
-
-        parent::init($values);
-    }
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        if (is_array($value)) {
-            foreach ($value as $val) {
-                if (!$this->isValid($var, $vars, $val, $message)) {
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        if (empty($value) && ((string)(int)$value !== $value)) {
-            if ($var->isRequired()) {
-                $message = _("This field is required.");
-                return false;
-            } else {
-                return true;
-            }
-        }
-
-        if (count($this->_values) == 0 || isset($this->_values[$value])) {
-            return true;
-        }
-
-        $message = _("Invalid data.");
-        return false;
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array(
-            'name' => _("Multiple selection"),
-            'params' => array(
-                'values' => array('label' => _("Values"),
-                                  'type'  => 'stringlist'),
-                'size'   => array('label' => _("Size"),
-                                  'type'  => 'int'))
-        );
-    }
-
-}
-
-class Horde_Form_Type_keyval_multienum extends Horde_Form_Type_multienum {
-
-    function getInfo($vars, $var, &$info)
-    {
-        $value = $vars->get($var->getVarName());
-        $info = array();
-        foreach ($value as $key) {
-            $info[$key] = $this->_values[$key];
-        }
-    }
-
-}
-
-class Horde_Form_Type_radio extends Horde_Form_Type_enum {
-
-    /* Entirely implemented by Horde_Form_Type_enum; just a different
-     * view. */
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array(
-            'name' => _("Radio selection"),
-            'params' => array(
-                'values' => array('label' => _("Values"),
-                                  'type'  => 'stringlist')));
-    }
-
-}
-
-class Horde_Form_Type_set extends Horde_Form_Type {
-
-    var $_values;
-    var $_checkAll = false;
-
-    function init(&$values, $checkAll = false)
-    {
-        $this->_values = $values;
-        $this->_checkAll = $checkAll;
-    }
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        if (count($this->_values) == 0 || count($value) == 0) {
-            return true;
-        }
-        foreach ($value as $item) {
-            if (!isset($this->_values[$item])) {
-                $error = true;
-                break;
-            }
-        }
-        if (!isset($error)) {
-            return true;
-        }
-
-        $message = _("Invalid data.");
-        return false;
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array(
-            'name' => _("Set"),
-            'params' => array(
-                'values' => array('label' => _("Values"),
-                                  'type'  => 'stringlist')));
-    }
-
-}
-
-class Horde_Form_Type_date extends Horde_Form_Type {
-
-    var $_format;
-
-    function init($format = '%a %d %B')
-    {
-        $this->_format = $format;
-    }
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        $valid = true;
-
-        if ($var->isRequired()) {
-            $valid = strlen(trim($value)) > 0;
-
-            if (!$valid) {
-                $message = sprintf(_("%s is required"), $var->getHumanName());
-            }
-        }
-
-        return $valid;
-    }
-
-    /**
-     * @static
-     */
-    function getAgo($timestamp)
-    {
-        if ($timestamp === null) {
-            return '';
-        }
-
-        $diffdays = Date_Calc::dateDiff(date('j', $timestamp),
-                                        date('n', $timestamp),
-                                        date('Y', $timestamp),
-                                        date('j'), date('n'), date('Y'));
-
-        /* An error occured. */
-        if ($diffdays == -1) {
-            return;
-        }
-
-        $ago = $diffdays * Date_Calc::compareDates(date('j', $timestamp),
-                                                   date('n', $timestamp),
-                                                   date('Y', $timestamp),
-                                                   date('j'), date('n'),
-                                                   date('Y'));
-        if ($ago < -1) {
-            return sprintf(_(" (%s days ago)"), $diffdays);
-        } elseif ($ago == -1) {
-            return _(" (yesterday)");
-        } elseif ($ago == 0) {
-            return _(" (today)");
-        } elseif ($ago == 1) {
-            return _(" (tomorrow)");
-        } else {
-            return sprintf(_(" (in %s days)"), $diffdays);
-        }
-    }
-
-    function getFormattedTime($timestamp, $format = null, $showago = true)
-    {
-        if (empty($format)) {
-            $format = $this->_format;
-        }
-        if (!empty($timestamp)) {
-            return strftime($format, $timestamp) . ($showago ? Horde_Form_Type_date::getAgo($timestamp) : '');
-        } else {
-            return '';
-        }
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array('name' => _("Date"));
-    }
-
-}
-
-class Horde_Form_Type_time extends Horde_Form_Type {
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        if ($var->isRequired() && empty($value) && ((string)(double)$value !== $value)) {
-            $message = _("This field is required.");
-            return false;
-        }
-
-        if (empty($value) || preg_match('/^[0-2]?[0-9]:[0-5][0-9]$/', $value)) {
-            return true;
-        }
-
-        $message = _("This field may only contain numbers and the colon.");
-        return false;
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array('name' => _("Time"));
-    }
-
-}
-
-class Horde_Form_Type_hourminutesecond extends Horde_Form_Type {
-
-    var $_show_seconds;
-
-    function init($show_seconds = false)
-    {
-        $this->_show_seconds = $show_seconds;
-    }
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        $time = $vars->get($var->getVarName());
-        if (!$this->_show_seconds && !isset($time['second'])) {
-            $time['second'] = 0;
-        }
-
-        if (!$this->emptyTimeArray($time) && !$this->checktime($time['hour'], $time['minute'], $time['second'])) {
-            $message = _("Please enter a valid time.");
-            return false;
-        } elseif ($this->emptyTimeArray($time) && $var->isRequired()) {
-            $message = _("This field is required.");
-            return false;
-        }
-
-        return true;
-    }
-
-    function checktime($hour, $minute, $second)
-    {
-        if (!isset($hour) || $hour == '' || ($hour < 0 || $hour > 23)) {
-            return false;
-        }
-        if (!isset($minute) || $minute == '' || ($minute < 0 || $minute > 60)) {
-            return false;
-        }
-        if (!isset($second) || $second === '' || ($second < 0 || $second > 60)) {
-            return false;
-        }
-
-        return true;
-    }
-
-    /**
-     * Return the time supplied as a Horde_Date object.
-     *
-     * @param string $time_in  Date in one of the three formats supported by
-     *                         Horde_Form and Horde_Date (ISO format
-     *                         YYYY-MM-DD HH:MM:SS, timestamp YYYYMMDDHHMMSS and
-     *                         UNIX epoch).
-     *
-     * @return Date  The time object.
-     */
-    function getTimeOb($time_in)
-    {
-        if (is_array($time_in)) {
-            if (!$this->emptyTimeArray($time_in)) {
-                $time_in = sprintf('1970-01-01 %02d:%02d:%02d', $time_in['hour'], $time_in['minute'], $this->_show_seconds ? $time_in['second'] : 0);
-            }
-        }
-
-        return new Horde_Date($time_in);
-    }
-
-    /**
-     * Return the time supplied split up into an array.
-     *
-     * @param string $time_in  Time in one of the three formats supported by
-     *                         Horde_Form and Horde_Date (ISO format
-     *                         YYYY-MM-DD HH:MM:SS, timestamp YYYYMMDDHHMMSS and
-     *                         UNIX epoch).
-     *
-     * @return array  Array with three elements - hour, minute and seconds.
-     */
-    function getTimeParts($time_in)
-    {
-        if (is_array($time_in)) {
-            /* This is probably a failed isValid input so just return the
-             * parts as they are. */
-            return $time_in;
-        } elseif (empty($time_in)) {
-            /* This is just an empty field so return empty parts. */
-            return array('hour' => '', 'minute' => '', 'second' => '');
-        }
-        $time = $this->getTimeOb($time_in);
-        return array('hour' => $time->hour,
-                     'minute' => $time->min,
-                     'second' => $time->sec);
-    }
-
-    function emptyTimeArray($time)
-    {
-        return (is_array($time) && empty($time['hour']) && empty($time['minute']) && empty($time['second']));
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array(
-            'name' => _("Time selection"),
-            'params' => array(
-                'seconds' => array('label' => _("Show seconds?"),
-                                  'type'  => 'boolean')));
-    }
-
-}
-
-class Horde_Form_Type_monthyear extends Horde_Form_Type {
-
-    var $_start_year;
-    var $_end_year;
-
-    function init($start_year = null, $end_year = null)
-    {
-        if (empty($start_year)) {
-            $start_year = 1920;
-        }
-        if (empty($end_year)) {
-            $end_year = date('Y');
-        }
-
-        $this->_start_year = $start_year;
-        $this->_end_year = $end_year;
-    }
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        if (!$var->isRequired()) {
-            return true;
-        }
-
-        if (!$vars->get($this->getMonthVar($var)) ||
-            !$vars->get($this->getYearVar($var))) {
-            $message = _("Please enter a month and a year.");
-            return false;
-        }
-
-        return true;
-    }
-
-    function getMonthVar($var)
-    {
-        return $var->getVarName() . '[month]';
-    }
-
-    function getYearVar($var)
-    {
-        return $var->getVarName() . '[year]';
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array('name' => _("Month and year"),
-                     'params' => array(
-                         'start_year' => array('label' => _("Start year"),
-                                               'type'  => 'int'),
-                         'end_year'   => array('label' => _("End year"),
-                                               'type'  => 'int')));
-    }
-
-}
-
-class Horde_Form_Type_monthdayyear extends Horde_Form_Type {
-
-    var $_start_year;
-    var $_end_year;
-    var $_picker;
-    var $_format_in = null;
-    var $_format_out = '%x';
-
-    /**
-     * Return the date supplied as a Horde_Date object.
-     *
-     * @param integer $start_year  The first available year for input.
-     * @param integer $end_year    The last available year for input.
-     * @param boolean $picker      Do we show the DHTML calendar?
-     * @param integer $format_in   The format to use when sending the date
-     *                             for storage. Defaults to Unix epoch.
-     *                             Similar to the strftime() function.
-     * @param integer $format_out  The format to use when displaying the
-     *                             date. Similar to the strftime() function.
-     */
-    function init($start_year = '', $end_year = '', $picker = true,
-                  $format_in = null, $format_out = '%x')
-    {
-        if (empty($start_year)) {
-            $start_year = date('Y');
-        }
-        if (empty($end_year)) {
-            $end_year = date('Y') + 10;
-        }
-
-        $this->_start_year = $start_year;
-        $this->_end_year = $end_year;
-        $this->_picker = $picker;
-        $this->_format_in = $format_in;
-        $this->_format_out = $format_out;
-    }
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        $date = $vars->get($var->getVarName());
-        $empty = $this->emptyDateArray($date);
-
-        if ($empty == 1 && $var->isRequired()) {
-            $message = _("This field is required.");
-            return false;
-        } elseif ($empty == 0 && !checkdate($date['month'], $date['day'], $date['year'])) {
-            $message = _("Please enter a valid date, check the number of days in the month.");
-            return false;
-        } elseif ($empty == -1) {
-            $message = _("Select all date components.");
-            return false;
-        }
-
-        return true;
-    }
-
-    function emptyDateArray($date)
-    {
-        if (!is_array($date)) {
-            return empty($date);
-        }
-
-        $empty = 0;
-        /* Check each date array component. */
-        foreach ($date as $key => $val) {
-            if (empty($val)) {
-                $empty++;
-            }
-        }
-
-        /* Check state of empty. */
-        if ($empty == 0) {
-            /* If no empty parts return 0. */
-            return 0;
-        } elseif ($empty == count($date)) {
-            /* If all empty parts return 1. */
-            return 1;
-        } else {
-            /* If some empty parts return -1. */
-            return -1;
-        }
-    }
-
-    /**
-     * Return the date supplied split up into an array.
-     *
-     * @param string $date_in  Date in one of the three formats supported by
-     *                         Horde_Form and Horde_Date (ISO format
-     *                         YYYY-MM-DD HH:MM:SS, timestamp YYYYMMDDHHMMSS
-     *                         and UNIX epoch) plus the fourth YYYY-MM-DD.
-     *
-     * @return array  Array with three elements - year, month and day.
-     */
-    function getDateParts($date_in)
-    {
-        if (is_array($date_in)) {
-            /* This is probably a failed isValid input so just return
-             * the parts as they are. */
-            return $date_in;
-        } elseif (empty($date_in)) {
-            /* This is just an empty field so return empty parts. */
-            return array('year' => '', 'month' => '', 'day' => '');
-        }
-
-        $date = $this->getDateOb($date_in);
-        return array('year' => $date->year,
-                     'month' => $date->month,
-                     'day' => $date->mday);
-    }
-
-    /**
-     * Return the date supplied as a Horde_Date object.
-     *
-     * @param string $date_in  Date in one of the three formats supported by
-     *                         Horde_Form and Horde_Date (ISO format
-     *                         YYYY-MM-DD HH:MM:SS, timestamp YYYYMMDDHHMMSS
-     *                         and UNIX epoch) plus the fourth YYYY-MM-DD.
-     *
-     * @return Date  The date object.
-     */
-    function getDateOb($date_in)
-    {
-        if (is_array($date_in)) {
-            /* If passed an array change it to the ISO format. */
-            if ($this->emptyDateArray($date_in) == 0) {
-                $date_in = sprintf('%04d-%02d-%02d 00:00:00',
-                                   $date_in['year'],
-                                   $date_in['month'],
-                                   $date_in['day']);
-            }
-        } elseif (preg_match('/^\d{4}-?\d{2}-?\d{2}$/', $date_in)) {
-            /* Fix the date if it is the shortened ISO. */
-            $date_in = $date_in . ' 00:00:00';
-        }
-
-        return new Horde_Date($date_in);
-    }
-
-    /**
-     * Return the date supplied as a Horde_Date object.
-     *
-     * @param string $date  Either an already set up Horde_Date object or a
-     *                      string date in one of the three formats supported
-     *                      by Horde_Form and Horde_Date (ISO format
-     *                      YYYY-MM-DD HH:MM:SS, timestamp YYYYMMDDHHMMSS and
-     *                      UNIX epoch) plus the fourth YYYY-MM-DD.
-     *
-     * @return string  The date formatted according to the $format_out
-     *                 parameter when setting up the monthdayyear field.
-     */
-    function formatDate($date)
-    {
-        if (!is_a($date, 'Date')) {
-            $date = $this->getDateOb($date);
-        }
-
-        return $date->strftime($this->_format_out);
-    }
-
-    /**
-     * Insert the date input through the form into $info array, in the format
-     * specified by the $format_in parameter when setting up monthdayyear
-     * field.
-     */
-    function getInfo($vars, &$var, &$info)
-    {
-        $info = $this->_validateAndFormat($var->getValue($vars), $var);
-    }
-
-    /**
-     * Validate/format a date submission.
-     */
-    function _validateAndFormat($value, $var)
-    {
-        /* If any component is empty consider it a bad date and return the
-         * default. */
-        if ($this->emptyDateArray($value) == 1) {
-            return $var->getDefault();
-        } else {
-            $date = $this->getDateOb($value);
-            if ($this->_format_in === null) {
-                return $date->timestamp();
-            } else {
-                return $date->strftime($this->_format_in);
-            }
-        }
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array(
-            'name' => _("Date selection"),
-            'params' => array(
-                'start_year' => array('label' => _("Start year"),
-                                      'type'  => 'int'),
-                'end_year'   => array('label' => _("End year"),
-                                      'type'  => 'int'),
-                'picker'     => array('label' => _("Show picker?"),
-                                      'type'  => 'boolean'),
-                'format_in'  => array('label' => _("Storage format"),
-                                      'type'  => 'text'),
-                'format_out' => array('label' => _("Display format"),
-                                      'type'  => 'text')));
-    }
-
-}
-
-/**
- * @since Horde 3.2
- */
-class Horde_Form_Type_datetime extends Horde_Form_Type {
-
-    var $_mdy;
-    var $_hms;
-
-    /**
-     * Return the date supplied as a Horde_Date object.
-     *
-     * @param integer $start_year  The first available year for input.
-     * @param integer $end_year    The last available year for input.
-     * @param boolean $picker      Do we show the DHTML calendar?
-     * @param integer $format_in   The format to use when sending the date
-     *                             for storage. Defaults to Unix epoch.
-     *                             Similar to the strftime() function.
-     * @param integer $format_out  The format to use when displaying the
-     *                             date. Similar to the strftime() function.
-     * @param boolean $show_seconds Include a form input for seconds.
-     */
-    function init($start_year = '', $end_year = '', $picker = true,
-                  $format_in = null, $format_out = '%x', $show_seconds = false)
-    {
-        $this->_mdy = new Horde_Form_Type_monthdayyear();
-        $this->_mdy->init($start_year, $end_year, $picker, $format_in, $format_out);
-
-        $this->_hms = new Horde_Form_Type_hourminutesecond();
-        $this->_hms->init($show_seconds);
-    }
-
-    function isValid(&$var, &$vars, $value, &$message)
-    {
-        if ($var->isRequired()) {
-            return $this->_mdy->isValid($var, $vars, $value, $message) &&
-                $this->_hms->isValid($var, $vars, $value, $message);
-        }
-        return true;
-    }
-
-    function getInfo(&$vars, &$var, &$info)
-    {
-        /* If any component is empty consider it a bad date and return the
-         * default. */
-        $value = $var->getValue($vars);
-        if ($this->emptyDateArray($value) == 1 || $this->emptyTimeArray($value)) {
-            $info = $var->getDefault();
-            return;
-        }
-
-        $date = $this->getDateOb($value);
-        $time = $this->getTimeOb($value);
-        $date->hour = $time->hour;
-        $date->min = $time->min;
-        $date->sec = $time->sec;
-        if (is_null($this->format_in)) {
-            $info = $date->timestamp();
-        } else {
-            $info = $date->strftime($this->format_in);
-        }
-    }
-
-    function __get($property)
-    {
-        if ($property == 'show_seconds') {
-            return $this->_hms->$property;
-        } else {
-            return $this->_mdy->$property;
-        }
-    }
-
-    function __set($property, $value)
-    {
-        if ($property == 'show_seconds') {
-            $this->_hms->$property = $value;
-        } else {
-            $this->_mdy->$property = $value;
-        }
-    }
-
-    function checktime($hour, $minute, $second)
-    {
-        return $this->_hms->checktime($hour, $minute, $second);
-    }
-
-    function getTimeOb($time_in)
-    {
-        return $this->_hms->getTimeOb($time_in);
-    }
-
-    function getTimeParts($time_in)
-    {
-        return $this->_hms->getTimeParts($time_in);
-    }
-
-    function emptyTimeArray($time)
-    {
-        return $this->_hms->emptyTimeArray($time);
-    }
-
-    function emptyDateArray($date)
-    {
-        return $this->_mdy->emptyDateArray($date);
-    }
-
-    function getDateParts($date_in)
-    {
-        return $this->_mdy->getDateParts($date_in);
-    }
-
-    function getDateOb($date_in)
-    {
-        return $this->_mdy->getDateOb($date_in);
-    }
-
-    function formatDate($date)
-    {
-        if ($date === null) {
-            return '';
-        }
-        return $this->_mdy->formatDate($date);
-    }
-
-    function about()
-    {
-        return array(
-            'name' => _("Date and time selection"),
-            'params' => array(
-                'start_year' => array('label' => _("Start year"),
-                                      'type'  => 'int'),
-                'end_year'   => array('label' => _("End year"),
-                                      'type'  => 'int'),
-                'picker'     => array('label' => _("Show picker?"),
-                                      'type'  => 'boolean'),
-                'format_in'  => array('label' => _("Storage format"),
-                                      'type'  => 'text'),
-                'format_out' => array('label' => _("Display format"),
-                                      'type'  => 'text'),
-                'seconds'    => array('label' => _("Show seconds?"),
-                                      'type'  => 'boolean')));
-    }
-
-}
-
-class Horde_Form_Type_colorpicker extends Horde_Form_Type {
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        if ($var->isRequired() && empty($value)) {
-            $message = _("This field is required.");
-            return false;
-        }
-
-        if (empty($value) || preg_match('/^#([0-9a-z]){6}$/i', $value)) {
-            return true;
-        }
-
-        $message = _("This field must contain a color code in the RGB Hex format, for example '#1234af'.");
-        return false;
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array('name' => _("Colour selection"));
-    }
-
-}
-
-class Horde_Form_Type_sorter extends Horde_Form_Type {
-
-    var $_instance;
-    var $_values;
-    var $_size;
-    var $_header;
-
-    function init($values, $size = 8, $header = '')
-    {
-        static $horde_sorter_instance = 0;
-
-        /* Get the next progressive instance count for the horde
-         * sorter so that multiple sorters can be used on one page. */
-        $horde_sorter_instance++;
-        $this->_instance = 'horde_sorter_' . $horde_sorter_instance;
-        $this->_values = $values;
-        $this->_size   = $size;
-        $this->_header = $header;
-    }
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        return true;
-    }
-
-    function getOptions($keys = null)
-    {
-        $html = '';
-        if ($this->_header) {
-            $html .= '<option value="">' . htmlspecialchars($this->_header) . '</option>';
-        }
-
-        if (empty($keys)) {
-            $keys = array_keys($this->_values);
-        } else {
-            $keys = explode("\t", $keys['array']);
-        }
-        foreach ($keys as $sl_key) {
-            $html .= '<option value="' . $sl_key . '">' . htmlspecialchars($this->_values[$sl_key]) . '</option>';
-        }
-
-        return $html;
-    }
-
-    function getInfo($vars, &$var, &$info)
-    {
-        $value = $vars->get($var->getVarName());
-        $info = explode("\t", $value['array']);
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array(
-            'name' => _("Sort order selection"),
-            'params' => array(
-                'values' => array('label' => _("Values"),
-                                  'type'  => 'stringlist'),
-                'size'   => array('label' => _("Size"),
-                                  'type'  => 'int'),
-                'header' => array('label' => _("Header"),
-                                  'type'  => 'text')));
-    }
-
-}
-
-class Horde_Form_Type_selectfiles extends Horde_Form_Type {
-
-    /**
-     * The text to use in the link.
-     *
-     * @var string
-     */
-    var $_link_text;
-
-    /**
-     * The style to use for the link.
-     *
-     * @var string
-     */
-    var $_link_style;
-
-    /**
-     *  Create the link with an icon instead of text?
-     *
-     * @var boolean
-     */
-    var $_icon;
-
-    /**
-     * Contains gollem selectfile selectionID
-     *
-     * @var string
-     */
-    var $_selectid;
-
-    function init($selectid, $link_text = null, $link_style = '',
-                  $icon = false)
-    {
-        $this->_selectid = $selectid;
-        if (is_null($link_text)) {
-            $link_text = _("Select Files");
-        }
-        $this->_link_text = $link_text;
-        $this->_link_style = $link_style;
-        $this->_icon = $icon;
-    }
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        return true;
-    }
-
-    function getInfo($var, &$vars, &$info)
-    {
-        $value = $vars->getValue($var);
-        $info = $GLOBALS['registry']->call('files/selectlistResults', array($value));
-    }
-
-    function about()
-    {
-        return array(
-            'name' => _("File selection"),
-            'params' => array(
-                'selectid'   => array('label' => _("Id"),
-                                      'type' => 'text'),
-                'link_text'  => array('label' => _("Link text"),
-                                      'type' => 'text'),
-                'link_style' => array('label' => _("Link style"),
-                                      'type' => 'text'),
-                'icon'       => array('label' => _("Show icon?"),
-                                      'type' => 'boolean')));
-    }
-
-}
-
-class Horde_Form_Type_assign extends Horde_Form_Type {
-
-    var $_leftValues;
-    var $_rightValues;
-    var $_leftHeader;
-    var $_rightHeader;
-    var $_size;
-    var $_width;
-
-    function init($leftValues, $rightValues, $leftHeader = '',
-                  $rightHeader = '', $size = 8, $width = '200px')
-    {
-        $this->_leftValues = $leftValues;
-        $this->_rightValues = $rightValues;
-        $this->_leftHeader = $leftHeader;
-        $this->_rightHeader = $rightHeader;
-        $this->_size = $size;
-        $this->_width = $width;
-    }
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        return true;
-    }
-
-    function setValues($side, $values)
-    {
-        if ($side) {
-            $this->_rightValues = $values;
-        } else {
-            $this->_leftValues = $values;
-        }
-    }
-
-    function getHeader($side)
-    {
-        return $side ? $this->_rightHeader : $this->_leftHeader;
-    }
-
-    function getOptions($side, $formname, $varname)
-    {
-        $html = '';
-        $headers = false;
-        if ($side) {
-            $values = $this->_rightValues;
-            if (!empty($this->_rightHeader)) {
-                $values = array('' => $this->_rightHeader) + $values;
-                $headers = true;
-            }
-        } else {
-            $values = $this->_leftValues;
-            if (!empty($this->_leftHeader)) {
-                $values = array('' => $this->_leftHeader) + $values;
-                $headers = true;
-            }
-        }
-
-        foreach ($values as $key => $val) {
-            $html .= '<option value="' . htmlspecialchars($key) . '"';
-            if ($headers) {
-                $headers = false;
-            } else {
-                $html .= ' ondblclick="Horde_Form_Assign.move(\'' . $formname . '\', \'' . $varname . '\', ' . (int)$side . ');"';
-            }
-            $html .= '>' . htmlspecialchars($val) . '</option>';
-        }
-
-        return $html;
-    }
-
-    function getInfo($vars, &$var, &$info)
-    {
-        $value = $vars->get($var->getVarName() . '__values');
-        if (strpos($value, "\t\t") === false) {
-            $left = $value;
-            $right = '';
-        } else {
-            list($left, $right) = explode("\t\t", $value);
-        }
-        if (empty($left)) {
-            $info['left'] = array();
-        } else {
-            $info['left'] = explode("\t", $left);
-        }
-        if (empty($right)) {
-            $info['right'] = array();
-        } else {
-            $info['right'] = explode("\t", $right);
-        }
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array(
-            'name' => _("Assignment columns"),
-            'params' => array(
-                'leftValues'  => array('label' => _("Left values"),
-                                       'type'  => 'stringlist'),
-                'rightValues' => array('label' => _("Right values"),
-                                       'type'  => 'stringlist'),
-                'leftHeader'  => array('label' => _("Left header"),
-                                       'type'  => 'text'),
-                'rightHeader' => array('label' => _("Right header"),
-                                       'type'  => 'text'),
-                'size'        => array('label' => _("Size"),
-                                       'type'  => 'int'),
-                'width'       => array('label' => _("Width in CSS units"),
-                                       'type'  => 'text')));
-    }
-
-}
-
-class Horde_Form_Type_creditcard extends Horde_Form_Type {
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        if (empty($value) && $var->isRequired()) {
-            $message = _("This field is required.");
-            return false;
-        }
-
-        if (!empty($value)) {
-            /* getCardType() will also verify the checksum. */
-            $type = $this->getCardType($value);
-            if ($type === false || $type == 'unknown') {
-                $message = _("This does not seem to be a valid card number.");
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    function getChecksum($ccnum)
-    {
-        $len = strlen($ccnum);
-        if (!is_long($len / 2)) {
-            $weight = 2;
-            $digit = $ccnum[0];
-        } elseif (is_long($len / 2)) {
-            $weight = 1;
-            $digit = $ccnum[0] * 2;
-        }
-        if ($digit > 9) {
-            $digit = $digit - 9;
-        }
-        $i = 1;
-        $checksum = $digit;
-        while ($i < $len) {
-            if ($ccnum[$i] != ' ') {
-                $digit = $ccnum[$i] * $weight;
-                $weight = ($weight == 1) ? 2 : 1;
-                if ($digit > 9) {
-                    $digit = $digit - 9;
-                }
-                $checksum += $digit;
-            }
-            $i++;
-        }
-
-        return $checksum;
-    }
-
-    function getCardType($ccnum)
-    {
-        $sum = $this->getChecksum($ccnum);
-        $l = strlen($ccnum);
-
-        // Screen checksum.
-        if (($sum % 10) != 0) {
-            return false;
-        }
-
-        // Check for Visa.
-        if ((($l == 16) || ($l == 13)) &&
-            ($ccnum[0] == 4)) {
-            return 'visa';
-        }
-
-        // Check for MasterCard.
-        if (($l == 16) &&
-            ($ccnum[0] == 5) &&
-            ($ccnum[1] >= 1) &&
-            ($ccnum[1] <= 5)) {
-            return 'mastercard';
-        }
-
-        // Check for Amex.
-        if (($l == 15) &&
-            ($ccnum[0] == 3) &&
-            (($ccnum[1] == 4) || ($ccnum[1] == 7))) {
-            return 'amex';
-        }
-
-        // Check for Discover (Novus).
-        if (strlen($ccnum) == 16 &&
-            substr($ccnum, 0, 4) == '6011') {
-            return 'discover';
-        }
-
-        // If we got this far, then no card matched.
-        return 'unknown';
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array('name' => _("Credit card number"));
-    }
-
-}
-
-class Horde_Form_Type_obrowser extends Horde_Form_Type {
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        return true;
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array('name' => _("Relationship browser"));
-    }
-
-}
-
-class Horde_Form_Type_dblookup extends Horde_Form_Type_enum {
-
-    function init($dsn, $sql, $prompt = null)
-    {
-        $values = array();
-        $db = DB::connect($dsn);
-        if (!is_a($db, 'PEAR_Error')) {
-            // Set DB portability options.
-            switch ($db->phptype) {
-            case 'mssql':
-                $db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM);
-                break;
-            default:
-                $db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS);
-            }
-
-            $col = $db->getCol($sql);
-            if (!is_a($col, 'PEAR_Error')) {
-                $values = Horde_Array::combine($col, $col);
-            }
-        }
-        parent::init($values, $prompt);
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array(
-            'name' => _("Database lookup"),
-            'params' => array(
-                'dsn' => array('label' => _("DSN (see http://pear.php.net/manual/en/package.database.db.intro-dsn.php)"),
-                               'type'  => 'text'),
-                'sql' => array('label' => _("SQL statement for value lookups"),
-                               'type'  => 'text'),
-                'prompt' => array('label' => _("Prompt text"),
-                                  'type'  => 'text'))
-            );
-    }
-
-}
-
-class Horde_Form_Type_figlet extends Horde_Form_Type {
-
-    var $_text;
-    var $_font;
-
-    function init($text, $font)
-    {
-        $this->_text = $text;
-        $this->_font = $font;
-    }
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        if (empty($value) && $var->isRequired()) {
-            $message = _("This field is required.");
-            return false;
-        }
-
-        if (String::lower($value) != String::lower($this->_text)) {
-            $message = _("The text you entered did not match the text on the screen.");
-            return false;
-        }
-
-        return true;
-    }
-
-    /**
-     * Return info about field type.
-     */
-    function about()
-    {
-        return array(
-            'name' => _("Figlet CAPTCHA"),
-            'params' => array(
-                'text' => array('label' => _("Text"),
-                                'type'  => 'text'),
-                'font' => array('label' => _("Figlet font"),
-                                'type'  => 'text'))
-            );
-    }
-
-}
-
-class Horde_Form_Type_invalid extends Horde_Form_Type {
-
-    var $message;
-
-    function init($message)
-    {
-        $this->message = $message;
-    }
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        return false;
-    }
-
-}
-
-/**
- * This class represents a single form variable that may be rendered as one or
- * more form fields.
- *
- * @author  Robert E. Coyle <robertecoyle@hotmail.com>
- * @package Horde_Form
- */
-class Horde_Form_Variable {
-
-    /**
-     * The form instance this variable is assigned to.
-     *
-     * @var Horde_Form
-     */
-    var $form;
-
-    /**
-     * A short description of this variable's purpose.
-     *
-     * @var string
-     */
-    var $humanName;
-
-    /**
-     * The internally used name.
-     *
-     * @var string
-     */
-    var $varName;
-
-    /**
-     * A {@link Horde_Form_Type} instance.
-     *
-     * @var Horde_Form_Type
-     */
-    var $type;
-
-    /**
-     * Whether this is a required variable.
-     *
-     * @var boolean
-     */
-    var $required;
-
-    /**
-     * Whether this is a readonly variable.
-     *
-     * @var boolean
-     */
-    var $readonly;
-
-    /**
-     * A long description of the variable's purpose, special instructions, etc.
-     *
-     * @var string
-     */
-    var $description;
-
-    /**
-     * The variable help text.
-     *
-     * @var string
-     */
-    var $help;
-
-    /**
-     * Whether this is an array variable.
-     *
-     * @var boolean
-     */
-    var $_arrayVal;
-
-    /**
-     * The default value.
-     *
-     * @var mixed
-     */
-    var $_defValue = null;
-
-    /**
-     * A {@link Horde_Form_Action} instance.
-     *
-     * @var Horde_Form_Action
-     */
-    var $_action;
-
-    /**
-     * Whether this variable is disabled.
-     *
-     * @var boolean
-     */
-    var $_disabled = false;
-
-    /**
-     * TODO
-     *
-     * @var boolean
-     */
-    var $_autofilled = false;
-
-    /**
-     * Whether this is a hidden variable.
-     *
-     * @var boolean
-     */
-    var $_hidden = false;
-
-    /**
-     * TODO
-     *
-     * @var array
-     */
-    var $_options = array();
-
-    /**
-     * Variable constructor.
-     *
-     * @param string $humanName      A short description of the variable's
-     *                               purpose.
-     * @param string $varName        The internally used name.
-     * @param Horde_Form_Type $type  A {@link Horde_Form_Type} instance.
-     * @param boolean $required      Whether this is a required variable.
-     * @param boolean $readonly      Whether this is a readonly variable.
-     * @param string $description    A long description of the variable's
-     *                               purpose, special instructions, etc.
-     */
-    function Horde_Form_Variable($humanName, $varName, $type, $required,
-                                 $readonly = false, $description = null)
-    {
-        $this->humanName   = $humanName;
-        $this->varName     = $varName;
-        $this->type        = $type;
-        $this->required    = $required;
-        $this->readonly    = $readonly;
-        $this->description = $description;
-        $this->_arrayVal   = (strpos($varName, '[]') !== false);
-    }
-
-    /**
-     * Assign this variable to the specified form.
-     *
-     * @param Horde_Form $form  The form instance to assign this variable to.
-     */
-    function setFormOb($form)
-    {
-        $this->form = $form;
-    }
-
-    /**
-     * Sets a default value for this variable.
-     *
-     * @param mixed $value  A variable value.
-     */
-    function setDefault($value)
-    {
-        $this->_defValue = $value;
-    }
-
-    /**
-     * Returns this variable's default value.
-     *
-     * @return mixed  This variable's default value.
-     */
-    function getDefault()
-    {
-        return $this->_defValue;
-    }
-
-    /**
-     * Assigns an action to this variable.
-     *
-     * Example:
-     * <code>
-     * $v = $form->addVariable('My Variable', 'var1', 'text', false);
-     * $action = new Horde_Form_Action_submit;
-     * $v->setAction($action);
-     * </code>
-     *
-     * @param Horde_Form_Action $action  A {@link Horde_Form_Action} instance.
-     */
-    function setAction($action)
-    {
-        $this->_action = $action;
-    }
-
-    /**
-     * Returns whether this variable has an attached action.
-     *
-     * @return boolean  True if this variable has an attached action.
-     */
-    function hasAction()
-    {
-        return !is_null($this->_action);
-    }
-
-    /**
-     * Makes this a hidden variable.
-     */
-    function hide()
-    {
-        $this->_hidden = true;
-    }
-
-    /**
-     * Returns whether this is a hidden variable.
-     *
-     * @return boolean  True if this a hidden variable.
-     */
-    function isHidden()
-    {
-        return $this->_hidden;
-    }
-
-    /**
-     * Disables this variable.
-     */
-    function disable()
-    {
-        $this->_disabled = true;
-    }
-
-    /**
-     * Returns whether this variable is disabled.
-     *
-     * @return boolean  True if this variable is disabled.
-     */
-    function isDisabled()
-    {
-        return $this->_disabled;
-    }
-
-    /**
-     * Return the short description of this variable.
-     *
-     * @return string  A short description
-     */
-    function getHumanName()
-    {
-        return $this->humanName;
-    }
-
-    /**
-     * Returns the internally used variable name.
-     *
-     * @return string  This variable's internal name.
-     */
-    function getVarName()
-    {
-        return $this->varName;
-    }
-
-    /**
-     * Returns this variable's type.
-     *
-     * @return Horde_Form_Type  This variable's {@link Horde_Form_Type}
-     *                          instance.
-     */
-    function getType()
-    {
-        return $this->type;
-    }
-
-    /**
-     * Returns whether this is a required variable.
-     *
-     * @return boolean  True if this is a required variable.
-     */
-    function isRequired()
-    {
-        return $this->required;
-    }
-
-    /**
-     * Returns whether this is a readonly variable.
-     *
-     * @return boolean  True if this a readonly variable.
-     */
-    function isReadonly()
-    {
-        return $this->readonly;
-    }
-
-    /**
-     * Returns the possible values of this variable.
-     *
-     * @return array  The possible values of this variable or null.
-     */
-    function getValues()
-    {
-        return $this->type->values;
-    }
-
-    /**
-     * Returns whether this variable has a long description.
-     *
-     * @return boolean  True if this variable has a long description.
-     */
-    function hasDescription()
-    {
-        return !empty($this->description);
-    }
-
-    /**
-     * Returns this variable's long description.
-     *
-     * @return string  This variable's long description.
-     */
-    function getDescription()
-    {
-        return $this->description;
-    }
-
-    /**
-     * Returns whether this is an array variable.
-     *
-     * @return boolean  True if this an array variable.
-     */
-    function isArrayVal()
-    {
-        return $this->_arrayVal;
-    }
-
-    /**
-     * Returns whether this variable is to upload a file.
-     *
-     * @return boolean  True if variable is to upload a file.
-     */
-    function isUpload()
-    {
-        return ($this->type instanceof Horde_Form_Type_file);
-    }
-
-    /**
-     * Assigns a help text to this variable.
-     *
-     * @param string $help  The variable help text.
-     */
-    function setHelp($help)
-    {
-        $this->form->_help = true;
-        $this->help = $help;
-    }
-
-    /**
-     * Returns whether this variable has some help text assigned.
-     *
-     * @return boolean  True if this variable has a help text.
-     */
-    function hasHelp()
-    {
-        return !empty($this->help);
-    }
-
-    /**
-     * Returns the help text of this variable.
-     *
-     * @return string  This variable's help text.
-     */
-    function getHelp()
-    {
-        return $this->help;
-    }
-
-    /**
-     * Sets a variable option.
-     *
-     * @param string $option  The option name.
-     * @param mixed $val      The option's value.
-     */
-    function setOption($option, $val)
-    {
-        $this->_options[$option] = $val;
-    }
-
-    /**
-     * Returns a variable option's value.
-     *
-     * @param string $option  The option name.
-     *
-     * @return mixed          The option's value.
-     */
-    function getOption($option)
-    {
-        return isset($this->_options[$option]) ? $this->_options[$option] : null;
-    }
-
-    /**
-     * Processes the submitted value of this variable according to the rules of
-     * the variable type.
-     *
-     * @param Variables $vars  The {@link Variables} instance of the submitted
-     *                         form.
-     * @param mixed $info      A variable passed by reference that will be
-     *                         assigned the processed value of the submitted
-     *                         variable value.
-     *
-     * @return mixed  Depending on the variable type.
-     */
-    function getInfo($vars, &$info)
-    {
-        return $this->type->getInfo($vars, $this, $info);
-    }
-
-    /**
-     * Returns whether this variable if it had the "trackchange" option set
-     * has actually been changed.
-     *
-     * @param Variables $vars  The {@link Variables} instance of the submitted
-     *                         form.
-     *
-     * @return boolean  Null if this variable doesn't have the "trackchange"
-     *                  option set or the form wasn't submitted yet. A boolean
-     *                  indicating whether the variable was changed otherwise.
-     */
-    function wasChanged($vars)
-    {
-        if (!$this->getOption('trackchange')) {
-            return null;
-        }
-        $old = $vars->get('__old_' . $this->getVarName());
-        if (is_null($old)) {
-            return null;
-        }
-        return $old != $vars->get($this->getVarName());
-    }
-
-    /**
-     * Validates this variable.
-     *
-     * @param Variables $vars  The {@link Variables} instance of the submitted
-     *                         form.
-     * @param string $message  A variable passed by reference that will be
-     *                         assigned a descriptive error message if
-     *                         validation failed.
-     *
-     * @return boolean  True if the variable validated.
-     */
-    function validate($vars, &$message)
-    {
-        if ($this->_arrayVal) {
-            $vals = $this->getValue($vars);
-            if (!is_array($vals)) {
-                if ($this->required) {
-                    $message = _("This field is required.");
-                    return false;
-                } else {
-                    return true;
-                }
-            }
-            foreach ($vals as $i => $value) {
-                if ($value === null && $this->required) {
-                    $message = _("This field is required.");
-                    return false;
-                } else {
-                    if (!$this->type->isValid($this, $vars, $value, $message)) {
-                        return false;
-                    }
-                }
-            }
-        } else {
-            $value = $this->getValue($vars);
-            return $this->type->isValid($this, $vars, $value, $message);
-        }
-
-        return true;
-    }
-
-    /**
-     * Returns the submitted or default value of this variable.
-     * If an action is attached to this variable, the value will get passed to
-     * the action object.
-     *
-     * @param Variables $vars  The {@link Variables} instance of the submitted
-     *                         form.
-     * @param integer $index   If the variable is an array variable, this
-     *                         specifies the array element to return.
-     *
-     * @return mixed  The variable or element value.
-     */
-    function getValue($vars, $index = null)
-    {
-        if ($this->_arrayVal) {
-            $name = str_replace('[]', '', $this->varName);
-        } else {
-            $name = $this->varName;
-        }
-        $value = $vars->getExists($name, $wasset);
-
-        if (!$wasset) {
-            $value = $this->getDefault();
-        }
-
-        if ($this->_arrayVal && !is_null($index)) {
-            if (!$wasset && !is_array($value)) {
-                $return = $value;
-            } else {
-                $return = isset($value[$index]) ? $value[$index] : null;
-            }
-        } else {
-            $return = $value;
-        }
-
-        if ($this->hasAction()) {
-            $this->_action->setValues($vars, $return, $this->_arrayVal);
-        }
-
-        return $return;
-    }
-
-}
diff --git a/framework/Horde_Form/lib/Horde/Form/Renderer.php b/framework/Horde_Form/lib/Horde/Form/Renderer.php
deleted file mode 100644 (file)
index 867f114..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-<?php
-/**
- * @package Horde_Form
- */
-
-/**
- * The Horde_Form_Renderer class provides HTML and other renderings of
- * forms for the Horde_Form:: package.
- *
- * $Horde: incubator/Horde_Form/Horde/Form/Renderer.php,v 1.7 2007/09/16 19:51:08 chuck Exp $
- *
- * Copyright 2001-2007 Robert E. Coyle <robertecoyle@hotmail.com>
- * Copyright 2005-2007 Matt Warden <mwarden@gmail.com>
- *
- * See the enclosed file COPYING for license information (LGPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
- *
- * @author  Robert E. Coyle <robertecoyle@hotmail.com>
- * @author  Matt Warden <mwarden@gmail.com>
- * @package Horde_Form
- */
-abstract class Horde_Form_Renderer {
-
-    var $_name;
-    var $_requiredLegend = false;
-    var $_helpMarker = '?';
-    var $_onLoadJS = array();
-    var $_showHeader = true;
-    var $_cols = 2;
-    var $_varRenderer = null;
-    var $_firstField = null;
-    var $_stripedRows = true;
-
-    protected $_submit = array();
-    protected $_reset = false;
-
-    /**
-     * Does the title of the form contain HTML? If so, you are responsible for
-     * doing any needed escaping/sanitization yourself. Otherwise the title
-     * will be run through htmlspecialchars() before being output.
-     *
-     * @var boolean
-     */
-    var $_encodeTitle = true;
-
-    /**
-     * Construct a new Horde_Form_Renderer::.
-     *
-     * @param array $params  This is a hash of renderer-specific parameters.
-     *                       Possible keys:<code>
-     *                       'encode_title': @see $_encodeTitle</code>
-     */
-    function __construct($params = array())
-    {
-        if (isset($params['encode_title'])) {
-            $this->encodeTitle($params['encode_title']);
-        }
-
-        $this->_varRenderer = new Horde_Form_VarRenderer_Xhtml;
-    }
-
-    abstract public function renderActive($form, $action, $method = 'get', $enctype = null, $focus = true);
-
-    public function setButtons($submit, $reset = false)
-    {
-        if ($submit === true || is_null($submit) || empty($submit)) {
-            /* Default to 'Submit'. */
-            $submit = array(_("Submit"));
-        } elseif (!is_array($submit)) {
-            /* Default to array if not passed. */
-            $submit = array($submit);
-        }
-        /* Only if $reset is strictly true insert default 'Reset'. */
-        if ($reset === true) {
-            $reset = _("Reset");
-        }
-
-        $this->_submit = $submit;
-        $this->_reset = $reset;
-
-        return $this;
-    }
-
-    public function addButtons($buttons)
-    {
-        if (!is_array($buttons)) {
-            $buttons = array($buttons);
-        }
-
-        $this->_submit = array_merge($this->_submit, $buttons);
-    }
-
-    public function showHeader($bool)
-    {
-        $this->_showHeader = $bool;
-    }
-
-    /**
-     * Sets or returns whether the form title should be encoded with
-     * htmlspecialchars().
-     *
-     * @param boolean $encode  If true, the form title gets encoded.  If false
-     *                         the title can contain HTML, but the class user
-     *                         is responsible to encode any special characters.
-     *
-     * @return boolean  Whether the form title should be encoded.
-     */
-    function encodeTitle($encode = null)
-    {
-        if (!is_null($encode)) {
-            $this->_encodeTitle = $encode;
-        }
-        return $this->_encodeTitle = $encode;
-    }
-
-}
diff --git a/framework/Horde_Form/lib/Horde/Form/Renderer/Xhtml.php b/framework/Horde_Form/lib/Horde/Form/Renderer/Xhtml.php
deleted file mode 100644 (file)
index 3caac34..0000000
+++ /dev/null
@@ -1,455 +0,0 @@
-<?php
-/**
- */
-class Horde_Form_Renderer_Xhtml extends Horde_Form_Renderer {
-
-    protected $_enctype = 'multipart/form-data';
-
-    function _renderSectionTabs($form)
-    {
-        /* If javascript is not available, do not render tabs. */
-        if (!$GLOBALS['browser']->hasFeature('javascript')) {
-            return;
-        }
-
-        $open_section = $form->getOpenSection();
-
-        /* Add the javascript for the toggling the sections. */
-        Horde::addScriptFile('form_sections.js', 'horde', true);
-        echo '<script type="text/javascript">' . "\n" .
-            sprintf('var sections_%1$s = new Horde_Form_Sections(\'%1$s\', \'%2$s\');',
-                    $form->getName(),
-                    $open_section) .
-            '</script>';
-
-        /* Loop through the sections and print out a tab for each. */
-        echo "<div class=\"tabset\">\n";
-        $js = array();
-        foreach ($form->_sections as $section => $val) {
-            $class = ($section == $open_section) ? ' class="activeTab"' : '';
-            $tabid = htmlspecialchars($form->getName() . '_tab_' . $section);
-            $js[$linkid] = sprintf('sections_%s.toggle(\'%s\'); return false;"',
-                                   $form->getName(),
-                                   $section);
-            printf('<div%s id="%s"><a href="#" id="%s">%s%s</a></div>' . "\n",
-                   $class,
-                   $tabid,
-                   '_tablink_' . $section,
-                   $form->getSectionImage($section),
-                   $form->getSectionDesc($section));
-        }
-        echo "</div>\n";
-
-        // This doesn't help a whole lot now, but if there is a way to
-        // buffer output of JS, then we can keep JS separated from
-        // markup, whereas before the onclicks were assigned as an
-        // HTML attribute.
-        echo '<script type="text/javascript">' . "\n";
-        echo 'if (document.getElementById) {' . "\n";
-        echo '    addEvent(window, \'load\', function() {' . "\n";
-        foreach ($js as $id => $onclick) {
-            $line = '
-if (document.getElementById(%1$s)){
-    document.getElementById(%1$s).onclick = function() {
-        %2$s
-    };
-}';
-            printf($line, $id, $onclick);
-        }
-        echo '    });}</script>' . "\n";
-    }
-
-    function _renderSectionBegin($form, $section)
-    {
-        // Stripe alternate rows if that option is turned on.
-        if ($this->_stripedRows) {
-            Horde::addScriptFile('stripe.js', 'horde', true);
-            $class = 'striped';
-        } else {
-            $class = '';
-        }
-
-        $open_section = $form->getOpenSection();
-        if (empty($open_section)) {
-            $open_section = '__base';
-        }
-
-        // include a general class name for styling purposes. also helps select
-        // ULs, which only get a className currently if they are striped.
-        printf('<fieldset id="%s" class="%s form-section %s">',
-               htmlspecialchars($form->getName() . '_section_' . $section),
-               ($open_section == $section ? 'form-sectionshown' : 'form-sectionhidden'),
-               $class);
-    }
-
-    function _renderSectionEnd()
-    {
-        echo '</fieldset>';
-    }
-
-    function preserveVarByPost($vars, $varname, $alt_varname = '')
-    {
-        $value = $vars->getExists($varname, $wasset);
-
-        if ($alt_varname) {
-            $varname = $alt_varname;
-        }
-
-        if ($wasset) {
-            $this->_preserveVarByPost($varname, $value);
-        }
-    }
-
-    function _preserveVarByPost($varname, $value)
-    {
-        if (is_array($value)) {
-            foreach ($value as $id => $val) {
-                $this->_preserveVarByPost($varname . '[' . $id . ']', $val);
-            }
-        } else {
-            $varname = htmlspecialchars($varname);
-            $value = htmlspecialchars($value);
-            printf('<input type="hidden" id="%1$s" name="%1$s" value="%2$s" />'."\n",
-                   $varname,
-                   $value);
-        }
-    }
-
-    function listFormVars($form)
-    {
-        $variables = $form->getVariables(true, true);
-        $vars = array();
-        if ($variables) {
-            foreach ($variables as $var) {
-                if (is_object($var)) {
-                    if (!$var->isReadonly()) {
-                        $vars[$var->getVarName()] = 1;
-                    }
-                } else {
-                    $vars[$var] = 1;
-                }
-            }
-        }
-        require_once 'Horde/NLS.php';
-        echo '<input type="hidden" name="_formvars" value="'
-            . htmlspecialchars(serialize($vars), ENT_QUOTES, NLS::getCharset())
-            . '" />';
-    }
-
-    public function renderActive($form, $action, $method = 'get', $enctype = null, $focus = true)
-    {
-        $this->_name = $form->getName();
-
-        echo "<form class=\"horde-form\" action=\"$action\" method=\"$method\""
-            . (empty($this->_name) ? '' : ' id="' . $this->_name. '"')
-            . (is_null($this->_enctype) ? '' : ' enctype="' . $this->_enctype . '"')
-            . ">\n";
-        echo Util::formInput();
-
-        $this->listFormVars($form);
-
-        if (!empty($this->_name)) {
-            $this->_preserveVarByPost('formname', $this->_name);
-        }
-
-        if ($form->useToken()) {
-            $this->_preserveVarByPost($this->_name . '_formToken', Horde_Token::generateId($this->_name));
-        }
-
-        if (count($form->getSections())) {
-            $this->_preserveVarByPost('__formOpenSection', $form->getOpenSection());
-        }
-
-        $vars = $form->getVars();
-
-        $variables = $form->getVariables();
-        foreach ($variables as $var) {
-            if ($var->getOption('trackchange')) {
-                $varname = $var->getVarName();
-                $this->preserveVarByPost($vars, $varname, '__old_' . $varname);
-            }
-        }
-
-        foreach ($form->getHiddenVariables() as $var) {
-            $this->preserveVarByPost($vars, $var->getVarName());
-        }
-
-        $this->_renderBeginActive($form->getTitle());
-        $this->_renderForm($form, true);
-        $this->submit($this->_submit, $this->_reset);
-
-        echo "\n</fieldset>\n</form>\n";
-        if ($focus && !empty($this->_firstField)) {
-            echo '<script type="text/javascript">
-try {
-    document.getElementById("'. $this->_firstField .'").focus();
-} catch (e) {}
-</script>
-';
-        }
-    }
-
-    function renderInactive($form)
-    {
-        $this->_name = $form->getName();
-        $this->_renderBeginInactive($form->getTitle());
-        $this->_renderForm($form, false);
-    }
-
-    function _renderForm($form, $active)
-    {
-        $vars = $form->getVars();
-
-        /* If help is present 3 columns are needed. */
-        $this->_cols = $form->hasHelp() ? 3 : 2;
-
-        $variables = $form->getVariables(false);
-
-        /* Check for a form token error. */
-        if (($tokenError = $form->getError('_formToken')) !== null) {
-            printf('<p class="form-error">%s</p>'."\n", $tokenError);
-        }
-
-        $error_section = null;
-        reset($variables);
-        if (count($variables) > 1 || key($variables) != '__base') {
-            $this->_renderSectionTabs($form);
-        }
-
-        foreach ($variables as $section_id => $section) {
-            $this->_renderSectionBegin($form, $section_id);
-            foreach ($section as $var) {
-                switch (get_class($var->type)) {
-                case 'Horde_Form_Type_header':
-                    $this->_renderHeader($var->getHumanName(), $form->getError($var->getVarName()));
-                    break;
-
-                case 'Horde_Form_Type_description':
-                    $this->_renderDescription($var->getHumanName());
-                    break;
-
-                case 'Horde_Form_Type_spacer':
-                    $this->_renderSpacer();
-                    break;
-
-                default:
-                    $isInput = ($active && !$var->isReadonly());
-                    $format = $isInput ? 'Input' : 'Display';
-                    $begin = "_renderVar${format}Begin";
-                    $end = "_renderVar${format}End";
-
-                    $this->$begin($form, $var);
-                    echo $this->_varRenderer->render($form, $var, $vars, $isInput);
-                    $this->$end($form, $var);
-
-                    /* Print any javascript if actions present. */
-                    if ($var->hasAction()) {
-                        $var->_action->printJavaScript();
-                    }
-
-                    /* Keep first field. */
-                    if ($active && empty($this->_firstField) && !$var->isReadonly()
-                        && !$var->isHidden()) {
-                        $this->_firstField = $var->getVarName();
-                    }
-
-                    /* Keep section with first error. */
-                    if (is_null($error_section) && $form->getError($var)) {
-                        $error_section = $section_id;
-                    }
-                }
-            }
-
-            $this->_renderSectionEnd();
-        }
-
-        if (!is_null($error_section)) {
-            echo '<script type="text/javascript">' .
-                "\n" . sprintf('sections_%s.toggle(\'%s\');',
-                               $form->getName(),
-                               $error_section) .
-                "\n</script>\n";
-        }
-
-        echo '</fieldset>' . $this->_varRenderer->renderEnd();
-    }
-
-    function submit($submit = null, $reset = false)
-    {
-        if (is_null($submit) || empty($submit)) {
-            $submit = _("Submit");
-        }
-        if ($reset === true) {
-            $reset = _("Reset");
-        }
-        $this->_renderSubmit($submit, $reset);
-    }
-
-    /**
-     * Implementation specific begin function.
-     */
-    function _renderBeginActive($name)
-    {
-        echo '<fieldset class="horde-form" id="fieldset_' . htmlspecialchars($this->_name) . '">'."\n";
-        if ($this->_showHeader) {
-            $this->_renderSectionHeader($name);
-        }
-        if ($this->_requiredLegend) {
-            echo '<div class="form-error-example">' . $this->_requiredMarker
-                . ' &#61; ' . _("Required Field") . '</div>'."\n";
-        }
-    }
-
-    /**
-     * Implementation specific begin function.
-     */
-    function _renderBeginInactive($name)
-    {
-        echo '<fieldset class="horde-form" id="fieldset_' . htmlspecialchars($this->_name) . '">';
-        if ($this->_showHeader) {
-            $this->_renderSectionHeader($name);
-        }
-    }
-
-    function _renderHeader($header, $error = '')
-    {
-        echo '<div class="form-header">'. $header . '</div>';
-        if (!empty($error)) {
-            echo '<div class="form-error">'. $error . '</div>';
-        }
-    }
-
-    function _renderDescription($description)
-    {
-        echo '<div class="form-description">'. $description . '</div>';
-    }
-
-    function _renderSpacer()
-    {
-        // TODO: fix this later so we're not inserting nonsemantic elements just for spacing
-        // ... maybe append form-spacer to class of next or previous element
-        echo '<div class="form-spacer">&nbsp;</div>';
-    }
-
-    function _renderSubmit($submit, $reset)
-    {
-        echo '<fieldset class="form-buttons">'."\n";
-        if (!is_array($submit)) $submit = array($submit);
-        foreach ($submit as $submitbutton) {
-            echo '<input class="button" name="submitbutton" type="submit"';
-            // allow for default-value submit buttons (e.g. _renderSubmit(""))
-            if (!empty($submitbutton)) {
-                echo ' value="'. $submitbutton .'"';
-            }
-            echo ' />'."\n";
-        }
-        if (!empty($reset)) {
-            echo '<input class="button" name="resetbutton" type="reset"
-                value="'. $reset .'" />'."\n";
-        }
-    }
-
-    /**
-     * Renders the beginning of an writeable form entry, including the label
-     * and any form error related to this variable.
-     *
-     * @access private
-     * @author Matt Warden <mwarden@gmail.com>
-     * @author  Robert E. Coyle <robertecoyle@hotmail.com>
-     */
-    function _renderVarInputBegin($form, $var, $readonly = false)
-    {
-        // get error message for variable, if any
-        $message = $form->getError($var);
-        // if no message, then no error
-        $isvalid = empty($message);
-
-        $classnames = 'form-input'
-            . (!$isvalid ? ' form-error' : '')
-            . ($var->isRequired() ? ' form-required' : '');
-
-        echo '<div class="', $classnames, '">';
-
-        if (!$isvalid) {
-            echo '<p class="form-error">', $message, '</p>', "\n";
-        }
-
-        printf('<label%s>%s</label>',
-            ($readonly ? '' : ' for="'. $var->getVarName() .'"'),
-            $var->getHumanName());
-    }
-
-    /**
-     * Renders the end of an writeable form entry, including any form notes
-     * and help info.
-     *
-     * @access private
-     * @author Matt Warden <mwarden@gmail.com>
-     * @author  Robert E. Coyle <robertecoyle@hotmail.com>
-     */
-    function _renderVarInputEnd($form, $var)
-    {
-        /* Display any help for the field. */
-        if ($var->hasHelp()) {
-            global $registry;
-            if (isset($registry) && is_a($registry, 'Registry')) {
-                $help = Help::link($GLOBALS['registry']->getApp(), $var->getHelp());
-            } else {
-                $help = @htmlspecialchars($var->getHelp());
-            }
-            echo '<p class="form-hint">', $help, '</p>';
-        }
-
-        /* Display any description for the field. */
-        if ($var->hasDescription()) {
-            echo '<div class="form-note"><p>', $var->getDescription(), '</p></div>';
-        } else {
-            echo '<br class="clear" />';
-        }
-
-        echo '</div>';
-    }
-
-    /**
-     * Renders the beginning of a readonly form entry.
-     *
-     * @access private
-     * @author Matt Warden <mwarden@gmail.com>
-     * @author  Robert E. Coyle <robertecoyle@hotmail.com>
-     */
-    function _renderVarDisplayBegin($form, $var)
-    {
-        return $this->_renderVarInputBegin($form, $var, true);
-    }
-
-    /**
-     * Renders the end of a readonly form entry. Help and notes are not
-     * applicable.
-     *
-     * @access private
-     * @author Matt Warden <mwarden@gmail.com>
-     * @author  Robert E. Coyle <robertecoyle@hotmail.com>
-     */
-    function _renderVarDisplayEnd()
-    {
-        echo '</div>';
-    }
-
-    /**
-     * Renders the header for the section.
-     *
-     * @access private
-     * @author Matt Warden <mwarden@gmail.com>
-     * @author  Robert E. Coyle <robertecoyle@hotmail.com>
-     * @param string $title section header title
-     */
-    function _renderSectionHeader($title)
-    {
-        if (!empty($title)) {
-            echo "\n".'<legend>';
-            echo $this->_encodeTitle ? htmlspecialchars($title) : $title;
-            echo '</legend>'."\n";
-        }
-    }
-
-}
diff --git a/framework/Horde_Form/lib/Horde/Form/Type.php b/framework/Horde_Form/lib/Horde/Form/Type.php
deleted file mode 100644 (file)
index 707bb71..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-<?php
-/**
- * Horde_Form_Type Class
- *
- * @author  Robert E. Coyle <robertecoyle@hotmail.com>
- * @package Horde_Form
- */
-abstract class Horde_Form_Type {
-
-    protected $_properties = array();
-
-    /**
-     * Type constructor. Takes a hash of key/value parameters.
-     *
-     * @param array $properties Any type properties to initialize.
-     */
-    public function __construct($properties = array())
-    {
-        $this->_properties = array();
-        $vars = array_keys(get_object_vars($this));
-        foreach ($vars as $var) {
-            $this->_properties[] = substr($var, 1);
-        }
-
-        if ($this->_properties && $properties) {
-            $properties = array_combine(array_slice($this->_properties, 0, count($properties)), $properties);
-            foreach ($properties as $property => $value) {
-                $this->__set($property, $value);
-            }
-        }
-    }
-
-    /**
-     */
-    abstract public function isValid($var, $vars, $value, &$message);
-
-    /**
-     */
-    function getInfo($vars, $var, &$info)
-    {
-        $info = $var->getValue($vars);
-    }
-
-    /**
-     */
-    public function onSubmit()
-    {
-    }
-
-    /**
-     * To get the 'escape' property of a type:
-     *   $escape = $type->escape;
-     * If the property is not set this will return null.
-     *
-     * @param string $property The property to retrieve.
-     */
-    protected function __get($property)
-    {
-        if (in_array($property, $this->_properties)) {
-            $prop = '_' . $property;
-            return $this->$prop;
-        }
-
-        return null;
-    }
-
-    /**
-     * To set the 'escape' property of a type to true:
-     *   $type->escape = true;
-     *
-     * @param string $property The property name to set.
-     * @param mixed $value The property value.
-     */
-    protected function __set($property, $value)
-    {
-        if (in_array($property, $this->_properties)) {
-            $prop = '_' . $property;
-            $this->$prop = $value;
-        }
-    }
-
-    /**
-     * To check if a type has a property named 'escape':
-     *  if (isset($type->escape)) { ... }
-     *
-     * @param string $property Property name to check existance of.
-     */
-    protected function __isset($property)
-    {
-        $prop = '_' . $property;
-        return isset($this->$prop);
-    }
-
-    /**
-     * To unset a Type property named 'escape':
-     *   unset($type->escape);
-     *
-     * @param string $property Property name to unset.
-     */
-    protected function __unset($property)
-    {
-        $prop = '_' . $property;
-        unset($this->$prop);
-    }
-
-}
diff --git a/framework/Horde_Form/lib/Horde/Form/Type/Boolean.php b/framework/Horde_Form/lib/Horde/Form/Type/Boolean.php
deleted file mode 100644 (file)
index 9ae0e86..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-<?php
-/**
- * An on/off value
- */
-class Horde_Form_Type_Boolean extends Horde_Form_Type {
-
-    public function isValid($var, $vars, $value, &$message)
-    {
-        return true;
-    }
-
-    public function getInfo($vars, $var, &$info)
-    {
-        $info = String::lower($vars->get($var->name)) == 'on';
-    }
-
-}
diff --git a/framework/Horde_Form/lib/Horde/Form/Type/Color.php b/framework/Horde_Form/lib/Horde/Form/Type/Color.php
deleted file mode 100644 (file)
index 383a16a..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-/**
- * Color
- */
-class Horde_Form_Type_Color extends Horde_Form_Type {
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        if ($var->required && empty($value)) {
-            $message = _("This field is required.");
-            return false;
-        }
-
-        if (empty($value) || preg_match('/^#([0-9a-z]){6}$/i', $value)) {
-            return true;
-        }
-
-        $message = _("This field must contain a color code in the RGB Hex format, for example '#1234af'.");
-        return false;
-    }
-
-}
diff --git a/framework/Horde_Form/lib/Horde/Form/Type/CreditCard.php b/framework/Horde_Form/lib/Horde/Form/Type/CreditCard.php
deleted file mode 100644 (file)
index 294834c..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-<?php
-/**
- * Credit card number
- */
-class Horde_Form_Type_creditcard extends Horde_Form_Type {
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        if (empty($value) && $var->required) {
-            $message = _("This field is required.");
-            return false;
-        }
-
-        if (!empty($value)) {
-            /* getCardType() will also verify the checksum. */
-            $type = $this->getCardType($value);
-            if ($type === false || $type == 'unknown') {
-                $message = _("This does not seem to be a valid card number.");
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    function getChecksum($ccnum)
-    {
-        $len = strlen($ccnum);
-        if (!is_long($len / 2)) {
-            $weight = 2;
-            $digit = $ccnum[0];
-        } elseif (is_long($len / 2)) {
-            $weight = 1;
-            $digit = $ccnum[0] * 2;
-        }
-        if ($digit > 9) {
-            $digit = $digit - 9;
-        }
-        $i = 1;
-        $checksum = $digit;
-        while ($i < $len) {
-            if ($ccnum[$i] != ' ') {
-                $digit = $ccnum[$i] * $weight;
-                $weight = ($weight == 1) ? 2 : 1;
-                if ($digit > 9) {
-                    $digit = $digit - 9;
-                }
-                $checksum += $digit;
-            }
-            $i++;
-        }
-
-        return $checksum;
-    }
-
-    function getCardType($ccnum)
-    {
-        $sum = $this->getChecksum($ccnum);
-        $l = strlen($ccnum);
-
-        // Screen checksum.
-        if (($sum % 10) != 0) {
-            return false;
-        }
-
-        // Check for Visa.
-        if ((($l == 16) || ($l == 13)) &&
-            ($ccnum[0] == 4)) {
-            return 'visa';
-        }
-
-        // Check for MasterCard.
-        if (($l == 16) &&
-            ($ccnum[0] == 5) &&
-            ($ccnum[1] >= 1) &&
-            ($ccnum[1] <= 5)) {
-            return 'mastercard';
-        }
-
-        // Check for Amex.
-        if (($l == 15) &&
-            ($ccnum[0] == 3) &&
-            (($ccnum[1] == 4) || ($ccnum[1] == 7))) {
-            return 'amex';
-        }
-
-        // Check for Discover (Novus).
-        if (strlen($ccnum) == 16 &&
-            substr($ccnum, 0, 4) == '6011') {
-            return 'discover';
-        }
-
-        // If we got this far, then no card matched.
-        return 'unknown';
-    }
-
-}
diff --git a/framework/Horde_Form/lib/Horde/Form/Type/Date.php b/framework/Horde_Form/lib/Horde/Form/Type/Date.php
deleted file mode 100644 (file)
index c4415e7..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-<?php
-/**
- * Date
- */
-class Horde_Form_Type_Date extends Horde_Form_Type {
-
-    var $_format = '%a %d %B';
-
-    public function isValid($var, $vars, $value, &$message)
-    {
-        $valid = true;
-
-        if ($var->required) {
-            $valid = strlen(trim($value)) > 0;
-
-            if (!$valid) {
-                $message = _("This field is required.");
-            }
-        }
-
-        return $valid;
-    }
-
-    public static function getAgo($timestamp)
-    {
-        if ($timestamp === null) {
-            return '';
-        }
-
-        $diffdays = Date_Calc::dateDiff(date('j', $timestamp),
-                                        date('n', $timestamp),
-                                        date('Y', $timestamp),
-                                        date('j'), date('n'), date('Y'));
-
-        /* An error occured. */
-        if ($diffdays == -1) {
-            return;
-        }
-
-        $ago = $diffdays * Date_Calc::compareDates(date('j', $timestamp),
-                                                   date('n', $timestamp),
-                                                   date('Y', $timestamp),
-                                                   date('j'), date('n'),
-                                                   date('Y'));
-        if ($ago < -1) {
-            return sprintf(_(" (%s days ago)"), $diffdays);
-        } elseif ($ago == -1) {
-            return _(" (yesterday)");
-        } elseif ($ago == 0) {
-            return _(" (today)");
-        } elseif ($ago == 1) {
-            return _(" (tomorrow)");
-        } else {
-            return sprintf(_(" (in %s days)"), $diffdays);
-        }
-    }
-
-    public function getFormattedTime($timestamp, $format = null, $showago = true)
-    {
-        if (empty($format)) {
-            $format = $this->_format;
-        }
-        if (!empty($timestamp)) {
-            return strftime($format, $timestamp) . ($showago ? self::getAgo($timestamp) : '');
-        } else {
-            return '';
-        }
-    }
-
-}
diff --git a/framework/Horde_Form/lib/Horde/Form/Type/DateTime.php b/framework/Horde_Form/lib/Horde/Form/Type/DateTime.php
deleted file mode 100644 (file)
index 98adaa0..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-<?php
-/**
- * Date and time selection
- */
-class Horde_Form_Type_DateTime extends Horde_Form_Type {
-
-    var $_date;
-    var $_time;
-
-    /**
-     * Return the date supplied as a Horde_Date object.
-     *
-     * @param integer $start_year  The first available year for input.
-     * @param integer $end_year    The last available year for input.
-     * @param boolean $picker      Do we show the DHTML calendar?
-     * @param integer $format_in   The format to use when sending the date
-     *                             for storage. Defaults to Unix epoch.
-     *                             Similar to the strftime() function.
-     * @param integer $format_out  The format to use when displaying the
-     *                             date. Similar to the strftime() function.
-     * @param boolean $show_seconds Include a form input for seconds.
-     */
-    function init($start_year = '', $end_year = '', $picker = true,
-                  $format_in = null, $format_out = '%x', $show_seconds = false)
-    {
-        $this->_date = new Horde_Form_Type_Date();
-        $this->_date->init($start_year, $end_year, $picker, $format_in, $format_out);
-
-        $this->_time = new Horde_Form_Type_Time();
-        $this->_time->init($show_seconds);
-    }
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        if ($var->required) {
-            return $this->_date->isValid($var, $vars, $value, $message) &&
-                $this->_time->isValid($var, $vars, $value, $message);
-        }
-        return true;
-    }
-
-    function getInfo(&$vars, &$var, &$info)
-    {
-        /* If any component is empty consider it a bad date and return the
-         * default. */
-        $value = $var->getValue($vars);
-        if ($this->emptyDateArray($value) == 1 || $this->emptyTimeArray($value)) {
-            $info = $var->getDefault();
-            return;
-        }
-
-        $date = $this->getDateOb($value);
-        $time = $this->getTimeOb($value);
-        $date->hour = $time->hour;
-        $date->min = $time->min;
-        $date->sec = $time->sec;
-        if (is_null($this->format_in)) {
-            $info = $date->timestamp();
-        } else {
-            $info = $date->strftime($this->format_in);
-        }
-    }
-
-    function __get($property)
-    {
-        if ($property == 'show_seconds') {
-            return $this->_time->$property;
-        } else {
-            return $this->_date->$property;
-        }
-    }
-
-    function __set($property, $value)
-    {
-        if ($property == 'show_seconds') {
-            $this->_time->$property = $value;
-        } else {
-            $this->_date->$property = $value;
-        }
-    }
-
-    function checktime($hour, $minute, $second)
-    {
-        return $this->_time->checktime($hour, $minute, $second);
-    }
-
-    function getTimeOb($time_in)
-    {
-        return $this->_time->getTimeOb($time_in);
-    }
-
-    function getTimeParts($time_in)
-    {
-        return $this->_time->getTimeParts($time_in);
-    }
-
-    function emptyTimeArray($time)
-    {
-        return $this->_time->emptyTimeArray($time);
-    }
-
-    function emptyDateArray($date)
-    {
-        return $this->_date->emptyDateArray($date);
-    }
-
-    function getDateParts($date_in)
-    {
-        return $this->_date->getDateParts($date_in);
-    }
-
-    function getDateOb($date_in)
-    {
-        return $this->_date->getDateOb($date_in);
-    }
-
-    function formatDate($date)
-    {
-        if ($date === null) {
-            return '';
-        }
-        return $this->_date->formatDate($date);
-    }
-
-}
diff --git a/framework/Horde_Form/lib/Horde/Form/Type/Email.php b/framework/Horde_Form/lib/Horde/Form/Type/Email.php
deleted file mode 100644 (file)
index 96cf756..0000000
+++ /dev/null
@@ -1,452 +0,0 @@
-<?php
-/**
- * Email
- */
-class Horde_Form_Type_Email extends Horde_Form_Type {
-
-    /**
-     * Allow multiple addresses?
-     *
-     * @type boolean
-     * @var boolean
-    */
-    var $_allow_multi = false;
-
-    /**
-     * Strip domain from the address?
-     *
-     * @type boolean
-     * @var boolean
-     */
-    var $_strip_domain = false;
-
-    /**
-     * Make displayed email addresses clickable?
-     *
-     * @type boolean
-     * @var boolean
-     */
-    var $_link_compose = false;
-
-    /**
-     * The compose name to use
-     *
-     * @type text
-     * @var boolean
-     */
-    var $_link_name;
-
-    /**
-     * The character to separate multiple email addresses
-     *
-     * @type text
-     * @var string
-     */
-    var $_delimiters = ',';
-
-    /**
-     * Contact the target mail server to see if the email address is deliverable?
-     *
-     * @type boolean
-     * @var boolean
-     */
-    var $_check_smtp = false;
-
-    /**
-     */
-    public function init($allow_multi = false, $strip_domain = false,
-                  $link_compose = false, $link_name = null,
-                  $delimiters = ',')
-    {
-        $this->_allow_multi = $allow_multi;
-        $this->_strip_domain = $strip_domain;
-        $this->_link_compose = $link_compose;
-        $this->_link_name = $link_name;
-        $this->_delimiters = $delimiters;
-    }
-
-    /**
-     */
-    public function isValid($var, $vars, $value, &$message)
-    {
-        // Split into individual addresses.
-        $emails = $this->splitEmailAddresses($value);
-
-        // Check for too many.
-        if (!$this->_allow_multi && count($emails) > 1) {
-            $message = _("Only one email address is allowed.");
-            return false;
-        }
-
-        // Check for all valid and at least one non-empty.
-        $nonEmpty = 0;
-        foreach ($emails as $email) {
-            if (!strlen($email)) {
-                continue;
-            }
-            if (!$this->validateEmailAddress($email)) {
-                $message = sprintf(_("\"%s\" is not a valid email address."), $email);
-                return false;
-            }
-            ++$nonEmpty;
-        }
-
-        if (!$nonEmpty && $var->required) {
-            if ($this->_allow_multi) {
-                $message = _("You must enter at least one email address.");
-            } else {
-                $message = _("You must enter an email address.");
-            }
-            return false;
-        }
-
-        return true;
-    }
-
-    /**
-     * Explodes an RFC 2822 string, ignoring a delimiter if preceded
-     * by a "\" character, or if the delimiter is inside single or
-     * double quotes.
-     *
-     * @param string $string     The RFC 822 string.
-     *
-     * @return array  The exploded string in an array.
-     */
-    public function splitEmailAddresses($string)
-    {
-        $quotes = array('"', "'");
-        $emails = array();
-        $pos = 0;
-        $in_quote = null;
-        $in_group = false;
-        $prev = null;
-
-        if (!strlen($string)) {
-            return array();
-        }
-
-        $char = $string[0];
-        if (in_array($char, $quotes)) {
-            $in_quote = $char;
-        } elseif ($char == ':') {
-            $in_group = true;
-        } elseif (strpos($this->_delimiters, $char) !== false) {
-            $emails[] = '';
-            $pos = 1;
-        }
-
-        for ($i = 1, $iMax = strlen($string); $i < $iMax; ++$i) {
-            $char = $string[$i];
-            if (in_array($char, $quotes)) {
-                if ($prev !== '\\') {
-                    if ($in_quote === $char) {
-                        $in_quote = null;
-                    } elseif (is_null($in_quote)) {
-                        $in_quote = $char;
-                    }
-                }
-            } elseif ($in_group) {
-                if ($char == ';') {
-                    $emails[] = substr($string, $pos, $i - $pos + 1);
-                    $pos = $i + 1;
-                    $in_group = false;
-                }
-            } elseif ($char == ':') {
-                $in_group = true;
-            } elseif (strpos($this->_delimiters, $char) !== false &&
-                      $prev !== '\\' &&
-                      is_null($in_quote)) {
-                $emails[] = substr($string, $pos, $i - $pos);
-                $pos = $i + 1;
-            }
-            $prev = $char;
-        }
-
-        if ($pos != $i) {
-            /* The string ended without a delimiter. */
-            $emails[] = substr($string, $pos, $i - $pos);
-        }
-
-        return $emails;
-    }
-
-    /**
-     * RFC(2)822 Email Parser.
-     *
-     * By Cal Henderson <cal@iamcal.com>
-     * This code is licensed under a Creative Commons Attribution-ShareAlike 2.5 License
-     * http://creativecommons.org/licenses/by-sa/2.5/
-     *
-     * http://code.iamcal.com/php/rfc822/
-     *
-     * http://iamcal.com/publish/articles/php/parsing_email
-     *
-     * Revision 4
-     *
-     * @param string $email An individual email address to validate.
-     *
-     * @return boolean
-     */
-    public function validateEmailAddress($email)
-    {
-        static $comment_regexp, $email_regexp;
-        if ($comment_regexp === null) {
-            $this->_defineValidationRegexps($comment_regexp, $email_regexp);
-        }
-
-        // We need to strip comments first (repeat until we can't find
-        // any more).
-        while (true) {
-            $new = preg_replace("!$comment_regexp!", '', $email);
-            if (strlen($new) == strlen($email)){
-                break;
-            }
-            $email = $new;
-        }
-
-        // Now match what's left.
-        $result = (bool)preg_match("!^$email_regexp$!", $email);
-        if ($result && $this->_check_smtp) {
-            $result = $this->validateEmailAddressSmtp($email);
-        }
-
-        return $result;
-    }
-
-    /**
-     * Attempt partial delivery of mail to an address to validate it.
-     *
-     * @param string $email An individual email address to validate.
-     *
-     * @return boolean
-     */
-    public function validateEmailAddressSmtp($email)
-    {
-        list(, $maildomain) = explode('@', $email, 2);
-
-        // Try to get the real mailserver from MX records.
-        if (function_exists('getmxrr') &&
-            @getmxrr($maildomain, $mxhosts, $mxpriorities)) {
-            // MX record found.
-            array_multisort($mxpriorities, $mxhosts);
-            $mailhost = $mxhosts[0];
-        } else {
-            // No MX record found, try the root domain as the mail
-            // server.
-            $mailhost = $maildomain;
-        }
-
-        $fp = @fsockopen($mailhost, 25, $errno, $errstr, 5);
-        if (!$fp) {
-            return false;
-        }
-
-        // Read initial response.
-        fgets($fp, 4096);
-
-        // HELO
-        fputs($fp, "HELO $mailhost\r\n");
-        fgets($fp, 4096);
-
-        // MAIL FROM
-        fputs($fp, "MAIL FROM: <root@example.com>\r\n");
-        fgets($fp, 4096);
-
-        // RCPT TO - gets the result we want.
-        fputs($fp, "RCPT TO: <$email>\r\n");
-        $result = trim(fgets($fp, 4096));
-
-        // QUIT
-        fputs($fp, "QUIT\r\n");
-        fgets($fp, 4096);
-        fclose($fp);
-
-        return substr($result, 0, 1) == '2';
-    }
-
-    /**
-     * RFC(2)822 Email Parser.
-     *
-     * By Cal Henderson <cal@iamcal.com>
-     * This code is licensed under a Creative Commons Attribution-ShareAlike 2.5 License
-     * http://creativecommons.org/licenses/by-sa/2.5/
-     *
-     * http://code.iamcal.com/php/rfc822/
-     *
-     * http://iamcal.com/publish/articles/php/parsing_email
-     *
-     * Revision 4
-     *
-     * @param string &$comment The regexp for comments.
-     * @param string &$addr_spec The regexp for email addresses.
-     */
-    protected function _defineValidationRegexps(&$comment, &$addr_spec)
-    {
-        /**
-         * NO-WS-CTL       =       %d1-8 /         ; US-ASCII control characters
-         *                         %d11 /          ;  that do not include the
-         *                         %d12 /          ;  carriage return, line feed,
-         *                         %d14-31 /       ;  and white space characters
-         *                         %d127
-         * ALPHA          =  %x41-5A / %x61-7A   ; A-Z / a-z
-         * DIGIT          =  %x30-39
-         */
-        $no_ws_ctl  = "[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]";
-        $alpha      = "[\\x41-\\x5a\\x61-\\x7a]";
-        $digit      = "[\\x30-\\x39]";
-        $cr         = "\\x0d";
-        $lf         = "\\x0a";
-        $crlf       = "($cr$lf)";
-
-        /**
-         * obs-char        =       %d0-9 / %d11 /          ; %d0-127 except CR and
-         *                         %d12 / %d14-127         ;  LF
-         * obs-text        =       *LF *CR *(obs-char *LF *CR)
-         * text            =       %d1-9 /         ; Characters excluding CR and LF
-         *                         %d11 /
-         *                         %d12 /
-         *                         %d14-127 /
-         *                         obs-text
-         * obs-qp          =       "\" (%d0-127)
-         * quoted-pair     =       ("\" text) / obs-qp
-         */
-        $obs_char       = "[\\x00-\\x09\\x0b\\x0c\\x0e-\\x7f]";
-        $obs_text       = "($lf*$cr*($obs_char$lf*$cr*)*)";
-        $text           = "([\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f]|$obs_text)";
-        $obs_qp         = "(\\x5c[\\x00-\\x7f])";
-        $quoted_pair    = "(\\x5c$text|$obs_qp)";
-
-        /**
-         * obs-FWS         =       1*WSP *(CRLF 1*WSP)
-         * FWS             =       ([*WSP CRLF] 1*WSP) /   ; Folding white space
-         *                         obs-FWS
-         * ctext           =       NO-WS-CTL /     ; Non white space controls
-         *                         %d33-39 /       ; The rest of the US-ASCII
-         *                         %d42-91 /       ;  characters not including "(",
-         *                         %d93-126        ;  ")", or "\"
-         * ccontent        =       ctext / quoted-pair / comment
-         * comment         =       "(" *([FWS] ccontent) [FWS] ")"
-         * CFWS            =       *([FWS] comment) (([FWS] comment) / FWS)
-         *
-         * @note: We translate ccontent only partially to avoid an
-         * infinite loop. Instead, we'll recursively strip comments
-         * before processing the input.
-         */
-        $wsp        = "[\\x20\\x09]";
-        $obs_fws    = "($wsp+($crlf$wsp+)*)";
-        $fws        = "((($wsp*$crlf)?$wsp+)|$obs_fws)";
-        $ctext      = "($no_ws_ctl|[\\x21-\\x27\\x2A-\\x5b\\x5d-\\x7e])";
-        $ccontent   = "($ctext|$quoted_pair)";
-        $comment    = "(\\x28($fws?$ccontent)*$fws?\\x29)";
-        $cfws       = "(($fws?$comment)*($fws?$comment|$fws))";
-        $cfws       = "$fws*";
-
-        /**
-         * atext           =       ALPHA / DIGIT / ; Any character except controls,
-         *                         "!" / "#" /     ;  SP, and specials.
-         *                         "$" / "%" /     ;  Used for atoms
-         *                         "&" / "'" /
-         *                         "*" / "+" /
-         *                         "-" / "/" /
-         *                         "=" / "?" /
-         *                         "^" / "_" /
-         *                         "`" / "{" /
-         *                         "|" / "}" /
-         *                         "~"
-         * atom            =       [CFWS] 1*atext [CFWS]
-         */
-        $atext      = "($alpha|$digit|[\\x21\\x23-\\x27\\x2a\\x2b\\x2d\\x2e\\x3d\\x3f\\x5e\\x5f\\x60\\x7b-\\x7e])";
-        $atom       = "($cfws?$atext+$cfws?)";
-
-        /**
-         * qtext           =       NO-WS-CTL /     ; Non white space controls
-         *                         %d33 /          ; The rest of the US-ASCII
-         *                         %d35-91 /       ;  characters not including "\"
-         *                         %d93-126        ;  or the quote character
-         * qcontent        =       qtext / quoted-pair
-         * quoted-string   =       [CFWS]
-         *                         DQUOTE *([FWS] qcontent) [FWS] DQUOTE
-         *                         [CFWS]
-         * word            =       atom / quoted-string
-         */
-        $qtext      = "($no_ws_ctl|[\\x21\\x23-\\x5b\\x5d-\\x7e])";
-        $qcontent   = "($qtext|$quoted_pair)";
-        $quoted_string  = "($cfws?\\x22($fws?$qcontent)*$fws?\\x22$cfws?)";
-        $word       = "($atom|$quoted_string)";
-
-        /**
-         * obs-local-part  =       word *("." word)
-         * obs-domain      =       atom *("." atom)
-         */
-        $obs_local_part = "($word(\\x2e$word)*)";
-        $obs_domain = "($atom(\\x2e$atom)*)";
-
-        /**
-         * dot-atom-text   =       1*atext *("." 1*atext)
-         * dot-atom        =       [CFWS] dot-atom-text [CFWS]
-         */
-        $dot_atom_text  = "($atext+(\\x2e$atext+)*)";
-        $dot_atom   = "($cfws?$dot_atom_text$cfws?)";
-
-        /**
-         * domain-literal  =       [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS]
-         * dcontent        =       dtext / quoted-pair
-         * dtext           =       NO-WS-CTL /     ; Non white space controls
-         *
-         *                         %d33-90 /       ; The rest of the US-ASCII
-         *                         %d94-126        ;  characters not including "[",
-         *                                         ;  "]", or "\"
-         */
-        $dtext      = "($no_ws_ctl|[\\x21-\\x5a\\x5e-\\x7e])";
-        $dcontent   = "($dtext|$quoted_pair)";
-        $domain_literal = "($cfws?\\x5b($fws?$dcontent)*$fws?\\x5d$cfws?)";
-
-        /**
-         * local-part      =       dot-atom / quoted-string / obs-local-part
-         * domain          =       dot-atom / domain-literal / obs-domain
-         * addr-spec       =       local-part "@" domain
-         */
-        $local_part = "($dot_atom|$quoted_string|$obs_local_part)";
-        $domain     = "($dot_atom|$domain_literal|$obs_domain)";
-        $addr_spec  = "($local_part\\x40$domain)";
-    }
-
-}
-
-/**
- * Email with confirmation
- */
-class Horde_Form_Type_EmailConfirm extends Horde_Form_Type {
-
-    public function isValid($var, $vars, $value, &$message)
-    {
-        if ($var->required && empty($value['original'])) {
-            $message = _("This field is required.");
-            return false;
-        }
-
-        if ($value['original'] != $value['confirm']) {
-            $message = _("Email addresses must match.");
-            return false;
-        } else {
-            require_once 'Horde/MIME.php';
-            $parsed_email = MIME::parseAddressList($value['original'], false,
-                                                   true);
-            if (is_a($parsed_email, 'PEAR_Error')) {
-                $message = $parsed_email->getMessage();
-                return false;
-            }
-            if (count($parsed_email) > 1) {
-                $message = _("Only one email address allowed.");
-                return false;
-            }
-            if (empty($parsed_email[0]->mailbox)) {
-                $message = _("You did not enter a valid email address.");
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-}
diff --git a/framework/Horde_Form/lib/Horde/Form/Type/Enum.php b/framework/Horde_Form/lib/Horde/Form/Type/Enum.php
deleted file mode 100644 (file)
index c5e638e..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-<?php
-/**
- * Choose one from a list of values
- */
-class Horde_Form_Type_Enum extends Horde_Form_Type {
-
-    /**
-     * List of values to choose from
-     *
-     * @type stringlist
-     * @var array
-     */
-    protected $_values = array();
-
-    /**
-     * Initial prompt value, if any
-     *
-     * @type text
-     * @var string
-     */
-    protected $_prompt;
-
-    /**
-     */
-    public function isValid($var, $vars, $value, &$message)
-    {
-        if ($var->required && $value == '' && !isset($this->_values[$value])) {
-            $message = _("This field is required.");
-            return false;
-        }
-
-        if (count($this->_values) == 0 || isset($this->_values[$value]) ||
-            ($this->_prompt && empty($value))) {
-            return true;
-        }
-
-        $message = _("Invalid data.");
-        return false;
-    }
-
-}
diff --git a/framework/Horde_Form/lib/Horde/Form/Type/Int.php b/framework/Horde_Form/lib/Horde/Form/Type/Int.php
deleted file mode 100644 (file)
index 31375a7..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-/**
- * Integer
- */
-class Horde_Form_Type_Int extends Horde_Form_Type {
-
-    public function isValid($var, $vars, $value, &$message)
-    {
-        if ($var->required && empty($value) && ((string)(int)$value !== $value)) {
-            $message = _("This field is required.");
-            return false;
-        }
-
-        if (empty($value) || preg_match('/^[0-9]+$/', $value)) {
-            return true;
-        }
-
-        $message = _("This field may only contain integers.");
-        return false;
-    }
-
-}
diff --git a/framework/Horde_Form/lib/Horde/Form/Type/Invalid.php b/framework/Horde_Form/lib/Horde/Form/Type/Invalid.php
deleted file mode 100644 (file)
index 20ee3c3..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-<?php
-class Horde_Form_Type_invalid extends Horde_Form_Type {
-
-    var $message;
-
-    function init($message)
-    {
-        $this->message = $message;
-    }
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        return false;
-    }
-
-}
diff --git a/framework/Horde_Form/lib/Horde/Form/Type/Number.php b/framework/Horde_Form/lib/Horde/Form/Type/Number.php
deleted file mode 100644 (file)
index 6b58fd6..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-<?php
-/**
- * Number
- */
-class Horde_Form_Type_Number extends Horde_Form_Type {
-
-    /**
-     */
-    protected $_fraction;
-
-    public function isValid($var, $vars, $value, &$message)
-    {
-        if ($var->required && empty($value) && ((string)(double)$value !== $value)) {
-            $message = _("This field is required.");
-            return false;
-        } elseif (empty($value)) {
-            return true;
-        }
-
-        /* If matched, then this is a correct numeric value. */
-        if (preg_match($this->_getValidationPattern(), $value)) {
-            return true;
-        }
-
-        $message = _("This field must be a valid number.");
-        return false;
-    }
-
-    /**
-     */
-    public function getInfo($vars, $var, &$info)
-    {
-        $value = $vars->get($var->name);
-        $linfo = NLS::getLocaleInfo();
-        $value = str_replace($linfo['mon_thousands_sep'], '', $value);
-        $info = str_replace($linfo['mon_decimal_point'], '.', $value);
-    }
-
-    /**
-     */
-    protected function _getValidationPattern()
-    {
-        static $pattern = '';
-        if (!empty($pattern)) {
-            return $pattern;
-        }
-
-        /* Get current locale information. */
-        $linfo = NLS::getLocaleInfo();
-
-        /* Build the pattern. */
-        $pattern = '(-)?';
-
-        /* Only check thousands separators if locale has any. */
-        if (!empty($linfo['mon_thousands_sep'])) {
-            /* Regex to check for correct thousands separators (if any). */
-            $pattern .= '((\d+)|((\d{0,3}?)([' . $linfo['mon_thousands_sep'] . ']\d{3})*?))';
-        } else {
-            /* No locale thousands separator, check for only digits. */
-            $pattern .= '(\d+)';
-        }
-        /* If no decimal point specified default to dot. */
-        if (empty($linfo['mon_decimal_point'])) {
-            $linfo['mon_decimal_point'] = '.';
-        }
-        /* Regex to check for correct decimals (if any). */
-        if (empty($this->_fraction)) {
-            $fraction = '*';
-        } else {
-            $fraction = '{0,' . $this->_fraction . '}';
-        }
-        $pattern .= '([' . $linfo['mon_decimal_point'] . '](\d' . $fraction . '))?';
-
-        /* Put together the whole regex pattern. */
-        $pattern = '/^' . $pattern . '$/';
-
-        return $pattern;
-    }
-
-}
diff --git a/framework/Horde_Form/lib/Horde/Form/Type/Octal.php b/framework/Horde_Form/lib/Horde/Form/Type/Octal.php
deleted file mode 100644 (file)
index 111cdb7..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-/**
- * Octal
- */
-class Horde_Form_Type_Octal extends Horde_Form_Type {
-
-    function isValid($var, $vars, $value, &$message)
-    {
-        if ($var->required && empty($value) && ((string)(int)$value !== $value)) {
-            $message = _("This field is required.");
-            return false;
-        }
-
-        if (empty($value) || preg_match('/^[0-7]+$/', $value)) {
-            return true;
-        }
-
-        $message = _("This field may only contain octal values.");
-        return false;
-    }
-
-}
diff --git a/framework/Horde_Form/lib/Horde/Form/Type/Password.php b/framework/Horde_Form/lib/Horde/Form/Type/Password.php
deleted file mode 100644 (file)
index a47a178..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-<?php
-/**
- * Password
- */
-class Horde_Form_Type_Password extends Horde_Form_Type {
-
-    public function isValid($var, $vars, $value, &$message)
-    {
-        $valid = true;
-
-        if ($var->required) {
-            $valid = strlen(trim($value)) > 0;
-
-            if (!$valid) {
-                $message = _("This field is required.");
-            }
-        }
-
-        return $valid;
-    }
-
-}
-
-
-/**
- * Password with confirmation
- */
-class Horde_Form_Type_passwordConfirm extends Horde_Form_Type {
-
-    public function isValid($var, $vars, $value, &$message)
-    {
-        if ($var->required && empty($value['original'])) {
-            $message = _("This field is required.");
-            return false;
-        }
-
-        if ($value['original'] != $value['confirm']) {
-            $message = _("Passwords must match.");
-            return false;
-        }
-
-        return true;
-    }
-
-    function getInfo($vars, $var, &$info)
-    {
-        $value = $vars->get($var->name);
-        $info = $value['original'];
-    }
-
-}
diff --git a/framework/Horde_Form/lib/Horde/Form/Type/Phone.php b/framework/Horde_Form/lib/Horde/Form/Type/Phone.php
deleted file mode 100644 (file)
index 488fa57..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<?php
-/**
- * Phone number
- */
-class Horde_Form_Type_Phone extends Horde_Form_Type {
-
-    public function isValid($var, $vars, $value, &$message)
-    {
-        $valid = true;
-
-        if ($var->required) {
-            $valid = strlen(trim($value)) > 0;
-            if (!$valid) {
-                $message = _("This field is required.");
-            }
-        } else {
-            $valid = preg_match('/^\+?[\d()\-\/ ]*$/', $value);
-            if (!$valid) {
-                $message = _("You must enter a valid phone number, digits only with an optional '+' for the international dialing prefix.");
-            }
-        }
-
-        return $valid;
-    }
-
-}
diff --git a/framework/Horde_Form/lib/Horde/Form/Type/Phone/Mobile.php b/framework/Horde_Form/lib/Horde/Form/Type/Phone/Mobile.php
deleted file mode 100644 (file)
index 5c97969..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<?php
-/**
- * Mobile Phone Number
- */
-class Horde_Form_Type_Phone_Mobile extends Horde_Form_Type_Phone {}
diff --git a/framework/Horde_Form/lib/Horde/Form/Type/Set.php b/framework/Horde_Form/lib/Horde/Form/Type/Set.php
deleted file mode 100644 (file)
index db805ad..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-<?php
-/**
- * Set of values
- */
-class Horde_Form_Type_Set extends Horde_Form_Type {
-
-    /**
-     * Values
-     *
-     * @type stringlist
-     * @var string
-     */
-    protected $_values;
-
-    public function isValid($var, $vars, $value, &$message)
-    {
-        if (count($this->_values) == 0 || count($value) == 0) {
-            return true;
-        }
-        foreach ($value as $item) {
-            if (!isset($this->_values[$item])) {
-                $error = true;
-                break;
-            }
-        }
-        if (!isset($error)) {
-            return true;
-        }
-
-        $message = _("Invalid data.");
-        return false;
-    }
-
-}
-
-class Horde_Form_Type_multienum extends Horde_Form_Type_enum {
-
-    public function isValid($var, $vars, $value, &$message)
-    {
-        if (is_array($value)) {
-            foreach ($value as $val) {
-                if (!$this->isValid($var, $vars, $val, $message)) {
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        if (empty($value) && ((string)(int)$value !== $value)) {
-            if ($var->required) {
-                $message = _("This field is required.");
-                return false;
-            } else {
-                return true;
-            }
-        }
-
-        if (count($this->_values) == 0 || isset($this->_values[$value])) {
-            return true;
-        }
-
-        $message = _("Invalid data.");
-        return false;
-    }
-
-}
-
-class Horde_Form_Type_keyval_multienum extends Horde_Form_Type_multienum {
-
-    function getInfo($vars, $var, &$info)
-    {
-        $value = $vars->get($var->name);
-        $info = array();
-        foreach ($value as $key) {
-            $info[$key] = $this->_values[$key];
-        }
-    }
-
-}
diff --git a/framework/Horde_Form/lib/Horde/Form/Type/String.php b/framework/Horde_Form/lib/Horde/Form/Type/String.php
deleted file mode 100644 (file)
index 276f541..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-<?php
-/**
- * String
- */
-class Horde_Form_Type_String extends Horde_Form_Type {
-
-    /**
-     * Validation regex
-     *
-     * @type string
-     * @var string
-     */
-    protected $_regex;
-
-    /**
-     * Maximum length
-     *
-     * @type int
-     * @var integer
-     */
-    protected $_maxlength;
-
-    public function isValid($var, $vars, $value, &$message)
-    {
-        $valid = true;
-
-        if (!empty($this->_maxlength) && String::length($value) > $this->_maxlength) {
-            $valid = false;
-            $message = sprintf(_("Value is over the maximum length of %s."), $this->_maxlength);
-        } elseif ($var->required && empty($this->_regex)) {
-            if (!($valid = strlen(trim($value)) > 0)) {
-                $message = _("This field is required.");
-            }
-        } elseif (strlen($this->_regex)) {
-            if (!($valid = preg_match($this->_regex, $value))) {
-                $message = _("You must enter a valid value.");
-            }
-        }
-
-        return $valid;
-    }
-
-}
diff --git a/framework/Horde_Form/lib/Horde/Form/Type/Time.php b/framework/Horde_Form/lib/Horde/Form/Type/Time.php
deleted file mode 100644 (file)
index 3b26133..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-/**
- * Time
- */
-class Horde_Form_Type_Time extends Horde_Form_Type {
-
-    public function isValid($var, $vars, $value, &$message)
-    {
-        if ($var->required && empty($value) && ((string)(double)$value !== $value)) {
-            $message = _("This field is required.");
-            return false;
-        }
-
-        if (empty($value) || preg_match('/^[0-2]?[0-9]:[0-5][0-9]$/', $value)) {
-            return true;
-        }
-
-        $message = _("This field may only contain numbers and the colon.");
-        return false;
-    }
-
-}
diff --git a/framework/Horde_Form/lib/Horde/Form/VarRenderer.php b/framework/Horde_Form/lib/Horde/Form/VarRenderer.php
deleted file mode 100644 (file)
index b9d3761..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-<?php
-/**
- * The Horde_Form_VarRenderer:: class provides base functionality for
- * other Horde_Form elements.
- *
- * $Horde: incubator/Horde_Form/Horde/Form/VarRenderer.php,v 1.8 2008/01/02 11:12:48 jan Exp $
- *
- * Copyright 2003-2008 The Horde Project (http://www.horde.org/)
- * Copyright 2005-2007 Matt Warden <mwarden@gmail.com>
- *
- * See the enclosed file LICENSE for license information (LGPL).
- *
- * @author  Jason M. Felice <jason.m.felice@gmail.com>
- * @package Horde_Form
- */
-class Horde_Form_VarRenderer {
-
-    /**
-     * Renders a variable.
-     *
-     * @param Horde_Form $form            Reference to a Horde_Form instance,
-     *                                    or null if none is available.
-     * @param Horde_Form_Variable $var    Reference to a Horde_Form_Variable.
-     * @param Variables $vars             A Variables instance.
-     * @param boolean $isInput            Whether this is an input field.
-     */
-    public function render($form, $var, $vars, $isInput = false)
-    {
-        if ($isInput) {
-            $state = 'Input';
-        } else {
-            $state = 'Display';
-        }
-        $method = "_renderVar${state}_" . str_replace('Horde_Form_Type_', '', get_class($var->type));
-        if (!method_exists($this, $method)) {
-            $method = "_renderVar${state}Default";
-        }
-        return $this->$method($form, $var, $vars);
-    }
-
-    /**
-     * Finishes rendering after all fields are output.
-     */
-    public function renderEnd()
-    {
-        return '';
-    }
-
-}
diff --git a/framework/Horde_Form/lib/Horde/Form/VarRenderer/Xhtml.php b/framework/Horde_Form/lib/Horde/Form/VarRenderer/Xhtml.php
deleted file mode 100644 (file)
index 5dfbc51..0000000
+++ /dev/null
@@ -1,1541 +0,0 @@
-<?php
-/**
- * The Horde_Form_VarRenderer_Xhtml:: class renders variables as Xhtml.
- *
- * $Horde: incubator/Horde_Form/Horde/Form/VarRenderer/Xhtml.php,v 1.14 2008/01/02 11:12:48 jan Exp $
- *
- * Copyright 2003-2008 The Horde Project (http://www.horde.org/)
- * Copyright 2005 Matt Warden <mwarden@gmail.com>
- *
- * See the enclosed file LICENSE for license information (LGPL).
- *
- * @author  Jason M. Felice <jason.m.felice@gmail.com>
- * @package Horde_Form
- */
-class Horde_Form_VarRenderer_Xhtml extends Horde_Form_VarRenderer {
-
-    protected $_onLoadJS = array();
-
-    /**
-     * Handles the end of rendering of variables; writes onload JavaScript.
-     *
-     * @access public
-     * @author ?
-     * @return string the javascript to execute, and its container script tags,
-     *            or and empty string if there is nothing to execute onload
-     */
-    public function renderEnd()
-    {
-        if (count($this->_onLoadJS)) {
-            return "<script type=\"text/javascript\">" .
-                "<!--\n" .  implode("\n", $this->_onLoadJS) . "\n// -->\n" .
-                "</script>";
-        } else {
-            return '';
-        }
-    }
-
-    function _renderVarInputDefault($form, $var, $vars)
-    {
-        throw new Horde_Form_Exception('Unknown variable type:' . get_class($var->type));
-    }
-
-    function _renderVarInput_number($form, $var, $vars)
-    {
-        $value = $var->getValue($vars);
-        if ($var->type->fraction) {
-            $value = sprintf('%01.' . $var->type->fraction . 'f', $value);
-        }
-        $linfo = NLS::getLocaleInfo();
-        /* Only if there is a mon_decimal_point do the
-         * substitution. */
-        if (!empty($linfo['mon_decimal_point'])) {
-            $value = str_replace('.', $linfo['mon_decimal_point'], $value);
-        }
-        return sprintf('    <input type="text" class="form-input-number" name="%1$s" id="%1$s" value="%2$s"%3$s />',
-                       $var->getVarName(),
-                       $value,
-                       $this->_getActionScripts($form, $var)
-               );
-    }
-
-    function _renderVarInput_int($form, $var, $vars)
-    {
-        return sprintf('    <input type="text" class="form-input-int" name="%1$s" id="%1$s" value="%2$s"%3$s />',
-                       $var->getVarName(),
-                       $value = $var->getValue($vars),
-                       $this->_getActionScripts($form, $var)
-               );
-    }
-
-    function _renderVarInput_octal($form, $var, $vars)
-    {
-        return sprintf('<input type="text" class="form-input-octal" name="%1$s" id="%1$s" value="%2$s"%3$s />',
-                       $var->getVarName(),
-                       sprintf('0%o', octdec($var->getValue($vars))),
-                       $this->_getActionScripts($form, $var)
-               );
-    }
-
-    function _renderVarInput_intlist($form, $var, $vars)
-    {
-        return sprintf('<input type="text" class="form-input-intlist" name="%1$s" id="%1$s" value="%2$s"%3$s />',
-                       $var->getVarName(),
-                       $value = $var->getValue($vars),
-                       $this->_getActionScripts($form, $var)
-               );
-    }
-
-    function _renderVarInput_text($form, $var, $vars)
-    {
-        return sprintf(
-            '<input type="text" class="form-input-text%1$s" name="%2$s" '
-            . 'id="%2$s" value="%3$s"%4$s%5$s%6$s />',
-            ($var->isDisabled() ? ' form-input-disabled" ' : ''),
-            $var->getVarName(),
-            htmlspecialchars($var->getValue($vars), ENT_QUOTES, NLS::getCharset()),
-            ($var->isDisabled() ? ' disabled="disabled" ' : ''),
-            ($var->type->maxlength ? ' maxlength="' . $var->type->maxlength . '"' : ''),
-            $this->_getActionScripts($form, $var)
-        );
-    }
-
-    function _renderVarInput_stringlist($form, $var, $vars)
-    {
-        return sprintf(
-            '<input type="text" class="form-input-stringlist" name="%s" value="%s"%s />',
-            $var->getVarName(),
-            $value = $var->getValue($vars),
-            $this->_getActionScripts($form, $var)
-        );
-    }
-
-    function _renderVarInput_phone($form, $var, $vars)
-    {
-        return sprintf(
-            '<input type="text" class="form-input-phone" name="%1$s" id="%1$s" value="%2$s" %3$s%4$s />',
-            $var->getVarName(),
-            htmlspecialchars($var->getValue($vars), ENT_QUOTES, NLS::getCharset()),
-            ($var->isDisabled() ? ' disabled="disabled" ' : ''),
-            $this->_getActionScripts($form, $var)
-        );
-    }
-
-    function _renderVarInput_cellphone($form, $var, $vars)
-    {
-        return $this->_renderVarInput_phone($form, $var, $vars);
-    }
-
-    function _renderVarInput_ipaddress($form, $var, $vars)
-    {
-        return sprintf('    <input type="text" class="form-input-ipaddress" name="%1$s" id="%1$s" value="%2$s" %3$s%4$s />',
-                       $var->getVarName(),
-                       htmlspecialchars($var->getValue($vars), ENT_QUOTES, NLS::getCharset()),
-                       $var->isDisabled() ? ' disabled="disabled" ' : '',
-                       $this->_getActionScripts($form, $var)
-               );
-    }
-
-    function _renderVarInput_file($form, $var, $vars)
-    {
-        $file = $var->getValue($vars);
-        return sprintf('    <input type="file" class="form-input-file" name="%1$s" id="%1$s"%2$s />',
-                       $var->getVarName(),
-                       $this->_getActionScripts($form, $var));
-    }
-
-    /**
-     * @todo Show image dimensions in the width/height boxes.
-     */
-    function _renderVarInput_image($form, $var, $vars)
-    {
-        $varname = $var->getVarName();
-        $image = $var->getValue($vars);
-
-        /* Check if existing image data is being loaded. */
-        $var->type->loadImageData($image);
-
-        Horde::addScriptFile('image.js', 'horde', true);
-        $graphics_dir = $GLOBALS['registry']->getImageDir('horde');
-        $img_dir = $graphics_dir . '/image';
-
-        $html = '';
-
-        /* Check if there is existing img information stored. */
-        if (isset($image['img'])) {
-            /* Hidden tag to store the preview image filename. */
-            $html = sprintf('    <input type="hidden" name="%1$s" id="%1$s" value="%2$s" />',
-                   $varname . '[img]',
-                   htmlspecialchars($image['img'], ENT_QUOTES, NLS::getCharset()));
-
-            /* Unserialize the img information to get the full array. */
-            $image['img'] = @unserialize($image['img']);
-        }
-
-        /* Output the input tag. */
-        if (empty($image['img'])) {
-            $js = "
-var p = document.getElementById('" . $varname . "[preview]');
-o = '\\\\'; a = '/';
-tmp = '' + document.getElementById('" . $varname . "[new]').value;
-if (tmp) {
-    while (tmp.indexOf(o) > -1) {
-        pos = tmp.indexOf(o);
-        tmp = '' + (tmp.substring(0, pos) + a + tmp.substring((pos + o.length), tmp.length));
-    }
-    p.src = 'file:///' + tmp;
-    p.alt = '" . addslashes(_("If you see this message but no image, the image you want to upload can't be displayed by your browser.")) . "';
-}";
-            $browser = Browser::singleton();
-            if ($browser->isBrowser('msie')) {
-                $html .= sprintf('    <input type="file" class="form-input-file" name="%1$s" id="%1$s" onchange="%2$s" />',
-                             $varname . '[new]',
-                             $js);
-            } else {
-                $html .= sprintf('    <input type="file" class="form-input-file" name="%1$s" id="%1$s"
-                        onclick="window.setTimeout(\'document.getElementById(\\\'%1$s\\\').blur();\', 5);"
-                        onblur="%2$s" />',
-                             $varname . '[new]',
-                             $js);
-            }
-        } else {
-            $html .= sprintf('    <input type="file" class="form-input-file" name="%1$s" id="%1$s" />',
-                             $varname . '[new]');
-        }
-
-        /* Output the button to upload/reset the image. */
-        if ($var->type->show_upload) {
-            $html .= '&nbsp;';
-            $html .= sprintf('    <input class="form-button-upload" name="%1$s" id="%1$s" type="submit" value="%2$s" /> ',
-                         '_do_' . $varname,
-                         _("Upload"));
-        }
-
-        if (empty($image['img'])) {
-            /* No image information stored yet, show a blank
-             * preview. */
-            $html .= Horde::img('tree/blank.png', _("Preview"),
-                    'class="form-image-preview-blank" id="' . $varname . '[preview]"',
-                    $graphics_dir);
-        } else {
-            /* Image information stored, show preview, add buttons for
-             * image manipulation. */
-            $html .= '<br />';
-            $img = Horde::url($GLOBALS['registry']->get('webroot', 'horde') . '/services/images/view.php');
-            if (isset($image['img']['vfs_id'])) {
-                /* Calling an image from VFS. */
-                $img = Util::addParameter($img, array('f' => $image['img']['vfs_id'],
-                                                      's' => 'vfs',
-                                                      'p' => $image['img']['vfs_path']));
-            } else {
-                /* Calling an image from a tmp directory (uploads). */
-                $img = Util::addParameter($img, 'f', $image['img']['file']);
-            }
-
-            // TODO: possible to change to unobtrusive JS?
-            /* Rotate 270. */
-            $html .= Horde::link('#', '', '', '', 'showImage(\'' . Util::addParameter($img, array('a' => 'rotate', 'v' => '270')) . '\', \'_p_' . $varname . '\', true);') . Horde::img('rotate-270.png', _("Rotate Left"), '', $img_dir) . '</a>';
-
-            /* Rotate 180. */
-            $html .= Horde::link('#', '', '', '', 'showImage(\'' . Util::addParameter($img, array('a' => 'rotate', 'v' => '180')) . '\', \'_p_' . $varname . '\', true);') . Horde::img('rotate-180.png', _("Rotate 180"), '', $img_dir) . '</a>';
-
-            /* Rotate 90. */
-            $html .= Horde::link('#', '', '', '', 'showImage(\'' . Util::addParameter($img, array('a' => 'rotate', 'v' => '90')) . '\', \'_p_' . $varname . '\', true);') . Horde::img('rotate-90.png', _("Rotate Right"), '', $img_dir) . '</a>';
-
-            /* Flip image. */
-            $html .= Horde::link('#', '', '', '', 'showImage(\'' . Util::addParameter($img, 'a', 'flip') . '\', \'_p_' . $varname . '\', true);') . Horde::img('flip.png', _("Flip"), '', $img_dir) . '</a>';
-
-            /* Mirror image. */
-            $html .= Horde::link('#', '', '', '', 'showImage(\'' . Util::addParameter($img, 'a', 'mirror') . '\', \'_p_' . $varname . '\', true);') . Horde::img('mirror.png', _("Mirror"), '', $img_dir) . '</a>';
-
-            /* Apply grayscale. */
-            $html .= Horde::link('#', '', '', '', 'showImage(\'' . Util::addParameter($img, 'a', 'grayscale') . '\', \'_p_' . $varname . '\', true);') . Horde::img('grayscale.png', _("Grayscale"), '', $img_dir) . '</a>';
-
-            /* Resize width. */
-            $html .= sprintf('%s    <input type="text" class="form-input-resize" onchange="src=getResizeSrc(\'%s\', \'%s\');showImage(src, \'_p_%s\', true);" %s />',
-                   _("w:"),
-                   Util::addParameter($img, 'a', 'resize'),
-                   $varname,
-                   $varname,
-                   '_w_'. $varname);
-
-            /* Resize height. */
-            $html .= sprintf('%s    <input type="text" class="form-input-resize" onchange="src=getResizeSrc(\'%s\', \'%s\');showImage(src, \'_p_%s\', true);" %s />',
-                   _("h:"),
-                   Util::addParameter($img, 'a', 'resize'),
-                   $varname,
-                   $varname,
-                   '_h_'. $varname);
-
-            /* Apply fixed ratio resize. */
-            $html .= Horde::link('#', '', '', '', 'src=getResizeSrc(\'' . Util::addParameter($img, 'a', 'resize') . '\', \'' . $varname . '\', \'1\');showImage(src, \'_p_' . $varname . '\', true);') . Horde::img('ratio.png', _("Fix ratio"), '', $img_dir) . '</a>';
-
-            /* Keep also original if it has been requested. */
-            if ($var->type->show_keeporig) {
-                $html .= sprintf('    <input type="checkbox" class="form-input-checkbox" name="%s"%s />%s' . "\n",
-                       $varname . '[keep_orig]',
-                       !empty($image['keep_orig']) ? ' checked="checked"' : '',
-                       _("Keep original?"));
-            }
-
-            /* The preview image element. */
-            $html .= '<br /><img src="' . $img . '" id="_p_' . $varname .'" />'."\n";
-        }
-
-        return $html;
-    }
-
-    function _renderVarInput_longtext($form, $var, $vars)
-    {
-        global $browser;
-
-        $html = sprintf('<textarea class="form-input-longtext" id="%1$s" name="%1$s" '
-                            .'cols="%2$s" rows="%3$s"%4$s%5$s>%6$s</textarea>',
-                        $var->getVarName(),
-                        $var->type->cols,
-                        $var->type->rows,
-                        $this->_getActionScripts($form, $var),
-                        $var->isDisabled() ? ' disabled="disabled"' : '',
-                        htmlspecialchars($var->getValue($vars)));
-
-        if ($var->type->hasHelper('rte') && $browser->hasFeature('rte')) {
-            $editor = Horde_Editor::factory('xinha', array('id' => $var->getVarName()));
-        }
-
-        if ($var->type->hasHelper() && $browser->hasFeature('javascript')) {
-            $html .= '<div class="form-html-helper">';
-            Horde::addScriptFile('open_html_helper.js', 'horde');
-            $imgId = $var->getVarName() . 'ehelper';
-            if ($var->type->hasHelper('emoticons')) {
-                $html .= Horde::link('#', _("Emoticons"), '', '', 'openHtmlHelper(\'emoticons\', \'' . $var->getVarName() . '\'); return false;')
-                    . Horde::img('smile.png', _("Emoticons"), 'id="' . $imgId . '" align="middle"', $GLOBALS['registry']->getImageDir('horde') . '/emoticons')
-                    . '</a>'."\n";
-            }
-            $html .= '</div><div id="htmlhelper_' . $var->getVarName()
-                    . '" class="form-control"></div>'."\n";
-        }
-
-        return $html;
-    }
-
-    function _renderVarInput_countedtext($form, $var, $vars)
-    {
-        return sprintf('<textarea class="form-input-countedtext" id="%1$s" name="%1$s" '
-                        .'cols="%2$s" rows="%3$s"%4$s%5$s>%6$s</textarea>',
-                       $var->getVarName(),
-                       $var->type->cols,
-                       $var->type->rows,
-                       $this->_getActionScripts($form, $var),
-                       $var->isDisabled() ? ' disabled="disabled"' : '',
-                       $var->getValue($vars));
-    }
-
-    function _renderVarInput_address($form, $var, $vars)
-    {
-        return sprintf('<textarea class="form-input-address" id="%1$s" name="%1$s" '
-                        .'cols="%2$s" rows="%3$s"%4$s%5$s>%6$s</textarea>',
-                       $var->getVarName(),
-                       $var->type->cols,
-                       $var->type->rows,
-                       $this->_getActionScripts($form, $var),
-                       $var->isDisabled() ? ' disabled="disabled"' : '',
-                       $var->getValue($vars));
-    }
-
-    function _renderVarInput_date($form, $var, $vars)
-    {
-        return sprintf('    <input type="text" class="form-input-date" name="%1$s" id="%1$s" '
-                            .'value="%2$s"%3$s />',
-                        $var->getVarName(),
-                        $value = $var->getValue($vars),
-                        $this->_getActionScripts($form, $var));
-    }
-
-    function _renderVarInput_time($form, $var, $vars)
-    {
-        return sprintf('    <input type="text" class="form-input-time" name="%1$s" id="%1$s" '
-                            .'value="%2$s"%3$s />',
-                       $var->getVarName(),
-                       $value = $var->getValue($vars),
-                       $this->_getActionScripts($form, $var));
-    }
-
-    function _renderVarInput_hourminutesecond($form, $var, $vars)
-    {
-        $varname = $var->getVarName();
-        $time = $var->type->getTimeParts($var->getValue($vars));
-
-        /* Output hours. */
-        $hours = array('' => _("hh"));
-        for ($i = 0; $i <= 23; $i++) {
-            $hours[sprintf('%02d', $i)] = $i;
-        }
-        $html = sprintf('<select name="%1$s[hour]" id="%1$s[hour]"%2$s>%3$s    </select>',
-                        $varname,
-                        $this->_selectOptions($hours, $time['hour']),
-                        $this->_getActionScripts($form, $var));
-
-        /* Output minutes. */
-        $minutes = array('' => _("mm"));
-        for ($i = 0; $i <= 59; $i++) {
-            $minutes[sprintf('%02d', $i)] = $i;
-        }
-        $html .= sprintf('<select name="%1$s[minute]" id="%1$s[minute]"%2$s>%3$s    </select>',
-                         $varname,
-                         $this->_selectOptions($minutes, $time['minute']),
-                         $this->_getActionScripts($form, $var));
-
-        /* Return if seconds are not required. */
-        if ($var->type->show_seconds) {
-            /* Output seconds. */
-            $seconds = array('' => _("ss"));
-            for ($i = 0; $i <= 59; $i++) {
-                $seconds[sprintf('%02d', $i)] = $i;
-            }
-            $html .= sprintf('<select name="%1$s[second]" id="%1$s[second]"%2$s>%3$s    </select>',
-                            $varname,
-                            $this->_getActionScripts($form, $var),
-                            $this->_selectOptions($seconds, $time['second']));
-        }
-
-        return $html;
-    }
-
-    function _renderVarInput_monthyear($form, $var, $vars)
-    {
-        $dates = array();
-        $dates['month'] = array('' => _("MM"),
-                                1 => _("January"),
-                                2 => _("February"),
-                                3 => _("March"),
-                                4 => _("April"),
-                                5 => _("May"),
-                                6 => _("June"),
-                                7 => _("July"),
-                                8 => _("August"),
-                                9 => _("September"),
-                                10 => _("October"),
-                                11 => _("November"),
-                                12 => _("December"));
-        $dates['year'] = array('' => _("YYYY"));
-        if ($var->type->start_year > $var->type->end_year) {
-            for ($i = $var->type->start_year; $i >= $var->type->end_year; $i--) {
-                $dates['year'][$i] = $i;
-            }
-        } else {
-            for ($i = $var->type->start_year; $i <= $var->type->end_year; $i++) {
-                $dates['year'][$i] = $i;
-            }
-        }
-        $html = sprintf('<select name="%1$s" id="%1$s"%2$s>%3$s    </select>',
-               $var->type->getMonthVar($var),
-               $this->_getActionScripts($form, $var),
-               $this->_selectOptions($dates['month'], $vars->get($var->type->getMonthVar($var))));
-
-        $html .= sprintf('<select name="%1$s" id="%1$s"%2$s>%3$s    </select>',
-               $var->type->getYearVar($var),
-               $this->_getActionScripts($form, $var),
-               $this->_selectOptions($dates['year'], $vars->get($var->type->getYearVar($var))));
-
-        return $html;
-    }
-
-    function _renderVarInput_monthdayyear($form, $var, $vars)
-    {
-        $dates = array();
-        $dates['month'] = array(''   => _("MM"),
-                                '1'  => _("January"),
-                                '2'  => _("February"),
-                                '3'  => _("March"),
-                                '4'  => _("April"),
-                                '5'  => _("May"),
-                                '6'  => _("June"),
-                                '7'  => _("July"),
-                                '8'  => _("August"),
-                                '9'  => _("September"),
-                                '10' => _("October"),
-                                '11' => _("November"),
-                                '12' => _("December"));
-        $dates['day'] = array('' => _("DD"));
-        for ($i = 1; $i <= 31; $i++) {
-            $dates['day'][$i] = $i;
-        }
-        $dates['year'] = array('' => _("YYYY"));
-        if ($var->type->start_year > $var->type->end_year) {
-            for ($i = $var->type->start_year; $i >= $var->type->end_year; $i--) {
-                $dates['year'][$i] = $i;
-            }
-        } else {
-            for ($i = $var->type->start_year; $i <= $var->type->end_year; $i++) {
-                $dates['year'][$i] = $i;
-            }
-        }
-        $date = $var->type->getDateParts($var->getValue($vars));
-
-        // TODO: use NLS to get the order right for the Rest Of The
-        // World.
-        $html = '';
-        $date_parts = array('month', 'day', 'year');
-        foreach ($date_parts as $part) {
-            $varname = $var->getVarName() . '[' . $part . ']';
-            $html .= sprintf('<select name="%1$s" id="%1$s"%2$s>%3$s    </select>',
-                             $varname,
-                             $this->_getActionScripts($form, $var),
-                             $this->_selectOptions($dates[$part], $date[$part]));
-        }
-
-        if ($var->type->picker && $GLOBALS['browser']->hasFeature('javascript')) {
-            Horde::addScriptFile('open_calendar.js', 'horde');
-            $imgId = $var->getVarName() .'goto';
-            $html .= '<div id="goto"></div>';
-            $html .= Horde::link('#', _("Select a date"), '', '', 'openCalendar(\'' . $imgId . '\', \'' . $var->getVarName() . '\'); return false;')
-                . Horde::img('calendar.png', _("Calendar"), 'id="' . $imgId . '" ', $GLOBALS['registry']->getImageDir('horde'))
-                . '</a>';
-        }
-
-        return $html;
-    }
-
-    function _renderVarInput_datetime($form, $var, $vars)
-    {
-        return parent::_renderVarInput_monthdayyear($form, $var, $vars) .
-            parent::_renderVarInput_hourminutesecond($form, $var, $vars);
-    }
-
-    function _renderVarInput_colorpicker($form, $var, $vars)
-    {
-        $html = '<div class="form-colorpicker">'
-            . '<input type="text" maxlength="7" name="'
-            . $var->getVarName() . '" id="' . $var->getVarName()
-            . '" value="' . $var->getValue($vars) . '" />';
-
-        if ($GLOBALS['browser']->hasFeature('javascript')) {
-            Horde::addScriptFile('open_colorpicker.js', 'horde', true);
-            $html .= Horde::img('blank.gif', '', array('class' => 'form-colorpicker-preview',
-                                                       'id' => 'colordemo_' . $var->getVarName(),
-                                                       'style' => 'background:' . $var->getValue($vars)), $GLOBALS['registry']->getImageDir('horde'))
-                . Horde::link('#', _("Color Picker"), '', '', 'openColorPicker(\''. $var->getVarName() .'\'); return false;')
-                . Horde::img('colorpicker.png', _("Color Picker"), '', $GLOBALS['registry']->getImageDir('horde')) . '</a>'
-                . '<div id="colorpicker_' . $var->getVarName() . '" class="form-colorpicker-palette"></div>';
-        }
-
-        return $html . '</div>';
-    }
-
-    function _renderVarInput_sorter($form, $var, $vars)
-    {
-        global $registry;
-
-        $varname = $var->getVarName();
-        $instance = $var->type->instance;
-
-        Horde::addScriptFile('sorter.js', 'horde', true);
-
-        return '    <input type="hidden" name="'. $varname
-            . '[array]" value="" id="'. $varname .'-array-" />'."\n"
-            . '    <select class="leftFloat" multiple="multiple" size="'
-            . $var->type->size . '" name="' . $varname
-            . '[list]" onchange="' . $instance . '.deselectHeader();" '
-            . ' id="'. $varname . '-list-">'
-            . $var->type->getOptions($var->getValue($vars)) . '    </select><div class="leftFloat">'
-            . Horde::link('#', _("Move up"), '', '', $instance . '.moveColumnUp(); return false;')
-                . Horde::img('nav/up.png', _("Move up"), '', $registry->getImageDir('horde'))
-                . '</a><br />'
-            . Horde::link('#', _("Move up"), '', '', $instance . '.moveColumnDown(); return false;')
-                . Horde::img('nav/down.png', _("Move down"), '', $registry->getImageDir('horde'))
-                . '</a></div>'
-            . '<script type="text/javascript">' . "\n"
-            . sprintf('%1$s = new Horde_Form_Sorter(\'%1$s\', \'%2$s\', \'%3$s\');' . "\n",
-                    $instance, $varname, $var->type->header)
-            . sprintf("%s.setHidden();\n</script>\n", $instance);
-    }
-
-    function _renderVarInput_assign($form, $var, $vars)
-    {
-        global $registry;
-
-        Horde::addScriptFile('form_assign.js', 'horde', true);
-
-        $name = $var->getVarName();
-        $fname = $form->getName() . '.' . $name;
-        $width = $var->type->width;
-        $lhdr = (bool)$var->type->getHeader(0);
-        $rhdr = (bool)$var->type->getHeader(1);
-        $this->_onLoadJS[] = 'Horde_Form_Assign.setField(\'' . $fname . '\');';
-
-        $html = '<div class="form-input-assign">'
-             . '    <input type="hidden" name="' . $name . '__values" id="' . $name . '__values" />'
-             . sprintf('    <select name="%1$s__left" id="%1$s__left" multiple="multiple" '
-                         .'size="%2$d" style="width:%3$s"%4$s>',
-                     $name, $var->type->size, $width,
-                     $lhdr ? ' onchange="Horde_Form_Assign.deselectHeaders(\'' . $fname . '\', 0);"' : '')
-             . $var->type->getOptions(0, $fname)
-             . '    </select>'
-             . '<div><a href="" onclick="Horde_Form_Assign.move(\''. $fname .'\', 0); return false;">'
-             . Horde::img('rhand.png', _("Add column"), null, $registry->getImageDir('horde'))
-             . '</a><br /><a href="" onclick="Horde_Form_Assign.move(\''
-             . $fname . '\', 1); return false;">'
-             . Horde::img('lhand.png', _("Remove column"), null, $registry->getImageDir('horde'))
-             . '</a></div>'
-             . sprintf('    <select name="%s__right" multiple="multiple" size="%d" style="width:%s"%s>',
-                     $name, $size, $width,
-                     $rhdr ? ' onchange="Horde_Form_Assign.deselectHeaders(\'' . $fname . '\', 1);"' : '')
-             . $var->type->getOptions(1, $fname)
-             . '    </select></div>';
-
-        return $html;
-    }
-
-    function _renderVarInput_invalid($form, $var, $vars)
-    {
-        return $this->_renderVarDisplay_invalid($form, $var, $vars);
-    }
-
-    function _renderVarInput_enum($form, $var, $vars)
-    {
-        $values = $var->getValues();
-        $prompt = $var->type->prompt;
-        $htmlchars = $var->getOption('htmlchars');
-        if ($prompt) {
-            $prompt = '<option value="">' . ($htmlchars ? htmlspecialchars($prompt, ENT_QUOTES, NLS::getCharset()) : $prompt) . '</option>';
-        }
-        return sprintf('    <select name="%1$s" id="%1$s"%2$s>%3$s%4$s    </select>',
-               $var->getVarName(),
-               $this->_getActionScripts($form, $var),
-               $prompt,
-               $this->_selectOptions($values, $var->getValue($vars), $htmlchars));
-    }
-
-    function _renderVarInput_mlenum($form, $var, $vars)
-    {
-        $varname = $var->getVarName();
-        $values = $var->getValues();
-        $prompts = $var->type->prompts;
-        $selected = $var->getValue($vars);
-
-        /* If passing a non-array value need to get the keys. */
-        if (!is_array($selected)) {
-            foreach ($values as $key_1 => $values_2) {
-                if (isset($values_2[$selected])) {
-                    $selected = array('1' => $key_1, '2' => $selected);
-                    break;
-                }
-            }
-        }
-
-        /* Hidden tag to store the current first level. */
-        $html = sprintf('    <input type="hidden" name="%1$s[old]" id="%1$s[old]" value="%2$s" />',
-                        $varname,
-                        htmlspecialchars($selected['1'], ENT_QUOTES, NLS::getCharset()));
-
-        /* First level. */
-        $values_1 = Horde_Array::valuesToKeys(array_keys($values));
-        $html .= sprintf('    <select id="%1$s[1]" name="%1$s[1]" onchange="%2$s"%3$s>',
-                         $varname,
-                         'if (this.value) { document.' . $form->getName() . '.formname.value=\'\';' . 'document.' . $form->getName() . '.submit() }',
-                         ($var->hasAction() ? ' ' . $this->_genActionScript($form, $var->_action, $varname) : ''));
-        if (!empty($prompts)) {
-            $html .= '<option value="">' . htmlspecialchars($prompts[0], ENT_QUOTES, NLS::getCharset()) . '</option>';
-        }
-        $html .= $this->_selectOptions($values_1, $selected['1']);
-        $html .= '    </select>';
-
-        /* Second level. */
-        $html .= sprintf('    <select id="%1$s[2]" name="%1$s[2]"%2$s>',
-                         $varname,
-                         ($var->hasAction() ? ' ' . $this->_genActionScript($form, $var->_action, $varname) : ''));
-        if (!empty($prompts)) {
-            $html .= '<option value="">' . htmlspecialchars($prompts[1], ENT_QUOTES, NLS::getCharset()) . '</option>';
-        }
-        $values_2 = array();
-        if (!empty($selected['1'])) {
-            $values_2 = $values[$selected['1']];
-        }
-        return $html . $this->_selectOptions($values_2, $selected['2']) . '    </select>';
-    }
-
-    function _renderVarInput_multienum($form, $var, $vars)
-    {
-        $values = $var->getValues();
-        $selected = $vars->getExists($var->getVarName(), $wasset);
-        if (!$wasset) {
-            $selected = $var->getDefault();
-        }
-        $html = sprintf('    <select multiple="multiple" size="%1$s" name="%2$s[]" id="%2$s[]" %3$s>%4$s    </select>',
-                        $var->type->size,
-                        $var->getVarName(),
-                        $this->_getActionScripts($form, $var),
-                        $this->_multiSelectOptions($values, $selected));
-        return $html . '<p class="form-hint">'
-            . _("To select multiple items, hold down the Control (PC) or Command (Mac) key while clicking.")
-            . "</p>\n";
-    }
-
-    function _renderVarInput_keyval_multienum($form, $var, $vars)
-    {
-        return $this->_renderVarInput_multienum($form, $var, $vars);
-    }
-
-    function _renderVarInput_radio($form, $var, $vars)
-    {
-        return $this->_radioButtons($var->getVarName(),
-                                    $var->getValues(),
-                                    $var->getValue($vars),
-                                    $this->_getActionScripts($form, $var));
-    }
-
-    function _renderVarInput_set($form, $var, $vars)
-    {
-        $html = $this->_checkBoxes($var->getVarName(),
-                                   $var->getValues(),
-                                   $var->getValue($vars),
-                                   $this->_getActionScripts($form, $var));
-
-        if ($var->type->checkAll) {
-            $form_name = $form->getName();
-            $var_name = $var->getVarName() . '[]';
-            $function_name = 'select'  . $form_name . $var->getVarName();
-            $enable = _("Select all");
-            $disable = _("Select none");
-            $invert = _("Invert selection");
-            $html .= <<<EOT
-<script type="text/javascript">
-function $function_name()
-{
-    for (var i = 0; i < document.$form_name.elements.length; i++) {
-        f = document.$form_name.elements[i];
-        if (f.name != '$var_name') {
-            continue;
-        }
-        if (arguments.length) {
-            f.checked = arguments[0];
-        } else {
-            f.checked = !f.checked;
-        }
-    }
-}
-</script>
-<a href="#" onclick="$function_name(true); return false;">$enable</a>,
-<a href="#" onclick="$function_name(false); return false;">$disable</a>,
-<a href="#" onclick="$function_name(); return false;">$invert</a>
-EOT;
-        }
-
-        return $html;
-    }
-
-    function _renderVarInput_link($form, $var, $vars)
-    {
-        return $this->_renderVarDisplay_link($form, $var, $vars);
-    }
-
-    function _renderVarInput_html($form, $var, $vars)
-    {
-        return $this->_renderVarDisplay_html($form, $var, $vars);
-    }
-
-    function _renderVarInput_email($form, $var, $vars)
-    {
-        return sprintf('    <input type="text" id="%1$s" name="%1$s" value="%2$s"%3$s />',
-               $var->getVarName(),
-               $value = $var->getValue($vars),
-               $this->_getActionScripts($form, $var));
-    }
-
-    function _renderVarInput_matrix($form, $var, $vars)
-    {
-        $varname   = $var->getVarName();
-        $var_array = $var->getValue($vars);
-        $cols      = $var->type->cols;
-        $rows      = $var->type->rows;
-        $matrix    = $var->type->matrix;
-        $new_input = $var->type->new_input;
-
-        $html = '<table cellspacing="0"><tr>';
-
-        $html .= '<td align="right" width="20%"></td>';
-        foreach ($cols as $col_title) {
-            $html .= sprintf('<td align="center" width="1%%">%s</td>', $col_title);
-        }
-        $html .= '<td align="right" width="60%"></td></tr>';
-
-        /* Offer a new row of data to be added to the matrix? */
-        if ($new_input) {
-            $html .= '<tr><td>'."\n";
-            if (is_array($new_input)) {
-                $html .= sprintf('    <select%s name="%s[n][r]"><option value="">%s</option>%s    </select><br />'."\n",
-                       ' id="'. $varname .'-n--r-"',
-                       $varname,
-                       _("-- select --"),
-                       $this->_selectOptions($new_input, $var_array['n']['r']));
-            } elseif ($new_input == true) {
-                $html .= sprintf('    <input%s type="text" name="%s[n][r]" value="%s" />',
-                       ' id="'. $varname .'-n--r-',
-                       $varname,
-                       $var_array['n']['r']);
-            }
-            $html .= ' </td>';
-            foreach ($cols as $col_id => $col_title) {
-                $html .= sprintf('<td align="center"><input type="checkbox" class="checkbox" name="%s[n][v][%s]" /></td>', $varname, $col_id);
-            }
-            $html .= '<td>&nbsp;</td></tr>'."\n";
-        }
-
-        /* Loop through the rows and create checkboxes for each column. */
-        foreach ($rows as $row_id => $row_title) {
-            $html .= sprintf('<tr><td>%s</td>', $row_title);
-            foreach ($cols as $col_id => $col_title) {
-                $html .= sprintf('<td align="center"><input type="checkbox" class="checkbox" name="%s[r][%s][%s]"%s /></td>', $varname, $row_id, $col_id, (!empty($matrix[$row_id][$col_id]) ? ' checked="checked"' : ''));
-            }
-            $html .= '<td>&nbsp;</td></tr>'."\n";
-        }
-
-        $html .= '</table>'."\n";
-        return $html;
-    }
-
-    function _renderVarInput_password($form, $var, $vars)
-    {
-        return sprintf('<input type="password" id="%1$s" name="%1$s" value="%2$s"%3$s />',
-               $var->getVarName(),
-               $value = $var->getValue($vars),
-               $this->_getActionScripts($form, $var));
-    }
-
-    function _renderVarInput_emailconfirm($form, $var, $vars)
-    {
-        $email = $var->getValue($vars);
-        return '<ul><li>' . sprintf('<input type="text" class="form-input-emailconfirm"' .
-                                    ' id="%1$s" name="%1$s[original]" value="%2$s"%3$s />',
-                                    $var->getVarName(),
-                                    $value = $email['original'],
-                                    $this->_getActionScripts($form, $var)) . '</li><li>' .
-            sprintf('<input type="text" class="form-input-emailconfirm"' .
-                    ' id="%1$s-confirm-" name="%1$s[confirm]" value="%2$s"%3$s />',
-                    $var->getVarName(),
-                    $value = $email['confirm'],
-                    $this->_getActionScripts($form, $var)) . '</li></ul>';
-    }
-
-    function _renderVarInput_passwordconfirm($form, $var, $vars)
-    {
-        $password = $var->getValue($vars);
-        return '<ul><li>' . sprintf('<input type="password" class="form-input-passwordconfirm"'
-                                    .' id="%1$s" name="%1$s[original]" value="%2$s"%3$s />',
-                                    $var->getVarName(),
-                                    $value = $password['original'],
-                                    $this->_getActionScripts($form, $var)) . '</li><li>' .
-            sprintf('<input type="password" class="form-input-passwordconfirm"'
-                    .' id="%1$s-confirm-" name="%1$s[confirm]" value="%2$s"%3$s />',
-                    $var->getVarName(),
-                    $value = $password['confirm'],
-                    $this->_getActionScripts($form, $var)) . '</li></ul>';
-    }
-
-    function _renderVarInput_boolean($form, $var, $vars)
-    {
-        $varName = $var->getVarName();
-
-        $html = '    <input type="checkbox" class="form-input-checkbox" id="' .  $varName . '"'
-            .  ' name="' .  $varName . '"'
-            . ($var->getValue($vars) ? ' checked="checked"' : '');
-        if ($var->hasAction()) {
-            $html .= $this->_genActionScript($form, $var->_action,
-                                             $var->getVarName());
-        }
-        $html .= ' />';
-        return $html;
-    }
-
-    function _renderVarInput_creditcard($form, $var, $vars)
-    {
-        $varName = $var->getVarName();
-
-        $html = '    <input type="text" class="form-input-creditcard" id="' .  $varName . '"'
-            .  ' name="' .  $varName . '"'
-            .$var->getValue($vars);
-        if ($var->hasAction()) {
-            $html .= $this->_genActionScript($form, $var->_action,
-                                             $var->getVarName());
-        }
-
-        return $html . ' />';
-    }
-
-    function _renderVarInput_obrowser($form, $var, $vars)
-    {
-        $varname = $var->getVarName();
-        $varvalue = $vars->get($varname);
-        $fieldId = 'obrowser_' . md5(uniqid(rand(), true));
-        $html = '
-            <script type="text/javascript">
-            var obrowserWindowName;
-            function obrowserCallback(name, oid)
-            {
-                if (name == obrowserWindowName) {
-                    document.getElementById(\'' . $fieldId . '\').value = oid;
-                    return false;
-                } else {
-                    return "Invalid window name supplied";
-                }
-            }
-            </script>
-            ';
-        $html .= sprintf('<input type="hidden" name="%s" id="%s"%s value="%s" />',
-                         $varname,
-                         $fieldId,
-                         $this->_getActionScripts($form, $var),
-                         $varvalue);
-        if (!empty($varvalue)) {
-            $html .= $varvalue;
-        }
-
-        if ($GLOBALS['browser']->hasFeature('javascript')) {
-            Horde::addScriptFile('popup.js', 'horde', true);
-            $imgId = $varname .'goto';
-            $html .= '<div id="goto" class="headerbox"
-                    style="position:absolute;visibility:hidden;padding:0"></div>';
-            $html .= Horde::link('#', _("Select an object"), '', '', 'obrowserWindow = popup(\'' . $GLOBALS['registry']->get('webroot', 'horde') . '/services/obrowser/' . '\'); obrowserWindowName = obrowserWindow.name; return false;') . Horde::img('tree/leaf.png', _("Object"), 'id="' . $imgId . '" align="middle"', $GLOBALS['registry']->getImageDir('horde')) . "</a>\n";
-        }
-
-        return $html;
-    }
-
-    function _renderVarInput_dblookup($form, $var, $vars)
-    {
-        return $this->_renderVarInput_enum($form, $var, $vars);
-    }
-
-    function _renderVarInput_figlet($form, $var, $vars)
-    {
-        return sprintf('    <input type="text" class="form-input-figlet" id="%1$s" name="%1$s" size="%2$s" value="%3$s" />',
-                       $var->getVarName(),
-                       strlen($var->type->text),
-                       htmlspecialchars($var->getValue($vars))) .
-            '<p class="form-input-figlet">' . _("Enter the letters below:") . '</p>' .
-            $this->_renderVarDisplay_figlet($form, $var, $vars);
-    }
-
-    function _renderVarDisplayDefault($form, $var, $vars)
-    {
-        return nl2br(htmlspecialchars($var->getValue($vars), ENT_QUOTES,
-            NLS::getCharset()));
-    }
-
-    function _renderVarDisplay_html($form, $var, $vars)
-    {
-        return $var->getValue($vars);
-    }
-
-    function _renderVarDisplay_email($form, $var, $vars)
-    {
-        $display_email = $email = $var->getValue($vars);
-
-        if ($var->type->strip_domain && strpos($email, '@') !== false) {
-            $display_email = str_replace(array('@', '.'),
-                                         array(' (at) ', ' (dot) '),
-                                         $email);
-        }
-
-        if ($var->type->link_compose) {
-            $email_val = trim($email);
-
-            // Format the address according to RFC822.
-            $mailbox_host = explode('@', $email_val);
-            if (!isset($mailbox_host[1])) {
-                $mailbox_host[1] = '';
-            }
-
-            $name = $var->type->link_name;
-
-            require_once 'Horde/MIME.php';
-            $address = MIME::rfc822WriteAddress($mailbox_host[0], $mailbox_host[1], $name);
-
-            // Get rid of the trailing @ (when no host is included in
-            // the email address).
-            $address = str_replace('@>', '>', $address);
-            $mail_link = $GLOBALS['registry']->call('mail/compose', array(array('to' => addslashes($address))));
-            if (is_a($mail_link, 'PEAR_Error')) {
-                $mail_link = 'mailto:' . urlencode($address);
-            }
-
-            return Horde::link($mail_link, $email_val)
-                . htmlspecialchars($display_email) . '</a>';
-        } else {
-            return nl2br(htmlspecialchars($display_email, ENT_QUOTES, NLS::getCharset()));
-        }
-    }
-
-    function _renderVarDisplay_password($form, $var, $vars)
-    {
-        return '********';
-    }
-
-    function _renderVarDisplay_passwordconfirm($form, $var, $vars)
-    {
-        return '********';
-    }
-
-    function _renderVarDisplay_octal($form, $var, $vars)
-    {
-        return sprintf('0%o', octdec($var->getValue($vars)));
-    }
-
-    function _renderVarDisplay_boolean($form, $var, $vars)
-    {
-        return $var->getValue($vars) ? _("Yes") : _("No");
-    }
-
-    function _renderVarDisplay_enum($form, $var, $vars)
-    {
-        $values = $var->getValues();
-        $value = $var->getValue($vars);
-        if (count($values) == 0) {
-            return _("No values");
-        } elseif (isset($values[$value]) && $value != '') {
-            return htmlspecialchars($values[$value], ENT_QUOTES, NLS::getCharset());
-        }
-    }
-
-    function _renderVarDisplay_radio($form, $var, $vars)
-    {
-        $values = $var->getValues();
-        if (count($values) == 0) {
-            return _("No values");
-        } elseif (isset($values[$var->getValue($vars)])) {
-            return htmlspecialchars($values[$var->getValue($vars)], ENT_QUOTES, NLS::getCharset());
-        }
-    }
-
-    function _renderVarDisplay_multienum($form, $var, $vars)
-    {
-        $values = $var->getValues();
-        $on = $var->getValue($vars);
-        if (!count($values) || !count($on)) {
-            return _("No values");
-        } else {
-            $display = array();
-            foreach ($values as $value => $name) {
-                if (in_array($value, $on)) {
-                    $display[] = $name;
-                }
-            }
-            return htmlspecialchars(implode(', ', $display), ENT_QUOTES, NLS::getCharset());
-        }
-    }
-
-    function _renderVarDisplay_set($form, $var, $vars)
-    {
-        $values = $var->getValues();
-        $on = $var->getValue($vars);
-        if (!count($values) || !count($on)) {
-            return _("No values");
-        } else {
-            $display = array();
-            foreach ($values as $value => $name) {
-                if (in_array($value, $on)) {
-                    $display[] = $name;
-                }
-            }
-            return htmlspecialchars(implode(', ', $display), ENT_QUOTES, NLS::getCharset());
-        }
-    }
-
-    function _renderVarDisplay_image($form, $var, $vars)
-    {
-        $img_params = $var->getValue($vars);
-        $img_url = Horde::url($GLOBALS['registry']->get('webroot', 'horde') . '/services/images/view.php');
-        $img_url = Util::addParameter($img_url, $img_params);
-
-        return Horde::img($img_url, isset($img_params['f']) ? $img_params['f'] : '', '', '');
-    }
-
-    function _renderVarDisplay_phone($form, &$var, &$vars)
-    {
-        global $registry;
-
-        $number = $var->getValue($vars);
-        $html = htmlspecialchars($number, ENT_QUOTES, $this->_charset);
-
-        if ($number && $registry->hasMethod('telephony/dial')) {
-            $url = $registry->call('telephony/dial', array($number));
-            $label = sprintf(_("Dial %s"), $number);
-            $html .= ' ' . Horde::link($url, $label) . Horde::img('phone.png', $label, '', $registry->getImageDir('horde')) . '</a>';
-        }
-
-        return $html;
-    }
-
-    function _renderVarDisplay_cellphone($form, &$var, &$vars)
-    {
-        global $registry;
-
-        $html = $this->_renderVarDisplay_phone($form, $var, $vars);
-
-        $number = $var->getValue($vars);
-        if ($number && $registry->hasMethod('sms/compose')) {
-            $url = $registry->link('sms/compose', array('to' => $number));
-            $html .= ' ' . Horde::link($url, _("Send SMS")) . Horde::img('mobile.png', _("Send SMS"), '', $registry->getImageDir('horde')) . '</a>';
-        }
-
-        return $html;
-    }
-
-    function _renderVarDisplay_address($form, $var, $vars)
-    {
-        global $registry;
-
-        $address = $var->getValue($vars);
-
-        if (preg_match('/((?:A[BL]|B[ABDHLNRST]?|C[ABFHMORTVW]|D[ADEGHLNTY]|E[CHNX]?|F[KY]|G[LUY]?|H[ADGPRSUX]|I[GMPV]|JE|K[ATWY]|L[ADELNSU]?|M[EKL]?|N[EGNPRW]?|O[LX]|P[AEHLOR]|R[GHM]|S[AEGKLMNOPRSTWY]?|T[ADFNQRSW]|UB|W[ACDFNRSV]?|YO|ZE)\d(?:\d|[A-Z])? \d[A-Z]{2})/', $address, $postcode)) {
-            /* UK postcode detected. */
-            /* Multimap.co.uk generated map */
-            $mapurl = 'http://www.multimap.com/map/browse.cgi?pc=' . urlencode($postcode[1]);
-            $desc = _("Multimap UK map");
-            $icon = 'map.png';
-        } elseif (preg_match('/ACT|NSW|NT|QLD|SA|TAS|VIC|WA/', $address)) {
-            /* Australian state detected. */
-            /* Whereis.com.au generated map */
-            $mapurl = 'http://www.whereis.com.au/whereis/mapping/geocodeAddress.do?';
-            $desc = _("Whereis Australia map");
-            $icon = 'map.png';
-            /* Split out the address, line-by-line. */
-            $addressLines = explode("\n", $address);
-            for ($i = 0; $i < count($addressLines); $i++) {
-                /* See if it's the street number & name. */
-                if (preg_match('/(\d+\s*\/\s*)?(\d+|\d+[a-zA-Z])\s+([a-zA-Z ]*)/', $addressLines[$i], $lineParts)) {
-                    $mapurl .= '&streetNumber=' . urlencode($lineParts[2]);
-                    $mapurl .= '&streetName=' . urlencode($lineParts[3]);
-                }
-                /* Look for "Suburb, State". */
-                if (preg_match('/([a-zA-Z ]*),?\s+' . $aus_state_regexp . '/', $addressLines[$i], $lineParts)) {
-                    $mapurl .= '&suburb=' . urlencode($lineParts[1]);
-                }
-                /* Look for "State <4 digit postcode>". */
-                if (preg_match('/(' . $aus_state_regexp . ')\s+(\d{4})/', $addressLines[$i], $lineParts)) {
-                    $mapurl .= '&state=' . urlencode($lineParts[1]);
-                }
-            }
-        } elseif (preg_match('/(.*)\n(.*)\s*,\s*(\w+)\.?\s+(\d+|[a-zA-Z]\d[a-zA-Z]\s?\d[a-zA-Z]\d)/', $address, $addressParts)) {
-            /* American/Canadian address style. */
-            /* Mapquest generated map */
-            $mapurl = 'http://www.mapquest.com/maps/map.adp?size=big&zoom=7';
-            $desc = _("MapQuest map");
-            $icon = 'map.png';
-            $country = null;
-            if (!empty($addressParts[4]) && preg_match('|[a-zA-Z]\d[a-zA-Z]\s?\d[a-zA-Z]\d|', $addressParts[4])) {
-                $country = 'CA';
-            }
-            if (!empty($addressParts[1])) {
-                $mapurl .= '&address=' . urlencode($addressParts[1]);
-            }
-            if (!empty($addressParts[2])) {
-                $mapurl .= '&city=' . urlencode($addressParts[2]);
-            }
-            if (!empty($addressParts[3])) {
-                $mapurl .= '&state=' . urlencode($addressParts[3]);
-            }
-            if (!empty($addressParts[4])) {
-                if ($country == 'CA') {
-                    $mapurl .= '&country=CA';
-                }
-                $mapurl .= '&zipcode=' . urlencode($addressParts[4]);
-            }
-
-            /* Yahoo! generated map. */
-            $mapurl2 = 'http://us.rd.yahoo.com/maps/home/submit_a/*-http://maps.yahoo.com/maps?srchtype=a&getmap=Get+Map&';
-            $desc2 = _("Yahoo! map");
-            $icon2 = 'map.png';
-            if (!empty($addressParts[1])) {
-                $mapurl2 .= '&addr=' . urlencode($addressParts[1]);
-            }
-            /* Give precedence to zipcode over city/state */
-            if (empty($addressParts[4]) && !empty($addressParts[2]) && !empty($addressParts[3])) {
-                $mapurl2 .= '&csz=' . urlencode($addressParts[2] . ' ' . $addressParts[3]);
-            }
-            if (!empty($addressParts[4])) {
-                if (preg_match('|([a-zA-Z]\d[a-zA-Z])\s?(\d[a-zA-Z]\d)|', $addressParts[4], $pcParts)) {
-                    $mapurl2 .= '&country=ca';
-                    /* make sure the postal-code has a space */
-                    $addressParts[4] = $pcParts[1] . ' ' . $pcParts[2];
-                }
-                $mapurl2 .= '&csz=' . urlencode($addressParts[4]);
-            }
-
-            /* Google generated map. */
-            $mapurl3 = 'http://maps.google.com/maps?q=' . urlencode($addressParts[0]) . '&hl=en';
-            $desc3 = _("Google Maps");
-            $icon3 = 'map.png';
-
-        } elseif (preg_match('/(.*?)\r?\n([A-Z]{1,3})-(\d{5})\s+(.*)/i', $address, $addressParts)) {
-            /* European address style. */
-            include 'Horde/NLS/carsigns.php';
-            $country = array_search(String::upper($addressParts[2]), $carsigns);
-
-            /* Map24 generated map. */
-            if (in_array($country, array('al', 'ad', 'am', 'az', 'be', 'ba',
-                                         'bg', 'de', 'dk', 'ee', 'fo', 'fi',
-                                         'fr', 'ge', 'gr', 'gb', 'ie', 'is',
-                                         'it', 'hr', 'lv', 'li', 'lt', 'lu',
-                                         'mt', 'mk', 'md', 'mc', 'nl', 'no',
-                                         'pl', 'pt', 'ro', 'ru', 'se', 'ch',
-                                         'cs', 'sk', 'si', 'es', 'cz', 'tr',
-                                         'ua', 'hu', 'by', 'cy', 'at'))) {
-                if (in_array($country, array('at', 'be', 'ch', 'de', 'dk',
-                                             'es', 'fi', 'fr', 'it', 'nl',
-                                             'no', 'se'))) {
-                    $mirror = $country;
-                } else {
-                    $mirror = 'uk';
-                }
-                $mapurl = 'http://www.' . $mirror . '.map24.com/source/address/v2.0.0/cnt_nav_maplet.php?cid=validateaddr&country=' . $country;
-                $desc = _("Map24 map");
-                $icon = 'map_eu.png';
-                if (!empty($addressParts[1])) {
-                    $mapurl .= '&street=' . urlencode($addressParts[1]);
-                }
-                if (!empty($addressParts[3])) {
-                    $mapurl .= '&zip=' . urlencode($addressParts[3]);
-                }
-                if (!empty($addressParts[4])) {
-                    $mapurl .= '&city=' . urlencode($addressParts[4]);
-                }
-            }
-
-            /* Mapquest generated map. */
-            $mapurl2 = 'http://www.mapquest.com/maps/map.adp?country=' . String::upper($country);
-            $desc2 = _("MapQuest map");
-            $icon2 = 'map_eu.png';
-            if (!empty($addressParts[1])) {
-                $mapurl2 .= '&address=' . urlencode($addressParts[1]);
-            }
-            if (!empty($addressParts[3])) {
-                $mapurl2 .= '&zipcode=' . urlencode($addressParts[3]);
-            }
-            if (!empty($addressParts[4])) {
-                $mapurl2 .= '&city=' . urlencode($addressParts[4]);
-            }
-        }
-
-        $html = nl2br(htmlspecialchars($var->getValue($vars), ENT_QUOTES, NLS::getCharset()));
-        if (!empty($mapurl)) {
-            $html .= '&nbsp;&nbsp;' . Horde::link(Horde::externalUrl($mapurl), $desc, null, '_blank') . Horde::img($icon, $desc, '', $registry->getImageDir('horde')) . '</a>';
-        }
-        if (!empty($mapurl2)) {
-            $html .= '&nbsp;' . Horde::link(Horde::externalUrl($mapurl2), $desc2, null, '_blank') . Horde::img($icon2, $desc2, '', $registry->getImageDir('horde')) . '</a>';
-        }
-        if (!empty($mapurl3)) {
-            $html .= '&nbsp;' . Horde::link(Horde::externalUrl($mapurl3), $desc3, null, '_blank') . Horde::img($icon3, $desc3, '', $registry->getImageDir('horde')) . '</a>';
-        }
-
-        return $html;
-    }
-
-    function _renderVarDisplay_date($form, $var, $vars)
-    {
-        return $var->type->getFormattedTime($var->getValue($vars));
-    }
-
-    function _renderVarDisplay_monthyear($form, $var, $vars)
-    {
-        return $vars->get($var->getVarName() . '[month]') . ', ' . $vars->get($var->getVarName() . '[year]');
-    }
-
-    function _renderVarDisplay_monthdayyear($form, $var, $vars)
-    {
-        $date = $var->getValue($vars);
-        if ((is_array($date) && !empty($date['year']) &&
-             !empty($date['month']) && !empty($date['day']))
-            || (!is_array($date) && !empty($date))) {
-            return $var->type->formatDate($date);
-        }
-        return '';
-    }
-
-    function _renderVarDisplay_invalid($form, $var, $vars)
-    {
-        return '<p class="form-error form-inline">'
-                . htmlspecialchars($var->type->message, ENT_QUOTES, NLS::getCharset())
-                . '</p>';
-    }
-
-    function _renderVarDisplay_link($form, $var, $vars)
-    {
-        $values = $var->getValues();
-        if (!isset($values[0])) {
-            $values = array($values);
-        }
-
-
-        $count = count($values);
-        $html = '';
-        for ($i = 0; $i < $count; $i++) {
-            if (empty($values[$i]['url']) || empty($values[$i]['text'])) {
-                continue;
-            }
-            if (!isset($values[$i]['target'])) {
-                $values[$i]['target'] = '';
-            }
-            if (!isset($values[$i]['onclick'])) {
-                $values[$i]['onclick'] = '';
-            }
-            if (!isset($values[$i]['title'])) {
-                $values[$i]['title'] = '';
-            }
-            if (!isset($values[$i]['accesskey'])) {
-                $values[$i]['accesskey'] = '';
-            }
-            if ($i > 0) {
-                $html .= ' | ';
-            }
-            $html .= Horde::link($values[$i]['url'], $values[$i]['text'],
-                        'widget', $values[$i]['target'], $values[$i]['onclick'],
-                        $values[$i]['title'], $values[$i]['accesskey'])
-                    . $values[$i]['text'] . '</a>';
-        }
-
-        return $html;
-    }
-
-    function _renderVarDisplay_dblookup($form, $var, $vars)
-    {
-        return $this->_renderVarDisplay_enum($form, $var, $vars);
-    }
-
-    function _renderVarDisplay_figlet($form, $var, $vars)
-    {
-        $figlet = new Text_Figlet();
-        $result = $figlet->loadFont($var->type->font);
-        if (is_a($result, 'PEAR_Error')) {
-            return $result->getMessage();
-        }
-
-        return '<pre>' . $figlet->lineEcho($var->type->text) . '</pre>';
-    }
-
-    function _renderVarInput_selectFiles($form, $var, $vars)
-    {
-        /* Needed for gollem js calls */
-        $html = sprintf('<input type="hidden" id="%1$s" name="%1$s" value="%2$s" />',
-                        'selectlist_selectid',
-                        $var->type->selectid)
-            . sprintf('<input type="hidden" id="%1$s" name="%1$s" />', 'actionID');
-
-        /* Form field. */
-        $html .= sprintf('<input type="hidden" id="%1$s" name="%1$s" value="%2$s" />',
-                         $var->getVarName(),
-                         $var->type->selectid);
-
-        /* Open window link. */
-        $param = array($var->type->link_text,
-                       $var->type->link_style,
-                       $form->getName(),
-                       $var->type->icon,
-                       $var->type->selectid);
-        $html .= "<p>\n" . $GLOBALS['registry']->call('files/selectlistLink', $param) . "</p>\n";
-
-        if ($var->type->selectid) {
-            $param = array($var->type->selectid);
-            $files = $GLOBALS['registry']->call('files/selectlistResults', $param);
-            if ($files) {
-                $html .= '<ol>';
-                foreach ($files as $id => $file) {
-                    $dir = key($file);
-                    $filename = current($file);
-                    if ($GLOBALS['registry']->hasMethod('files/getViewLink')) {
-                        $filename = basename($filename);
-                        $url = $GLOBALS['registry']->call('files/getViewLink', array($dir, $filename));
-                        $filename = Horde::link($url, _("Preview"), null, 'form_file_view') . htmlspecialchars(Util::realPath($dir . '/' . $filename), ENT_QUOTES, $this->_charset) . '</a>';
-                    } else {
-                        if (!empty($dir) && ($dir != '.')) {
-                            $filename = $dir . '/' . $filename;
-                        }
-                        $filename = htmlspecialchars($filename, ENT_QUOTES, $this->_charset);
-                    }
-                    $html .= '<li>' . $filename . "</li>\n";
-                }
-                $html .= '</ol>';
-            }
-        }
-
-        return $html;
-    }
-
-    function _selectOptions($values, $selectedValue = false, $htmlchars = true)
-    {
-        $result = '';
-        $sel = false;
-        foreach ($values as $value => $display) {
-            if (!is_null($selectedValue) && !$sel && $value == $selectedValue
-                && strlen($value) == strlen($selectedValue)) {
-                $selected = ' selected="selected"';
-                $sel = true;
-            } else {
-                $selected = '';
-            }
-            $result .= '        <option value="';
-            $result .= ($htmlchars) ? htmlspecialchars($value, ENT_QUOTES, NLS::getCharset()) : $value;
-            $result .= '"' . $selected . '>';
-            $result .= ($htmlchars) ? htmlspecialchars($display) : $display;
-            $result .= "</option>\n";
-        }
-
-        return $result;
-    }
-
-    function _multiSelectOptions($values, $selectedValues)
-    {
-        $result = '';
-        $sel = false;
-        foreach ($values as $value => $display) {
-            if (@in_array($value, $selectedValues)) {
-                $selected = ' selected="selected"';
-            } else {
-                $selected = '';
-            }
-            $result .= " <option value=\""
-                . htmlspecialchars($value, ENT_QUOTES, NLS::getCharset())
-                . "\"$selected>" . htmlspecialchars($display) . "</option>\n";
-        }
-
-        return $result;
-    }
-
-    function _checkBoxes($name, $values, $checkedValues, $actions = '')
-    {
-        $result = '';
-        if (!is_array($checkedValues)) {
-            $checkedValues = array();
-        }
-
-        if (count($values) > 0) {
-            $result .= "    <ul>\n";
-        }
-
-        $i = 0;
-        foreach ($values as $value => $display) {
-            $checked = in_array($value, $checkedValues) ? ' checked="checked"' : '';
-            $result .= sprintf('        <li>'
-                                .'<input id="%1$s%2$s" type="checkbox"'
-                                    .' class="form-input-checkbox" name="%1$s[]"'
-                                    .' value="%3$s"%4$s%5$s />'
-                                .'&nbsp;<label class="form-inline" for="%1$s%2$s">'
-                                    .'%6$s</label></li>'."\n",
-                            $name,
-                            $i,
-                            $value,
-                            $checked,
-                            $actions,
-                            $display);
-            $i++;
-        }
-
-        if (count($values) > 0) {
-            $result .= "    </ul>";
-        }
-
-
-        return $result;
-    }
-
-    function _radioButtons($name, $values, $checkedValue = null, $actions = '')
-    {
-        $result = '';
-
-        if (count($values) > 0) {
-            $result .= "    <ul>\n";
-        }
-
-        $i = 0;
-        foreach ($values as $value => $display) {
-            $checked = (!is_null($checkedValue) && $value == $checkedValue) ? ' checked="checked"' : '';
-            $result .= sprintf('        <li>'
-                                .'<input id="%1$s%2$s" type="radio"'
-                                    .' class="form-input-checkbox" name="%1$s"'
-                                    .' value="%3$s"%4$s%5$s />'
-                                .'&nbsp;<label class="form-inline" for="%1$s%2$s">'
-                                    .'%6$s</label></li>'."\n",
-                            $name,
-                            $i,
-                            $value,
-                            $checked,
-                            $actions,
-                            $display);
-            $i++;
-        }
-
-        if (count($values) > 0) {
-            $result .= "    </ul>";
-        }
-
-        return $result;
-    }
-
-    /**
-     *
-     * @access private
-     * @author ?
-     * @deprecated
-     */
-    function _genID($name, $fulltag = true)
-    {
-        return $fulltag ? 'id="' . htmlspecialchars($name) . '"' : $name;
-    }
-
-    /**
-     * Returns script for an rendered variable. TODO: make this unobtrusive.
-     *
-     * @access private
-     * @author ?
-     * @return string html representing an attribute with action script as value,
-     *         or and empty string, if the action is to happen window.onload
-     */
-    function _genActionScript($form, $action, $varname)
-    {
-        $html = '';
-        $triggers = $action->getTrigger();
-        if (!is_array($triggers)) {
-            $triggers = array($triggers);
-        }
-        $js = $action->getActionScript($form, $this, $varname);
-        foreach ($triggers as $trigger) {
-            if ($trigger == 'onload') {
-                $this->_onLoadJS[] = $js;
-            } else {
-                $html .= ' ' . $trigger . '="' . $js . '"';
-            }
-        }
-        return $html;
-    }
-
-    /**
-     * Returns scripts for an rendered variable. TODO: make this unobtrusive.
-     *
-     * @access private
-     * @author ?
-     * @return string html representing attributes with action script as values,
-     *         or and empty string, if the actions are all to happen window.onload
-     */
-    function _getActionScripts($form, $var)
-    {
-        $actions = '';
-        if ($var->hasAction()) {
-            $varname = $var->getVarName();
-            $action = &$var->_action;
-            $triggers = $action->getTrigger();
-            if (!is_array($triggers)) {
-                $triggers = array($triggers);
-            }
-            $js = $action->getActionScript($form, $this, $varname);
-            foreach ($triggers as $trigger) {
-                if ($trigger == 'onload') {
-                    $this->_onLoadJS[] = $js;
-                } else {
-                    $actions .= ' ' . $trigger . '="' . $js . '"';
-                }
-            }
-        }
-        return $actions;
-    }
-
-}
diff --git a/framework/Horde_Form/www/js/maxlength.js b/framework/Horde_Form/www/js/maxlength.js
deleted file mode 100644 (file)
index 200ab8e..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/* http://www.quirksmode.org/dom/maxlength.html */
-
-/*
-<textarea id="text" name="text" maxlength="1500"></textarea>
-*/
-
-
-function setMaxLength()
-{
-       var x = document.getElementsByTagName('textarea');
-       var counter = document.createElement('div');
-       counter.className = 'counter';
-       for (var i=0;i<x.length;i++)
-       {
-               if (x[i].getAttribute('maxlength'))
-               {
-                       var counterClone = counter.cloneNode(true);
-                       counterClone.relatedElement = x[i];
-                       counterClone.innerHTML = '<span>0</span>/'+x[i].getAttribute('maxlength');
-                       x[i].parentNode.insertBefore(counterClone,x[i].nextSibling);
-                       x[i].relatedElement = counterClone.getElementsByTagName('span')[0];
-
-                       x[i].onkeyup = x[i].onchange = checkMaxLength;
-                       x[i].onkeyup();
-               }
-       }
-}
-
-function checkMaxLength()
-{
-       var maxLength = this.getAttribute('maxlength');
-       var currentLength = this.value.length;
-       if (currentLength > maxLength)
-               this.relatedElement.className = 'toomuch';
-       else
-               this.relatedElement.className = '';
-       this.relatedElement.firstChild.nodeValue = currentLength;
-       // not innerHTML
-}
diff --git a/framework/Horde_Form/www/test.php b/framework/Horde_Form/www/test.php
deleted file mode 100644 (file)
index 8267f25..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-<?php
-/**
- * Incubator Horde_Form rewrite example page.
- *
- * The initial Horde_Form xhtml rewrite was supported by Google SoC
- * 2005.
- *
- * $Horde: incubator/Horde_Form/test.php,v 1.25 2008/09/02 17:43:09 chuck Exp $
- */
-
-@define('HORDE_BASE', dirname(__FILE__) . '/../..');
-@define('INCUBATOR_BASE', dirname(__FILE__));
-
-require_once HORDE_BASE . '/lib/core.php';
-require_once 'Horde/Variables.php';
-require_once 'Horde/Autoloader.php';
-Horde_Autoloader::addClassPath(dirname(__FILE__));
-$registry = Registry::singleton();
-$vars = Variables::getDefaultVariables();
-
-$vars->set('example_bar', 'text with a beginning and an end');
-$form = new Horde_Form($vars, 'Horde_Form Test');
-
-$choices = array('big' => 'BIG',
-                 'small' => 'small',
-                 'other' => 'Other');
-$form->add('condchoices', 'Enum', _("Select something"), '', true, false, array($choices, true));
-
-$o = $form->add('other_text', 'String', _("If other, please describe"), '', false);
-$params = array('target' => 'condchoices',
-                'enabled' => true,
-                'values' => array('other'));
-$o->setAction(new Horde_Form_Action_ConditionalEnable($params));
-
-$form->add('color', 'Color', _("Color"), null, false);
-
-$vars->set('form', 'add');
-$enum = array('' => _("Select:"),
-              1 => _("Yes"),
-              0 => _("No"));
-$form->add('opciones', 'Enum', _("Simple description"), '', true, false, array($enum));
-$form->add('bool', 'Boolean', _("Boolean"));
-$form->add('number', 'Int', _("Integer"));
-$form->add('mybday', 'date', _("A Date"), '', false);
-$form->addHidden('form', 'String', true);
-$unamevar = $form->add('user_name', 'String', _("Username"));
-$form->add('password', 'password', _("Password"));
-$form->addHidden('example_hidden', 'int', false);
-$form->add('some_text', 'String', _("Insert some text"), _("Insert some text in this box"), false);
-$choices = array('big' => 'BIG',
-                 'small' => 'small',
-                 'mixed' => 'mIxED');
-$form->add('choices', 'enum', _("Select something2"), 'Use the selection box to make your choice', true, false, array($choices, true));
-$form->add('email_address', 'email', _("Email"));
-$form->add('email_address2', 'emailconfirm', _("Email2"));
-$form->add('a_creditcard', 'creditcard', _("Credit Card"));
-$form->add('a_password', 'password', _("Password"));
-$form->add('a_password2', 'passwordconfirm', _("Password with confirmation"), _("type the password twice to confirm"));
-$form->add('a_octal', 'Octal', _("Octal"), false);
-$form->add('a_radiogroup', 'set', _("Radio Group"), '', true, false, array($choices));
-
-$t = $form->add('example_bar', 'String', _("Bar field"), _("You have to fill in some long text here"), true, false, array(4, 40));
-$t->setAction(new Horde_Form_Action_setcursorpos(array(4)));
-
-$form->add('a_checkboxgroup', 'set', _("Checkbox Group"), '', false, false, array($choices));
-//$form->add('a_obrowser', 'obrowser', _("obrowser"));
-
-?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html>
-<head>
-<title>Incubator Horde_Form Test</title>
-<link rel="stylesheet" type="text/css" href="themes/form.css" />
-<script type="text/javascript" src="<?=$registry->get('jsuri', 'horde')?>/horde.js"></script>
-<script type="text/javascript" src="<?=$registry->get('jsuri', 'horde')?>/form_helpers.js"></script>
-</head>
-<body>
-<?php
-if ($form->validate()) {
-    $form->getInfo($info);
-    echo 'You have submitted:<br /><pre>';
-    var_dump($info);
-    echo '</pre>';
-}
-
-/* Render the form. */
-$renderer = new Horde_Form_Renderer_Xhtml;
-$renderer->setButtons(_("Add user"), _("Reset"));
-$renderer->renderActive($form, 'test.php', 'post');
-
-?>
-</body>
-</html>
diff --git a/framework/Horde_Form/www/themes/form.css b/framework/Horde_Form/www/themes/form.css
deleted file mode 100644 (file)
index 0c3ae39..0000000
+++ /dev/null
@@ -1,262 +0,0 @@
-form.horde-form {
-    font-size: 100%;
-    font-weight: normal;
-}
-
-form.horde-form input[type="text"], form.horde-form input[type="password"],
-form.horde-form select, form.horde-form textarea {
-    width: 130px;
-    margin: .2em 0 .1em .1em;
-}
-
-form.horde-form input[type="text"], form.horde-form input[type="password"], form.horde-form textarea {
-    padding: .1em;
-}
-
-.clear {
-    clear: both;
-}
-
-.horde-form fieldset {
-    padding: .5em;
-    margin: 0;
-    border: 1px solid #ccc;
-}
-
-.horde-form fieldset.form-buttons {
-    color: #000;
-    background: #ccc;
-    border: 1px solid #aaa;
-}
-
-fieldset legend {
-    color: #000;
-    background: #ccc;
-    padding: .5em;
-    margin-bottom: -1em;
-    border: 1px solid #999;
-    border-bottom: none;
-    font-weight: bold;
-}
-
-fieldset.form-section {
-    padding: .5em;
-    margin: .5em 0;
-}
-
-.form-button, .form-button-upload {
-    font-size: 90%;
-}
-
-.form-colorpicker {
-    float: left;
-}
-.form-colorpicker img {
-    border: 0;
-    padding: 0;
-    margin: 2px;
-    height: 16px;
-    width: 16px;
-}
-.form-colorpicker input {
-    width: 100px;
-    clear: none;
-}
-.form-colorpicker-palette {
-    display: none;
-    margin: 2px;
-}
-
-.form-description {
-    padding: 8px;
-}
-
-.form-error, .form-error-example {
-    color: #f00;
-}
-
-p.form-error {
-    background-color: #f00;
-    color: #fff;
-    padding: 3px;
-    border: 1px solid #000;
-    margin: auto 70px;
-}
-
-div.form-error {
-    background-color: #ffffe1;
-    background-image: url("graphics/required.png");
-    background-repeat: no-repeat;
-    background-position: top left;
-    color: #000;
-}
-
-.form-hint {
-    display: block;
-    margin: 0 0 5px 142px;
-    padding: 1px 3px;
-    font-size: 88%;
-}
-
-.form-header {
-    vertical-align: bottom;
-    font-weight: bold;
-}
-
-.form-image-preview-blank {
-    width: 50px;
-    height: 40px;
-    vertical-align: top;
-}
-
-.form-inline {
-    display: inline;
-}
-
-.form-input {
-    vertical-align: top;
-    clear: both;
-    padding: 4px;
-    margin: 2px 0;
-}
-
-.form-input label {
-    vertical-align: top;
-    text-align: right;
-    float: left;
-    clear: left;
-    display: block;
-    width: 10em;
-    padding: 0.3em;
-}
-
-.form-input label.form-inline {
-    float: none;
-    width: auto;
-    display: inline;
-}
-
-.form-input ul, .form-input ol {
-    list-style-type: none;
-    display: block;
-    float: left;
-}
-
-.form-input ul, .form-input ol, .form-input li {
-    margin-top: 0;
-    margin-left: 0;
-    padding-top: 0;
-    padding-left: 0;
-}
-.form-input ul {
-    margin-bottom: .5em;
-}
-
-.form-input-address {
-}
-
-.form-input-assign {
-}
-
-.form-input-assign select, .form-input-assign div {
-    display: inline;
-    clear: none;
-}
-
-.form-input-checkbox {
-    border: 0;
-    height: 14px;
-    width: 14px;
-    background: transparent;
-}
-
-.form-input-text, .form-input-intlist, .form-input-octal, .form-input-int,
-.form-input-number, .form-input-phone, .form-input-file {
-    width: 100px;
-}
-
-.form-input-resize {
-    width: 25px;
-}
-
-.form-input-stringlist {
-    width: 150px;
-}
-
-.form-input-time {
-    width: 30px;
-}
-
-.form-note {
-    clear: left;
-    width: 130px;
-    height: auto;
-    padding: .3em;
-    margin: 0 0 0 10.7em;
-    border: 1px solid #666;
-    background-color: #ffc;
-}
-.form-note p {
-    margin: 0;
-    padding: 0;
-    font-style: italic;
-    font-size: 70%;
-}
-
-.form-required {
-    background: url("graphics/required.png") .5em .5em no-repeat;
-}
-
-.form-required label {
-    font-weight: bold;
-}
-
-.form-sectionhidden {
-    position: absolute;
-    left: 0;
-    top: -500px;
-    width: 1px;
-    height: 1px;
-    overflow: hidden;
-    display: block;
-}
-
-.form-sectionshown {
-    display: block;
-}
-
-.form-spacer {
-    padding: .9em;
-}
-
-.rowEven {
-    background-color: #eef;
-}
-.rowOdd {
-    background-color: #fff;
-}
-
-/* Form styles. */
-.htmlarea .statusBar .statusBarTree a {
-    font: inherit;
-}
-.htmlarea table {
-    width: auto;
-}
-input, select {
-}
-input {
-    padding: 1px;
-}
-option {
-    padding: 0 5px 0 3px;
-}
-
-.button {
-    font-size: 90%;
-}
-a.button {
-    padding: 2px;
-    font-weight: normal;
-    text-decoration: none;
-}
diff --git a/framework/Horde_Form/www/themes/graphics/required.png b/framework/Horde_Form/www/themes/graphics/required.png
deleted file mode 100644 (file)
index 1756362..0000000
Binary files a/framework/Horde_Form/www/themes/graphics/required.png and /dev/null differ
diff --git a/framework/VC/lib/Horde/VC.php b/framework/VC/lib/Horde/VC.php
deleted file mode 100644 (file)
index f6225df..0000000
+++ /dev/null
@@ -1,927 +0,0 @@
-<?php
-
-require_once dirname(__FILE__) . '/VC/Exception.php';
-
-/* Need to define this outside of class since constants in class can not be
- * assigned from a function return. */
-define('VC_WINDOWS', !strncasecmp(PHP_OS, 'WIN', 3));
-
-/**
- * Version Control generalized library.
- *
- * Copyright 2008 The Horde Project (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (LGPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
- *
- * @package Horde_VC
- */
-class Horde_VC
-{
-    /* Sorting options */
-    const SORT_NONE = 0;    // don't sort
-    const SORT_AGE = 1;     // sort by age
-    const SORT_NAME = 2;    // sort by filename
-    const SORT_REV = 3;     // sort by revision number
-    const SORT_AUTHOR = 4;  // sort by author name
-
-    const SORT_ASCENDING = 0;   // ascending order
-    const SORT_DESCENDING = 1;  // descending order
-
-    /**
-     * The source root of the repository.
-     *
-     * @var string
-     */
-    protected $_sourceroot;
-
-    /**
-     * Hash with the locations of all necessary binaries.
-     *
-     * @var array
-     */
-    protected $_paths = array();
-
-    /**
-     * Hash caching the parsed users file.
-     *
-     * @var array
-     */
-    protected $_users;
-
-    /**
-     * The current driver.
-     *
-     * @var string
-     */
-    protected $_driver;
-
-    /**
-     * Attempts to return a concrete Horde_VC instance based on $driver.
-     *
-     * @param mixed $driver  The type of concrete Horde_VC subclass to return.
-     *                       The code is dynamically included.
-     * @param array $params  A hash containing any additional configuration
-     *                       or  parameters a subclass might need.
-     *
-     * @return Horde_VC  The newly created concrete instance, or PEAR_Error on
-     *                   failure.
-     */
-    static public function factory($driver, $params = array())
-    {
-        $class = 'Horde_VC_' . $driver;
-        if (class_exists($class)) {
-            return new $class($params);
-        }
-
-        return PEAR::raiseError($class . ' not found.');
-    }
-
-    /**
-     * Attempts to return a reference to a concrete Horde_VC instance based
-     * on $driver. It will only create a new instance if no Horde_VC
-     * instance with the same parameters currently exists.
-     *
-     * This should be used if multiple types of file backends (and,
-     * thus, multiple Horde_VC instances) are required.
-     *
-     * This method must be invoked as: $var = &Horde_VC::singleton()
-     *
-     * @param mixed $driver  The type of concrete Horde_VC subclass to return.
-     *                       The code is dynamically included.
-     * @param array $params  A hash containing any additional configuration
-     *                       or parameters a subclass might need.
-     *
-     * @return Horde_VC  The concrete reference, or PEAR_Error on failure.
-     */
-    static public function &singleton($driver, $params = array())
-    {
-        static $instances = array();
-
-        $signature = serialize(array($driver, $params));
-        if (!isset($instances[$signature])) {
-            $instances[$signature] = &Horde_VC::factory($driver, $params);
-        }
-
-        return $instances[$signature];
-    }
-
-    /**
-     * Constructor.
-     */
-    public function __construct()
-    {
-        $pos = strpos(get_class($this), '_');
-        $this->_driver = substr(get_class($this), $pos + 1);
-    }
-
-    /**
-     * Return the source root for this repository, with no trailing /
-     *
-     * @return string  Source root for this repository.
-     */
-    public function sourceroot()
-    {
-        return $this->_sourceroot;
-    }
-
-    /**
-     * Validation function to ensure that a revision number is of the right
-     * form.
-     *
-     * @param mixed $rev  The purported revision number.
-     *
-     * @return boolean  True if it is a revision number.
-     */
-    public function isValidRevision($rev)
-    {
-        return true;
-    }
-
-    /**
-     * TODO
-     */
-    public function isFile($where)
-    {
-        return true;
-    }
-
-    /**
-     * Throw an exception if the revision number isn't valid.
-     *
-     * @param mixed $rev The revision number
-     *
-     * @return void
-     * @throws Horde_VC_Exception
-     */
-    public function assertValidRevision($rev)
-    {
-        if (!$this->isValidRevision($rev)) {
-            throw new Horde_VC_Exception('Invalid revision number');
-        }
-    }
-
-    /**
-     * Create a range of revisions between two revision numbers.
-     *
-     * @param Horde_VC_File $file  The desired file.
-     * @param string $r1           The initial revision.
-     * @param string $r2           The ending revision.
-     *
-     * @return array  The revision range, or empty if there is no straight
-     *                line path between the revisions.
-     */
-    public function getRevisionRange($file, $r1, $r2)
-    {
-        $class = 'Horde_VC_Diff_' . $this->_driver;
-        $vc_diff = new $class();
-        return $vc_diff->getRevisionRange($this, $file, $r1, $r2);
-    }
-
-    /**
-     * Returns the location of the specified binary.
-     *
-     * @param string $binary  An external program name.
-     *
-     * @return boolean|string  The location of the external program or false if
-     *                         it wasn't specified.
-     */
-    public function getPath($binary)
-    {
-        if (isset($this->_paths[$binary])) {
-            return $this->_paths[$binary];
-        }
-
-        return false;
-    }
-
-    /**
-     * Parse the users file, if present in the source root, and return
-     * a hash containing the requisite information, keyed on the
-     * username, and with the 'desc', 'name', and 'mail' values inside.
-     *
-     * @return boolean|array  False if the file is not present, otherwise
-     *                        $this->_users populated with the data
-     */
-    public function getUsers($usersfile)
-    {
-        /* Check that we haven't already parsed users. */
-        if (isset($this->_users) && is_array($this->_users)) {
-            return $this->_users;
-        }
-
-        if (!@is_file($usersfile) || !($fl = @fopen($usersfile, VC_WINDOWS ? 'rb' : 'r'))) {
-            return false;
-        }
-
-        $this->_users = array();
-
-        /* Discard the first line, since it'll be the header info. */
-        fgets($fl, 4096);
-
-        /* Parse the rest of the lines into a hash, keyed on
-         * username. */
-        while ($line = fgets($fl, 4096)) {
-            if (preg_match('/^\s*$/', $line)) {
-                continue;
-            }
-            if (!preg_match('/^(\w+)\s+(.+)\s+([\w\.\-\_]+@[\w\.\-\_]+)\s+(.*)$/', $line, $regs)) {
-                continue;
-            }
-
-            $this->_users[$regs[1]]['name'] = trim($regs[2]);
-            $this->_users[$regs[1]]['mail'] = trim($regs[3]);
-            $this->_users[$regs[1]]['desc'] = trim($regs[4]);
-        }
-
-        return $this->_users;
-    }
-
-    public function queryDir($where)
-    {
-        $class = 'Horde_VC_Directory_' . $this->_driver;
-        return new $class($this, $where);
-    }
-
-    public function getCheckout($file, $rev)
-    {
-        $class = 'Horde_VC_Checkout_' . $this->_driver;
-        $vc_co = new $class();
-        return $vc_co->get($this, $file->queryFullPath(), $rev);
-    }
-
-    public function getDiff($file, $rev1, $rev2, $type = 'unified', $num = 3,
-                            $ws = true)
-    {
-        $class = 'Horde_VC_Diff_' . $this->_driver;
-        $vc_diff = new $class();
-        return $vc_diff->get($this, $file, $rev1, $rev2, $type, $num, $ws);
-    }
-
-    public function availableDiffTypes()
-    {
-        $class = 'Horde_VC_Diff_' . $this->_driver;
-        $vc_diff = new $class();
-        return $vc_diff->availableDiffTypes();
-    }
-
-    public function getFileObject($filename, $cache = null, $quicklog = false)
-    {
-        $class = 'Horde_VC_File_' . $this->_driver;
-        $vc_file = new $class($this, $filename, $cache, $quicklog);
-        return $vc_file->getFileObject();
-    }
-
-    public function getAnnotateObject($filename)
-    {
-        $class = 'Horde_VC_Annotate_' . $this->_driver;
-        return new $class($this, $filename);
-    }
-
-    public function getPatchsetObject($filename, $cache = null)
-    {
-        $class = 'Horde_VC_Patchset_' . $this->_driver;
-        $vc_patchset = new $class();
-        return $vc_patchset->getPatchsetObject($this, $filename, $cache);
-    }
-
-    public function getRevisionObject()
-    {
-        $class = 'Horde_VC_Revision_' . $this->_driver;
-        return new $class();
-    }
-}
-
-/**
- * Horde_VC annotate class.
- *
- * @package Horde_VC
- */
-abstract class Horde_VC_Annotate
-{
-    protected $_file;
-    protected $_rep;
-
-    /**
-     * Constructor
-     *
-     * TODO
-     */
-    public function __construct($rep, $file)
-    {
-        $this->_rep = $rep;
-        $this->_file = $file;
-    }
-
-    /**
-     * TODO
-     */
-    abstract public function doAnnotate($rev);
-}
-
-/**
- * @package Horde_VC
- */
-abstract class Horde_VC_Checkout
-{
-    /**
-     * Function which returns a file pointing to the head of the requested
-     * revision of an SVN file.
-     *
-     * @param Horde_VC $rep     A repository object
-     * @param string $fullname  Fully qualified pathname of the desired file
-     *                          to checkout
-     * @param string $rev       Revision number to check out
-     *
-     * @return resource|object  Either a PEAR_Error object, or a stream
-     *                          pointer to the head of the checkout.
-     */
-    abstract function get($rep, $fullname, $rev);
-}
-
-/**
- * @package Horde_VC
- */
-class Horde_VC_Diff
-{
-    /**
-     * The available diff types.
-     *
-     * @var array
-     */
-    protected $_diffTypes = array('column', 'context', 'ed', 'unified');
-
-    /**
-     * Obtain a tree containing information about the changes between
-     * two revisions.
-     *
-     * @param array $raw  An array of lines of the raw unified diff,
-     *                    normally obtained through Horde_VC_Diff::get().
-     *
-     * @return array  @TODO
-     */
-    public function humanReadable($raw)
-    {
-        $ret = array();
-
-        /* Hold the left and right columns of lines for change
-         * blocks. */
-        $cols = array(array(), array());
-        $state = 'empty';
-
-        /* Iterate through every line of the diff. */
-        foreach ($raw as $line) {
-            /* Look for a header which indicates the start of a diff
-             * chunk. */
-            if (preg_match('/^@@ \-([0-9]+).*\+([0-9]+).*@@(.*)/', $line, $regs)) {
-                /* Push any previous header information to the return
-                 * stack. */
-                if (isset($data)) {
-                    $ret[] = $data;
-                }
-                $data = array('type' => 'header', 'oldline' => $regs[1],
-                              'newline' => $regs[2], 'contents'> array());
-                $data['function'] = isset($regs[3]) ? $regs[3] : '';
-                $state = 'dump';
-            } elseif ($state != 'empty') {
-                /* We are in a chunk, so split out the action (+/-)
-                 * and the line. */
-                preg_match('/^([\+\- ])(.*)/', $line, $regs);
-                if (count($regs) > 2) {
-                    $action = $regs[1];
-                    $content = $regs[2];
-                } else {
-                    $action = ' ';
-                    $content = '';
-                }
-
-                if ($action == '+') {
-                    /* This is just an addition line. */
-                    if ($state == 'dump' || $state == 'add') {
-                        /* Start adding to the addition stack. */
-                        $cols[0][] = $content;
-                        $state = 'add';
-                    } else {
-                        /* This is inside a change block, so start
-                         * accumulating lines. */
-                        $state = 'change';
-                        $cols[1][] = $content;
-                    }
-                } elseif ($action == '-') {
-                    /* This is a removal line. */
-                    $state = 'remove';
-                    $cols[0][] = $content;
-                } else {
-                    /* An empty block with no action. */
-                    switch ($state) {
-                    case 'add':
-                        $data['contents'][] = array('type' => 'add', 'lines' => $cols[0]);
-                        break;
-
-                    case 'remove':
-                        /* We have some removal lines pending in our
-                         * stack, so flush them. */
-                        $data['contents'][] = array('type' => 'remove', 'lines' => $cols[0]);
-                        break;
-
-                    case 'change':
-                        /* We have both remove and addition lines, so
-                         * this is a change block. */
-                        $data['contents'][] = array('type' => 'change', 'old' => $cols[0], 'new' => $cols[1]);
-                        break;
-                    }
-                    $cols = array(array(), array());
-                    $data['contents'][] = array('type' => 'empty', 'line' => $content);
-                    $state = 'dump';
-                }
-            }
-        }
-
-        /* Just flush any remaining entries in the columns stack. */
-        switch ($state) {
-        case 'add':
-            $data['contents'][] = array('type' => 'add', 'lines' => $cols[0]);
-            break;
-
-        case 'remove':
-            /* We have some removal lines pending in our stack, so
-             * flush them. */
-            $data['contents'][] = array('type' => 'remove', 'lines' => $cols[0]);
-            break;
-
-        case 'change':
-            /* We have both remove and addition lines, so this is a
-             * change block. */
-            $data['contents'][] = array('type' => 'change', 'old' => $cols[0], 'new' => $cols[1]);
-            break;
-        }
-
-        if (isset($data)) {
-            $ret[] = $data;
-        }
-
-        return $ret;
-    }
-
-    /**
-     * Create a range of revisions between two revision numbers.
-     *
-     * @param Horde_VC $rep        A repository object.
-     * @param Horde_VC_File $file  The desired file.
-     * @param string $r1           The initial revision.
-     * @param string $r2           The ending revision.
-     *
-     * @return array  The revision range, or empty if there is no straight
-     *                line path between the revisions.
-     */
-    public function getRevisionRange($rep, $file, $r1, $r2)
-    {
-        $rev_ob = $rep->getRevisionObject();
-
-        if ($rev_ob->cmp($r1, $r2) == 1) {
-            $curr = $rev_ob->prev($r1);
-            $stop = $rev_ob->prev($r2);
-            $flip = true;
-        } else {
-            $curr = $r2;
-            $stop = $r1;
-            $flip = false;
-        }
-
-        $ret_array = array();
-
-        do {
-            $ret_array[] = $curr;
-            $curr = $rev_ob->prev($curr);
-            if ($curr == $stop) {
-                return ($flip) ? array_reverse($ret_array) : $ret_array;
-            }
-        } while ($rev_ob->cmp($curr, $stop) != -1);
-
-        return array();
-    }
-
-    /**
-     * Return the list of available diff types.
-     *
-     * @return array  The list of available diff types for use with get().
-     */
-    public function availableDiffTypes()
-    {
-        return $this->_diffTypes;
-    }
-}
-
-/**
- * @package Horde_VC
- */
-abstract class Horde_VC_Directory
-{
-    protected $_rep;
-    protected $_dirName;
-    protected $_files;
-    protected $_atticFiles;
-    protected $_mergedFiles;
-    protected $_dirs;
-    protected $_parent;
-    protected $_moduleName;
-
-    /**
-     * Create a Directory object to store information about the files in a
-     * single directory in the repository
-     *
-     * @param Horde_VC $rp            The Repository object this directory
-     *                                is part of.
-     * @param string $dn              Path to the directory.
-     * @param Horde_VC_Directory $pn  The parent Directory object to this one.
-     */
-    public function __construct($rep, $dn, $pn = '')
-    {
-        $this->_rep = $rep;
-        $this->_parent = $pn;
-        $this->_moduleName = $dn;
-        $this->_dirName = "/$dn";
-        $this->_dirs = $this->_files = array();
-    }
-
-    /**
-     * Return fully qualified pathname to this directory with no
-     * trailing /.
-     *
-     * @return Pathname of this directory
-     */
-    public function queryDir()
-    {
-        return $this->_dirName;
-    }
-
-    /**
-     * TODO
-     */
-    public function &queryDirList()
-    {
-        reset($this->_dirs);
-        return $this->_dirs;
-    }
-
-    /**
-     * TODO
-     */
-    public function &queryFileList($showattic = false)
-    {
-        return ($showattic && isset($this->_mergedFiles))
-            ? $this->_mergedFiles
-            : $this->_files;
-    }
-
-    /**
-     * TODO
-     */
-    abstract public function browseDir($cache = null, $quicklog = true,
-                                       $showattic = false);
-
-    /**
-     * Sort the contents of the directory in a given fashion and
-     * order.
-     *
-     * @param integer $how  Of the form SORT_* where * can be:
-     *                      NONE, NAME, AGE, REV for sorting by name, age or
-     *                      revision.
-     * @param integer $dir  Of the form SORT_* where * can be:
-     *                      ASCENDING, DESCENDING for the order of the sort.
-     */
-    public function applySort($how = Horde_VC::SORT_NONE,
-                              $dir = Horde_VC::SORT_ASCENDING)
-    {
-        // Always sort directories by name.
-        natcasesort($this->_dirs);
-
-        $this->_doFileSort($this->_files, $how);
-
-        if (isset($this->_atticFiles)) {
-            $this->_doFileSort($this->_atticFiles, $how);
-        }
-
-        if (isset($this->_mergedFiles)) {
-            $this->_doFileSort($this->_mergedFiles, $how);
-        }
-
-        if ($dir == Horde_VC::SORT_DESCENDING) {
-            $this->_dirs = array_reverse($this->_dirs);
-            $this->_files = array_reverse($this->_files);
-            if (isset($this->_mergedFiles)) {
-                $this->_mergedFiles = array_reverse($this->_mergedFiles);
-            }
-        }
-    }
-
-    /**
-     * TODO
-     */
-    protected function _doFileSort(&$fileList, $how = Horde_VC::SORT_NONE)
-    {
-        switch ($how) {
-        case Horde_VC::SORT_AGE:
-            usort($fileList, array($this, 'fileAgeSort'));
-            break;
-
-        case Horde_VC::SORT_NAME:
-            usort($fileList, array($this, 'fileNameSort'));
-            break;
-
-        case Horde_VC::SORT_AUTHOR:
-            usort($fileList, array($this, 'fileAuthorSort'));
-            break;
-
-        case Horde_VC::SORT_REV:
-            $this->_revob = $this->_rep->getRevisionObject();
-            usort($fileList, array($this, 'fileRevSort'));
-            break;
-
-        case Horde_VC::SORT_NONE:
-        default:
-            break;
-        }
-    }
-    /**
-     * Sort function for ascending age.
-     */
-    public function fileAgeSort($a, $b)
-    {
-        $aa = $a->queryLastLog();
-        $bb = $b->queryLastLog();
-        return ($aa->queryDate() == $bb->queryDate())
-            ? 0
-            : (($aa->queryDate() < $bb->queryDate()) ? 1 : -1);
-    }
-
-    /**
-     * Sort function by author name.
-     */
-    public function fileAuthorSort($a, $b)
-    {
-        $aa = $a->queryLastLog();
-        $bb = $b->queryLastLog();
-        return ($aa->queryAuthor() == $bb->queryAuthor())
-            ? 0
-            : (($aa->queryAuthor() > $bb->queryAuthor()) ? 1 : -1);
-    }
-
-    /**
-     * Sort function for ascending filename.
-     */
-    public function fileNameSort($a, $b)
-    {
-        return strcasecmp($a->name, $b->name);
-    }
-
-    /**
-     * Sort function for ascending revision.
-     */
-    public function fileRevSort($a, $b)
-    {
-        return $this->_revob->cmp($a->queryHead(), $b->queryHead());
-    }
-
-}
-
-/**
- * @package Horde_VC
- */
-class Horde_VC_File
-{
-    public $rep;
-    public $dir;
-    public $name;
-    public $logs;
-    public $revs;
-    public $head;
-    public $quicklog;
-    public $symrev;
-    public $revsym;
-    public $branches;
-    public $revob;
-
-    /**
-     * TODO
-     */
-    public function setRepository($rep)
-    {
-        $this->rep = $rep;
-    }
-
-    /**
-     * Has the file been deleted?
-     *
-     * @return boolean  Is this file deleted?
-     */
-    public function isDeleted()
-    {
-        return false;
-    }
-
-    /**
-     * Returns the name of the current file as in the repository
-     *
-     * @return string  Filename (without the path)
-     */
-    public function queryRepositoryName()
-    {
-        return $this->name;
-    }
-
-    /**
-     * Return the last revision of the current file on the HEAD branch
-     *
-     * @return Last revision of the current file
-     */
-    public function queryRevision()
-    {
-        if (!isset($this->revs[0])) {
-            return PEAR::raiseError('No revisions');
-        }
-        return $this->revs[0];
-    }
-
-    public function queryPreviousRevision($rev)
-    {
-        $last = false;
-        foreach ($this->revs as $entry) {
-            if ($last) {
-                return $entry;
-            }
-            if ($entry == $rev) {
-                $last = true;
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Return the HEAD (most recent) revision number for this file.
-     *
-     * @return HEAD revision number
-     */
-    public function queryHead()
-    {
-        return $this->queryRevision();
-    }
-
-   /**
-     * Return the last Horde_VC_Log object in the file.
-     *
-     * @return Horde_VC_Log of the last entry in the file
-     */
-    public function queryLastLog()
-    {
-        if (!isset($this->revs[0]) || !isset($this->logs[$this->revs[0]])) {
-            return PEAR::raiseError('No revisions');
-        }
-        return $this->logs[$this->revs[0]];
-    }
-
-    /**
-     * Sort the list of Horde_VC_Log objects that this file contains.
-     *
-     * @param integer $how  Horde_VC::SORT_REV (sort by revision),
-     *                      Horde_VC::SORT_NAME (sort by author name), or
-     *                      Horde_VC::SORT_AGE (sort by commit date).
-     */
-    public function applySort($how = Horde_VC::SORT_REV)
-    {
-        switch ($how) {
-        case Horde_VC::SORT_NAME:
-            $func = 'Name';
-            break;
-
-        case Horde_VC::SORT_AGE:
-            $func = 'Age';
-            break;
-
-        case Horde_VC::SORT_REV:
-        default:
-            $this->revob = $this->rep->getRevisionObject();
-            $func = 'Revision';
-            break;
-        }
-
-        uasort($this->logs, array($this, 'sortBy' . $func));
-        return true;
-    }
-
-    /**
-     * The sortBy*() functions are internally used by applySort.
-     */
-    public function sortByRevision($a, $b)
-    {
-        return $this->revob->cmp($b->rev, $a->rev);
-    }
-
-    public function sortByAge($a, $b)
-    {
-        return $b->date - $a->date;
-    }
-
-    public function sortByName($a, $b)
-    {
-        return strcmp($a->author, $b->author);
-    }
-
-    /**
-     * Return the fully qualified filename of this object.
-     *
-     * @return Fully qualified filename of this object
-     */
-    public function queryFullPath()
-    {
-        return $this->rep->sourceroot() . '/' . $this->queryModulePath();
-    }
-
-    /**
-     * Return the name of this file relative to its sourceroot.
-     *
-     * @return string  Pathname relative to the sourceroot.
-     */
-    public function queryModulePath()
-    {
-        return $this->dir . '/' . $this->name;
-    }
-
-}
-
-/**
- * Horde_VC patchset class.
- *
- * @package Horde_VC
- */
-class Horde_VC_Patchset
-{
-    protected $_rep;
-    protected $_patchsets = array();
-
-    public function setRepository($rep)
-    {
-        $this->_rep = $rep;
-    }
-}
-
-/**
- * Horde_VC revisions class.
- *
- * Copyright Anil Madhavapeddy, <anil@recoil.org>
- *
- * @author  Anil Madhavapeddy <anil@recoil.org>
- * @package Hored_VC
- */
-abstract class Horde_VC_Revision
-{
-    /**
-     * Given a revision number, remove a given number of portions from
-     * it. For example, if we remove 2 portions of 1.2.3.4, we are
-     * left with 1.2.
-     *
-     * @param string $val      Input revision
-     * @param integer $amount  Number of portions to strip
-     *
-     * @return string  Stripped revision number
-     */
-    abstract public function strip($val, $amount = 1);
-
-    /**
-     * The size of a revision number is the number of portions it has.
-     * For example, 1,2.3.4 is of size 4.
-     *
-     * @param string $val  Revision number to determine size of
-     *
-     * @return integer  Size of revision number
-     */
-    abstract public function sizeof($val);
-
-    /**
-     * Given two revision numbers, this figures out which one is
-     * greater than the other by stepping along the decimal points
-     * until a difference is found, at which point a sign comparison
-     * of the two is returned.
-     *
-     * @param string $rev1  Period delimited revision number
-     * @param string $rev2  Second period delimited revision number
-     *
-     * @return integer  1 if the first is greater, -1 if the second if greater,
-     *                  and 0 if they are equal
-     */
-    abstract public function cmp($rev1, $rev2);
-
-    /**
-     * Return the logical revision before this one. Normally, this
-     * will be the revision minus one, but in the case of a new
-     * branch, we strip off the last two decimal places to return the
-     * original branch point.
-     *
-     * @param string $rev  Revision number to decrement.
-     *
-     * @return string|boolean  Revision number, or false if none could be
-     *                         determined.
-     */
-    abstract public function prev($rev);
-}
diff --git a/framework/VC/lib/Horde/VC/Exception.php b/framework/VC/lib/Horde/VC/Exception.php
deleted file mode 100644 (file)
index 04c2a78..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-<?php
-class Horde_VC_Exception extends Exception {}
diff --git a/framework/VC/lib/Horde/VC/cvs.php b/framework/VC/lib/Horde/VC/cvs.php
deleted file mode 100644 (file)
index 51d790c..0000000
+++ /dev/null
@@ -1,968 +0,0 @@
-<?php
-
-require_once dirname(__FILE__) . '/rcs.php';
-
-/**
- * Horde_VC_cvs implementation.
- *
- * Copyright 2000-2008 The Horde Project (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (LGPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
- *
- * @author  Anil Madhavapeddy <anil@recoil.org>
- * @package Horde_VC
- */
-class Horde_VC_cvs extends Horde_VC_rcs
-{
-    /**
-     * Constructor.
-     *
-     * @param array $params  Any parameter the class expects.
-     *                       Current parameters:
-     * <pre>
-     * 'sourceroot': The source root for this repository
-     * 'paths': Hash with the locations of all necessary binaries: 'rcsdiff',
-     *          'rlog', 'cvsps', 'cvsps_home' and the temp path: 'temp'
-     * </pre>
-     */
-    public function __construct($params)
-    {
-        $this->_sourceroot = $params['sourceroot'];
-        $this->_paths = $params['paths'];
-        parent::__construct();
-    }
-
-    /**
-     * Returns the temporary file path.
-     *
-     * @return string  Temporary file path.
-     */
-    public function getTempPath()
-    {
-        return $this->_paths['temp'];
-    }
-
-    /**
-     * TODO
-     */
-    public function isFile($where)
-    {
-        return @is_file($where . ',v') ||
-               @is_file(dirname($where) . '/Attic/' . basename($where) . ',v');
-    }
-
-    /**
-     * TODO
-     */
-    public function getFileObject($filename, $cache = null, $quicklog = false)
-    {
-        if (substr($filename, 0, 1) != '/') {
-            $filename = '/' . $filename;
-        }
-        return parent::getFileObject($this->sourceroot() . $filename, $cache, $quicklog);
-    }
-
-    /**
-     * TODO
-     */
-    public function getAnnotateObject($filename)
-    {
-        return new Horde_VC_Annotate_cvs($this, $filename);
-    }
-
-    /**
-     * TODO
-     */
-    public function getPatchsetObject($filename, $cache = null)
-    {
-        return parent::getPatchsetObject($this->sourceroot() . '/' . $filename, $cache);
-    }
-
-    /**
-     * Validation function to ensure that a revision number is of the right
-     * form.
-     *
-     * @param mixed $rev  The purported revision number.
-     *
-     * @return boolean  True if it is a revision number.
-     */
-    public function isValidRevision($rev)
-    {
-           return $rev && preg_match('/^[\d\.]+$/', $rev);
-    }
-}
-
-/**
- * Horde_VC_cvs annotate class.
- *
- * Anil Madhavapeddy, <anil@recoil.org>
- *
- * @author  Anil Madhavapeddy <anil@recoil.org>
- * @package Horde_VC
- */
-class Horde_VC_Annotate_cvs extends Horde_VC_Annotate
-{
-    /**
-     * Temporary filename.
-     *
-     * @var string
-     */
-    protected $_tmpfile;
-
-    /**
-     * Constructor.
-     *
-     * TODO
-     */
-    public function __construct($rep, $file)
-    {
-        $this->_tmpfile = Util::getTempFile('vc', true, $rep->getTempPath());
-        parent::__construct($rep, $file);
-    }
-
-    /**
-     * TODO
-     */
-    public function doAnnotate($rev)
-    {
-        if (is_a($this->_file, 'PEAR_Error') ||
-            is_a($this->_rep, 'PEAR_Error') ||
-            !$this->_rep->isValidRevision($rev)) {
-            return false;
-        }
-
-        $where = $this->_file->queryModulePath();
-        $sourceroot = $this->_rep->sourceroot();
-
-        $pipe = popen($this->_rep->getPath('cvs') . ' -n server > ' . $this->_tmpfile, VC_WINDOWS ? 'wb' : 'w');
-
-        $out = array(
-            "Root $sourceroot",
-            'Valid-responses ok error Valid-requests Checked-in Updated Merged Removed M E',
-            'UseUnchanged',
-            'Argument -r',
-            "Argument $rev",
-            "Argument $where"
-        );
-
-        $dirs = explode('/', dirname($where));
-        while (count($dirs)) {
-            $out[] = 'Directory ' . implode('/', $dirs);
-            $out[] = "$sourceroot/" . implode('/', $dirs);
-            array_pop($dirs);
-        }
-
-        $out[] = 'Directory .';
-        $out[] = $sourceroot;
-        $out[] = 'annotate';
-
-        foreach ($out as $line) {
-            fwrite($pipe, "$line\n");
-        }
-        pclose($pipe);
-
-        if (!($fl = fopen($this->_tmpfile, VC_WINDOWS ? 'rb' : 'r'))) {
-            return false;
-        }
-
-        $lines = array();
-        $line = fgets($fl, 4096);
-
-        // Windows versions of cvs always return $where with forwards slashes.
-        if (VC_WINDOWS) {
-            $where = str_replace(DIRECTORY_SEPARATOR, '/', $where);
-        }
-
-        while ($line && !preg_match("|^E\s+Annotations for $where|", $line)) {
-            $line = fgets($fl, 4096);
-        }
-
-        if (!$line) {
-            return PEAR::raiseError('Unable to annotate; server said: ' . $line);
-        }
-
-        $lineno = 1;
-        while ($line = fgets($fl, 4096)) {
-            if (preg_match('/^M\s+([\d\.]+)\s+\((.+)\s+(\d+-\w+-\d+)\):.(.*)$/', $line, $regs)) {
-                $lines[] = array(
-                    'rev' => $regs[1],
-                    'author' => trim($regs[2]),
-                    'date' => $regs[3],
-                    'line' => $regs[4],
-                    'lineno' => $lineno++
-                );
-            }
-        }
-
-        fclose($fl);
-        return $lines;
-    }
-
-}
-
-/**
- * Horde_VC_cvs checkout class.
- *
- * Anil Madhavapeddy, <anil@recoil.org>
- *
- * @author  Anil Madhavapeddy <anil@recoil.org>
- * @package Horde_VC
- */
-class Horde_VC_Checkout_cvs extends Horde_VC_Checkout
-{
-    /**
-     * Static function which returns a file pointing to the head of the
-     * requested revision of an RCS file.
-     *
-     * @param Horde_VC_cvs $rep  A repository object
-     * @param string $fullname   Fully qualified pathname of the desired file
-     *                           to checkout
-     * @param string $rev        Revision number to check out
-     *
-     * @return resource|object  Either a PEAR_Error object, or a stream
-     *                          pointer to the head of the checkout
-     */
-    public function get($rep, $fullname, $rev)
-    {
-        if (!$rep->isValidRevision($rev)) {
-            return PEAR::raiseError('Invalid revision number');
-        }
-
-        if (VC_WINDOWS) {
-            $mode = 'rb';
-            $q_name = '"' . escapeshellcmd(str_replace('\\', '/', $fullname)) . '"';
-        } else {
-            $mode = 'r';
-            $q_name = escapeshellarg($fullname);
-        }
-
-        if (!($RCS = popen($rep->getPath('co') . " -p$rev $q_name 2>&1", $mode))) {
-            return PEAR::raiseError('Couldn\'t perform checkout of the requested file');
-        }
-
-        /* First line from co should be of the form :
-         * /path/to/filename,v  -->  standard out
-         * and we check that this is the case and error otherwise
-         */
-
-        $co = fgets($RCS, 1024);
-        if (!preg_match('/^([\S ]+),v\s+-->\s+st(andar)?d ?out(put)?\s*$/', $co, $regs) || $regs[1].',v' != $fullname) {
-            return PEAR::raiseError('Unexpected output from checkout: ' . $co);
-        }
-
-        /* Next line from co is of the form:
-         * revision 1.2.3
-         * TODO: compare this to $rev for consistency, atm we just
-         *       discard the value to move input pointer along - avsm
-         */
-        $co = fgets($RCS, 1024);
-
-        return $RCS;
-    }
-
-}
-
-/**
- * Horde_VC_cvs diff class.
- *
- * Copyright Anil Madhavapeddy, <anil@recoil.org>
- *
- * @author  Anil Madhavapeddy <anil@recoil.org>
- * @package Horde_VC
- */
-class Horde_VC_Diff_cvs extends Horde_VC_Diff
-{
-    /**
-     * Obtain the differences between two revisions of a file.
-     *
-     * @param Horde_VC $rep        A repository object.
-     * @param Horde_VC_File $file  The desired file.
-     * @param string $rev1         Original revision number to compare from.
-     * @param string $rev2         New revision number to compare against.
-     * @param string $type         The type of diff (e.g. 'unified').
-     * @param integer $num         Number of lines to be used in context and
-     *                             unified diffs.
-     * @param boolean $ws          Show whitespace in the diff?
-     *
-     * @return string|boolean  False on failure, or a string containing the
-     *                         diff on success.
-     */
-    public function get($rep, $file, $rev1, $rev2, $type = 'context',
-                        $num = 3, $ws = true)
-    {
-        /* Make sure that the file parameter is valid. */
-        if (is_a($file, 'PEAR_Error')) {
-            return false;
-        }
-
-        /* Check that the revision numbers are valid. */
-        $rev1 = $rep->isValidRevision($rev1) ? $rev1 : '1.1';
-        $rev2 = $rep->isValidRevision($rev1) ? $rev2 : '1.1';
-
-        $fullName = $file->queryFullPath();
-        $diff = array();
-        $options = '-kk ';
-        if (!$ws) {
-            $opts = ' -bB ';
-            $options .= $opts;
-        } else {
-            $opts = '';
-        }
-
-        switch ($type) {
-        case 'context':
-            $options = $opts . '-p --context=' . (int)$num;
-            break;
-
-        case 'unified':
-            $options = $opts . '-p --unified=' . (int)$num;
-            break;
-
-        case 'column':
-            $options = $opts . '--side-by-side --width=120';
-            break;
-
-        case 'ed':
-            $options = $opts . '-e';
-            break;
-        }
-
-        // Windows versions of cvs always return $where with forwards slashes.
-        if (VC_WINDOWS) {
-            $fullName = str_replace(DIRECTORY_SEPARATOR, '/', $fullName);
-        }
-
-        // TODO: add options for $hr options - however these may not be
-        // compatible with some diffs.
-        $command = $rep->getPath('rcsdiff') . " $options -r$rev1 -r$rev2 \"" . escapeshellcmd($fullName) . '" 2>&1';
-        if (VC_WINDOWS) {
-            $command .= ' < "' . __FILE__ . '"';
-        }
-
-        exec($command, $diff, $retval);
-        return ($retval > 0) ? $diff : array();
-    }
-
-}
-
-/**
- * Horde_VC_cvs directory class.
- *
- * Copyright Anil Madhavapeddy, <anil@recoil.org>
- *
- * @author  Anil Madhavapeddy <anil@recoil.org>
- * @package Horde_VC
- */
-class Horde_VC_Directory_cvs extends Horde_VC_Directory
-{
-    /**
-     * Creates a CVS Directory object to store information
-     * about the files in a single directory in the repository.
-     *
-     * @param Horde_VC $rep           A repository object
-     * @param string $dn              Path to the directory.
-     * @param Horde_VC_Directory $pn  The parent Directory object to this one.
-     */
-    public function __construct($rep, $dn, $pn = '')
-    {
-        parent::__construct($rep, $dn, $pn);
-        $this->_dirName = $rep->sourceroot() . "/$dn";
-    }
-
-    /**
-     * Tell the object to open and browse its current directory, and
-     * retrieve a list of all the objects in there.  It then populates
-     * the file/directory stack and makes it available for retrieval.
-     *
-     * @return boolean|object  PEAR_Error object on an error, true on success.
-     */
-    public function browseDir($cache = null, $quicklog = true,
-                              $showattic = false)
-    {
-        /* Make sure we are trying to list a directory */
-        if (!@is_dir($this->_dirName)) {
-            return PEAR::raiseError('Unable to find directory ' . $this->_dirName);
-        }
-
-        /* Open the directory for reading its contents */
-        if (!($DIR = @opendir($this->_dirName))) {
-            return PEAR::raiseError(empty($php_errormsg) ? 'Permission denied' : $php_errormsg);
-        }
-
-        /* Create two arrays - one of all the files, and the other of
-         * all the directories. */
-        while (($name = readdir($DIR)) !== false) {
-            if (($name == '.') || ($name == '..')) {
-                continue;
-            }
-
-            $path = $this->_dirName . '/' . $name;
-            if (@is_dir($path)) {
-                /* Skip Attic directory. */
-                if ($name != 'Attic') {
-                    $this->_dirs[] = $name;
-                }
-            } elseif (@is_file($path) && (substr($name, -2) == ',v')) {
-                /* Spawn a new file object to represent this file. */
-                $fl = $this->_rep->getFileObject(substr($path, strlen($this->_rep->sourceroot()), -2), $cache, $quicklog);
-                if (is_a($fl, 'PEAR_Error')) {
-                    return $fl;
-                } else {
-                    $this->_files[] = $fl;
-                }
-            }
-        }
-
-        /* Close the filehandle; we've now got a list of dirs and files. */
-        closedir($DIR);
-
-        /* If we want to merge the attic, add it in here. */
-        if ($showattic) {
-            $atticDir = new Horde_VC_Directory_cvs($this->_rep, $this->_moduleName . '/Attic', $this);
-            if (!is_a($atticDir->browseDir($cache, $quicklog), 'PEAR_Error')) {
-                $this->_atticFiles = $atticDir->queryFileList();
-                $this->_mergedFiles = array_merge($this->_files, $this->_atticFiles);
-            }
-        }
-
-        return true;
-    }
-
-}
-
-/**
- * Horde_VC_cvs file class.
- *
- * Copyright Anil Madhavapeddy, <anil@recoil.org>
- *
- * @author  Anil Madhavapeddy <anil@recoil.org>
- * @package Horde_VC
- */
-class Horde_VC_File_cvs extends Horde_VC_File
-{
-    /**
-     * Create a repository file object, and give it information about
-     * what its parent directory and repository objects are.
-     *
-     * @param string $fl  Full path to this file.
-     */
-    public function __construct($rep, $fl, $cache = null, $quicklog = false)
-    {
-        $fl .= ',v';
-        $this->rep = $rep;
-        $this->name = basename($fl);
-        $this->dir = dirname($fl);
-        $this->filename = $fl;
-        $this->cache = $cache;
-        $this->quicklog = $quicklog;
-        $this->logs = $this->revs = $this->branches = array();
-    }
-
-    function &getFileObject()
-    {
-        /* Assume file is in the Attic if it doesn't exist. */
-        $filename = $this->filename;
-        if (!@is_file($filename . ',v')) {
-            $filename = dirname($filename) . '/Attic/' . basename($filename);
-        }
-
-        /* The version of the cached data. Increment this whenever the
-         * internal storage format changes, such that we must
-         * invalidate prior cached data. */
-        $cacheVersion = 2;
-        $cacheId = $this->rep->sourceroot() . '_n' . $filename . '_f' . (int)$this->quicklog . '_v' . $cacheVersion;
-
-        $ctime = time() - filemtime($filename . ',v');
-        if ($this->cache &&
-            $this->cache->exists($cacheId, $ctime)) {
-            $fileOb = unserialize($this->cache->get($cacheId, $ctime));
-            $fileOb->setRepository($this->rep);
-        } else {
-            $fileOb = new Horde_VC_File_cvs($this->rep, $filename, $this->cache, $this->quicklog);
-            $fileOb->setRepository($this->rep);
-            if (is_a(($result = $fileOb->getBrowseInfo()), 'PEAR_Error')) {
-                return $result;
-            }
-            $fileOb->applySort(Horde_VC::SORT_AGE);
-
-            if ($this->cache) {
-                $this->cache->set($cacheId, serialize($fileOb));
-            }
-        }
-
-        return $fileOb;
-    }
-
-    /**
-     * If this file is present in an Attic directory, this indicates it.
-     *
-     * @return boolean  True if file is in the Attic, and false otherwise
-     */
-    function isDeleted()
-    {
-        return substr($this->dir, -5) == 'Attic';
-    }
-
-    /**
-     * Returns name of the current file without the repository
-     * extensions (usually ,v)
-     *
-     * @return string  Filename without repository extension
-     */
-    function queryName()
-    {
-        return preg_replace('/,v$/', '', $this->name);
-    }
-
-
-    function queryPreviousRevision($rev)
-    {
-        $ob = $this->rep->getRevisionObject();
-        return $ob->prev($rev);
-    }
-
-    /**
-     * Return the HEAD (most recent) revision number for this file.
-     *
-     * @return string  HEAD revision number
-     */
-    function queryHead()
-    {
-        return $this->head;
-    }
-
-    /**
-     * Populate the object with information about the revisions logs and dates
-     * of the file.
-     *
-     * @return boolean|object  PEAR_Error object on error, or true on success
-     */
-    function getBrowseInfo()
-    {
-        /* Check that we are actually in the filesystem. */
-        $file = $this->queryFullPath();
-        if (!is_file($file)) {
-            return PEAR::raiseError('File Not Found: ' . $file);
-        }
-
-        /* Call the RCS rlog command to retrieve the file
-         * information. */
-        $flag = $this->quicklog ? ' -r ' : ' ';
-        $q_file = VC_WINDOWS ? '"' . escapeshellcmd($file) . '"' : escapeshellarg($file);
-
-        $cmd = $this->rep->getPath('rlog') . $flag . $q_file;
-        exec($cmd, $return_array, $retval);
-
-        if ($retval) {
-            return PEAR::raiseError('Failed to spawn rlog to retrieve file log information for ' . $file);
-        }
-
-        $accum = array();
-        $symrev = array();
-        $revsym = array();
-        $state = 'init';
-        foreach ($return_array as $line) {
-            switch ($state) {
-            case 'init':
-                if (!strncmp('head: ', $line, 6)) {
-                    $this->head = substr($line, 6);
-                } elseif (!strncmp('branch:', $line, 7)) {
-                    $state = 'rev';
-                }
-                break;
-
-            case 'rev':
-                if (!strncmp('----------', $line, 10)) {
-                    $state = 'info';
-                    $this->symrev = $symrev;
-                    $this->revsym = $revsym;
-                } elseif (preg_match("/^\s+([^:]+):\s+([\d\.]+)/", $line, $regs)) {
-                    // Check to see if this is a branch
-                    if (preg_match('/^(\d+(\.\d+)+)\.0\.(\d+)$/', $regs[2])) {
-                        $branchRev = $this->toBranch($regs[2]);
-                        if (!isset($this->branches[$branchRev])) {
-                            $this->branches[$branchRev] = $regs[1];
-                        }
-                    } else {
-                        $symrev[$regs[1]] = $regs[2];
-                        if (empty($revsym[$regs[2]])) {
-                            $revsym[$regs[2]] = array();
-                        }
-                        $revsym[$regs[2]][] = $regs[1];
-                    }
-                }
-                break;
-
-            case 'info':
-                if (strncmp('==============================', $line, 30) &&
-                    strcmp('----------------------------', $line)) {
-                    $accum[] = $line;
-                } elseif (count($accum)) {
-                    // spawn a new Horde_VC_log object and add it to the logs
-                    // hash
-                    $log = new Horde_VC_Log_cvs($this);
-                    $err = $log->processLog($accum);
-                    // TODO: error checks - avsm
-                    $this->logs[$log->queryRevision()] = $log;
-                    $this->revs[] = $log->queryRevision();
-                    $accum = array();
-                }
-                break;
-            }
-        }
-
-        return true;
-    }
-
-    /**
-     * Return the fully qualified filename of this object.
-     *
-     * @return Fully qualified filename of this object
-     */
-    function queryFullPath()
-    {
-        return $this->dir . '/' . $this->name;
-    }
-
-    /**
-     * Return the name of this file relative to its sourceroot.
-     *
-     * @return string  Pathname relative to the sourceroot.
-     */
-    function queryModulePath()
-    {
-        return preg_replace('|^'. $this->rep->sourceroot() . '/?(.*),v$|', '\1', $this->queryFullPath());
-    }
-
-    /**
-     * Given a revision number of the form x.y.0.z, this remaps it
-     * into the appropriate branch number, which is x.y.z
-     *
-     * @param string $rev  Even-digit revision number of a branch
-     *
-     * @return string  Odd-digit Branch number
-     */
-    function toBranch($rev)
-    {
-        /* Check if we have a valid revision number */
-        $rev_ob = $this->rep->getRevisionObject();
-        if (!$rev_ob->valid($rev)) {
-            return false;
-        }
-
-        if (($end = strrpos($rev, '.')) === false) {
-            return false;
-        }
-
-        $rev[$end] = 0;
-        if (($end2 = strrpos($rev, '.')) === false) {
-            return substr($rev, ++$end);
-        }
-
-        return substr_replace($rev, '.', $end2, ($end - $end2 + 1));
-    }
-
-}
-
-/**
- * Horde_VC_cvs log class.
- *
- * @author  Anil Madhavapeddy <anil@recoil.org>
- * @package Horde_VC
- */
-class Horde_VC_Log_cvs {
-
-    var $rep;
-    var $file;
-    var $tags;
-    var $rev;
-    var $date;
-    var $log;
-    var $author;
-    var $state;
-    var $lines;
-    var $branches;
-
-    /**
-     *
-     */
-    public function __construct($fl)
-    {
-        $this->file = $fl;
-        $this->branches = array();
-    }
-
-    function processLog($raw)
-    {
-        /* Initialise a simple state machine to parse the output of rlog */
-        $state = 'init';
-        while (!empty($raw) && $state != 'done') {
-            switch ($state) {
-            /* Found filename, now looking for the revision number */
-            case 'init':
-                $line = array_shift($raw);
-                if (preg_match("/revision (.+)$/", $line, $parts)) {
-                    $this->rev = $parts[1];
-                    $state = 'date';
-                }
-                break;
-
-            /* Found revision and filename, now looking for date */
-            case 'date':
-                $line = array_shift($raw);
-                if (preg_match("|^date:\s+(\d+)[-/](\d+)[-/](\d+)\s+(\d+):(\d+):(\d+).*?;\s+author:\s+(.+);\s+state:\s+(\S+);(\s+lines:\s+([0-9\s+-]+))?|", $line, $parts)) {
-                    $this->date = gmmktime($parts[4], $parts[5], $parts[6], $parts[2], $parts[3], $parts[1]);
-                    $this->author = $parts[7];
-                    $this->state = $parts[8];
-                    $this->lines = isset($parts[10]) ? $parts[10] : '';
-                    $state = 'branches';
-                }
-                break;
-
-            /* Look for a branch point here - format is 'branches:
-             * x.y.z; a.b.c;' */
-            case 'branches':
-                /* If we find a branch tag, process and pop it,
-                   otherwise leave input stream untouched */
-                if (!empty($raw) && preg_match("/^branches:\s+(.*)/", $raw[0], $br)) {
-                    /* Get the list of branches from the string, and
-                     * push valid revisions into the branches array */
-                    $brs = preg_split('/;\s*/', $br[1]);
-                    foreach ($brs as $brpoint) {
-                        //@TODO
-                        //if (Horde_VC_Revision::valid($brpoint)) {
-                            $this->branches[] = $brpoint;
-                        //}
-                    }
-                    array_shift($raw);
-                }
-
-                $state = 'done';
-                break;
-            }
-        }
-
-        /* Assume the rest of the lines are the log message */
-        $this->log = implode("\n", $raw);
-        $this->tags = isset($this->file->revsym[$this->rev]) ?
-            $this->file->revsym[$this->rev] :
-            array();
-    }
-
-    function queryDate()
-    {
-        return $this->date;
-    }
-
-    function queryRevision()
-    {
-        return $this->rev;
-    }
-
-    function queryAuthor()
-    {
-        return $this->author;
-    }
-
-    function queryLog()
-    {
-        return $this->log;
-    }
-
-    function queryChangedLines()
-    {
-        return isset($this->lines) ? ($this->lines) : '';
-    }
-
-    /**
-     * Given a branch revision number, this function remaps it
-     * accordingly, and performs a lookup on the file object to
-     * return the symbolic name(s) of that branch in the tree.
-     *
-     * @return array  Hash of symbolic names => branch numbers
-     */
-    function querySymbolicBranches()
-    {
-        $symBranches = array();
-        foreach ($this->branches as $branch) {
-            $parts = explode('.', $branch);
-            $last = array_pop($parts);
-            $parts[] = '0';
-            $parts[] = $last;
-            $rev = implode('.', $parts);
-            if (isset($this->file->branches[$branch])) {
-                $symBranches[$this->file->branches[$branch]] = $branch;
-            }
-        }
-        return $symBranches;
-    }
-
-}
-
-/**
- * Horde_VC_cvs Patchset class.
- *
- * Copyright Anil Madhavapeddy, <anil@recoil.org>
- *
- * @author  Anil Madhavapeddy <anil@recoil.org>
- * @package Horde_VC
- */
-class Horde_VC_Patchset_cvs extends Horde_VC_Patchset {
-
-    var $_dir;
-    var $_name;
-
-    /**
-     * Create a patchset object.
-     *
-     * @param string $file  The filename to get patchsets for.
-     */
-    public function __construct($file)
-    {
-        $this->_name = basename($file);
-        $this->_dir = dirname($file);
-    }
-
-    function &getPatchsetObject($rep, $filename, $cache = null)
-    {
-        /* The version of the cached data. Increment this whenever the
-         * internal storage format changes, such that we must
-         * invalidate prior cached data. */
-        $cacheVersion = 1;
-        $cacheId = $rep->sourceroot() . '_n' . $filename . '_f_v' . $cacheVersion;
-
-        $ctime = time() - filemtime($filename . ',v');
-        if ($cache &&
-            $cache->exists($cacheId, $ctime)) {
-            $psOb = unserialize($cache->get($cacheId, $ctime));
-            $psOb->setRepository($rep);
-        } else {
-            $psOb = new Horde_VC_Patchset_cvs($filename);
-            $psOb->setRepository($rep);
-            if (is_a(($result = $psOb->getPatchsets()), 'PEAR_Error')) {
-                return $result;
-            }
-
-            if ($cache) {
-                $cache->set($cacheId, serialize($psOb));
-            }
-        }
-
-        return $psOb;
-    }
-
-    /**
-     * Populate the object with information about the patchsets that
-     * this file is involved in.
-     *
-     * @return boolean|object  PEAR_Error object on error, or true on success.
-     */
-    function getPatchsets()
-    {
-        /* Check that we are actually in the filesystem. */
-        if (!is_file($this->getFullPath() . ',v')) {
-            return PEAR::raiseError('File Not Found');
-        }
-
-        /* Call cvsps to retrieve all patchsets for this file. */
-        $q_root = $this->_rep->sourceroot();
-        $q_root = VC_WINDOWS ? '"' . escapeshellcmd($q_root) . '"' : escapeshellarg($q_root);
-
-        $cvsps_home = $this->_rep->getPath('cvsps_home');
-        $HOME = !empty($cvsps_home) ?
-            'HOME=' . $cvsps_home . ' ' :
-            '';
-
-        $cmd = $HOME . $this->_rep->getPath('cvsps') . ' -u --cvs-direct --root ' . $q_root . ' -f ' . escapeshellarg($this->_name) . ' ' . escapeshellarg($this->_dir);
-        exec($cmd, $return_array, $retval);
-        if ($retval) {
-            return PEAR::raiseError('Failed to spawn cvsps to retrieve patchset information');
-        }
-
-        $this->_patchsets = array();
-        $state = 'begin';
-        foreach ($return_array as $line) {
-            $line = trim($line);
-            if ($line == '---------------------') {
-                $state = 'begin';
-                continue;
-            }
-
-            switch ($state) {
-            case 'begin':
-                $id = str_replace('PatchSet ', '', $line);
-                $this->_patchsets[$id] = array();
-                $state = 'info';
-                break;
-
-            case 'info':
-                $info = explode(':', $line, 2);
-                switch ($info[0]) {
-                case 'Date':
-                    if (preg_match('|(\d{4})/(\d{2})/(\d{2}) (\d{2}):(\d{2}):(\d{2})|', $info[1], $date)) {
-                        $this->_patchsets[$id]['date'] = gmmktime($date[4], $date[5], $date[6], $date[2], $date[3], $date[1]);
-                    }
-                    break;
-
-                case 'Author':
-                    $this->_patchsets[$id]['author'] = trim($info[1]);
-                    break;
-
-                case 'Branch':
-                    if (trim($info[1]) != 'HEAD') {
-                        $this->_patchsets[$id]['branch'] = trim($info[1]);
-                    }
-                    break;
-
-                case 'Tag':
-                    if (trim($info[1]) != '(none)') {
-                        $this->_patchsets[$id]['tag'] = trim($info[1]);
-                    }
-                    break;
-
-                case 'Log':
-                    $state = 'log';
-                    $this->_patchsets[$id]['log'] = '';
-                    break;
-                }
-                break;
-
-            case 'log':
-                if ($line == 'Members:') {
-                    $state = 'members';
-                    $this->_patchsets[$id]['log'] = trim($this->_patchsets[$id]['log']);
-                    $this->_patchsets[$id]['members'] = array();
-                } else {
-                    $this->_patchsets[$id]['log'] .= $line . "\n";
-                }
-                break;
-
-            case 'members':
-                if (!empty($line)) {
-                    $parts = explode(':', $line);
-                    $revs = explode('->', $parts[1]);
-                    $this->_patchsets[$id]['members'][] = array('file' => $parts[0],
-                                                                'from' => $revs[0],
-                                                                'to' => $revs[1]);
-                }
-                break;
-            }
-        }
-
-        return true;
-    }
-
-    /**
-     * Return the fully qualified filename of this object.
-     *
-     * @return string  Fully qualified filename of this object
-     */
-    function getFullPath()
-    {
-        return $this->_dir . '/' . $this->_name;
-    }
-
-}
-
-class Horde_VC_Revision_cvs extends Horde_VC_Revision_rcs {}
diff --git a/framework/VC/lib/Horde/VC/git.php b/framework/VC/lib/Horde/VC/git.php
deleted file mode 100644 (file)
index cc33f5e..0000000
+++ /dev/null
@@ -1,665 +0,0 @@
-<?php
-/**
- * Horde_VC_git implementation.
- *
- * Copyright 2008 The Horde Project (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (LGPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
- *
- * @author  Chuck Hagenbuch <chuck@horde.org>
- * @author  Michael Slusarz <slusarz@horde.org>
- * @package Horde_VC
- */
-class Horde_VC_git extends Horde_VC
-{
-    /**
-     * Constructor.
-     *
-     * @param array $params  Any parameter the class expects.
-     *                       Current parameters:
-     *                       'sourceroot': The source root for this
-     *                                     repository
-     *                       'paths': Hash with the locations of all
-     *                                necessary binaries: 'git'
-     */
-    public function __construct($params)
-    {
-        $this->_sourceroot = escapeshellcmd($params['sourceroot']);
-        $this->_paths = $params['paths'];
-        parent::__construct();
-    }
-
-    /**
-     * Validation function to ensure that a revision number is of the right
-     * form.
-     *
-     * @param mixed $rev  The purported revision number.
-     *
-     * @return boolean  True if it is a revision number.
-     */
-    public function isValidRevision($rev)
-    {
-        return preg_match('/^[a-f0-9]+$/i', $rev);
-    }
-
-    public function getCommand()
-    {
-        return $this->getPath('git') . ' --git-dir=' . $this->_sourceroot;
-    }
-
-}
-
-/**
- * Horde_VC_git annotate class.
- *
- * Chuck Hagenbuch <chuck@horde.org>
- *
- * @author  Chuck Hagenbuch <chuck@horde.org>
- * @package Horde_VC
- */
-class Horde_VC_Annotate_git extends Horde_VC_Annotate
-{
-    public function __construct($rep, $file)
-    {
-        if (is_a($file, 'PEAR_Error')) {
-            throw new Horde_VC_Exception($file->getMessage());
-        }
-        parent::__construct($rep, $file);
-    }
-
-    /**
-     * TODO
-     */
-    public function doAnnotate($rev)
-    {
-        $this->_rep->assertValidRevision($rev);
-
-        $command = $this->_rep->getCommand() . ' blame -p ' . $rev . ' -- ' . escapeshellarg($this->_file->queryModulePath()) . ' 2>&1';
-        $pipe = popen($command, 'r');
-        if (!$pipe) {
-            return PEAR::raiseError('Failed to execute git annotate: ' . $command);
-        }
-
-        $curr_rev = null;
-        $db = $lines = array();
-        $lines_group = $line_num = 0;
-
-        while (!feof($pipe)) {
-            $line = rtrim(fgets($pipe, 4096));
-            if (!$line) {
-                continue;
-            }
-
-            if ($line[0] == "\t") {
-                $lines[] = array(
-                    'author' => $db[$curr_rev]['author'] . ' ' . $db[$curr_rev]['author-mail'],
-                    'date' => $db[$curr_rev]['author-time'],
-                    'line' => substr($line, 1),
-                    'lineno' => $line_num++,
-                    'rev' => $curr_rev
-                );
-                --$lines_group;
-            } elseif ($line != 'boundary') {
-                if ($lines_group) {
-                    list($prefix, $linedata) = explode(' ', $line, 2);
-                    switch ($prefix) {
-                    case 'author':
-                    case 'author-mail':
-                    case 'author-time':
-                    //case 'author-tz':
-                        $db[$curr_rev][$prefix] = trim($linedata);
-                        break;
-                    }
-                } else {
-                    list($curr_rev, , $line_num, $lines_group) = explode(' ', $line);
-                }
-            }
-        }
-
-        pclose($pipe);
-        return $lines;
-    }
-
-}
-
-/**
- * Horde_VC_git checkout class.
- *
- * Chuck Hagenbuch <chuck@horde.org>
- *
- * @author  Chuck Hagenbuch <chuck@horde.org>
- * @package Horde_VC
- */
-class Horde_VC_Checkout_git extends Horde_VC_Checkout
-{
-    /**
-     * Function which returns a file pointing to the head of the requested
-     * revision of an SVN file.
-     *
-     * @param Horde_VC $rep     A repository object
-     * @param string $fullname  Fully qualified pathname of the desired file
-     *                          to checkout
-     * @param string $rev       Revision number to check out
-     *
-     * @return resource|object  Either a PEAR_Error object, or a stream
-     *                          pointer to the head of the checkout.
-     */
-    function get($rep, $file, $rev)
-    {
-        $rep->assertValidRevision($rev);
-
-        return ($pipe = popen($rep->getCommand() . ' cat-file blob ' . $file->getHashForRevision($rev) . ' 2>&1', VC_WINDOWS ? 'rb' : 'r'))
-            ? $pipe
-            : PEAR::raiseError('Couldn\'t perform checkout of the requested file');
-    }
-
-}
-
-/**
- * Horde_VC_git diff class.
- *
- * Copyright Chuck Hagenbuch <chuck@horde.org>
- *
- * @author  Chuck Hagenbuch <chuck@horde.org>
- * @package Horde_VC
- */
-class Horde_VC_Diff_git extends Horde_VC_Diff
-{
-    /**
-     * The available diff types.
-     *
-     * @var array
-     */
-    protected $_diffTypes = array('unified');
-
-    /**
-     * Obtain the differences between two revisions of a file.
-     *
-     * @param Horde_VC $rep        A repository object.
-     * @param Horde_VC_File $file  The desired file.
-     * @param string $rev1         Original revision number to compare from.
-     * @param string $rev2         New revision number to compare against.
-     * @param string $type         The type of diff (e.g. 'unified').
-     * @param integer $num         Number of lines to be used in context and
-     *                             unified diffs.
-     * @param boolean $ws          Show whitespace in the diff?
-     *
-     * @return string|boolean  False on failure, or a string containing the
-     *                         diff on success.
-     */
-    public function get($rep, $file, $rev1, $rev2, $type = 'context',
-                               $num = 3, $ws = true)
-    {
-        /* Make sure that the file parameter is valid */
-        if (is_a($file, 'PEAR_Error')) {
-            return false;
-        }
-
-        /* Check that the revision numbers are valid */
-        $rev1 = $rep->isValidRevision($rev1) ? $rev1 : 0;
-        $rev2 = $rep->isValidRevision($rev1) ? $rev2 : 0;
-
-        $diff = array();
-        $options = '';
-        if (!$ws) {
-            $options .= ' -b -w ';
-        }
-
-        switch ($type) {
-        case 'unified':
-            $options .= '--unified=' . (int)$num;
-            break;
-        }
-
-        // TODO: add options for $hr options - however these may not
-        // be compatible with some diffs.
-        $command = $rep->getCommand() . " diff -M -C $options --no-color $rev1..$rev2 -- \"" . $file->queryModulePath() . '" 2>&1';
-
-        exec($command, $diff, $retval);
-        return $diff;
-    }
-
-    /**
-     * Create a range of revisions between two revision numbers.
-     *
-     * @param Horde_VC $rep        A repository object.
-     * @param Horde_VC_File $file  The desired file.
-     * @param string $r1           The initial revision.
-     * @param string $r2           The ending revision.
-     *
-     * @return array  The revision range, or empty if there is no straight
-     *                line path between the revisions.
-     */
-    public function getRevisionRange($rep, $file, $r1, $r2)
-    {
-        $cmd = $rep->getCommand() . ' rev-list ' . $r1 . '..' . $r2;
-        $pipe = popen($cmd, 'r');
-        if (!is_resource($pipe)) {
-            throw new Horde_VC_Exception('Unable to run ' . $cmd . ': ' . error_get_last());
-        }
-
-        $revs = array();
-
-        while (!feof($pipe)) {
-            if ($rev = trim(fgets($pipe, 4096))) {
-                $revs[] = $rev;
-            }
-        }
-
-        $revs[] = $r1;
-        pclose($pipe);
-
-        return $revs;
-    }
-}
-
-/**
- * Horde_VC_git directory class.
- *
- * Copyright Chuck Hagenbuch <chuck@horde.org>
- *
- * @author  Chuck Hagenbuch <chuck@horde.org>
- * @package Horde_VC
- */
-class Horde_VC_Directory_git extends Horde_VC_Directory
-{
-    /**
-     * Tell the object to open and browse its current directory, and
-     * retrieve a list of all the objects in there.  It then populates
-     * the file/directory stack and makes it available for retrieval.
-     *
-     * @return PEAR_Error object on an error, 1 on success.
-     */
-    public function browseDir($cache = null, $quicklog = true,
-                              $showattic = false)
-    {
-        //@TODO For now, we're browsing HEAD
-        $head = trim(shell_exec($this->_rep->getCommand() . ' rev-parse --verify HEAD'));
-        //@TODO can use this to see if we have a valid cache of the tree at this revision
-
-        $dir = $this->queryDir();
-        if (substr($dir, 0, 1) == '/') {
-            $dir = substr($dir, 1);
-        }
-        if (strlen($dir) && substr($dir, -1) != '/') {
-            $dir .= '/';
-        }
-
-        $cmd = $this->_rep->getCommand() . ' ls-tree --full-name ' . $head . ' ' . escapeshellarg($dir) . ' 2>&1';
-
-        $dir = popen($cmd, 'r');
-        if (!$dir) {
-            return PEAR::raiseError('Failed to execute git ls-tree: ' . $cmd);
-        }
-
-        // Create two arrays - one of all the files, and the other of
-        // all the dirs.
-        while (!feof($dir)) {
-            $line = chop(fgets($dir, 1024));
-            if (!strlen($line)) {
-                continue;
-            }
-
-            list( ,$type, , $file) = preg_split('/\s+/', $line, -1, PREG_SPLIT_NO_EMPTY);
-            if ($type == 'tree') {
-                $this->_dirs[] = basename($file);
-            } else {
-                $this->_files[] = $this->_rep->getFileObject($file, $cache, $quicklog);
-            }
-        }
-
-        pclose($dir);
-    }
-
-}
-
-/**
- * Horde_VC_git file class.
- *
- * Copyright Chuck Hagenbuch <chuck@horde.org>
- *
- * @author  Chuck Hagenbuch <chuck@horde.org>
- * @package Horde_VC
- */
-class Horde_VC_File_git extends Horde_VC_File
-{
-    /**
-     * Create a repository file object, and give it information about
-     * what its parent directory and repository objects are.
-     *
-     * @param string $fl  Full path to this file.
-     */
-    public function __construct($rep, $fl, $cache = null, $quicklog = false)
-    {
-        // FIXME:
-        $rep->cache = $cache;
-
-        $this->rep = $rep;
-        $this->fullname = $fl;
-        $this->name = basename($fl);
-        $this->dir = dirname($fl);
-        $this->quicklog = $quicklog;
-        $this->cache = $cache;
-
-        $this->logs = $this->revs = $this->revsym = $this->symrev = $this->branches = array();
-    }
-
-    public function getFileObject()
-    {
-        $this->getBrowseInfo();
-        return $this;
-    }
-
-    /**
-     * Get the hash name for this file at a specific revision.
-     *
-     * @param string $rev
-     *
-     * @return string Commit hash
-     */
-    public function getHashForRevision($rev)
-    {
-        return $this->logs[$rev]->getHashForPath($this->fullname);
-    }
-
-    /**
-     * Returns name of the current file without the repository
-     * extensions (usually ,v)
-     *
-     * @return Filename without repository extension
-     */
-    function queryName()
-    {
-        return $this->name;
-    }
-
-    /**
-     * Populate the object with information about the revisions logs
-     * and dates of the file.
-     *
-     * @return mixed  True on success, PEAR_Error on error.
-     */
-    function getBrowseInfo()
-    {
-        // Get the list of revisions that touch this path
-        $Q = VC_WINDOWS ? '"' : "'";
-        $cmd = $this->rep->getCommand() . ' rev-list HEAD -- ' . $Q . str_replace($Q, '\\' . $Q, $this->fullname) . $Q . ' 2>&1';
-        $revisions = shell_exec($cmd);
-        if (substr($revisions, 5) == 'fatal') {
-            throw new Horde_VC_Exception($revisions);
-        }
-
-        if (!strlen($revisions)) {
-            throw new Horde_VC_Exception('No revisions found');
-        }
-
-        $this->revs = explode("\n", trim($revisions));
-        foreach ($this->revs as $rev) {
-            $this->logs[$rev] = Horde_VC_Log_git::factory($this->rep, $this, $rev);
-            if ($this->quicklog) {
-                break;
-            }
-        }
-    }
-
-}
-
-/**
- * Horde_VC_git log class.
- *
- * Chuck Hagenbuch <chuck@horde.org>
- *
- * @author  Chuck Hagenbuch <chuck@horde.org>
- * @package Horde_VC
- */
-class Horde_VC_Log_git {
-
-    var $rep;
-    var $err;
-    var $file;
-    var $files = array();
-    var $tags;
-    var $rev;
-    var $date;
-    var $log;
-    var $author;
-    var $state;
-    var $lines;
-    var $branches;
-
-    public static function factory($rep, $file, $rev)
-    {
-        /* The version of the cached data. Increment this whenever the
-         * internal storage format changes, such that we must
-         * invalidate prior cached data. */
-        $cacheVersion = 1;
-        $cacheId = $rep->sourceroot() . '_r' . $rev . '_v' . $cacheVersion;
-
-        if (0/*@TODO no caching during dev*/ && $rep->cache &&
-            // Individual revisions can be cached forever
-            $rep->cache->exists($cacheId, 0)) {
-            $logOb = unserialize($rep->cache->get($cacheId, 0));
-            $logOb->setRepository($rep);
-        } else {
-            $logOb = new Horde_VC_Log_git($rep, $file, $rev);
-
-            if ($rep->cache) {
-                $rep->cache->set($cacheId, serialize($logOb));
-            }
-        }
-
-        return $logOb;
-    }
-
-    /**
-     * Constructor.
-     */
-    public function __construct($rep, $fl, $rev)
-    {
-        $this->rep = $rep;
-        $this->file = $fl;
-        $this->rev = $rev;
-        $this->branches = array();
-
-        $cmd = $this->rep->getCommand() . ' whatchanged --no-color --pretty=fuller --no-abbrev -n 1 ' . $this->rev;
-        $pipe = popen($cmd, 'r');
-        if (!is_resource($pipe)) {
-            throw new Horde_VC_Exception('Unable to run ' . $cmd . ': ' . error_get_last());
-        }
-
-        $commit = trim(array_pop(explode(' ', fgets($pipe))));
-        if ($commit != $rev) {
-            throw new Horde_VC_Exception('Expected ' . $rev . ', got ' . $commit);
-        }
-
-        $properties = array();
-        $line = trim(fgets($pipe));
-        while ($line != '') {
-            list($key, $value) = explode(':', $line, 2);
-            $properties[trim($key)] = trim($value);
-            $line = trim(fgets($pipe));
-        }
-
-        $this->author = $properties['Author'];
-        $this->date = strtotime($properties['AuthorDate']);
-        //@TODO use Committer, CommitterDate, and Merge properties
-
-        $log = '';
-        $line = fgets($pipe);
-        while (substr($line, 0, 1) != ':') {
-            $log .= $line;
-            $line = fgets($pipe);
-        }
-        $this->log = trim($log);
-        //@TODO internal line formatting
-
-        // Build list of files in this revision. The format of these lines is
-        // documented in the git diff-tree documentation:
-        // http://www.kernel.org/pub/software/scm/git/docs/git-diff-tree.html
-        while ($line) {
-            preg_match('/:(\d+) (\d+) (\w+) (\w+) (.+)\t(.+)(\t(.+))?/', $line, $matches);
-            $this->files[$matches[6]] = array(
-                'srcMode' => $matches[1],
-                'dstMode' => $matches[2],
-                'srcSha1' => $matches[3],
-                'dstSha1' => $matches[4],
-                'status' => $matches[5],
-                'srcPath' => $matches[6],
-                'dstPath' => isset($matches[7]) ? $matches[7] : '',
-            );
-
-            $line = fgets($pipe);
-        }
-    }
-
-    function setRepository($rep)
-    {
-        $this->rep = $rep;
-    }
-
-    public function getHashForPath($path)
-    {
-        //@TODO not confident yet abotu the choice of dstSha1 vs. srcSha1
-        return $this->files[$path]['dstSha1'];
-    }
-
-    function queryDate()
-    {
-        return $this->date;
-    }
-
-    function queryRevision()
-    {
-        return $this->rev;
-    }
-
-    function queryAuthor()
-    {
-        return $this->author;
-    }
-
-    function queryLog()
-    {
-        return $this->log;
-    }
-
-    function queryChangedLines()
-    {
-        return isset($this->lines) ? ($this->lines) : '';
-    }
-
-    /**
-     * Given a branch revision number, this function remaps it
-     * accordingly, and performs a lookup on the file object to
-     * return the symbolic name(s) of that branch in the tree.
-     *
-     * @return hash of symbolic names => branch numbers
-     */
-    function querySymbolicBranches()
-    {
-        $symBranches = array();
-        return $symBranches;
-    }
-
-}
-
-/**
- * Horde_VC_git Patchset class.
- *
- * Copyright Chuck Hagenbuch <chuck@horde.org>
- *
- * @author  Chuck Hagenbuch <chuck@horde.org>
- * @package Horde_VC
- */
-class Horde_VC_Patchset_git extends Horde_VC_Patchset {
-
-    var $_file;
-
-    /**
-     * Create a patchset object.
-     *
-     * @param string $file  The filename to get patchsets for.
-     */
-    public function __construct($file)
-    {
-        $this->_file = $file;
-    }
-
-    function &getPatchsetObject($rep, $filename, $cache = null)
-    {
-        /* The version of the cached data. Increment this whenever the
-         * internal storage format changes, such that we must
-         * invalidate prior cached data. */
-        $cacheVersion = 1;
-        $cacheId = $rep->sourceroot() . '_n' . $filename . '_f_v' . $cacheVersion;
-
-        if ($cache &&
-            // ?
-            $cache->exists($cacheId, 3600)) {
-            $psOb = unserialize($cache->get($cacheId, 3600));
-            $psOb->setRepository($rep);
-        } else {
-            $psOb = new Horde_VC_Patchset_git($filename);
-            $psOb->setRepository($rep);
-            if (is_a(($result = $psOb->getPatchsets()), 'PEAR_Error')) {
-                return $result;
-            }
-
-            if ($cache) {
-                $cache->set($cacheId, serialize($psOb));
-            }
-        }
-
-        return $psOb;
-    }
-
-    /**
-     * Populate the object with information about the patchsets that
-     * this file is involved in.
-     *
-     * @return mixed  PEAR_Error object on error, or true on success.
-     */
-    function getPatchsets()
-    {
-        $fileOb = new Horde_VC_File_git($this->_rep, $this->_file);
-        if (is_a(($result = $fileOb->getBrowseInfo()), 'PEAR_Error')) {
-            return $result;
-        }
-
-        $this->_patchsets = array();
-        foreach ($fileOb->logs as $rev => $log) {
-            $this->_patchsets[$rev] = array();
-            $this->_patchsets[$rev]['date'] = $log->queryDate();
-            $this->_patchsets[$rev]['author'] = $log->queryAuthor();
-            $this->_patchsets[$rev]['branch'] = '';
-            $this->_patchsets[$rev]['tag'] = '';
-            $this->_patchsets[$rev]['log'] = $log->queryLog();
-            $this->_patchsets[$rev]['members'] = array();
-            foreach ($log->files as $file) {
-                $action = substr($file, 0, 1);
-                $file = preg_replace('/.*?\s(.*?)(\s|$).*/', '\\1', $file);
-                $to = $rev;
-                if ($action == 'A') {
-                    $from = 'INITIAL';
-                } elseif ($action == 'D') {
-                    $from = $to;
-                    $to = '(DEAD)';
-                } else {
-                    // This technically isn't the previous revision,
-                    // but it works for diffing purposes.
-                    $from = $to - 1;
-                }
-
-                $this->_patchsets[$rev]['members'][] = array('file' => $file,
-                                                             'from' => $from,
-                                                             'to' => $to);
-            }
-        }
-
-        return true;
-    }
-
-}
-
-class Horde_VC_Revision_git extends Horde_VC_Revision {}
diff --git a/framework/VC/lib/Horde/VC/rcs.php b/framework/VC/lib/Horde/VC/rcs.php
deleted file mode 100644 (file)
index 0eb1812..0000000
+++ /dev/null
@@ -1,324 +0,0 @@
-<?php
-/**
- * Horde_VC_rcs implementation.
- *
- * Copyright 2004-2007 Jeff Schwentner <jeffrey.schwentner@lmco.com>
- *
- * @author  Jeff Schwentner <jeffrey.schwentner@lmco.com>
- * @author  Chuck Hagenbuch <chuck@horde.org>
- * @package Horde_VC
- */
-class Horde_VC_rcs extends Horde_VC
-{
-    /**
-     * Checks an RCS file in with a specified change log.
-     *
-     * @param string $filepath    Location of file to check in.
-     * @param string $message     Log of changes since last version.
-     * @param string $user        The user name to use for the check in.
-     * @param boolean $newBinary  Does the change involve binary data?
-     *
-     * @return string|object  The new revision number on success, or a
-     *                        PEAR_Error object on failure.
-     */
-    public function ci($filepath, $message, $user = null, $newBinary = false)
-    {
-        if ($user) {
-            putenv('LOGNAME=' . $user);
-        } else {
-            putenv('LOGNAME=guest');
-        }
-
-        $Q = VC_WINDOWS ? '"' : "'" ;
-
-        $ci_cmd = $this->getPath('ci') . ' ' . $Q . $filepath . $Q.' 2>&1';
-        $rcs_cmd = $this->getPath('rcs') . ' -i -kb ' . $Q . $filepath . $Q.' 2>&1';
-        $output = '';
-
-        $message_lines = explode("\n", $message);
-
-        $pipe_def = array(0 => array("pipe", 'r'),
-                          1 => array("pipe", 'w'));
-
-        if ($newBinary) {
-            $process = proc_open($rcs_cmd, $pipe_def, $pipes);
-        } else {
-            $process = proc_open($ci_cmd, $pipe_def, $pipes);
-        }
-
-        if (is_resource($process)) {
-            foreach ($message_lines as $line) {
-                if ($line == '.\n') {
-                    $line = '. \n';
-                }
-                fwrite($pipes[0], $line);
-            }
-
-            fwrite($pipes[0], "\n.\n");
-            fclose($pipes[0]);
-
-            while (!feof($pipes[1])) {
-                $output .= fread($pipes[1], 8192);
-            }
-            fclose($pipes[1]);
-            proc_close($process);
-        } else {
-            return PEAR::raiseError('Failed to open pipe in ci()');
-        }
-
-        if ($newBinary) {
-            exec($ci_cmd . ' 2>&1', $return_array, $retval);
-
-            if ($retval) {
-                return PEAR::raiseError("Unable to spawn ci on $filepath from ci()");
-            } else {
-                foreach ($return_array as $line) {
-                    $output .= $line;
-                }
-            }
-        }
-
-        $rev_start = strpos($output, 'new revision: ');
-
-        // If no new revision, see if this is an initial checkin.
-        if ($rev_start === false) {
-            $rev_start = strpos($output, 'initial revision: ');
-            $rev_end = strpos($output, ' ', $rev_start);
-        } else {
-            $rev_end = strpos($output, ';', $rev_start);
-        }
-
-        if ($rev_start !== false && $rev_end !== false) {
-            $rev_start += 14;
-            return substr($output, $rev_start, $rev_end - $rev_start);
-        } else {
-            unlock($filepath);
-            $temp_pos = strpos($output, 'file is unchanged');
-            if ($temp_pos !== false) {
-                return PEAR::raiseError('Check-in Failure: ' . basename($filepath) . ' has not been modified');
-            } else {
-                return PEAR::raiseError("Failed to checkin $filepath, $ci_cmd, $output");
-            }
-        }
-    }
-
-    /**
-     * Checks the locks on a CVS/RCS file.
-     *
-     * @param string $filepath    Location of file.
-     * @param string &$locked_by  Returns the username holding the lock.
-     *
-     * @return boolean|object  True on success, or a PEAR_Error on failure.
-     */
-    public function isLocked($filepath, &$locked_by)
-    {
-        $rlog_cmd  = $this->getPath('rlog');
-        $rlog_flag = ' -L ';
-
-        $Q = VC_WINDOWS ? '"' : "'";
-
-        $cmd = $rlog_cmd . $rlog_flag . $Q . $filepath . $Q;
-
-        exec($cmd.' 2>&1', $return_array, $retval);
-
-        if ($retval) {
-            return PEAR::raiseError("Unable to spawn rlog on $filepath from isLocked()");
-        } else {
-            $output = '';
-
-            foreach ($return_array as $line) {
-                $output .= $line;
-            }
-
-            $start_name = strpos($output, 'locked by: ');
-            $end_name = strpos($output, ';', $start_name);
-
-            if ($start_name !== false && $end_name !== false) {
-                $start_name += 11;
-                $locked_by = substr($output, $start_name, $end_name - $start_name);
-                return true;
-            }  elseif (strlen($output) == 0) {
-                return false;
-            } else {
-                return PEAR::raiseError('Failure running rlog in isLocked()');
-            }
-        }
-    }
-
-    /**
-     * Locks a CVS/RCS file.
-     *
-     * @param string $filepath  Location of file.
-     * @param string $user      User name to lock the file with
-     *
-     * @return boolean|object  True on success, or a PEAR_Error on failure.
-     */
-    public function lock($filepath, $user = null)
-    {
-        // Get username for RCS tag.
-        if ($user) {
-            putenv('LOGNAME=' . $user);
-        } else {
-            putenv('LOGNAME=guest');
-        }
-
-        $rcs_cmd = $this->getPath('rcs');
-        $rcs_flag = ' -l ';
-
-        $Q = VC_WINDOWS ? '"' : "'" ;
-        $cmd = $rcs_cmd . $rcs_flag . $Q . $filepath . $Q;
-        exec($cmd.' 2>&1', $return_array, $retval);
-
-        if ($retval) {
-            return PEAR::raiseError('Failed to spawn rcs ("' . $cmd . '") on "' . $filepath . '" (returned ' . $retval . ')');
-        } else {
-            $output = '';
-            foreach ($return_array as $line) {
-                $output .= $line;
-            }
-
-            $locked_pos = strpos($output, 'locked');
-            if ($locked_pos !== false) {
-                return true;
-            } else {
-                return PEAR::raiseError('Failed to lock "' . $filepath . '" (Ran "' . $cmd . '", got return code ' . $retval . ', output: ' . $output . ')');
-            }
-        }
-    }
-
-    /**
-     * Unlocks a CVS/RCS file.
-     *
-     * @param string $filepath  Location of file.
-     * @param string $user      User name to unlock the file with
-     *
-     * @return boolean|object  True on success, or a PEAR_Error on failure.
-     */
-    public function unlock($filepath, $user = null)
-    {
-        // Get username for RCS tag.
-        if ($user) {
-            putenv('LOGNAME=' . $user);
-        } else {
-            putenv('LOGNAME=guest');
-        }
-
-        $rcs_cmd = $this->getPath('rcs');
-        $rcs_flag = ' -u ';
-
-        $Q = VC_WINDOWS ? '"' : "'" ;
-        $cmd = $rcs_cmd . $rcs_flag . $Q . $filepath . $Q;
-        exec($cmd . ' 2>&1', $return_array, $retval);
-
-        if ($retval) {
-            return PEAR::raiseError('Failed to spawn rcs ("' . $cmd . '") on "' . $filepath . '" (returned ' . $retval . ')');
-        } else {
-            $output = '';
-
-            foreach ($return_array as $line) {
-                $output .= $line;
-            }
-
-            $unlocked_pos = strpos($output, 'unlocked');
-
-            if ($unlocked_pos !== false) {
-                return true;
-            } else {
-                // Already unlocked.
-                return true;
-            }
-        }
-    }
-
-}
-
-class Horde_VC_Revision_rcs extends Horde_VC_Revision
-{
-    /**
-     * Given a revision number, remove a given number of portions from
-     * it. For example, if we remove 2 portions of 1.2.3.4, we are
-     * left with 1.2.
-     *
-     * @param string $val      Input revision
-     * @param integer $amount  Number of portions to strip
-     *
-     * @return string  Stripped revision number
-     */
-    public function strip($val, $amount = 1)
-    {
-        //@TODO This concept is broken beyond CVS
-        //if (!Horde_VC_Revision::valid($val)) {
-        //    return false;
-        //}
-        $pos = 0;
-        while ($amount-- > 0 && ($pos = strrpos($val, '.')) !== false) {
-            $val = substr($val, 0, $pos);
-        }
-        return $pos !== false ? $val : false;
-    }
-
-    /**
-     * The size of a revision number is the number of portions it has.
-     * For example, 1,2.3.4 is of size 4.
-     *
-     * @param string $val  Revision number to determine size of
-     *
-     * @return integer  Size of revision number
-     */
-    public function sizeof($val)
-    {
-        //@TODO This concept is broken beyond CVS
-        //if (!Horde_VC_Revision::valid($val)) {
-        //    return false;
-        //}
-
-        return (substr_count($val, '.') + 1);
-    }
-
-    /**
-     * Given two revision numbers, this figures out which one is
-     * greater than the other by stepping along the decimal points
-     * until a difference is found, at which point a sign comparison
-     * of the two is returned.
-     *
-     * @param string $rev1  Period delimited revision number
-     * @param string $rev2  Second period delimited revision number
-     *
-     * @return integer  1 if the first is greater, -1 if the second if greater,
-     *                  and 0 if they are equal
-     */
-    public function cmp($rev1, $rev2)
-    {
-        return version_compare($rev1, $rev2);
-    }
-
-    /**
-     * Return the logical revision before this one. Normally, this
-     * will be the revision minus one, but in the case of a new
-     * branch, we strip off the last two decimal places to return the
-     * original branch point.
-     *
-     * @param string $rev  Revision number to decrement.
-     *
-     * @return string|boolean  Revision number, or false if none could be
-     *                         determined.
-     */
-    public function prev($rev)
-    {
-        $last_dot = strrpos($rev, '.');
-        $val = substr($rev, ++$last_dot);
-
-        if (--$val > 0) {
-            return substr($rev, 0, $last_dot) . $val;
-        } else {
-            $last_dot--;
-            while (--$last_dot) {
-                if ($rev[$last_dot] == '.') {
-                    return  substr($rev, 0, $last_dot);
-                } elseif ($rev[$last_dot] == null) {
-                    return false;
-                }
-            }
-        }
-    }
-}
diff --git a/framework/VC/lib/Horde/VC/svn.php b/framework/VC/lib/Horde/VC/svn.php
deleted file mode 100644 (file)
index cf6355c..0000000
+++ /dev/null
@@ -1,642 +0,0 @@
-<?php
-
-require_once dirname(__FILE__) . '/rcs.php';
-
-/**
- * Horde_VC_svn implementation.
- *
- * Copyright 2000-2008 The Horde Project (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (LGPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
- *
- * @author  Anil Madhavapeddy <anil@recoil.org>
- * @package Horde_VC
- */
-class Horde_VC_svn extends Horde_VC
-{
-    /**
-     * SVN username.
-     *
-     * @var string
-     */
-    protected $_username = '';
-
-    /**
-     * SVN password.
-     *
-     * @var string
-     */
-    protected $_password = '';
-
-    /**
-     * Constructor.
-     *
-     * @param array $params  Any parameter the class expects.
-     *                       Current parameters:
-     * <pre>
-     * 'sourceroot': The source root for this repository
-     * 'paths': Hash with the locations of all necessary binaries: 'svn',
-     *          'diff'
-     * </pre>
-     */
-    public function __construct($params)
-    {
-        $this->_sourceroot = $params['sourceroot'];
-        $this->_paths = $params['paths'];
-
-        if (!empty($params['username'])) {
-            $this->_username = $params['username'];
-        }
-
-        if (!empty($params['password'])) {
-            $this->_password = $params['password'];
-        }
-        parent::_construct();
-    }
-
-    /**
-     * TODO
-     */
-    public function getCommand()
-    {
-        $svnPath = $this->getPath('svn');
-        $tempDir = isset($this->_paths['svn_home'])
-            ? $this->_paths['svn_home']
-            : Util::getTempDir();
-        $command = $svnPath . ' --non-interactive --config-dir ' . $tempDir;
-
-        if ($this->_username) {
-            $command .= ' --username ' . $this->_username;
-        }
-
-        if ($this->_password) {
-            $command .= ' --password ' . $this->_password;
-        }
-
-        return $command;
-    }
-
-    /**
-     * Validation function to ensure that a revision number is of the right
-     * form.
-     *
-     * @param mixed $rev  The purported revision number.
-     *
-     * @return boolean  True if it is a revision number.
-     */
-    public function isValidRevision($rev)
-    {
-        return is_numeric($rev);
-    }
-
-}
-
-/**
- * Horde_VC_svn annotate class.
- *
- * Anil Madhavapeddy, <anil@recoil.org>
- *
- * @author  Anil Madhavapeddy <anil@recoil.org>
- * @package Horde_VC
- */
-class Horde_VC_Annotate_svn extends Horde_VC_Annotate
-{
-    /**
-     * TODO
-     */
-    public function doAnnotate($rev)
-    {
-        if (is_a($this->_file, 'PEAR_Error') ||
-            !$this->_rep->isValidRevision($rev)) {
-            return false;
-        }
-
-        $command = $this->_rep->getCommand() . ' annotate -r 1:' . $rev . ' ' . escapeshellarg($this->_file->queryFullPath()) . ' 2>&1';
-        $pipe = popen($command, 'r');
-        if (!$pipe) {
-            return PEAR::raiseError('Failed to execute svn annotate: ' . $command);
-        }
-
-        $lines = array();
-        $lineno = 1;
-
-        while (!feof($pipe)) {
-            $line = fgets($pipe, 4096);
-            if (preg_match('/^\s+(\d+)\s+([\w\.]+)\s(.*)$/', $line, $regs)) {
-                $lines[] = array(
-                    'rev' => $regs[1],
-                    'author' => trim($regs[2]),
-                    'date' => '',
-                    'line' => $regs[3],
-                    'lineno' => $lineno++
-                );
-            }
-        }
-
-        pclose($pipe);
-        return $lines;
-    }
-
-}
-
-/**
- * Horde_VC_svn checkout class.
- *
- * Anil Madhavapeddy, <anil@recoil.org>
- *
- * @author  Anil Madhavapeddy <anil@recoil.org>
- * @package Horde_VC
- */
-class Horde_VC_Checkout_svn extends Horde_VC_Checkout
-{
-    /**
-     * Function which returns a file pointing to the head of the requested
-     * revision of a file.
-     *
-     * @param Horde_VC $rep     A repository object
-     * @param string $fullname  Fully qualified pathname of the desired file
-     *                          to checkout
-     * @param string $rev       Revision number to check out
-     *
-     * @return resource|object  Either a PEAR_Error object, or a stream
-     *                          pointer to the head of the checkout.
-     */
-    public function get($rep, $fullname, $rev)
-    {
-        if (!$rep->isValidRevision($rev)) {
-            return PEAR::raiseError('Invalid revision number');
-        }
-
-        return ($RCS = popen($rep->getCommand() . ' cat -r ' . $rev . ' ' . escapeshellarg($fullname) . ' 2>&1', VC_WINDOWS ? 'rb' : 'r'))
-            ? $RCS
-            : PEAR::raiseError('Couldn\'t perform checkout of the requested file');
-    }
-
-}
-
-/**
- * Horde_VC_svn diff class.
- *
- * Copyright Anil Madhavapeddy, <anil@recoil.org>
- *
- * @author  Anil Madhavapeddy <anil@recoil.org>
- * @package Horde_VC
- */
-class Horde_VC_Diff_svn extends Horde_VC_Diff
-{
-    /**
-     * Obtain the differences between two revisions of a file.
-     *
-     * @param Horde_VC $rep        A repository object.
-     * @param Horde_VC_File $file  The desired file.
-     * @param string $rev1         Original revision number to compare from.
-     * @param string $rev2         New revision number to compare against.
-     * @param string $type         The type of diff (e.g. 'unified').
-     * @param integer $num         Number of lines to be used in context and
-     *                             unified diffs.
-     * @param boolean $ws          Show whitespace in the diff?
-     *
-     * @return string|boolean  False on failure, or a string containing the
-     *                         diff on success.
-     */
-    public function get($rep, $file, $rev1, $rev2, $type = 'context',
-                        $num = 3, $ws = true)
-    {
-        /* Make sure that the file parameter is valid */
-        if (is_a($file, 'PEAR_Error')) {
-            return false;
-        }
-
-        /* Check that the revision numbers are valid */
-        $rev1 = $rep->isValidRevision($rev1) ? $rev1 : 0;
-        $rev2 = $rep->isValidRevision($rev1) ? $rev2 : 0;
-
-        $fullName = $file->queryFullPath();
-        $diff = array();
-        $options = '';
-        if (!$ws) {
-            $options .= ' -bB ';
-        }
-
-        switch ($type) {
-        case 'context':
-            $options .= '--context=' . (int)$num;
-            break;
-
-        case 'unified':
-            $options .= '-p --unified=' . (int)$num;
-            break;
-
-        case 'column':
-            $options .= '--side-by-side --width=120';
-            break;
-
-        case 'ed':
-            $options .= '-e';
-            break;
-        }
-
-        // TODO: add options for $hr options - however these may not
-        // be compatible with some diffs.
-        $command = $rep->getCommand() . " diff --diff-cmd " . $rep->getPath('diff') . " -r $rev1:$rev2 -x " . escapeshellarg($options) . ' ' . escapeshellarg($file->queryFullPath()) . ' 2>&1';
-
-        exec($command, $diff, $retval);
-        return $diff;
-    }
-
-}
-
-/**
- * Horde_VC_svn directory class.
- *
- * Copyright Anil Madhavapeddy, <anil@recoil.org>
- *
- * @author  Anil Madhavapeddy <anil@recoil.org>
- * @package Horde_VC
- */
-class Horde_VC_Directory_svn extends Horde_VC_Directory
-{
-    /**
-     * Tell the object to open and browse its current directory, and
-     * retrieve a list of all the objects in there.  It then populates
-     * the file/directory stack and makes it available for retrieval.
-     *
-     * @return PEAR_Error object on an error, 1 on success.
-     */
-    public function browseDir($cache = null, $quicklog = true,
-                              $showattic = false)
-    {
-        $cmd = $this->_rep->getCommand() . ' ls ' . escapeshellarg($this->_rep->sourceroot() . $this->queryDir()) . ' 2>&1';
-
-        $dir = popen($cmd, 'r');
-        if (!$dir) {
-            return PEAR::raiseError('Failed to execute svn ls: ' . $cmd);
-        }
-
-        /* Create two arrays - one of all the files, and the other of
-         * all the dirs. */
-        $errors = array();
-        while (!feof($dir)) {
-            $line = chop(fgets($dir, 1024));
-            if (!strlen($line)) {
-                continue;
-            }
-
-            if (substr($line, 0, 4) == 'svn:') {
-                $errors[] = $line;
-            } elseif (substr($line, -1) == '/') {
-                $this->_dirs[] = substr($line, 0, -1);
-            } else {
-                $fl = $this->_rep->getFileObject($this->queryDir() . "/$line", $cache, $quicklog);
-                if (is_a($fl, 'PEAR_Error')) {
-                    return $fl;
-                } else {
-                    $this->_files[] = $fl;
-                }
-            }
-        }
-
-        pclose($dir);
-
-        return $errors
-            ? PEAR::raiseError(implode("\n", $errors))
-            : true;
-    }
-
-}
-
-/**
- * Horde_VC_svn file class.
- *
- * Copyright Anil Madhavapeddy, <anil@recoil.org>
- *
- * @author  Anil Madhavapeddy <anil@recoil.org>
- * @package Horde_VC
- */
-class Horde_VC_File_svn extends Horde_VC_File {
-
-    /**
-     * Create a repository file object, and give it information about
-     * what its parent directory and repository objects are.
-     *
-     * @param string $fl  Full path to this file.
-     */
-    public function __construct($rep, $fl, $cache = null, $quicklog = false)
-    {
-        $this->rep = $rep;
-        $this->name = basename($fl);
-        $this->dir = dirname($fl);
-        $this->filename = $fl;
-        $this->quicklog = $quicklog;
-        $this->cache = $cache;
-        $this->logs = $this->revs = $this->revsym = $this->symrev = $this->branches = array();
-    }
-
-    function &getFileObject()
-    {
-        /* The version of the cached data. Increment this whenever the
-         * internal storage format changes, such that we must
-         * invalidate prior cached data. */
-        $cacheVersion = 2;
-        $cacheId = $rep->sourceroot() . '_n' . $this->filename . '_f' . (int)$this->quicklog . '_v' . $cacheVersion;
-
-        if ($this->cache &&
-            // The file is cached for one hour no matter what, because
-            // there is no way to determine with Subversion the time
-            // the file last changed.
-            $this->cache->exists($cacheId, 3600)) {
-            $fileOb = unserialize($this->cache->get($cacheId, 3600));
-            $fileOb->setRepository($rep);
-        } else {
-            $fileOb = new Horde_VC_File_svn($rep, $this->filename, $this->cache, $this->quicklog);
-            $fileOb->setRepository($rep);
-            if (is_a(($result = $fileOb->getBrowseInfo()), 'PEAR_Error')) {
-                return $result;
-            }
-            $fileOb->applySort(Horde_VC::SORT_AGE);
-
-            if ($this->cache) {
-                $this->cache->set($cacheId, serialize($fileOb));
-            }
-        }
-
-        return $fileOb;
-    }
-
-    /**
-     * Returns name of the current file without the repository
-     * extensions (usually ,v)
-     *
-     * @return Filename without repository extension
-     */
-    function queryName()
-    {
-       return preg_replace('/,v$/', '', $this->name);
-    }
-
-    /**
-     * Populate the object with information about the revisions logs
-     * and dates of the file.
-     *
-     * @return mixed boolean            True on success,
-     *               PEAR_Error         On error.
-     */
-    function getBrowseInfo()
-    {
-        /* This doesn't work; need to find another way to simply
-         * request the most recent revision:
-         *
-         * $flag = $this->quicklog ? '-r HEAD ' : ''; */
-        $flag = '';
-        $Q = VC_WINDOWS ? '"' : "'";
-        $cmd = $this->rep->getCommand() . ' log -v ' . $flag . $Q . str_replace($Q, '\\' . $Q, $this->queryFullPath()) . $Q . ' 2>&1';
-        $pipe = popen($cmd, 'r');
-        if (!$pipe) {
-            return PEAR::raiseError('Failed to execute svn log: ' . $cmd);
-        }
-
-        $header = fgets($pipe);
-        if (!strspn($header, '-')) {
-            return PEAR::raiseError('Error executing svn log: ' . $header);
-        }
-
-        while (!feof($pipe)) {
-            $log = new Horde_VC_Log_svn($this->rep, $this);
-            $err = $log->processLog($pipe);
-            if ($err) {
-                $rev = $log->queryRevision();
-                $this->logs[$rev] = $log;
-                $this->revs[] = $rev;
-            }
-
-            if ($this->quicklog) {
-                break;
-            }
-        }
-
-        pclose($pipe);
-        return true;
-    }
-
-}
-
-/**
- * Horde_VC_svn log class.
- *
- * Anil Madhavapeddy, <anil@recoil.org>
- *
- * @author  Anil Madhavapeddy <anil@recoil.org>
- * @package Horde_VC
- */
-class Horde_VC_Log_svn {
-
-    var $rep;
-    var $err;
-    var $file;
-    var $files;
-    var $tags;
-    var $rev;
-    var $date;
-    var $log;
-    var $author;
-    var $state;
-    var $lines;
-    var $branches;
-
-    /**
-     * Constructor.
-     */
-    public function __construct($rep, $fl)
-    {
-        $this->rep = $rep;
-        $this->file = $fl;
-        $this->branches = array();
-    }
-
-    function processLog($pipe)
-    {
-        $line = fgets($pipe);
-
-        if (feof($pipe)) {
-            return false;
-        }
-
-        if (preg_match('/^r([0-9]*) \| (.*?) \| (.*) \(.*\) \| ([0-9]*) lines?$/', $line, $matches)) {
-            $this->rev = $matches[1];
-            $this->author = $matches[2];
-            $this->date = strtotime($matches[3]);
-            $size = $matches[4];
-        } else {
-            $this->err = $line;
-            return false;
-        }
-
-        fgets($pipe);
-
-        $this->files = array();
-        while (($line = trim(fgets($pipe))) != '') {
-            $this->files[] = $line;
-        }
-
-        for ($i = 0; $i != $size; ++$i) {
-            $this->log = $this->log . chop(fgets($pipe)) . "\n";
-        }
-
-        $this->log = chop($this->log);
-        fgets($pipe);
-
-        return true;
-    }
-
-    function queryDate()
-    {
-        return $this->date;
-    }
-
-    function queryRevision()
-    {
-        return $this->rev;
-    }
-
-    function queryAuthor()
-    {
-        return $this->author;
-    }
-
-    function queryLog()
-    {
-        return $this->log;
-    }
-
-    function queryChangedLines()
-    {
-        return isset($this->lines) ? ($this->lines) : '';
-    }
-
-    /**
-     * Given a branch revision number, this function remaps it
-     * accordingly, and performs a lookup on the file object to
-     * return the symbolic name(s) of that branch in the tree.
-     *
-     * @return hash of symbolic names => branch numbers
-     */
-    function querySymbolicBranches()
-    {
-        $symBranches = array();
-        foreach ($this->branches as $branch) {
-            $parts = explode('.', $branch);
-            $last = array_pop($parts);
-            $parts[] = '0';
-            $parts[] = $last;
-            $rev = implode('.', $parts);
-            if (isset($this->file->branches[$branch])) {
-                $symBranches[$this->file->branches[$branch]] = $branch;
-            }
-        }
-        return $symBranches;
-    }
-
-}
-
-/**
- * Horde_VC_svn Patchset class.
- *
- * Copyright Anil Madhavapeddy, <anil@recoil.org>
- *
- * @author  Anil Madhavapeddy <anil@recoil.org>
- * @package Horde_VC
- */
-class Horde_VC_Patchset_svn extends Horde_VC_Patchset {
-
-    var $_file;
-
-    /**
-     * Create a patchset object.
-     *
-     * @param string $file  The filename to get patchsets for.
-     */
-    public function __construct($file)
-    {
-        $this->_file = $file;
-    }
-
-    function &getPatchsetObject($rep, $filename, $cache = null)
-    {
-        /* The version of the cached data. Increment this whenever the
-         * internal storage format changes, such that we must
-         * invalidate prior cached data. */
-        $cacheVersion = 1;
-        $cacheId = $rep->sourceroot() . '_n' . $filename . '_f_v' . $cacheVersion;
-
-        if ($cache &&
-            // The file is cached for one hour no matter what, because
-            // there is no way to determine with svn the time the file
-            // last changed.
-            $cache->exists($cacheId, 3600)) {
-            $psOb = unserialize($cache->get($cacheId, 3600));
-            $psOb->setRepository($rep);
-        } else {
-            $psOb = new Horde_VC_Patchset_svn($filename);
-            $psOb->setRepository($rep);
-            if (is_a(($result = $psOb->getPatchsets()), 'PEAR_Error')) {
-                return $result;
-            }
-
-            if ($cache) {
-                $cache->set($cacheId, serialize($psOb));
-            }
-        }
-
-        return $psOb;
-    }
-
-    /**
-     * Populate the object with information about the patchsets that
-     * this file is involved in.
-     *
-     * @return mixed  PEAR_Error object on error, or true on success.
-     */
-    function getPatchsets()
-    {
-        $fileOb = new Horde_VC_File_svn($this->_rep, $this->_file);
-        if (is_a(($result = $fileOb->getBrowseInfo()), 'PEAR_Error')) {
-            return $result;
-        }
-
-        $this->_patchsets = array();
-        foreach ($fileOb->logs as $rev => $log) {
-            $this->_patchsets[$rev] = array();
-            $this->_patchsets[$rev]['date'] = $log->queryDate();
-            $this->_patchsets[$rev]['author'] = $log->queryAuthor();
-            $this->_patchsets[$rev]['branch'] = '';
-            $this->_patchsets[$rev]['tag'] = '';
-            $this->_patchsets[$rev]['log'] = $log->queryLog();
-            $this->_patchsets[$rev]['members'] = array();
-            foreach ($log->files as $file) {
-                $action = substr($file, 0, 1);
-                $file = preg_replace('/.*?\s(.*?)(\s|$).*/', '\\1', $file);
-                $to = $rev;
-                if ($action == 'A') {
-                    $from = 'INITIAL';
-                } elseif ($action == 'D') {
-                    $from = $to;
-                    $to = '(DEAD)';
-                } else {
-                    // This technically isn't the previous revision,
-                    // but it works for diffing purposes.
-                    $from = $to - 1;
-                }
-
-                $this->_patchsets[$rev]['members'][] = array('file' => $file,
-                                                             'from' => $from,
-                                                             'to' => $to);
-            }
-        }
-
-        return true;
-    }
-
-}
-
-class Horde_VC_Revision_svn extends Horde_VC_Revision_rcs {}
diff --git a/framework/VC/package.xml b/framework/VC/package.xml
deleted file mode 100644 (file)
index da124de..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<package packagerversion="1.4.9" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
-http://pear.php.net/dtd/tasks-1.0.xsd
-http://pear.php.net/dtd/package-2.0
-http://pear.php.net/dtd/package-2.0.xsd">
- <name>Horde_VC</name>
- <channel>pear.horde.org</channel>
- <summary>Version Control API</summary>
- <description>The Horde_VC package provides a generalized API to multiple
- version control systems.
- </description>
- <lead>
-  <name>Chuck Hagenbuch</name>
-  <user>chuck</user>
-  <email>chuck@horde.org</email>
-  <active>yes</active>
- </lead>
- <lead>
-  <name>Jan Schneider</name>
-  <user>jan</user>
-  <email>jan@horde.org</email>
-  <active>yes</active>
- </lead>
- <lead>
-  <name>Michael Slusarz</name>
-  <user>slusarz</user>
-  <email>slusarz@horde.org</email>
-  <active>yes</active>
- </lead>
- <date>2008-12-20</date>
- <version>
-  <release>0.1.0</release>
-  <api>0.1.0</api>
- </version>
- <stability>
-  <release>beta</release>
-  <api>beta</api>
- </stability>
- <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
- <notes>* Initial Horde 4 package.</notes>
- <contents>
-  <dir name="/">
-   <dir name="lib">
-    <dir name="Horde">
-     <dir name="VC">
-      <file name="cvs.php" role="php" />
-      <file name="Exception.php" role="php" />
-      <file name="git.php" role="php" />
-      <file name="rcs.php" role="php" />
-      <file name="svn.php" role="php" />
-     </dir> <!-- /lib/Horde/VC -->
-     <file name="VC.php" role="php" />
-    </dir> <!-- /lib/Horde -->
-   </dir> <!-- /lib -->
-  </dir> <!-- / -->
- </contents>
- <dependencies>
-  <required>
-   <php>
-    <min>5.2.0</min>
-   </php>
-   <pearinstaller>
-    <min>1.5.0</min>
-   </pearinstaller>
-   <package>
-    <name>Util</name>
-    <channel>pear.horde.org</channel>
-   </package>
-   <extension>
-    <name>pcre</name>
-   </extension>
-  </required>
-  <optional>
-   <package>
-    <name>Horde_Cache</name>
-    <channel>pear.horde.org</channel>
-   </package>
-  </optional>
- </dependencies>
- <phprelease>
-  <filelist>
-   <install name="lib/Horde/VC/cvs.php" as="Horde/VC/cvs.php" />
-   <install name="lib/Horde/VC/Exception.php" as="Horde/VC/Exception.php" />
-   <install name="lib/Horde/VC/git.php" as="Horde/VC/git.php" />
-   <install name="lib/Horde/VC/rcs.php" as="Horde/VC/rcs.php" />
-   <install name="lib/Horde/VC/svn.php" as="Horde/VC/svn.php" />
-   <install name="lib/Horde/VC.php" as="Horde/VC.php" />
-  </filelist>
- </phprelease>
- <changelog>
-  <release>
-   <version>
-    <release>0.0.4</release>
-    <api>0.0.4</api>
-   </version>
-   <stability>
-    <release>beta</release>
-    <api>beta</api>
-   </stability>
-   <date>2006-05-08</date>
-   <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
-   <notes>* Converted to package.xml 2.0 for pear.horde.org
-* Fix subversion diffs on files with spaces (helly@php.net, Bug #5244)
-* Pass username and password to the SVN client if set (duck@obala.net, Request #5958)
-   </notes>
-  </release>
-  <release>
-   <version>
-    <release>0.0.3</release>
-    <api>0.0.3</api>
-   </version>
-   <stability>
-    <release>beta</release>
-    <api>beta</api>
-   </stability>
-   <date>2004-01-01</date>
-   <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
-   <notes>- Fix caching of Patchset objects.
-- Add a VC_Patchset parent class for common Patchset functionality.
-   </notes>
-  </release>
- </changelog>
-</package>
diff --git a/framework/Vcs/lib/Horde/Vcs.php b/framework/Vcs/lib/Horde/Vcs.php
new file mode 100644 (file)
index 0000000..f6225df
--- /dev/null
@@ -0,0 +1,927 @@
+<?php
+
+require_once dirname(__FILE__) . '/VC/Exception.php';
+
+/* Need to define this outside of class since constants in class can not be
+ * assigned from a function return. */
+define('VC_WINDOWS', !strncasecmp(PHP_OS, 'WIN', 3));
+
+/**
+ * Version Control generalized library.
+ *
+ * Copyright 2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @package Horde_VC
+ */
+class Horde_VC
+{
+    /* Sorting options */
+    const SORT_NONE = 0;    // don't sort
+    const SORT_AGE = 1;     // sort by age
+    const SORT_NAME = 2;    // sort by filename
+    const SORT_REV = 3;     // sort by revision number
+    const SORT_AUTHOR = 4;  // sort by author name
+
+    const SORT_ASCENDING = 0;   // ascending order
+    const SORT_DESCENDING = 1;  // descending order
+
+    /**
+     * The source root of the repository.
+     *
+     * @var string
+     */
+    protected $_sourceroot;
+
+    /**
+     * Hash with the locations of all necessary binaries.
+     *
+     * @var array
+     */
+    protected $_paths = array();
+
+    /**
+     * Hash caching the parsed users file.
+     *
+     * @var array
+     */
+    protected $_users;
+
+    /**
+     * The current driver.
+     *
+     * @var string
+     */
+    protected $_driver;
+
+    /**
+     * Attempts to return a concrete Horde_VC instance based on $driver.
+     *
+     * @param mixed $driver  The type of concrete Horde_VC subclass to return.
+     *                       The code is dynamically included.
+     * @param array $params  A hash containing any additional configuration
+     *                       or  parameters a subclass might need.
+     *
+     * @return Horde_VC  The newly created concrete instance, or PEAR_Error on
+     *                   failure.
+     */
+    static public function factory($driver, $params = array())
+    {
+        $class = 'Horde_VC_' . $driver;
+        if (class_exists($class)) {
+            return new $class($params);
+        }
+
+        return PEAR::raiseError($class . ' not found.');
+    }
+
+    /**
+     * Attempts to return a reference to a concrete Horde_VC instance based
+     * on $driver. It will only create a new instance if no Horde_VC
+     * instance with the same parameters currently exists.
+     *
+     * This should be used if multiple types of file backends (and,
+     * thus, multiple Horde_VC instances) are required.
+     *
+     * This method must be invoked as: $var = &Horde_VC::singleton()
+     *
+     * @param mixed $driver  The type of concrete Horde_VC subclass to return.
+     *                       The code is dynamically included.
+     * @param array $params  A hash containing any additional configuration
+     *                       or parameters a subclass might need.
+     *
+     * @return Horde_VC  The concrete reference, or PEAR_Error on failure.
+     */
+    static public function &singleton($driver, $params = array())
+    {
+        static $instances = array();
+
+        $signature = serialize(array($driver, $params));
+        if (!isset($instances[$signature])) {
+            $instances[$signature] = &Horde_VC::factory($driver, $params);
+        }
+
+        return $instances[$signature];
+    }
+
+    /**
+     * Constructor.
+     */
+    public function __construct()
+    {
+        $pos = strpos(get_class($this), '_');
+        $this->_driver = substr(get_class($this), $pos + 1);
+    }
+
+    /**
+     * Return the source root for this repository, with no trailing /
+     *
+     * @return string  Source root for this repository.
+     */
+    public function sourceroot()
+    {
+        return $this->_sourceroot;
+    }
+
+    /**
+     * Validation function to ensure that a revision number is of the right
+     * form.
+     *
+     * @param mixed $rev  The purported revision number.
+     *
+     * @return boolean  True if it is a revision number.
+     */
+    public function isValidRevision($rev)
+    {
+        return true;
+    }
+
+    /**
+     * TODO
+     */
+    public function isFile($where)
+    {
+        return true;
+    }
+
+    /**
+     * Throw an exception if the revision number isn't valid.
+     *
+     * @param mixed $rev The revision number
+     *
+     * @return void
+     * @throws Horde_VC_Exception
+     */
+    public function assertValidRevision($rev)
+    {
+        if (!$this->isValidRevision($rev)) {
+            throw new Horde_VC_Exception('Invalid revision number');
+        }
+    }
+
+    /**
+     * Create a range of revisions between two revision numbers.
+     *
+     * @param Horde_VC_File $file  The desired file.
+     * @param string $r1           The initial revision.
+     * @param string $r2           The ending revision.
+     *
+     * @return array  The revision range, or empty if there is no straight
+     *                line path between the revisions.
+     */
+    public function getRevisionRange($file, $r1, $r2)
+    {
+        $class = 'Horde_VC_Diff_' . $this->_driver;
+        $vc_diff = new $class();
+        return $vc_diff->getRevisionRange($this, $file, $r1, $r2);
+    }
+
+    /**
+     * Returns the location of the specified binary.
+     *
+     * @param string $binary  An external program name.
+     *
+     * @return boolean|string  The location of the external program or false if
+     *                         it wasn't specified.
+     */
+    public function getPath($binary)
+    {
+        if (isset($this->_paths[$binary])) {
+            return $this->_paths[$binary];
+        }
+
+        return false;
+    }
+
+    /**
+     * Parse the users file, if present in the source root, and return
+     * a hash containing the requisite information, keyed on the
+     * username, and with the 'desc', 'name', and 'mail' values inside.
+     *
+     * @return boolean|array  False if the file is not present, otherwise
+     *                        $this->_users populated with the data
+     */
+    public function getUsers($usersfile)
+    {
+        /* Check that we haven't already parsed users. */
+        if (isset($this->_users) && is_array($this->_users)) {
+            return $this->_users;
+        }
+
+        if (!@is_file($usersfile) || !($fl = @fopen($usersfile, VC_WINDOWS ? 'rb' : 'r'))) {
+            return false;
+        }
+
+        $this->_users = array();
+
+        /* Discard the first line, since it'll be the header info. */
+        fgets($fl, 4096);
+
+        /* Parse the rest of the lines into a hash, keyed on
+         * username. */
+        while ($line = fgets($fl, 4096)) {
+            if (preg_match('/^\s*$/', $line)) {
+                continue;
+            }
+            if (!preg_match('/^(\w+)\s+(.+)\s+([\w\.\-\_]+@[\w\.\-\_]+)\s+(.*)$/', $line, $regs)) {
+                continue;
+            }
+
+            $this->_users[$regs[1]]['name'] = trim($regs[2]);
+            $this->_users[$regs[1]]['mail'] = trim($regs[3]);
+            $this->_users[$regs[1]]['desc'] = trim($regs[4]);
+        }
+
+        return $this->_users;
+    }
+
+    public function queryDir($where)
+    {
+        $class = 'Horde_VC_Directory_' . $this->_driver;
+        return new $class($this, $where);
+    }
+
+    public function getCheckout($file, $rev)
+    {
+        $class = 'Horde_VC_Checkout_' . $this->_driver;
+        $vc_co = new $class();
+        return $vc_co->get($this, $file->queryFullPath(), $rev);
+    }
+
+    public function getDiff($file, $rev1, $rev2, $type = 'unified', $num = 3,
+                            $ws = true)
+    {
+        $class = 'Horde_VC_Diff_' . $this->_driver;
+        $vc_diff = new $class();
+        return $vc_diff->get($this, $file, $rev1, $rev2, $type, $num, $ws);
+    }
+
+    public function availableDiffTypes()
+    {
+        $class = 'Horde_VC_Diff_' . $this->_driver;
+        $vc_diff = new $class();
+        return $vc_diff->availableDiffTypes();
+    }
+
+    public function getFileObject($filename, $cache = null, $quicklog = false)
+    {
+        $class = 'Horde_VC_File_' . $this->_driver;
+        $vc_file = new $class($this, $filename, $cache, $quicklog);
+        return $vc_file->getFileObject();
+    }
+
+    public function getAnnotateObject($filename)
+    {
+        $class = 'Horde_VC_Annotate_' . $this->_driver;
+        return new $class($this, $filename);
+    }
+
+    public function getPatchsetObject($filename, $cache = null)
+    {
+        $class = 'Horde_VC_Patchset_' . $this->_driver;
+        $vc_patchset = new $class();
+        return $vc_patchset->getPatchsetObject($this, $filename, $cache);
+    }
+
+    public function getRevisionObject()
+    {
+        $class = 'Horde_VC_Revision_' . $this->_driver;
+        return new $class();
+    }
+}
+
+/**
+ * Horde_VC annotate class.
+ *
+ * @package Horde_VC
+ */
+abstract class Horde_VC_Annotate
+{
+    protected $_file;
+    protected $_rep;
+
+    /**
+     * Constructor
+     *
+     * TODO
+     */
+    public function __construct($rep, $file)
+    {
+        $this->_rep = $rep;
+        $this->_file = $file;
+    }
+
+    /**
+     * TODO
+     */
+    abstract public function doAnnotate($rev);
+}
+
+/**
+ * @package Horde_VC
+ */
+abstract class Horde_VC_Checkout
+{
+    /**
+     * Function which returns a file pointing to the head of the requested
+     * revision of an SVN file.
+     *
+     * @param Horde_VC $rep     A repository object
+     * @param string $fullname  Fully qualified pathname of the desired file
+     *                          to checkout
+     * @param string $rev       Revision number to check out
+     *
+     * @return resource|object  Either a PEAR_Error object, or a stream
+     *                          pointer to the head of the checkout.
+     */
+    abstract function get($rep, $fullname, $rev);
+}
+
+/**
+ * @package Horde_VC
+ */
+class Horde_VC_Diff
+{
+    /**
+     * The available diff types.
+     *
+     * @var array
+     */
+    protected $_diffTypes = array('column', 'context', 'ed', 'unified');
+
+    /**
+     * Obtain a tree containing information about the changes between
+     * two revisions.
+     *
+     * @param array $raw  An array of lines of the raw unified diff,
+     *                    normally obtained through Horde_VC_Diff::get().
+     *
+     * @return array  @TODO
+     */
+    public function humanReadable($raw)
+    {
+        $ret = array();
+
+        /* Hold the left and right columns of lines for change
+         * blocks. */
+        $cols = array(array(), array());
+        $state = 'empty';
+
+        /* Iterate through every line of the diff. */
+        foreach ($raw as $line) {
+            /* Look for a header which indicates the start of a diff
+             * chunk. */
+            if (preg_match('/^@@ \-([0-9]+).*\+([0-9]+).*@@(.*)/', $line, $regs)) {
+                /* Push any previous header information to the return
+                 * stack. */
+                if (isset($data)) {
+                    $ret[] = $data;
+                }
+                $data = array('type' => 'header', 'oldline' => $regs[1],
+                              'newline' => $regs[2], 'contents'> array());
+                $data['function'] = isset($regs[3]) ? $regs[3] : '';
+                $state = 'dump';
+            } elseif ($state != 'empty') {
+                /* We are in a chunk, so split out the action (+/-)
+                 * and the line. */
+                preg_match('/^([\+\- ])(.*)/', $line, $regs);
+                if (count($regs) > 2) {
+                    $action = $regs[1];
+                    $content = $regs[2];
+                } else {
+                    $action = ' ';
+                    $content = '';
+                }
+
+                if ($action == '+') {
+                    /* This is just an addition line. */
+                    if ($state == 'dump' || $state == 'add') {
+                        /* Start adding to the addition stack. */
+                        $cols[0][] = $content;
+                        $state = 'add';
+                    } else {
+                        /* This is inside a change block, so start
+                         * accumulating lines. */
+                        $state = 'change';
+                        $cols[1][] = $content;
+                    }
+                } elseif ($action == '-') {
+                    /* This is a removal line. */
+                    $state = 'remove';
+                    $cols[0][] = $content;
+                } else {
+                    /* An empty block with no action. */
+                    switch ($state) {
+                    case 'add':
+                        $data['contents'][] = array('type' => 'add', 'lines' => $cols[0]);
+                        break;
+
+                    case 'remove':
+                        /* We have some removal lines pending in our
+                         * stack, so flush them. */
+                        $data['contents'][] = array('type' => 'remove', 'lines' => $cols[0]);
+                        break;
+
+                    case 'change':
+                        /* We have both remove and addition lines, so
+                         * this is a change block. */
+                        $data['contents'][] = array('type' => 'change', 'old' => $cols[0], 'new' => $cols[1]);
+                        break;
+                    }
+                    $cols = array(array(), array());
+                    $data['contents'][] = array('type' => 'empty', 'line' => $content);
+                    $state = 'dump';
+                }
+            }
+        }
+
+        /* Just flush any remaining entries in the columns stack. */
+        switch ($state) {
+        case 'add':
+            $data['contents'][] = array('type' => 'add', 'lines' => $cols[0]);
+            break;
+
+        case 'remove':
+            /* We have some removal lines pending in our stack, so
+             * flush them. */
+            $data['contents'][] = array('type' => 'remove', 'lines' => $cols[0]);
+            break;
+
+        case 'change':
+            /* We have both remove and addition lines, so this is a
+             * change block. */
+            $data['contents'][] = array('type' => 'change', 'old' => $cols[0], 'new' => $cols[1]);
+            break;
+        }
+
+        if (isset($data)) {
+            $ret[] = $data;
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Create a range of revisions between two revision numbers.
+     *
+     * @param Horde_VC $rep        A repository object.
+     * @param Horde_VC_File $file  The desired file.
+     * @param string $r1           The initial revision.
+     * @param string $r2           The ending revision.
+     *
+     * @return array  The revision range, or empty if there is no straight
+     *                line path between the revisions.
+     */
+    public function getRevisionRange($rep, $file, $r1, $r2)
+    {
+        $rev_ob = $rep->getRevisionObject();
+
+        if ($rev_ob->cmp($r1, $r2) == 1) {
+            $curr = $rev_ob->prev($r1);
+            $stop = $rev_ob->prev($r2);
+            $flip = true;
+        } else {
+            $curr = $r2;
+            $stop = $r1;
+            $flip = false;
+        }
+
+        $ret_array = array();
+
+        do {
+            $ret_array[] = $curr;
+            $curr = $rev_ob->prev($curr);
+            if ($curr == $stop) {
+                return ($flip) ? array_reverse($ret_array) : $ret_array;
+            }
+        } while ($rev_ob->cmp($curr, $stop) != -1);
+
+        return array();
+    }
+
+    /**
+     * Return the list of available diff types.
+     *
+     * @return array  The list of available diff types for use with get().
+     */
+    public function availableDiffTypes()
+    {
+        return $this->_diffTypes;
+    }
+}
+
+/**
+ * @package Horde_VC
+ */
+abstract class Horde_VC_Directory
+{
+    protected $_rep;
+    protected $_dirName;
+    protected $_files;
+    protected $_atticFiles;
+    protected $_mergedFiles;
+    protected $_dirs;
+    protected $_parent;
+    protected $_moduleName;
+
+    /**
+     * Create a Directory object to store information about the files in a
+     * single directory in the repository
+     *
+     * @param Horde_VC $rp            The Repository object this directory
+     *                                is part of.
+     * @param string $dn              Path to the directory.
+     * @param Horde_VC_Directory $pn  The parent Directory object to this one.
+     */
+    public function __construct($rep, $dn, $pn = '')
+    {
+        $this->_rep = $rep;
+        $this->_parent = $pn;
+        $this->_moduleName = $dn;
+        $this->_dirName = "/$dn";
+        $this->_dirs = $this->_files = array();
+    }
+
+    /**
+     * Return fully qualified pathname to this directory with no
+     * trailing /.
+     *
+     * @return Pathname of this directory
+     */
+    public function queryDir()
+    {
+        return $this->_dirName;
+    }
+
+    /**
+     * TODO
+     */
+    public function &queryDirList()
+    {
+        reset($this->_dirs);
+        return $this->_dirs;
+    }
+
+    /**
+     * TODO
+     */
+    public function &queryFileList($showattic = false)
+    {
+        return ($showattic && isset($this->_mergedFiles))
+            ? $this->_mergedFiles
+            : $this->_files;
+    }
+
+    /**
+     * TODO
+     */
+    abstract public function browseDir($cache = null, $quicklog = true,
+                                       $showattic = false);
+
+    /**
+     * Sort the contents of the directory in a given fashion and
+     * order.
+     *
+     * @param integer $how  Of the form SORT_* where * can be:
+     *                      NONE, NAME, AGE, REV for sorting by name, age or
+     *                      revision.
+     * @param integer $dir  Of the form SORT_* where * can be:
+     *                      ASCENDING, DESCENDING for the order of the sort.
+     */
+    public function applySort($how = Horde_VC::SORT_NONE,
+                              $dir = Horde_VC::SORT_ASCENDING)
+    {
+        // Always sort directories by name.
+        natcasesort($this->_dirs);
+
+        $this->_doFileSort($this->_files, $how);
+
+        if (isset($this->_atticFiles)) {
+            $this->_doFileSort($this->_atticFiles, $how);
+        }
+
+        if (isset($this->_mergedFiles)) {
+            $this->_doFileSort($this->_mergedFiles, $how);
+        }
+
+        if ($dir == Horde_VC::SORT_DESCENDING) {
+            $this->_dirs = array_reverse($this->_dirs);
+            $this->_files = array_reverse($this->_files);
+            if (isset($this->_mergedFiles)) {
+                $this->_mergedFiles = array_reverse($this->_mergedFiles);
+            }
+        }
+    }
+
+    /**
+     * TODO
+     */
+    protected function _doFileSort(&$fileList, $how = Horde_VC::SORT_NONE)
+    {
+        switch ($how) {
+        case Horde_VC::SORT_AGE:
+            usort($fileList, array($this, 'fileAgeSort'));
+            break;
+
+        case Horde_VC::SORT_NAME:
+            usort($fileList, array($this, 'fileNameSort'));
+            break;
+
+        case Horde_VC::SORT_AUTHOR:
+            usort($fileList, array($this, 'fileAuthorSort'));
+            break;
+
+        case Horde_VC::SORT_REV:
+            $this->_revob = $this->_rep->getRevisionObject();
+            usort($fileList, array($this, 'fileRevSort'));
+            break;
+
+        case Horde_VC::SORT_NONE:
+        default:
+            break;
+        }
+    }
+    /**
+     * Sort function for ascending age.
+     */
+    public function fileAgeSort($a, $b)
+    {
+        $aa = $a->queryLastLog();
+        $bb = $b->queryLastLog();
+        return ($aa->queryDate() == $bb->queryDate())
+            ? 0
+            : (($aa->queryDate() < $bb->queryDate()) ? 1 : -1);
+    }
+
+    /**
+     * Sort function by author name.
+     */
+    public function fileAuthorSort($a, $b)
+    {
+        $aa = $a->queryLastLog();
+        $bb = $b->queryLastLog();
+        return ($aa->queryAuthor() == $bb->queryAuthor())
+            ? 0
+            : (($aa->queryAuthor() > $bb->queryAuthor()) ? 1 : -1);
+    }
+
+    /**
+     * Sort function for ascending filename.
+     */
+    public function fileNameSort($a, $b)
+    {
+        return strcasecmp($a->name, $b->name);
+    }
+
+    /**
+     * Sort function for ascending revision.
+     */
+    public function fileRevSort($a, $b)
+    {
+        return $this->_revob->cmp($a->queryHead(), $b->queryHead());
+    }
+
+}
+
+/**
+ * @package Horde_VC
+ */
+class Horde_VC_File
+{
+    public $rep;
+    public $dir;
+    public $name;
+    public $logs;
+    public $revs;
+    public $head;
+    public $quicklog;
+    public $symrev;
+    public $revsym;
+    public $branches;
+    public $revob;
+
+    /**
+     * TODO
+     */
+    public function setRepository($rep)
+    {
+        $this->rep = $rep;
+    }
+
+    /**
+     * Has the file been deleted?
+     *
+     * @return boolean  Is this file deleted?
+     */
+    public function isDeleted()
+    {
+        return false;
+    }
+
+    /**
+     * Returns the name of the current file as in the repository
+     *
+     * @return string  Filename (without the path)
+     */
+    public function queryRepositoryName()
+    {
+        return $this->name;
+    }
+
+    /**
+     * Return the last revision of the current file on the HEAD branch
+     *
+     * @return Last revision of the current file
+     */
+    public function queryRevision()
+    {
+        if (!isset($this->revs[0])) {
+            return PEAR::raiseError('No revisions');
+        }
+        return $this->revs[0];
+    }
+
+    public function queryPreviousRevision($rev)
+    {
+        $last = false;
+        foreach ($this->revs as $entry) {
+            if ($last) {
+                return $entry;
+            }
+            if ($entry == $rev) {
+                $last = true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Return the HEAD (most recent) revision number for this file.
+     *
+     * @return HEAD revision number
+     */
+    public function queryHead()
+    {
+        return $this->queryRevision();
+    }
+
+   /**
+     * Return the last Horde_VC_Log object in the file.
+     *
+     * @return Horde_VC_Log of the last entry in the file
+     */
+    public function queryLastLog()
+    {
+        if (!isset($this->revs[0]) || !isset($this->logs[$this->revs[0]])) {
+            return PEAR::raiseError('No revisions');
+        }
+        return $this->logs[$this->revs[0]];
+    }
+
+    /**
+     * Sort the list of Horde_VC_Log objects that this file contains.
+     *
+     * @param integer $how  Horde_VC::SORT_REV (sort by revision),
+     *                      Horde_VC::SORT_NAME (sort by author name), or
+     *                      Horde_VC::SORT_AGE (sort by commit date).
+     */
+    public function applySort($how = Horde_VC::SORT_REV)
+    {
+        switch ($how) {
+        case Horde_VC::SORT_NAME:
+            $func = 'Name';
+            break;
+
+        case Horde_VC::SORT_AGE:
+            $func = 'Age';
+            break;
+
+        case Horde_VC::SORT_REV:
+        default:
+            $this->revob = $this->rep->getRevisionObject();
+            $func = 'Revision';
+            break;
+        }
+
+        uasort($this->logs, array($this, 'sortBy' . $func));
+        return true;
+    }
+
+    /**
+     * The sortBy*() functions are internally used by applySort.
+     */
+    public function sortByRevision($a, $b)
+    {
+        return $this->revob->cmp($b->rev, $a->rev);
+    }
+
+    public function sortByAge($a, $b)
+    {
+        return $b->date - $a->date;
+    }
+
+    public function sortByName($a, $b)
+    {
+        return strcmp($a->author, $b->author);
+    }
+
+    /**
+     * Return the fully qualified filename of this object.
+     *
+     * @return Fully qualified filename of this object
+     */
+    public function queryFullPath()
+    {
+        return $this->rep->sourceroot() . '/' . $this->queryModulePath();
+    }
+
+    /**
+     * Return the name of this file relative to its sourceroot.
+     *
+     * @return string  Pathname relative to the sourceroot.
+     */
+    public function queryModulePath()
+    {
+        return $this->dir . '/' . $this->name;
+    }
+
+}
+
+/**
+ * Horde_VC patchset class.
+ *
+ * @package Horde_VC
+ */
+class Horde_VC_Patchset
+{
+    protected $_rep;
+    protected $_patchsets = array();
+
+    public function setRepository($rep)
+    {
+        $this->_rep = $rep;
+    }
+}
+
+/**
+ * Horde_VC revisions class.
+ *
+ * Copyright Anil Madhavapeddy, <anil@recoil.org>
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @package Hored_VC
+ */
+abstract class Horde_VC_Revision
+{
+    /**
+     * Given a revision number, remove a given number of portions from
+     * it. For example, if we remove 2 portions of 1.2.3.4, we are
+     * left with 1.2.
+     *
+     * @param string $val      Input revision
+     * @param integer $amount  Number of portions to strip
+     *
+     * @return string  Stripped revision number
+     */
+    abstract public function strip($val, $amount = 1);
+
+    /**
+     * The size of a revision number is the number of portions it has.
+     * For example, 1,2.3.4 is of size 4.
+     *
+     * @param string $val  Revision number to determine size of
+     *
+     * @return integer  Size of revision number
+     */
+    abstract public function sizeof($val);
+
+    /**
+     * Given two revision numbers, this figures out which one is
+     * greater than the other by stepping along the decimal points
+     * until a difference is found, at which point a sign comparison
+     * of the two is returned.
+     *
+     * @param string $rev1  Period delimited revision number
+     * @param string $rev2  Second period delimited revision number
+     *
+     * @return integer  1 if the first is greater, -1 if the second if greater,
+     *                  and 0 if they are equal
+     */
+    abstract public function cmp($rev1, $rev2);
+
+    /**
+     * Return the logical revision before this one. Normally, this
+     * will be the revision minus one, but in the case of a new
+     * branch, we strip off the last two decimal places to return the
+     * original branch point.
+     *
+     * @param string $rev  Revision number to decrement.
+     *
+     * @return string|boolean  Revision number, or false if none could be
+     *                         determined.
+     */
+    abstract public function prev($rev);
+}
diff --git a/framework/Vcs/lib/Horde/Vcs/Cvs.php b/framework/Vcs/lib/Horde/Vcs/Cvs.php
new file mode 100644 (file)
index 0000000..51d790c
--- /dev/null
@@ -0,0 +1,968 @@
+<?php
+
+require_once dirname(__FILE__) . '/rcs.php';
+
+/**
+ * Horde_VC_cvs implementation.
+ *
+ * Copyright 2000-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @package Horde_VC
+ */
+class Horde_VC_cvs extends Horde_VC_rcs
+{
+    /**
+     * Constructor.
+     *
+     * @param array $params  Any parameter the class expects.
+     *                       Current parameters:
+     * <pre>
+     * 'sourceroot': The source root for this repository
+     * 'paths': Hash with the locations of all necessary binaries: 'rcsdiff',
+     *          'rlog', 'cvsps', 'cvsps_home' and the temp path: 'temp'
+     * </pre>
+     */
+    public function __construct($params)
+    {
+        $this->_sourceroot = $params['sourceroot'];
+        $this->_paths = $params['paths'];
+        parent::__construct();
+    }
+
+    /**
+     * Returns the temporary file path.
+     *
+     * @return string  Temporary file path.
+     */
+    public function getTempPath()
+    {
+        return $this->_paths['temp'];
+    }
+
+    /**
+     * TODO
+     */
+    public function isFile($where)
+    {
+        return @is_file($where . ',v') ||
+               @is_file(dirname($where) . '/Attic/' . basename($where) . ',v');
+    }
+
+    /**
+     * TODO
+     */
+    public function getFileObject($filename, $cache = null, $quicklog = false)
+    {
+        if (substr($filename, 0, 1) != '/') {
+            $filename = '/' . $filename;
+        }
+        return parent::getFileObject($this->sourceroot() . $filename, $cache, $quicklog);
+    }
+
+    /**
+     * TODO
+     */
+    public function getAnnotateObject($filename)
+    {
+        return new Horde_VC_Annotate_cvs($this, $filename);
+    }
+
+    /**
+     * TODO
+     */
+    public function getPatchsetObject($filename, $cache = null)
+    {
+        return parent::getPatchsetObject($this->sourceroot() . '/' . $filename, $cache);
+    }
+
+    /**
+     * Validation function to ensure that a revision number is of the right
+     * form.
+     *
+     * @param mixed $rev  The purported revision number.
+     *
+     * @return boolean  True if it is a revision number.
+     */
+    public function isValidRevision($rev)
+    {
+           return $rev && preg_match('/^[\d\.]+$/', $rev);
+    }
+}
+
+/**
+ * Horde_VC_cvs annotate class.
+ *
+ * Anil Madhavapeddy, <anil@recoil.org>
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @package Horde_VC
+ */
+class Horde_VC_Annotate_cvs extends Horde_VC_Annotate
+{
+    /**
+     * Temporary filename.
+     *
+     * @var string
+     */
+    protected $_tmpfile;
+
+    /**
+     * Constructor.
+     *
+     * TODO
+     */
+    public function __construct($rep, $file)
+    {
+        $this->_tmpfile = Util::getTempFile('vc', true, $rep->getTempPath());
+        parent::__construct($rep, $file);
+    }
+
+    /**
+     * TODO
+     */
+    public function doAnnotate($rev)
+    {
+        if (is_a($this->_file, 'PEAR_Error') ||
+            is_a($this->_rep, 'PEAR_Error') ||
+            !$this->_rep->isValidRevision($rev)) {
+            return false;
+        }
+
+        $where = $this->_file->queryModulePath();
+        $sourceroot = $this->_rep->sourceroot();
+
+        $pipe = popen($this->_rep->getPath('cvs') . ' -n server > ' . $this->_tmpfile, VC_WINDOWS ? 'wb' : 'w');
+
+        $out = array(
+            "Root $sourceroot",
+            'Valid-responses ok error Valid-requests Checked-in Updated Merged Removed M E',
+            'UseUnchanged',
+            'Argument -r',
+            "Argument $rev",
+            "Argument $where"
+        );
+
+        $dirs = explode('/', dirname($where));
+        while (count($dirs)) {
+            $out[] = 'Directory ' . implode('/', $dirs);
+            $out[] = "$sourceroot/" . implode('/', $dirs);
+            array_pop($dirs);
+        }
+
+        $out[] = 'Directory .';
+        $out[] = $sourceroot;
+        $out[] = 'annotate';
+
+        foreach ($out as $line) {
+            fwrite($pipe, "$line\n");
+        }
+        pclose($pipe);
+
+        if (!($fl = fopen($this->_tmpfile, VC_WINDOWS ? 'rb' : 'r'))) {
+            return false;
+        }
+
+        $lines = array();
+        $line = fgets($fl, 4096);
+
+        // Windows versions of cvs always return $where with forwards slashes.
+        if (VC_WINDOWS) {
+            $where = str_replace(DIRECTORY_SEPARATOR, '/', $where);
+        }
+
+        while ($line && !preg_match("|^E\s+Annotations for $where|", $line)) {
+            $line = fgets($fl, 4096);
+        }
+
+        if (!$line) {
+            return PEAR::raiseError('Unable to annotate; server said: ' . $line);
+        }
+
+        $lineno = 1;
+        while ($line = fgets($fl, 4096)) {
+            if (preg_match('/^M\s+([\d\.]+)\s+\((.+)\s+(\d+-\w+-\d+)\):.(.*)$/', $line, $regs)) {
+                $lines[] = array(
+                    'rev' => $regs[1],
+                    'author' => trim($regs[2]),
+                    'date' => $regs[3],
+                    'line' => $regs[4],
+                    'lineno' => $lineno++
+                );
+            }
+        }
+
+        fclose($fl);
+        return $lines;
+    }
+
+}
+
+/**
+ * Horde_VC_cvs checkout class.
+ *
+ * Anil Madhavapeddy, <anil@recoil.org>
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @package Horde_VC
+ */
+class Horde_VC_Checkout_cvs extends Horde_VC_Checkout
+{
+    /**
+     * Static function which returns a file pointing to the head of the
+     * requested revision of an RCS file.
+     *
+     * @param Horde_VC_cvs $rep  A repository object
+     * @param string $fullname   Fully qualified pathname of the desired file
+     *                           to checkout
+     * @param string $rev        Revision number to check out
+     *
+     * @return resource|object  Either a PEAR_Error object, or a stream
+     *                          pointer to the head of the checkout
+     */
+    public function get($rep, $fullname, $rev)
+    {
+        if (!$rep->isValidRevision($rev)) {
+            return PEAR::raiseError('Invalid revision number');
+        }
+
+        if (VC_WINDOWS) {
+            $mode = 'rb';
+            $q_name = '"' . escapeshellcmd(str_replace('\\', '/', $fullname)) . '"';
+        } else {
+            $mode = 'r';
+            $q_name = escapeshellarg($fullname);
+        }
+
+        if (!($RCS = popen($rep->getPath('co') . " -p$rev $q_name 2>&1", $mode))) {
+            return PEAR::raiseError('Couldn\'t perform checkout of the requested file');
+        }
+
+        /* First line from co should be of the form :
+         * /path/to/filename,v  -->  standard out
+         * and we check that this is the case and error otherwise
+         */
+
+        $co = fgets($RCS, 1024);
+        if (!preg_match('/^([\S ]+),v\s+-->\s+st(andar)?d ?out(put)?\s*$/', $co, $regs) || $regs[1].',v' != $fullname) {
+            return PEAR::raiseError('Unexpected output from checkout: ' . $co);
+        }
+
+        /* Next line from co is of the form:
+         * revision 1.2.3
+         * TODO: compare this to $rev for consistency, atm we just
+         *       discard the value to move input pointer along - avsm
+         */
+        $co = fgets($RCS, 1024);
+
+        return $RCS;
+    }
+
+}
+
+/**
+ * Horde_VC_cvs diff class.
+ *
+ * Copyright Anil Madhavapeddy, <anil@recoil.org>
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @package Horde_VC
+ */
+class Horde_VC_Diff_cvs extends Horde_VC_Diff
+{
+    /**
+     * Obtain the differences between two revisions of a file.
+     *
+     * @param Horde_VC $rep        A repository object.
+     * @param Horde_VC_File $file  The desired file.
+     * @param string $rev1         Original revision number to compare from.
+     * @param string $rev2         New revision number to compare against.
+     * @param string $type         The type of diff (e.g. 'unified').
+     * @param integer $num         Number of lines to be used in context and
+     *                             unified diffs.
+     * @param boolean $ws          Show whitespace in the diff?
+     *
+     * @return string|boolean  False on failure, or a string containing the
+     *                         diff on success.
+     */
+    public function get($rep, $file, $rev1, $rev2, $type = 'context',
+                        $num = 3, $ws = true)
+    {
+        /* Make sure that the file parameter is valid. */
+        if (is_a($file, 'PEAR_Error')) {
+            return false;
+        }
+
+        /* Check that the revision numbers are valid. */
+        $rev1 = $rep->isValidRevision($rev1) ? $rev1 : '1.1';
+        $rev2 = $rep->isValidRevision($rev1) ? $rev2 : '1.1';
+
+        $fullName = $file->queryFullPath();
+        $diff = array();
+        $options = '-kk ';
+        if (!$ws) {
+            $opts = ' -bB ';
+            $options .= $opts;
+        } else {
+            $opts = '';
+        }
+
+        switch ($type) {
+        case 'context':
+            $options = $opts . '-p --context=' . (int)$num;
+            break;
+
+        case 'unified':
+            $options = $opts . '-p --unified=' . (int)$num;
+            break;
+
+        case 'column':
+            $options = $opts . '--side-by-side --width=120';
+            break;
+
+        case 'ed':
+            $options = $opts . '-e';
+            break;
+        }
+
+        // Windows versions of cvs always return $where with forwards slashes.
+        if (VC_WINDOWS) {
+            $fullName = str_replace(DIRECTORY_SEPARATOR, '/', $fullName);
+        }
+
+        // TODO: add options for $hr options - however these may not be
+        // compatible with some diffs.
+        $command = $rep->getPath('rcsdiff') . " $options -r$rev1 -r$rev2 \"" . escapeshellcmd($fullName) . '" 2>&1';
+        if (VC_WINDOWS) {
+            $command .= ' < "' . __FILE__ . '"';
+        }
+
+        exec($command, $diff, $retval);
+        return ($retval > 0) ? $diff : array();
+    }
+
+}
+
+/**
+ * Horde_VC_cvs directory class.
+ *
+ * Copyright Anil Madhavapeddy, <anil@recoil.org>
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @package Horde_VC
+ */
+class Horde_VC_Directory_cvs extends Horde_VC_Directory
+{
+    /**
+     * Creates a CVS Directory object to store information
+     * about the files in a single directory in the repository.
+     *
+     * @param Horde_VC $rep           A repository object
+     * @param string $dn              Path to the directory.
+     * @param Horde_VC_Directory $pn  The parent Directory object to this one.
+     */
+    public function __construct($rep, $dn, $pn = '')
+    {
+        parent::__construct($rep, $dn, $pn);
+        $this->_dirName = $rep->sourceroot() . "/$dn";
+    }
+
+    /**
+     * Tell the object to open and browse its current directory, and
+     * retrieve a list of all the objects in there.  It then populates
+     * the file/directory stack and makes it available for retrieval.
+     *
+     * @return boolean|object  PEAR_Error object on an error, true on success.
+     */
+    public function browseDir($cache = null, $quicklog = true,
+                              $showattic = false)
+    {
+        /* Make sure we are trying to list a directory */
+        if (!@is_dir($this->_dirName)) {
+            return PEAR::raiseError('Unable to find directory ' . $this->_dirName);
+        }
+
+        /* Open the directory for reading its contents */
+        if (!($DIR = @opendir($this->_dirName))) {
+            return PEAR::raiseError(empty($php_errormsg) ? 'Permission denied' : $php_errormsg);
+        }
+
+        /* Create two arrays - one of all the files, and the other of
+         * all the directories. */
+        while (($name = readdir($DIR)) !== false) {
+            if (($name == '.') || ($name == '..')) {
+                continue;
+            }
+
+            $path = $this->_dirName . '/' . $name;
+            if (@is_dir($path)) {
+                /* Skip Attic directory. */
+                if ($name != 'Attic') {
+                    $this->_dirs[] = $name;
+                }
+            } elseif (@is_file($path) && (substr($name, -2) == ',v')) {
+                /* Spawn a new file object to represent this file. */
+                $fl = $this->_rep->getFileObject(substr($path, strlen($this->_rep->sourceroot()), -2), $cache, $quicklog);
+                if (is_a($fl, 'PEAR_Error')) {
+                    return $fl;
+                } else {
+                    $this->_files[] = $fl;
+                }
+            }
+        }
+
+        /* Close the filehandle; we've now got a list of dirs and files. */
+        closedir($DIR);
+
+        /* If we want to merge the attic, add it in here. */
+        if ($showattic) {
+            $atticDir = new Horde_VC_Directory_cvs($this->_rep, $this->_moduleName . '/Attic', $this);
+            if (!is_a($atticDir->browseDir($cache, $quicklog), 'PEAR_Error')) {
+                $this->_atticFiles = $atticDir->queryFileList();
+                $this->_mergedFiles = array_merge($this->_files, $this->_atticFiles);
+            }
+        }
+
+        return true;
+    }
+
+}
+
+/**
+ * Horde_VC_cvs file class.
+ *
+ * Copyright Anil Madhavapeddy, <anil@recoil.org>
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @package Horde_VC
+ */
+class Horde_VC_File_cvs extends Horde_VC_File
+{
+    /**
+     * Create a repository file object, and give it information about
+     * what its parent directory and repository objects are.
+     *
+     * @param string $fl  Full path to this file.
+     */
+    public function __construct($rep, $fl, $cache = null, $quicklog = false)
+    {
+        $fl .= ',v';
+        $this->rep = $rep;
+        $this->name = basename($fl);
+        $this->dir = dirname($fl);
+        $this->filename = $fl;
+        $this->cache = $cache;
+        $this->quicklog = $quicklog;
+        $this->logs = $this->revs = $this->branches = array();
+    }
+
+    function &getFileObject()
+    {
+        /* Assume file is in the Attic if it doesn't exist. */
+        $filename = $this->filename;
+        if (!@is_file($filename . ',v')) {
+            $filename = dirname($filename) . '/Attic/' . basename($filename);
+        }
+
+        /* The version of the cached data. Increment this whenever the
+         * internal storage format changes, such that we must
+         * invalidate prior cached data. */
+        $cacheVersion = 2;
+        $cacheId = $this->rep->sourceroot() . '_n' . $filename . '_f' . (int)$this->quicklog . '_v' . $cacheVersion;
+
+        $ctime = time() - filemtime($filename . ',v');
+        if ($this->cache &&
+            $this->cache->exists($cacheId, $ctime)) {
+            $fileOb = unserialize($this->cache->get($cacheId, $ctime));
+            $fileOb->setRepository($this->rep);
+        } else {
+            $fileOb = new Horde_VC_File_cvs($this->rep, $filename, $this->cache, $this->quicklog);
+            $fileOb->setRepository($this->rep);
+            if (is_a(($result = $fileOb->getBrowseInfo()), 'PEAR_Error')) {
+                return $result;
+            }
+            $fileOb->applySort(Horde_VC::SORT_AGE);
+
+            if ($this->cache) {
+                $this->cache->set($cacheId, serialize($fileOb));
+            }
+        }
+
+        return $fileOb;
+    }
+
+    /**
+     * If this file is present in an Attic directory, this indicates it.
+     *
+     * @return boolean  True if file is in the Attic, and false otherwise
+     */
+    function isDeleted()
+    {
+        return substr($this->dir, -5) == 'Attic';
+    }
+
+    /**
+     * Returns name of the current file without the repository
+     * extensions (usually ,v)
+     *
+     * @return string  Filename without repository extension
+     */
+    function queryName()
+    {
+        return preg_replace('/,v$/', '', $this->name);
+    }
+
+
+    function queryPreviousRevision($rev)
+    {
+        $ob = $this->rep->getRevisionObject();
+        return $ob->prev($rev);
+    }
+
+    /**
+     * Return the HEAD (most recent) revision number for this file.
+     *
+     * @return string  HEAD revision number
+     */
+    function queryHead()
+    {
+        return $this->head;
+    }
+
+    /**
+     * Populate the object with information about the revisions logs and dates
+     * of the file.
+     *
+     * @return boolean|object  PEAR_Error object on error, or true on success
+     */
+    function getBrowseInfo()
+    {
+        /* Check that we are actually in the filesystem. */
+        $file = $this->queryFullPath();
+        if (!is_file($file)) {
+            return PEAR::raiseError('File Not Found: ' . $file);
+        }
+
+        /* Call the RCS rlog command to retrieve the file
+         * information. */
+        $flag = $this->quicklog ? ' -r ' : ' ';
+        $q_file = VC_WINDOWS ? '"' . escapeshellcmd($file) . '"' : escapeshellarg($file);
+
+        $cmd = $this->rep->getPath('rlog') . $flag . $q_file;
+        exec($cmd, $return_array, $retval);
+
+        if ($retval) {
+            return PEAR::raiseError('Failed to spawn rlog to retrieve file log information for ' . $file);
+        }
+
+        $accum = array();
+        $symrev = array();
+        $revsym = array();
+        $state = 'init';
+        foreach ($return_array as $line) {
+            switch ($state) {
+            case 'init':
+                if (!strncmp('head: ', $line, 6)) {
+                    $this->head = substr($line, 6);
+                } elseif (!strncmp('branch:', $line, 7)) {
+                    $state = 'rev';
+                }
+                break;
+
+            case 'rev':
+                if (!strncmp('----------', $line, 10)) {
+                    $state = 'info';
+                    $this->symrev = $symrev;
+                    $this->revsym = $revsym;
+                } elseif (preg_match("/^\s+([^:]+):\s+([\d\.]+)/", $line, $regs)) {
+                    // Check to see if this is a branch
+                    if (preg_match('/^(\d+(\.\d+)+)\.0\.(\d+)$/', $regs[2])) {
+                        $branchRev = $this->toBranch($regs[2]);
+                        if (!isset($this->branches[$branchRev])) {
+                            $this->branches[$branchRev] = $regs[1];
+                        }
+                    } else {
+                        $symrev[$regs[1]] = $regs[2];
+                        if (empty($revsym[$regs[2]])) {
+                            $revsym[$regs[2]] = array();
+                        }
+                        $revsym[$regs[2]][] = $regs[1];
+                    }
+                }
+                break;
+
+            case 'info':
+                if (strncmp('==============================', $line, 30) &&
+                    strcmp('----------------------------', $line)) {
+                    $accum[] = $line;
+                } elseif (count($accum)) {
+                    // spawn a new Horde_VC_log object and add it to the logs
+                    // hash
+                    $log = new Horde_VC_Log_cvs($this);
+                    $err = $log->processLog($accum);
+                    // TODO: error checks - avsm
+                    $this->logs[$log->queryRevision()] = $log;
+                    $this->revs[] = $log->queryRevision();
+                    $accum = array();
+                }
+                break;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Return the fully qualified filename of this object.
+     *
+     * @return Fully qualified filename of this object
+     */
+    function queryFullPath()
+    {
+        return $this->dir . '/' . $this->name;
+    }
+
+    /**
+     * Return the name of this file relative to its sourceroot.
+     *
+     * @return string  Pathname relative to the sourceroot.
+     */
+    function queryModulePath()
+    {
+        return preg_replace('|^'. $this->rep->sourceroot() . '/?(.*),v$|', '\1', $this->queryFullPath());
+    }
+
+    /**
+     * Given a revision number of the form x.y.0.z, this remaps it
+     * into the appropriate branch number, which is x.y.z
+     *
+     * @param string $rev  Even-digit revision number of a branch
+     *
+     * @return string  Odd-digit Branch number
+     */
+    function toBranch($rev)
+    {
+        /* Check if we have a valid revision number */
+        $rev_ob = $this->rep->getRevisionObject();
+        if (!$rev_ob->valid($rev)) {
+            return false;
+        }
+
+        if (($end = strrpos($rev, '.')) === false) {
+            return false;
+        }
+
+        $rev[$end] = 0;
+        if (($end2 = strrpos($rev, '.')) === false) {
+            return substr($rev, ++$end);
+        }
+
+        return substr_replace($rev, '.', $end2, ($end - $end2 + 1));
+    }
+
+}
+
+/**
+ * Horde_VC_cvs log class.
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @package Horde_VC
+ */
+class Horde_VC_Log_cvs {
+
+    var $rep;
+    var $file;
+    var $tags;
+    var $rev;
+    var $date;
+    var $log;
+    var $author;
+    var $state;
+    var $lines;
+    var $branches;
+
+    /**
+     *
+     */
+    public function __construct($fl)
+    {
+        $this->file = $fl;
+        $this->branches = array();
+    }
+
+    function processLog($raw)
+    {
+        /* Initialise a simple state machine to parse the output of rlog */
+        $state = 'init';
+        while (!empty($raw) && $state != 'done') {
+            switch ($state) {
+            /* Found filename, now looking for the revision number */
+            case 'init':
+                $line = array_shift($raw);
+                if (preg_match("/revision (.+)$/", $line, $parts)) {
+                    $this->rev = $parts[1];
+                    $state = 'date';
+                }
+                break;
+
+            /* Found revision and filename, now looking for date */
+            case 'date':
+                $line = array_shift($raw);
+                if (preg_match("|^date:\s+(\d+)[-/](\d+)[-/](\d+)\s+(\d+):(\d+):(\d+).*?;\s+author:\s+(.+);\s+state:\s+(\S+);(\s+lines:\s+([0-9\s+-]+))?|", $line, $parts)) {
+                    $this->date = gmmktime($parts[4], $parts[5], $parts[6], $parts[2], $parts[3], $parts[1]);
+                    $this->author = $parts[7];
+                    $this->state = $parts[8];
+                    $this->lines = isset($parts[10]) ? $parts[10] : '';
+                    $state = 'branches';
+                }
+                break;
+
+            /* Look for a branch point here - format is 'branches:
+             * x.y.z; a.b.c;' */
+            case 'branches':
+                /* If we find a branch tag, process and pop it,
+                   otherwise leave input stream untouched */
+                if (!empty($raw) && preg_match("/^branches:\s+(.*)/", $raw[0], $br)) {
+                    /* Get the list of branches from the string, and
+                     * push valid revisions into the branches array */
+                    $brs = preg_split('/;\s*/', $br[1]);
+                    foreach ($brs as $brpoint) {
+                        //@TODO
+                        //if (Horde_VC_Revision::valid($brpoint)) {
+                            $this->branches[] = $brpoint;
+                        //}
+                    }
+                    array_shift($raw);
+                }
+
+                $state = 'done';
+                break;
+            }
+        }
+
+        /* Assume the rest of the lines are the log message */
+        $this->log = implode("\n", $raw);
+        $this->tags = isset($this->file->revsym[$this->rev]) ?
+            $this->file->revsym[$this->rev] :
+            array();
+    }
+
+    function queryDate()
+    {
+        return $this->date;
+    }
+
+    function queryRevision()
+    {
+        return $this->rev;
+    }
+
+    function queryAuthor()
+    {
+        return $this->author;
+    }
+
+    function queryLog()
+    {
+        return $this->log;
+    }
+
+    function queryChangedLines()
+    {
+        return isset($this->lines) ? ($this->lines) : '';
+    }
+
+    /**
+     * Given a branch revision number, this function remaps it
+     * accordingly, and performs a lookup on the file object to
+     * return the symbolic name(s) of that branch in the tree.
+     *
+     * @return array  Hash of symbolic names => branch numbers
+     */
+    function querySymbolicBranches()
+    {
+        $symBranches = array();
+        foreach ($this->branches as $branch) {
+            $parts = explode('.', $branch);
+            $last = array_pop($parts);
+            $parts[] = '0';
+            $parts[] = $last;
+            $rev = implode('.', $parts);
+            if (isset($this->file->branches[$branch])) {
+                $symBranches[$this->file->branches[$branch]] = $branch;
+            }
+        }
+        return $symBranches;
+    }
+
+}
+
+/**
+ * Horde_VC_cvs Patchset class.
+ *
+ * Copyright Anil Madhavapeddy, <anil@recoil.org>
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @package Horde_VC
+ */
+class Horde_VC_Patchset_cvs extends Horde_VC_Patchset {
+
+    var $_dir;
+    var $_name;
+
+    /**
+     * Create a patchset object.
+     *
+     * @param string $file  The filename to get patchsets for.
+     */
+    public function __construct($file)
+    {
+        $this->_name = basename($file);
+        $this->_dir = dirname($file);
+    }
+
+    function &getPatchsetObject($rep, $filename, $cache = null)
+    {
+        /* The version of the cached data. Increment this whenever the
+         * internal storage format changes, such that we must
+         * invalidate prior cached data. */
+        $cacheVersion = 1;
+        $cacheId = $rep->sourceroot() . '_n' . $filename . '_f_v' . $cacheVersion;
+
+        $ctime = time() - filemtime($filename . ',v');
+        if ($cache &&
+            $cache->exists($cacheId, $ctime)) {
+            $psOb = unserialize($cache->get($cacheId, $ctime));
+            $psOb->setRepository($rep);
+        } else {
+            $psOb = new Horde_VC_Patchset_cvs($filename);
+            $psOb->setRepository($rep);
+            if (is_a(($result = $psOb->getPatchsets()), 'PEAR_Error')) {
+                return $result;
+            }
+
+            if ($cache) {
+                $cache->set($cacheId, serialize($psOb));
+            }
+        }
+
+        return $psOb;
+    }
+
+    /**
+     * Populate the object with information about the patchsets that
+     * this file is involved in.
+     *
+     * @return boolean|object  PEAR_Error object on error, or true on success.
+     */
+    function getPatchsets()
+    {
+        /* Check that we are actually in the filesystem. */
+        if (!is_file($this->getFullPath() . ',v')) {
+            return PEAR::raiseError('File Not Found');
+        }
+
+        /* Call cvsps to retrieve all patchsets for this file. */
+        $q_root = $this->_rep->sourceroot();
+        $q_root = VC_WINDOWS ? '"' . escapeshellcmd($q_root) . '"' : escapeshellarg($q_root);
+
+        $cvsps_home = $this->_rep->getPath('cvsps_home');
+        $HOME = !empty($cvsps_home) ?
+            'HOME=' . $cvsps_home . ' ' :
+            '';
+
+        $cmd = $HOME . $this->_rep->getPath('cvsps') . ' -u --cvs-direct --root ' . $q_root . ' -f ' . escapeshellarg($this->_name) . ' ' . escapeshellarg($this->_dir);
+        exec($cmd, $return_array, $retval);
+        if ($retval) {
+            return PEAR::raiseError('Failed to spawn cvsps to retrieve patchset information');
+        }
+
+        $this->_patchsets = array();
+        $state = 'begin';
+        foreach ($return_array as $line) {
+            $line = trim($line);
+            if ($line == '---------------------') {
+                $state = 'begin';
+                continue;
+            }
+
+            switch ($state) {
+            case 'begin':
+                $id = str_replace('PatchSet ', '', $line);
+                $this->_patchsets[$id] = array();
+                $state = 'info';
+                break;
+
+            case 'info':
+                $info = explode(':', $line, 2);
+                switch ($info[0]) {
+                case 'Date':
+                    if (preg_match('|(\d{4})/(\d{2})/(\d{2}) (\d{2}):(\d{2}):(\d{2})|', $info[1], $date)) {
+                        $this->_patchsets[$id]['date'] = gmmktime($date[4], $date[5], $date[6], $date[2], $date[3], $date[1]);
+                    }
+                    break;
+
+                case 'Author':
+                    $this->_patchsets[$id]['author'] = trim($info[1]);
+                    break;
+
+                case 'Branch':
+                    if (trim($info[1]) != 'HEAD') {
+                        $this->_patchsets[$id]['branch'] = trim($info[1]);
+                    }
+                    break;
+
+                case 'Tag':
+                    if (trim($info[1]) != '(none)') {
+                        $this->_patchsets[$id]['tag'] = trim($info[1]);
+                    }
+                    break;
+
+                case 'Log':
+                    $state = 'log';
+                    $this->_patchsets[$id]['log'] = '';
+                    break;
+                }
+                break;
+
+            case 'log':
+                if ($line == 'Members:') {
+                    $state = 'members';
+                    $this->_patchsets[$id]['log'] = trim($this->_patchsets[$id]['log']);
+                    $this->_patchsets[$id]['members'] = array();
+                } else {
+                    $this->_patchsets[$id]['log'] .= $line . "\n";
+                }
+                break;
+
+            case 'members':
+                if (!empty($line)) {
+                    $parts = explode(':', $line);
+                    $revs = explode('->', $parts[1]);
+                    $this->_patchsets[$id]['members'][] = array('file' => $parts[0],
+                                                                'from' => $revs[0],
+                                                                'to' => $revs[1]);
+                }
+                break;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Return the fully qualified filename of this object.
+     *
+     * @return string  Fully qualified filename of this object
+     */
+    function getFullPath()
+    {
+        return $this->_dir . '/' . $this->_name;
+    }
+
+}
+
+class Horde_VC_Revision_cvs extends Horde_VC_Revision_rcs {}
diff --git a/framework/Vcs/lib/Horde/Vcs/Exception.php b/framework/Vcs/lib/Horde/Vcs/Exception.php
new file mode 100644 (file)
index 0000000..04c2a78
--- /dev/null
@@ -0,0 +1,2 @@
+<?php
+class Horde_VC_Exception extends Exception {}
diff --git a/framework/Vcs/lib/Horde/Vcs/Git.php b/framework/Vcs/lib/Horde/Vcs/Git.php
new file mode 100644 (file)
index 0000000..cc33f5e
--- /dev/null
@@ -0,0 +1,665 @@
+<?php
+/**
+ * Horde_VC_git implementation.
+ *
+ * Copyright 2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package Horde_VC
+ */
+class Horde_VC_git extends Horde_VC
+{
+    /**
+     * Constructor.
+     *
+     * @param array $params  Any parameter the class expects.
+     *                       Current parameters:
+     *                       'sourceroot': The source root for this
+     *                                     repository
+     *                       'paths': Hash with the locations of all
+     *                                necessary binaries: 'git'
+     */
+    public function __construct($params)
+    {
+        $this->_sourceroot = escapeshellcmd($params['sourceroot']);
+        $this->_paths = $params['paths'];
+        parent::__construct();
+    }
+
+    /**
+     * Validation function to ensure that a revision number is of the right
+     * form.
+     *
+     * @param mixed $rev  The purported revision number.
+     *
+     * @return boolean  True if it is a revision number.
+     */
+    public function isValidRevision($rev)
+    {
+        return preg_match('/^[a-f0-9]+$/i', $rev);
+    }
+
+    public function getCommand()
+    {
+        return $this->getPath('git') . ' --git-dir=' . $this->_sourceroot;
+    }
+
+}
+
+/**
+ * Horde_VC_git annotate class.
+ *
+ * Chuck Hagenbuch <chuck@horde.org>
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @package Horde_VC
+ */
+class Horde_VC_Annotate_git extends Horde_VC_Annotate
+{
+    public function __construct($rep, $file)
+    {
+        if (is_a($file, 'PEAR_Error')) {
+            throw new Horde_VC_Exception($file->getMessage());
+        }
+        parent::__construct($rep, $file);
+    }
+
+    /**
+     * TODO
+     */
+    public function doAnnotate($rev)
+    {
+        $this->_rep->assertValidRevision($rev);
+
+        $command = $this->_rep->getCommand() . ' blame -p ' . $rev . ' -- ' . escapeshellarg($this->_file->queryModulePath()) . ' 2>&1';
+        $pipe = popen($command, 'r');
+        if (!$pipe) {
+            return PEAR::raiseError('Failed to execute git annotate: ' . $command);
+        }
+
+        $curr_rev = null;
+        $db = $lines = array();
+        $lines_group = $line_num = 0;
+
+        while (!feof($pipe)) {
+            $line = rtrim(fgets($pipe, 4096));
+            if (!$line) {
+                continue;
+            }
+
+            if ($line[0] == "\t") {
+                $lines[] = array(
+                    'author' => $db[$curr_rev]['author'] . ' ' . $db[$curr_rev]['author-mail'],
+                    'date' => $db[$curr_rev]['author-time'],
+                    'line' => substr($line, 1),
+                    'lineno' => $line_num++,
+                    'rev' => $curr_rev
+                );
+                --$lines_group;
+            } elseif ($line != 'boundary') {
+                if ($lines_group) {
+                    list($prefix, $linedata) = explode(' ', $line, 2);
+                    switch ($prefix) {
+                    case 'author':
+                    case 'author-mail':
+                    case 'author-time':
+                    //case 'author-tz':
+                        $db[$curr_rev][$prefix] = trim($linedata);
+                        break;
+                    }
+                } else {
+                    list($curr_rev, , $line_num, $lines_group) = explode(' ', $line);
+                }
+            }
+        }
+
+        pclose($pipe);
+        return $lines;
+    }
+
+}
+
+/**
+ * Horde_VC_git checkout class.
+ *
+ * Chuck Hagenbuch <chuck@horde.org>
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @package Horde_VC
+ */
+class Horde_VC_Checkout_git extends Horde_VC_Checkout
+{
+    /**
+     * Function which returns a file pointing to the head of the requested
+     * revision of an SVN file.
+     *
+     * @param Horde_VC $rep     A repository object
+     * @param string $fullname  Fully qualified pathname of the desired file
+     *                          to checkout
+     * @param string $rev       Revision number to check out
+     *
+     * @return resource|object  Either a PEAR_Error object, or a stream
+     *                          pointer to the head of the checkout.
+     */
+    function get($rep, $file, $rev)
+    {
+        $rep->assertValidRevision($rev);
+
+        return ($pipe = popen($rep->getCommand() . ' cat-file blob ' . $file->getHashForRevision($rev) . ' 2>&1', VC_WINDOWS ? 'rb' : 'r'))
+            ? $pipe
+            : PEAR::raiseError('Couldn\'t perform checkout of the requested file');
+    }
+
+}
+
+/**
+ * Horde_VC_git diff class.
+ *
+ * Copyright Chuck Hagenbuch <chuck@horde.org>
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @package Horde_VC
+ */
+class Horde_VC_Diff_git extends Horde_VC_Diff
+{
+    /**
+     * The available diff types.
+     *
+     * @var array
+     */
+    protected $_diffTypes = array('unified');
+
+    /**
+     * Obtain the differences between two revisions of a file.
+     *
+     * @param Horde_VC $rep        A repository object.
+     * @param Horde_VC_File $file  The desired file.
+     * @param string $rev1         Original revision number to compare from.
+     * @param string $rev2         New revision number to compare against.
+     * @param string $type         The type of diff (e.g. 'unified').
+     * @param integer $num         Number of lines to be used in context and
+     *                             unified diffs.
+     * @param boolean $ws          Show whitespace in the diff?
+     *
+     * @return string|boolean  False on failure, or a string containing the
+     *                         diff on success.
+     */
+    public function get($rep, $file, $rev1, $rev2, $type = 'context',
+                               $num = 3, $ws = true)
+    {
+        /* Make sure that the file parameter is valid */
+        if (is_a($file, 'PEAR_Error')) {
+            return false;
+        }
+
+        /* Check that the revision numbers are valid */
+        $rev1 = $rep->isValidRevision($rev1) ? $rev1 : 0;
+        $rev2 = $rep->isValidRevision($rev1) ? $rev2 : 0;
+
+        $diff = array();
+        $options = '';
+        if (!$ws) {
+            $options .= ' -b -w ';
+        }
+
+        switch ($type) {
+        case 'unified':
+            $options .= '--unified=' . (int)$num;
+            break;
+        }
+
+        // TODO: add options for $hr options - however these may not
+        // be compatible with some diffs.
+        $command = $rep->getCommand() . " diff -M -C $options --no-color $rev1..$rev2 -- \"" . $file->queryModulePath() . '" 2>&1';
+
+        exec($command, $diff, $retval);
+        return $diff;
+    }
+
+    /**
+     * Create a range of revisions between two revision numbers.
+     *
+     * @param Horde_VC $rep        A repository object.
+     * @param Horde_VC_File $file  The desired file.
+     * @param string $r1           The initial revision.
+     * @param string $r2           The ending revision.
+     *
+     * @return array  The revision range, or empty if there is no straight
+     *                line path between the revisions.
+     */
+    public function getRevisionRange($rep, $file, $r1, $r2)
+    {
+        $cmd = $rep->getCommand() . ' rev-list ' . $r1 . '..' . $r2;
+        $pipe = popen($cmd, 'r');
+        if (!is_resource($pipe)) {
+            throw new Horde_VC_Exception('Unable to run ' . $cmd . ': ' . error_get_last());
+        }
+
+        $revs = array();
+
+        while (!feof($pipe)) {
+            if ($rev = trim(fgets($pipe, 4096))) {
+                $revs[] = $rev;
+            }
+        }
+
+        $revs[] = $r1;
+        pclose($pipe);
+
+        return $revs;
+    }
+}
+
+/**
+ * Horde_VC_git directory class.
+ *
+ * Copyright Chuck Hagenbuch <chuck@horde.org>
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @package Horde_VC
+ */
+class Horde_VC_Directory_git extends Horde_VC_Directory
+{
+    /**
+     * Tell the object to open and browse its current directory, and
+     * retrieve a list of all the objects in there.  It then populates
+     * the file/directory stack and makes it available for retrieval.
+     *
+     * @return PEAR_Error object on an error, 1 on success.
+     */
+    public function browseDir($cache = null, $quicklog = true,
+                              $showattic = false)
+    {
+        //@TODO For now, we're browsing HEAD
+        $head = trim(shell_exec($this->_rep->getCommand() . ' rev-parse --verify HEAD'));
+        //@TODO can use this to see if we have a valid cache of the tree at this revision
+
+        $dir = $this->queryDir();
+        if (substr($dir, 0, 1) == '/') {
+            $dir = substr($dir, 1);
+        }
+        if (strlen($dir) && substr($dir, -1) != '/') {
+            $dir .= '/';
+        }
+
+        $cmd = $this->_rep->getCommand() . ' ls-tree --full-name ' . $head . ' ' . escapeshellarg($dir) . ' 2>&1';
+
+        $dir = popen($cmd, 'r');
+        if (!$dir) {
+            return PEAR::raiseError('Failed to execute git ls-tree: ' . $cmd);
+        }
+
+        // Create two arrays - one of all the files, and the other of
+        // all the dirs.
+        while (!feof($dir)) {
+            $line = chop(fgets($dir, 1024));
+            if (!strlen($line)) {
+                continue;
+            }
+
+            list( ,$type, , $file) = preg_split('/\s+/', $line, -1, PREG_SPLIT_NO_EMPTY);
+            if ($type == 'tree') {
+                $this->_dirs[] = basename($file);
+            } else {
+                $this->_files[] = $this->_rep->getFileObject($file, $cache, $quicklog);
+            }
+        }
+
+        pclose($dir);
+    }
+
+}
+
+/**
+ * Horde_VC_git file class.
+ *
+ * Copyright Chuck Hagenbuch <chuck@horde.org>
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @package Horde_VC
+ */
+class Horde_VC_File_git extends Horde_VC_File
+{
+    /**
+     * Create a repository file object, and give it information about
+     * what its parent directory and repository objects are.
+     *
+     * @param string $fl  Full path to this file.
+     */
+    public function __construct($rep, $fl, $cache = null, $quicklog = false)
+    {
+        // FIXME:
+        $rep->cache = $cache;
+
+        $this->rep = $rep;
+        $this->fullname = $fl;
+        $this->name = basename($fl);
+        $this->dir = dirname($fl);
+        $this->quicklog = $quicklog;
+        $this->cache = $cache;
+
+        $this->logs = $this->revs = $this->revsym = $this->symrev = $this->branches = array();
+    }
+
+    public function getFileObject()
+    {
+        $this->getBrowseInfo();
+        return $this;
+    }
+
+    /**
+     * Get the hash name for this file at a specific revision.
+     *
+     * @param string $rev
+     *
+     * @return string Commit hash
+     */
+    public function getHashForRevision($rev)
+    {
+        return $this->logs[$rev]->getHashForPath($this->fullname);
+    }
+
+    /**
+     * Returns name of the current file without the repository
+     * extensions (usually ,v)
+     *
+     * @return Filename without repository extension
+     */
+    function queryName()
+    {
+        return $this->name;
+    }
+
+    /**
+     * Populate the object with information about the revisions logs
+     * and dates of the file.
+     *
+     * @return mixed  True on success, PEAR_Error on error.
+     */
+    function getBrowseInfo()
+    {
+        // Get the list of revisions that touch this path
+        $Q = VC_WINDOWS ? '"' : "'";
+        $cmd = $this->rep->getCommand() . ' rev-list HEAD -- ' . $Q . str_replace($Q, '\\' . $Q, $this->fullname) . $Q . ' 2>&1';
+        $revisions = shell_exec($cmd);
+        if (substr($revisions, 5) == 'fatal') {
+            throw new Horde_VC_Exception($revisions);
+        }
+
+        if (!strlen($revisions)) {
+            throw new Horde_VC_Exception('No revisions found');
+        }
+
+        $this->revs = explode("\n", trim($revisions));
+        foreach ($this->revs as $rev) {
+            $this->logs[$rev] = Horde_VC_Log_git::factory($this->rep, $this, $rev);
+            if ($this->quicklog) {
+                break;
+            }
+        }
+    }
+
+}
+
+/**
+ * Horde_VC_git log class.
+ *
+ * Chuck Hagenbuch <chuck@horde.org>
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @package Horde_VC
+ */
+class Horde_VC_Log_git {
+
+    var $rep;
+    var $err;
+    var $file;
+    var $files = array();
+    var $tags;
+    var $rev;
+    var $date;
+    var $log;
+    var $author;
+    var $state;
+    var $lines;
+    var $branches;
+
+    public static function factory($rep, $file, $rev)
+    {
+        /* The version of the cached data. Increment this whenever the
+         * internal storage format changes, such that we must
+         * invalidate prior cached data. */
+        $cacheVersion = 1;
+        $cacheId = $rep->sourceroot() . '_r' . $rev . '_v' . $cacheVersion;
+
+        if (0/*@TODO no caching during dev*/ && $rep->cache &&
+            // Individual revisions can be cached forever
+            $rep->cache->exists($cacheId, 0)) {
+            $logOb = unserialize($rep->cache->get($cacheId, 0));
+            $logOb->setRepository($rep);
+        } else {
+            $logOb = new Horde_VC_Log_git($rep, $file, $rev);
+
+            if ($rep->cache) {
+                $rep->cache->set($cacheId, serialize($logOb));
+            }
+        }
+
+        return $logOb;
+    }
+
+    /**
+     * Constructor.
+     */
+    public function __construct($rep, $fl, $rev)
+    {
+        $this->rep = $rep;
+        $this->file = $fl;
+        $this->rev = $rev;
+        $this->branches = array();
+
+        $cmd = $this->rep->getCommand() . ' whatchanged --no-color --pretty=fuller --no-abbrev -n 1 ' . $this->rev;
+        $pipe = popen($cmd, 'r');
+        if (!is_resource($pipe)) {
+            throw new Horde_VC_Exception('Unable to run ' . $cmd . ': ' . error_get_last());
+        }
+
+        $commit = trim(array_pop(explode(' ', fgets($pipe))));
+        if ($commit != $rev) {
+            throw new Horde_VC_Exception('Expected ' . $rev . ', got ' . $commit);
+        }
+
+        $properties = array();
+        $line = trim(fgets($pipe));
+        while ($line != '') {
+            list($key, $value) = explode(':', $line, 2);
+            $properties[trim($key)] = trim($value);
+            $line = trim(fgets($pipe));
+        }
+
+        $this->author = $properties['Author'];
+        $this->date = strtotime($properties['AuthorDate']);
+        //@TODO use Committer, CommitterDate, and Merge properties
+
+        $log = '';
+        $line = fgets($pipe);
+        while (substr($line, 0, 1) != ':') {
+            $log .= $line;
+            $line = fgets($pipe);
+        }
+        $this->log = trim($log);
+        //@TODO internal line formatting
+
+        // Build list of files in this revision. The format of these lines is
+        // documented in the git diff-tree documentation:
+        // http://www.kernel.org/pub/software/scm/git/docs/git-diff-tree.html
+        while ($line) {
+            preg_match('/:(\d+) (\d+) (\w+) (\w+) (.+)\t(.+)(\t(.+))?/', $line, $matches);
+            $this->files[$matches[6]] = array(
+                'srcMode' => $matches[1],
+                'dstMode' => $matches[2],
+                'srcSha1' => $matches[3],
+                'dstSha1' => $matches[4],
+                'status' => $matches[5],
+                'srcPath' => $matches[6],
+                'dstPath' => isset($matches[7]) ? $matches[7] : '',
+            );
+
+            $line = fgets($pipe);
+        }
+    }
+
+    function setRepository($rep)
+    {
+        $this->rep = $rep;
+    }
+
+    public function getHashForPath($path)
+    {
+        //@TODO not confident yet abotu the choice of dstSha1 vs. srcSha1
+        return $this->files[$path]['dstSha1'];
+    }
+
+    function queryDate()
+    {
+        return $this->date;
+    }
+
+    function queryRevision()
+    {
+        return $this->rev;
+    }
+
+    function queryAuthor()
+    {
+        return $this->author;
+    }
+
+    function queryLog()
+    {
+        return $this->log;
+    }
+
+    function queryChangedLines()
+    {
+        return isset($this->lines) ? ($this->lines) : '';
+    }
+
+    /**
+     * Given a branch revision number, this function remaps it
+     * accordingly, and performs a lookup on the file object to
+     * return the symbolic name(s) of that branch in the tree.
+     *
+     * @return hash of symbolic names => branch numbers
+     */
+    function querySymbolicBranches()
+    {
+        $symBranches = array();
+        return $symBranches;
+    }
+
+}
+
+/**
+ * Horde_VC_git Patchset class.
+ *
+ * Copyright Chuck Hagenbuch <chuck@horde.org>
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @package Horde_VC
+ */
+class Horde_VC_Patchset_git extends Horde_VC_Patchset {
+
+    var $_file;
+
+    /**
+     * Create a patchset object.
+     *
+     * @param string $file  The filename to get patchsets for.
+     */
+    public function __construct($file)
+    {
+        $this->_file = $file;
+    }
+
+    function &getPatchsetObject($rep, $filename, $cache = null)
+    {
+        /* The version of the cached data. Increment this whenever the
+         * internal storage format changes, such that we must
+         * invalidate prior cached data. */
+        $cacheVersion = 1;
+        $cacheId = $rep->sourceroot() . '_n' . $filename . '_f_v' . $cacheVersion;
+
+        if ($cache &&
+            // ?
+            $cache->exists($cacheId, 3600)) {
+            $psOb = unserialize($cache->get($cacheId, 3600));
+            $psOb->setRepository($rep);
+        } else {
+            $psOb = new Horde_VC_Patchset_git($filename);
+            $psOb->setRepository($rep);
+            if (is_a(($result = $psOb->getPatchsets()), 'PEAR_Error')) {
+                return $result;
+            }
+
+            if ($cache) {
+                $cache->set($cacheId, serialize($psOb));
+            }
+        }
+
+        return $psOb;
+    }
+
+    /**
+     * Populate the object with information about the patchsets that
+     * this file is involved in.
+     *
+     * @return mixed  PEAR_Error object on error, or true on success.
+     */
+    function getPatchsets()
+    {
+        $fileOb = new Horde_VC_File_git($this->_rep, $this->_file);
+        if (is_a(($result = $fileOb->getBrowseInfo()), 'PEAR_Error')) {
+            return $result;
+        }
+
+        $this->_patchsets = array();
+        foreach ($fileOb->logs as $rev => $log) {
+            $this->_patchsets[$rev] = array();
+            $this->_patchsets[$rev]['date'] = $log->queryDate();
+            $this->_patchsets[$rev]['author'] = $log->queryAuthor();
+            $this->_patchsets[$rev]['branch'] = '';
+            $this->_patchsets[$rev]['tag'] = '';
+            $this->_patchsets[$rev]['log'] = $log->queryLog();
+            $this->_patchsets[$rev]['members'] = array();
+            foreach ($log->files as $file) {
+                $action = substr($file, 0, 1);
+                $file = preg_replace('/.*?\s(.*?)(\s|$).*/', '\\1', $file);
+                $to = $rev;
+                if ($action == 'A') {
+                    $from = 'INITIAL';
+                } elseif ($action == 'D') {
+                    $from = $to;
+                    $to = '(DEAD)';
+                } else {
+                    // This technically isn't the previous revision,
+                    // but it works for diffing purposes.
+                    $from = $to - 1;
+                }
+
+                $this->_patchsets[$rev]['members'][] = array('file' => $file,
+                                                             'from' => $from,
+                                                             'to' => $to);
+            }
+        }
+
+        return true;
+    }
+
+}
+
+class Horde_VC_Revision_git extends Horde_VC_Revision {}
diff --git a/framework/Vcs/lib/Horde/Vcs/Rcs.php b/framework/Vcs/lib/Horde/Vcs/Rcs.php
new file mode 100644 (file)
index 0000000..0eb1812
--- /dev/null
@@ -0,0 +1,324 @@
+<?php
+/**
+ * Horde_VC_rcs implementation.
+ *
+ * Copyright 2004-2007 Jeff Schwentner <jeffrey.schwentner@lmco.com>
+ *
+ * @author  Jeff Schwentner <jeffrey.schwentner@lmco.com>
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @package Horde_VC
+ */
+class Horde_VC_rcs extends Horde_VC
+{
+    /**
+     * Checks an RCS file in with a specified change log.
+     *
+     * @param string $filepath    Location of file to check in.
+     * @param string $message     Log of changes since last version.
+     * @param string $user        The user name to use for the check in.
+     * @param boolean $newBinary  Does the change involve binary data?
+     *
+     * @return string|object  The new revision number on success, or a
+     *                        PEAR_Error object on failure.
+     */
+    public function ci($filepath, $message, $user = null, $newBinary = false)
+    {
+        if ($user) {
+            putenv('LOGNAME=' . $user);
+        } else {
+            putenv('LOGNAME=guest');
+        }
+
+        $Q = VC_WINDOWS ? '"' : "'" ;
+
+        $ci_cmd = $this->getPath('ci') . ' ' . $Q . $filepath . $Q.' 2>&1';
+        $rcs_cmd = $this->getPath('rcs') . ' -i -kb ' . $Q . $filepath . $Q.' 2>&1';
+        $output = '';
+
+        $message_lines = explode("\n", $message);
+
+        $pipe_def = array(0 => array("pipe", 'r'),
+                          1 => array("pipe", 'w'));
+
+        if ($newBinary) {
+            $process = proc_open($rcs_cmd, $pipe_def, $pipes);
+        } else {
+            $process = proc_open($ci_cmd, $pipe_def, $pipes);
+        }
+
+        if (is_resource($process)) {
+            foreach ($message_lines as $line) {
+                if ($line == '.\n') {
+                    $line = '. \n';
+                }
+                fwrite($pipes[0], $line);
+            }
+
+            fwrite($pipes[0], "\n.\n");
+            fclose($pipes[0]);
+
+            while (!feof($pipes[1])) {
+                $output .= fread($pipes[1], 8192);
+            }
+            fclose($pipes[1]);
+            proc_close($process);
+        } else {
+            return PEAR::raiseError('Failed to open pipe in ci()');
+        }
+
+        if ($newBinary) {
+            exec($ci_cmd . ' 2>&1', $return_array, $retval);
+
+            if ($retval) {
+                return PEAR::raiseError("Unable to spawn ci on $filepath from ci()");
+            } else {
+                foreach ($return_array as $line) {
+                    $output .= $line;
+                }
+            }
+        }
+
+        $rev_start = strpos($output, 'new revision: ');
+
+        // If no new revision, see if this is an initial checkin.
+        if ($rev_start === false) {
+            $rev_start = strpos($output, 'initial revision: ');
+            $rev_end = strpos($output, ' ', $rev_start);
+        } else {
+            $rev_end = strpos($output, ';', $rev_start);
+        }
+
+        if ($rev_start !== false && $rev_end !== false) {
+            $rev_start += 14;
+            return substr($output, $rev_start, $rev_end - $rev_start);
+        } else {
+            unlock($filepath);
+            $temp_pos = strpos($output, 'file is unchanged');
+            if ($temp_pos !== false) {
+                return PEAR::raiseError('Check-in Failure: ' . basename($filepath) . ' has not been modified');
+            } else {
+                return PEAR::raiseError("Failed to checkin $filepath, $ci_cmd, $output");
+            }
+        }
+    }
+
+    /**
+     * Checks the locks on a CVS/RCS file.
+     *
+     * @param string $filepath    Location of file.
+     * @param string &$locked_by  Returns the username holding the lock.
+     *
+     * @return boolean|object  True on success, or a PEAR_Error on failure.
+     */
+    public function isLocked($filepath, &$locked_by)
+    {
+        $rlog_cmd  = $this->getPath('rlog');
+        $rlog_flag = ' -L ';
+
+        $Q = VC_WINDOWS ? '"' : "'";
+
+        $cmd = $rlog_cmd . $rlog_flag . $Q . $filepath . $Q;
+
+        exec($cmd.' 2>&1', $return_array, $retval);
+
+        if ($retval) {
+            return PEAR::raiseError("Unable to spawn rlog on $filepath from isLocked()");
+        } else {
+            $output = '';
+
+            foreach ($return_array as $line) {
+                $output .= $line;
+            }
+
+            $start_name = strpos($output, 'locked by: ');
+            $end_name = strpos($output, ';', $start_name);
+
+            if ($start_name !== false && $end_name !== false) {
+                $start_name += 11;
+                $locked_by = substr($output, $start_name, $end_name - $start_name);
+                return true;
+            }  elseif (strlen($output) == 0) {
+                return false;
+            } else {
+                return PEAR::raiseError('Failure running rlog in isLocked()');
+            }
+        }
+    }
+
+    /**
+     * Locks a CVS/RCS file.
+     *
+     * @param string $filepath  Location of file.
+     * @param string $user      User name to lock the file with
+     *
+     * @return boolean|object  True on success, or a PEAR_Error on failure.
+     */
+    public function lock($filepath, $user = null)
+    {
+        // Get username for RCS tag.
+        if ($user) {
+            putenv('LOGNAME=' . $user);
+        } else {
+            putenv('LOGNAME=guest');
+        }
+
+        $rcs_cmd = $this->getPath('rcs');
+        $rcs_flag = ' -l ';
+
+        $Q = VC_WINDOWS ? '"' : "'" ;
+        $cmd = $rcs_cmd . $rcs_flag . $Q . $filepath . $Q;
+        exec($cmd.' 2>&1', $return_array, $retval);
+
+        if ($retval) {
+            return PEAR::raiseError('Failed to spawn rcs ("' . $cmd . '") on "' . $filepath . '" (returned ' . $retval . ')');
+        } else {
+            $output = '';
+            foreach ($return_array as $line) {
+                $output .= $line;
+            }
+
+            $locked_pos = strpos($output, 'locked');
+            if ($locked_pos !== false) {
+                return true;
+            } else {
+                return PEAR::raiseError('Failed to lock "' . $filepath . '" (Ran "' . $cmd . '", got return code ' . $retval . ', output: ' . $output . ')');
+            }
+        }
+    }
+
+    /**
+     * Unlocks a CVS/RCS file.
+     *
+     * @param string $filepath  Location of file.
+     * @param string $user      User name to unlock the file with
+     *
+     * @return boolean|object  True on success, or a PEAR_Error on failure.
+     */
+    public function unlock($filepath, $user = null)
+    {
+        // Get username for RCS tag.
+        if ($user) {
+            putenv('LOGNAME=' . $user);
+        } else {
+            putenv('LOGNAME=guest');
+        }
+
+        $rcs_cmd = $this->getPath('rcs');
+        $rcs_flag = ' -u ';
+
+        $Q = VC_WINDOWS ? '"' : "'" ;
+        $cmd = $rcs_cmd . $rcs_flag . $Q . $filepath . $Q;
+        exec($cmd . ' 2>&1', $return_array, $retval);
+
+        if ($retval) {
+            return PEAR::raiseError('Failed to spawn rcs ("' . $cmd . '") on "' . $filepath . '" (returned ' . $retval . ')');
+        } else {
+            $output = '';
+
+            foreach ($return_array as $line) {
+                $output .= $line;
+            }
+
+            $unlocked_pos = strpos($output, 'unlocked');
+
+            if ($unlocked_pos !== false) {
+                return true;
+            } else {
+                // Already unlocked.
+                return true;
+            }
+        }
+    }
+
+}
+
+class Horde_VC_Revision_rcs extends Horde_VC_Revision
+{
+    /**
+     * Given a revision number, remove a given number of portions from
+     * it. For example, if we remove 2 portions of 1.2.3.4, we are
+     * left with 1.2.
+     *
+     * @param string $val      Input revision
+     * @param integer $amount  Number of portions to strip
+     *
+     * @return string  Stripped revision number
+     */
+    public function strip($val, $amount = 1)
+    {
+        //@TODO This concept is broken beyond CVS
+        //if (!Horde_VC_Revision::valid($val)) {
+        //    return false;
+        //}
+        $pos = 0;
+        while ($amount-- > 0 && ($pos = strrpos($val, '.')) !== false) {
+            $val = substr($val, 0, $pos);
+        }
+        return $pos !== false ? $val : false;
+    }
+
+    /**
+     * The size of a revision number is the number of portions it has.
+     * For example, 1,2.3.4 is of size 4.
+     *
+     * @param string $val  Revision number to determine size of
+     *
+     * @return integer  Size of revision number
+     */
+    public function sizeof($val)
+    {
+        //@TODO This concept is broken beyond CVS
+        //if (!Horde_VC_Revision::valid($val)) {
+        //    return false;
+        //}
+
+        return (substr_count($val, '.') + 1);
+    }
+
+    /**
+     * Given two revision numbers, this figures out which one is
+     * greater than the other by stepping along the decimal points
+     * until a difference is found, at which point a sign comparison
+     * of the two is returned.
+     *
+     * @param string $rev1  Period delimited revision number
+     * @param string $rev2  Second period delimited revision number
+     *
+     * @return integer  1 if the first is greater, -1 if the second if greater,
+     *                  and 0 if they are equal
+     */
+    public function cmp($rev1, $rev2)
+    {
+        return version_compare($rev1, $rev2);
+    }
+
+    /**
+     * Return the logical revision before this one. Normally, this
+     * will be the revision minus one, but in the case of a new
+     * branch, we strip off the last two decimal places to return the
+     * original branch point.
+     *
+     * @param string $rev  Revision number to decrement.
+     *
+     * @return string|boolean  Revision number, or false if none could be
+     *                         determined.
+     */
+    public function prev($rev)
+    {
+        $last_dot = strrpos($rev, '.');
+        $val = substr($rev, ++$last_dot);
+
+        if (--$val > 0) {
+            return substr($rev, 0, $last_dot) . $val;
+        } else {
+            $last_dot--;
+            while (--$last_dot) {
+                if ($rev[$last_dot] == '.') {
+                    return  substr($rev, 0, $last_dot);
+                } elseif ($rev[$last_dot] == null) {
+                    return false;
+                }
+            }
+        }
+    }
+}
diff --git a/framework/Vcs/lib/Horde/Vcs/Svn.php b/framework/Vcs/lib/Horde/Vcs/Svn.php
new file mode 100644 (file)
index 0000000..cf6355c
--- /dev/null
@@ -0,0 +1,642 @@
+<?php
+
+require_once dirname(__FILE__) . '/rcs.php';
+
+/**
+ * Horde_VC_svn implementation.
+ *
+ * Copyright 2000-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @package Horde_VC
+ */
+class Horde_VC_svn extends Horde_VC
+{
+    /**
+     * SVN username.
+     *
+     * @var string
+     */
+    protected $_username = '';
+
+    /**
+     * SVN password.
+     *
+     * @var string
+     */
+    protected $_password = '';
+
+    /**
+     * Constructor.
+     *
+     * @param array $params  Any parameter the class expects.
+     *                       Current parameters:
+     * <pre>
+     * 'sourceroot': The source root for this repository
+     * 'paths': Hash with the locations of all necessary binaries: 'svn',
+     *          'diff'
+     * </pre>
+     */
+    public function __construct($params)
+    {
+        $this->_sourceroot = $params['sourceroot'];
+        $this->_paths = $params['paths'];
+
+        if (!empty($params['username'])) {
+            $this->_username = $params['username'];
+        }
+
+        if (!empty($params['password'])) {
+            $this->_password = $params['password'];
+        }
+        parent::_construct();
+    }
+
+    /**
+     * TODO
+     */
+    public function getCommand()
+    {
+        $svnPath = $this->getPath('svn');
+        $tempDir = isset($this->_paths['svn_home'])
+            ? $this->_paths['svn_home']
+            : Util::getTempDir();
+        $command = $svnPath . ' --non-interactive --config-dir ' . $tempDir;
+
+        if ($this->_username) {
+            $command .= ' --username ' . $this->_username;
+        }
+
+        if ($this->_password) {
+            $command .= ' --password ' . $this->_password;
+        }
+
+        return $command;
+    }
+
+    /**
+     * Validation function to ensure that a revision number is of the right
+     * form.
+     *
+     * @param mixed $rev  The purported revision number.
+     *
+     * @return boolean  True if it is a revision number.
+     */
+    public function isValidRevision($rev)
+    {
+        return is_numeric($rev);
+    }
+
+}
+
+/**
+ * Horde_VC_svn annotate class.
+ *
+ * Anil Madhavapeddy, <anil@recoil.org>
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @package Horde_VC
+ */
+class Horde_VC_Annotate_svn extends Horde_VC_Annotate
+{
+    /**
+     * TODO
+     */
+    public function doAnnotate($rev)
+    {
+        if (is_a($this->_file, 'PEAR_Error') ||
+            !$this->_rep->isValidRevision($rev)) {
+            return false;
+        }
+
+        $command = $this->_rep->getCommand() . ' annotate -r 1:' . $rev . ' ' . escapeshellarg($this->_file->queryFullPath()) . ' 2>&1';
+        $pipe = popen($command, 'r');
+        if (!$pipe) {
+            return PEAR::raiseError('Failed to execute svn annotate: ' . $command);
+        }
+
+        $lines = array();
+        $lineno = 1;
+
+        while (!feof($pipe)) {
+            $line = fgets($pipe, 4096);
+            if (preg_match('/^\s+(\d+)\s+([\w\.]+)\s(.*)$/', $line, $regs)) {
+                $lines[] = array(
+                    'rev' => $regs[1],
+                    'author' => trim($regs[2]),
+                    'date' => '',
+                    'line' => $regs[3],
+                    'lineno' => $lineno++
+                );
+            }
+        }
+
+        pclose($pipe);
+        return $lines;
+    }
+
+}
+
+/**
+ * Horde_VC_svn checkout class.
+ *
+ * Anil Madhavapeddy, <anil@recoil.org>
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @package Horde_VC
+ */
+class Horde_VC_Checkout_svn extends Horde_VC_Checkout
+{
+    /**
+     * Function which returns a file pointing to the head of the requested
+     * revision of a file.
+     *
+     * @param Horde_VC $rep     A repository object
+     * @param string $fullname  Fully qualified pathname of the desired file
+     *                          to checkout
+     * @param string $rev       Revision number to check out
+     *
+     * @return resource|object  Either a PEAR_Error object, or a stream
+     *                          pointer to the head of the checkout.
+     */
+    public function get($rep, $fullname, $rev)
+    {
+        if (!$rep->isValidRevision($rev)) {
+            return PEAR::raiseError('Invalid revision number');
+        }
+
+        return ($RCS = popen($rep->getCommand() . ' cat -r ' . $rev . ' ' . escapeshellarg($fullname) . ' 2>&1', VC_WINDOWS ? 'rb' : 'r'))
+            ? $RCS
+            : PEAR::raiseError('Couldn\'t perform checkout of the requested file');
+    }
+
+}
+
+/**
+ * Horde_VC_svn diff class.
+ *
+ * Copyright Anil Madhavapeddy, <anil@recoil.org>
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @package Horde_VC
+ */
+class Horde_VC_Diff_svn extends Horde_VC_Diff
+{
+    /**
+     * Obtain the differences between two revisions of a file.
+     *
+     * @param Horde_VC $rep        A repository object.
+     * @param Horde_VC_File $file  The desired file.
+     * @param string $rev1         Original revision number to compare from.
+     * @param string $rev2         New revision number to compare against.
+     * @param string $type         The type of diff (e.g. 'unified').
+     * @param integer $num         Number of lines to be used in context and
+     *                             unified diffs.
+     * @param boolean $ws          Show whitespace in the diff?
+     *
+     * @return string|boolean  False on failure, or a string containing the
+     *                         diff on success.
+     */
+    public function get($rep, $file, $rev1, $rev2, $type = 'context',
+                        $num = 3, $ws = true)
+    {
+        /* Make sure that the file parameter is valid */
+        if (is_a($file, 'PEAR_Error')) {
+            return false;
+        }
+
+        /* Check that the revision numbers are valid */
+        $rev1 = $rep->isValidRevision($rev1) ? $rev1 : 0;
+        $rev2 = $rep->isValidRevision($rev1) ? $rev2 : 0;
+
+        $fullName = $file->queryFullPath();
+        $diff = array();
+        $options = '';
+        if (!$ws) {
+            $options .= ' -bB ';
+        }
+
+        switch ($type) {
+        case 'context':
+            $options .= '--context=' . (int)$num;
+            break;
+
+        case 'unified':
+            $options .= '-p --unified=' . (int)$num;
+            break;
+
+        case 'column':
+            $options .= '--side-by-side --width=120';
+            break;
+
+        case 'ed':
+            $options .= '-e';
+            break;
+        }
+
+        // TODO: add options for $hr options - however these may not
+        // be compatible with some diffs.
+        $command = $rep->getCommand() . " diff --diff-cmd " . $rep->getPath('diff') . " -r $rev1:$rev2 -x " . escapeshellarg($options) . ' ' . escapeshellarg($file->queryFullPath()) . ' 2>&1';
+
+        exec($command, $diff, $retval);
+        return $diff;
+    }
+
+}
+
+/**
+ * Horde_VC_svn directory class.
+ *
+ * Copyright Anil Madhavapeddy, <anil@recoil.org>
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @package Horde_VC
+ */
+class Horde_VC_Directory_svn extends Horde_VC_Directory
+{
+    /**
+     * Tell the object to open and browse its current directory, and
+     * retrieve a list of all the objects in there.  It then populates
+     * the file/directory stack and makes it available for retrieval.
+     *
+     * @return PEAR_Error object on an error, 1 on success.
+     */
+    public function browseDir($cache = null, $quicklog = true,
+                              $showattic = false)
+    {
+        $cmd = $this->_rep->getCommand() . ' ls ' . escapeshellarg($this->_rep->sourceroot() . $this->queryDir()) . ' 2>&1';
+
+        $dir = popen($cmd, 'r');
+        if (!$dir) {
+            return PEAR::raiseError('Failed to execute svn ls: ' . $cmd);
+        }
+
+        /* Create two arrays - one of all the files, and the other of
+         * all the dirs. */
+        $errors = array();
+        while (!feof($dir)) {
+            $line = chop(fgets($dir, 1024));
+            if (!strlen($line)) {
+                continue;
+            }
+
+            if (substr($line, 0, 4) == 'svn:') {
+                $errors[] = $line;
+            } elseif (substr($line, -1) == '/') {
+                $this->_dirs[] = substr($line, 0, -1);
+            } else {
+                $fl = $this->_rep->getFileObject($this->queryDir() . "/$line", $cache, $quicklog);
+                if (is_a($fl, 'PEAR_Error')) {
+                    return $fl;
+                } else {
+                    $this->_files[] = $fl;
+                }
+            }
+        }
+
+        pclose($dir);
+
+        return $errors
+            ? PEAR::raiseError(implode("\n", $errors))
+            : true;
+    }
+
+}
+
+/**
+ * Horde_VC_svn file class.
+ *
+ * Copyright Anil Madhavapeddy, <anil@recoil.org>
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @package Horde_VC
+ */
+class Horde_VC_File_svn extends Horde_VC_File {
+
+    /**
+     * Create a repository file object, and give it information about
+     * what its parent directory and repository objects are.
+     *
+     * @param string $fl  Full path to this file.
+     */
+    public function __construct($rep, $fl, $cache = null, $quicklog = false)
+    {
+        $this->rep = $rep;
+        $this->name = basename($fl);
+        $this->dir = dirname($fl);
+        $this->filename = $fl;
+        $this->quicklog = $quicklog;
+        $this->cache = $cache;
+        $this->logs = $this->revs = $this->revsym = $this->symrev = $this->branches = array();
+    }
+
+    function &getFileObject()
+    {
+        /* The version of the cached data. Increment this whenever the
+         * internal storage format changes, such that we must
+         * invalidate prior cached data. */
+        $cacheVersion = 2;
+        $cacheId = $rep->sourceroot() . '_n' . $this->filename . '_f' . (int)$this->quicklog . '_v' . $cacheVersion;
+
+        if ($this->cache &&
+            // The file is cached for one hour no matter what, because
+            // there is no way to determine with Subversion the time
+            // the file last changed.
+            $this->cache->exists($cacheId, 3600)) {
+            $fileOb = unserialize($this->cache->get($cacheId, 3600));
+            $fileOb->setRepository($rep);
+        } else {
+            $fileOb = new Horde_VC_File_svn($rep, $this->filename, $this->cache, $this->quicklog);
+            $fileOb->setRepository($rep);
+            if (is_a(($result = $fileOb->getBrowseInfo()), 'PEAR_Error')) {
+                return $result;
+            }
+            $fileOb->applySort(Horde_VC::SORT_AGE);
+
+            if ($this->cache) {
+                $this->cache->set($cacheId, serialize($fileOb));
+            }
+        }
+
+        return $fileOb;
+    }
+
+    /**
+     * Returns name of the current file without the repository
+     * extensions (usually ,v)
+     *
+     * @return Filename without repository extension
+     */
+    function queryName()
+    {
+       return preg_replace('/,v$/', '', $this->name);
+    }
+
+    /**
+     * Populate the object with information about the revisions logs
+     * and dates of the file.
+     *
+     * @return mixed boolean            True on success,
+     *               PEAR_Error         On error.
+     */
+    function getBrowseInfo()
+    {
+        /* This doesn't work; need to find another way to simply
+         * request the most recent revision:
+         *
+         * $flag = $this->quicklog ? '-r HEAD ' : ''; */
+        $flag = '';
+        $Q = VC_WINDOWS ? '"' : "'";
+        $cmd = $this->rep->getCommand() . ' log -v ' . $flag . $Q . str_replace($Q, '\\' . $Q, $this->queryFullPath()) . $Q . ' 2>&1';
+        $pipe = popen($cmd, 'r');
+        if (!$pipe) {
+            return PEAR::raiseError('Failed to execute svn log: ' . $cmd);
+        }
+
+        $header = fgets($pipe);
+        if (!strspn($header, '-')) {
+            return PEAR::raiseError('Error executing svn log: ' . $header);
+        }
+
+        while (!feof($pipe)) {
+            $log = new Horde_VC_Log_svn($this->rep, $this);
+            $err = $log->processLog($pipe);
+            if ($err) {
+                $rev = $log->queryRevision();
+                $this->logs[$rev] = $log;
+                $this->revs[] = $rev;
+            }
+
+            if ($this->quicklog) {
+                break;
+            }
+        }
+
+        pclose($pipe);
+        return true;
+    }
+
+}
+
+/**
+ * Horde_VC_svn log class.
+ *
+ * Anil Madhavapeddy, <anil@recoil.org>
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @package Horde_VC
+ */
+class Horde_VC_Log_svn {
+
+    var $rep;
+    var $err;
+    var $file;
+    var $files;
+    var $tags;
+    var $rev;
+    var $date;
+    var $log;
+    var $author;
+    var $state;
+    var $lines;
+    var $branches;
+
+    /**
+     * Constructor.
+     */
+    public function __construct($rep, $fl)
+    {
+        $this->rep = $rep;
+        $this->file = $fl;
+        $this->branches = array();
+    }
+
+    function processLog($pipe)
+    {
+        $line = fgets($pipe);
+
+        if (feof($pipe)) {
+            return false;
+        }
+
+        if (preg_match('/^r([0-9]*) \| (.*?) \| (.*) \(.*\) \| ([0-9]*) lines?$/', $line, $matches)) {
+            $this->rev = $matches[1];
+            $this->author = $matches[2];
+            $this->date = strtotime($matches[3]);
+            $size = $matches[4];
+        } else {
+            $this->err = $line;
+            return false;
+        }
+
+        fgets($pipe);
+
+        $this->files = array();
+        while (($line = trim(fgets($pipe))) != '') {
+            $this->files[] = $line;
+        }
+
+        for ($i = 0; $i != $size; ++$i) {
+            $this->log = $this->log . chop(fgets($pipe)) . "\n";
+        }
+
+        $this->log = chop($this->log);
+        fgets($pipe);
+
+        return true;
+    }
+
+    function queryDate()
+    {
+        return $this->date;
+    }
+
+    function queryRevision()
+    {
+        return $this->rev;
+    }
+
+    function queryAuthor()
+    {
+        return $this->author;
+    }
+
+    function queryLog()
+    {
+        return $this->log;
+    }
+
+    function queryChangedLines()
+    {
+        return isset($this->lines) ? ($this->lines) : '';
+    }
+
+    /**
+     * Given a branch revision number, this function remaps it
+     * accordingly, and performs a lookup on the file object to
+     * return the symbolic name(s) of that branch in the tree.
+     *
+     * @return hash of symbolic names => branch numbers
+     */
+    function querySymbolicBranches()
+    {
+        $symBranches = array();
+        foreach ($this->branches as $branch) {
+            $parts = explode('.', $branch);
+            $last = array_pop($parts);
+            $parts[] = '0';
+            $parts[] = $last;
+            $rev = implode('.', $parts);
+            if (isset($this->file->branches[$branch])) {
+                $symBranches[$this->file->branches[$branch]] = $branch;
+            }
+        }
+        return $symBranches;
+    }
+
+}
+
+/**
+ * Horde_VC_svn Patchset class.
+ *
+ * Copyright Anil Madhavapeddy, <anil@recoil.org>
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @package Horde_VC
+ */
+class Horde_VC_Patchset_svn extends Horde_VC_Patchset {
+
+    var $_file;
+
+    /**
+     * Create a patchset object.
+     *
+     * @param string $file  The filename to get patchsets for.
+     */
+    public function __construct($file)
+    {
+        $this->_file = $file;
+    }
+
+    function &getPatchsetObject($rep, $filename, $cache = null)
+    {
+        /* The version of the cached data. Increment this whenever the
+         * internal storage format changes, such that we must
+         * invalidate prior cached data. */
+        $cacheVersion = 1;
+        $cacheId = $rep->sourceroot() . '_n' . $filename . '_f_v' . $cacheVersion;
+
+        if ($cache &&
+            // The file is cached for one hour no matter what, because
+            // there is no way to determine with svn the time the file
+            // last changed.
+            $cache->exists($cacheId, 3600)) {
+            $psOb = unserialize($cache->get($cacheId, 3600));
+            $psOb->setRepository($rep);
+        } else {
+            $psOb = new Horde_VC_Patchset_svn($filename);
+            $psOb->setRepository($rep);
+            if (is_a(($result = $psOb->getPatchsets()), 'PEAR_Error')) {
+                return $result;
+            }
+
+            if ($cache) {
+                $cache->set($cacheId, serialize($psOb));
+            }
+        }
+
+        return $psOb;
+    }
+
+    /**
+     * Populate the object with information about the patchsets that
+     * this file is involved in.
+     *
+     * @return mixed  PEAR_Error object on error, or true on success.
+     */
+    function getPatchsets()
+    {
+        $fileOb = new Horde_VC_File_svn($this->_rep, $this->_file);
+        if (is_a(($result = $fileOb->getBrowseInfo()), 'PEAR_Error')) {
+            return $result;
+        }
+
+        $this->_patchsets = array();
+        foreach ($fileOb->logs as $rev => $log) {
+            $this->_patchsets[$rev] = array();
+            $this->_patchsets[$rev]['date'] = $log->queryDate();
+            $this->_patchsets[$rev]['author'] = $log->queryAuthor();
+            $this->_patchsets[$rev]['branch'] = '';
+            $this->_patchsets[$rev]['tag'] = '';
+            $this->_patchsets[$rev]['log'] = $log->queryLog();
+            $this->_patchsets[$rev]['members'] = array();
+            foreach ($log->files as $file) {
+                $action = substr($file, 0, 1);
+                $file = preg_replace('/.*?\s(.*?)(\s|$).*/', '\\1', $file);
+                $to = $rev;
+                if ($action == 'A') {
+                    $from = 'INITIAL';
+                } elseif ($action == 'D') {
+                    $from = $to;
+                    $to = '(DEAD)';
+                } else {
+                    // This technically isn't the previous revision,
+                    // but it works for diffing purposes.
+                    $from = $to - 1;
+                }
+
+                $this->_patchsets[$rev]['members'][] = array('file' => $file,
+                                                             'from' => $from,
+                                                             'to' => $to);
+            }
+        }
+
+        return true;
+    }
+
+}
+
+class Horde_VC_Revision_svn extends Horde_VC_Revision_rcs {}
diff --git a/framework/Vcs/package.xml b/framework/Vcs/package.xml
new file mode 100644 (file)
index 0000000..da124de
--- /dev/null
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<package packagerversion="1.4.9" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
+http://pear.php.net/dtd/tasks-1.0.xsd
+http://pear.php.net/dtd/package-2.0
+http://pear.php.net/dtd/package-2.0.xsd">
+ <name>Horde_VC</name>
+ <channel>pear.horde.org</channel>
+ <summary>Version Control API</summary>
+ <description>The Horde_VC package provides a generalized API to multiple
+ version control systems.
+ </description>
+ <lead>
+  <name>Chuck Hagenbuch</name>
+  <user>chuck</user>
+  <email>chuck@horde.org</email>
+  <active>yes</active>
+ </lead>
+ <lead>
+  <name>Jan Schneider</name>
+  <user>jan</user>
+  <email>jan@horde.org</email>
+  <active>yes</active>
+ </lead>
+ <lead>
+  <name>Michael Slusarz</name>
+  <user>slusarz</user>
+  <email>slusarz@horde.org</email>
+  <active>yes</active>
+ </lead>
+ <date>2008-12-20</date>
+ <version>
+  <release>0.1.0</release>
+  <api>0.1.0</api>
+ </version>
+ <stability>
+  <release>beta</release>
+  <api>beta</api>
+ </stability>
+ <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+ <notes>* Initial Horde 4 package.</notes>
+ <contents>
+  <dir name="/">
+   <dir name="lib">
+    <dir name="Horde">
+     <dir name="VC">
+      <file name="cvs.php" role="php" />
+      <file name="Exception.php" role="php" />
+      <file name="git.php" role="php" />
+      <file name="rcs.php" role="php" />
+      <file name="svn.php" role="php" />
+     </dir> <!-- /lib/Horde/VC -->
+     <file name="VC.php" role="php" />
+    </dir> <!-- /lib/Horde -->
+   </dir> <!-- /lib -->
+  </dir> <!-- / -->
+ </contents>
+ <dependencies>
+  <required>
+   <php>
+    <min>5.2.0</min>
+   </php>
+   <pearinstaller>
+    <min>1.5.0</min>
+   </pearinstaller>
+   <package>
+    <name>Util</name>
+    <channel>pear.horde.org</channel>
+   </package>
+   <extension>
+    <name>pcre</name>
+   </extension>
+  </required>
+  <optional>
+   <package>
+    <name>Horde_Cache</name>
+    <channel>pear.horde.org</channel>
+   </package>
+  </optional>
+ </dependencies>
+ <phprelease>
+  <filelist>
+   <install name="lib/Horde/VC/cvs.php" as="Horde/VC/cvs.php" />
+   <install name="lib/Horde/VC/Exception.php" as="Horde/VC/Exception.php" />
+   <install name="lib/Horde/VC/git.php" as="Horde/VC/git.php" />
+   <install name="lib/Horde/VC/rcs.php" as="Horde/VC/rcs.php" />
+   <install name="lib/Horde/VC/svn.php" as="Horde/VC/svn.php" />
+   <install name="lib/Horde/VC.php" as="Horde/VC.php" />
+  </filelist>
+ </phprelease>
+ <changelog>
+  <release>
+   <version>
+    <release>0.0.4</release>
+    <api>0.0.4</api>
+   </version>
+   <stability>
+    <release>beta</release>
+    <api>beta</api>
+   </stability>
+   <date>2006-05-08</date>
+   <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+   <notes>* Converted to package.xml 2.0 for pear.horde.org
+* Fix subversion diffs on files with spaces (helly@php.net, Bug #5244)
+* Pass username and password to the SVN client if set (duck@obala.net, Request #5958)
+   </notes>
+  </release>
+  <release>
+   <version>
+    <release>0.0.3</release>
+    <api>0.0.3</api>
+   </version>
+   <stability>
+    <release>beta</release>
+    <api>beta</api>
+   </stability>
+   <date>2004-01-01</date>
+   <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+   <notes>- Fix caching of Patchset objects.
+- Add a VC_Patchset parent class for common Patchset functionality.
+   </notes>
+  </release>
+ </changelog>
+</package>