From b4b0c7e72d7eaee2fbfc7022022c9698323203dd Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 31 Dec 2009 13:16:41 +0000 Subject: [PATCH] import rt 3.8.7 --- rt/Makefile | 377 +- rt/Makefile.in | 409 +- rt/README | 220 +- rt/README.Oracle | 66 +- rt/UPGRADING | 150 +- rt/UPGRADING.mysql | 85 + rt/aclocal.m4 | 10 +- rt/bin/mason_handler.fcgi | 13 +- rt/bin/mason_handler.fcgi.in | 13 +- rt/bin/mason_handler.scgi | 12 +- rt/bin/mason_handler.scgi.in | 12 +- rt/bin/mason_handler.svc | 13 +- rt/bin/mason_handler.svc.in | 13 +- rt/bin/rt | 665 +- rt/bin/rt-crontool | 211 +- rt/bin/rt-crontool.in | 211 +- rt/bin/rt-mailgate | 164 +- rt/bin/rt-mailgate.in | 164 +- rt/bin/rt.in | 665 +- rt/bin/standalone_httpd | 141 +- rt/bin/standalone_httpd.in | 141 +- rt/bin/webmux.pl | 57 +- rt/bin/webmux.pl.in | 57 +- rt/config.layout | 37 +- rt/config.pld | 17 +- rt/config.status | 207 +- rt/configure | 1348 ++- rt/configure.ac | 187 +- rt/docs/creating_external_custom_fields.pod | 97 + rt/docs/design_docs/TransactionTypes.txt | 6 +- .../design_docs/gnupg_details_on_output_formats | 1253 +++ rt/docs/design_docs/rql_parser_machine.graphviz | 3 + rt/docs/design_docs/string-extraction-guide.txt | 6 +- rt/docs/extending_clickable_links.pod | 157 + rt/docs/gnupg_integration.pod | 1 + rt/docs/porting.windows | 24 + rt/docs/queue_subject_tag.pod | 79 + rt/docs/templates.pod | 60 + rt/docs/using_forms_widgets.pod | 113 + rt/etc/RT_Config.pm | 1808 ++- rt/etc/RT_Config.pm.in | 1808 ++- rt/etc/RT_SiteConfig.pm | 1 + rt/etc/acl.Informix | 5 +- rt/etc/acl.Oracle | 12 +- rt/etc/acl.Pg | 113 +- rt/etc/acl.Sybase | 10 +- rt/etc/acl.mysql | 30 +- rt/etc/initialdata | 481 +- rt/etc/schema.Oracle | 3 +- rt/etc/schema.Pg | 5 +- rt/etc/schema.SQLite | 2 +- rt/etc/schema.mysql-4.0 | 464 + rt/etc/schema.mysql-4.1 | 466 + rt/etc/upgrade/3.1.0/acl.Pg | 2 +- rt/etc/upgrade/3.3.0/acl.Pg | 2 +- rt/etc/upgrade/3.5.1/content | 2 +- rt/etc/upgrade/3.7.1/content | 14 + rt/etc/upgrade/3.7.10/content | 49 + rt/etc/upgrade/3.7.15/content | 12 + rt/etc/upgrade/3.7.19/content | 37 + rt/etc/upgrade/3.7.3/schema.Oracle | 5 + rt/etc/upgrade/3.7.3/schema.Pg | 1 + rt/etc/upgrade/3.7.3/schema.mysql | 1 + rt/etc/upgrade/3.7.81/schema.Oracle | 2 + rt/etc/upgrade/3.7.81/schema.mysql | 2 + rt/etc/upgrade/3.7.82/content | 13 + rt/etc/upgrade/3.7.85/content | 11 + rt/etc/upgrade/3.7.86/content | 23 + rt/etc/upgrade/3.7.87/content | 28 + rt/etc/upgrade/3.8-branded-queues-extension | 95 + rt/etc/upgrade/3.8-branded-queues-extension.in | 95 + rt/etc/upgrade/3.8-ical-extension | 96 + rt/etc/upgrade/3.8-ical-extension.in | 96 + rt/etc/upgrade/3.8.0/content | 22 + rt/etc/upgrade/3.8.1/content | 24 + rt/etc/upgrade/3.8.2/content | 186 + rt/etc/upgrade/3.8.3/content | 91 + rt/etc/upgrade/3.8.3/schema.Pg | 3 + rt/etc/upgrade/3.8.4/content | 59 + rt/etc/upgrade/3.8.6/content | 10 + rt/etc/upgrade/shrink_cgm_table.pl | 72 + rt/etc/upgrade/split-out-cf-categories | 171 + rt/etc/upgrade/split-out-cf-categories.in | 171 + rt/etc/upgrade/upgrade-mysql-schema.pl | 390 + rt/install-sh | 676 +- rt/lib/RT.pm | 619 +- rt/lib/RT.pm.in | 611 +- rt/lib/RT/ACE.pm | 11 +- rt/lib/RT/ACE_Overlay.pm | 317 +- rt/lib/RT/ACL.pm | 10 +- rt/lib/RT/ACL_Overlay.pm | 15 +- rt/lib/RT/Action.pm | 227 + rt/lib/RT/Action/AutoOpen.pm | 52 +- rt/lib/RT/Action/Autoreply.pm | 63 +- rt/lib/RT/Action/CreateTickets.pm | 265 +- rt/lib/RT/Action/EscalatePriority.pm | 11 +- rt/lib/RT/Action/ExtractSubjectTag.pm | 103 + rt/lib/RT/Action/Generic.pm | 177 +- rt/lib/RT/Action/LinearEscalate.pm | 279 + rt/lib/RT/Action/Notify.pm | 76 +- rt/lib/RT/Action/NotifyAsComment.pm | 10 +- rt/lib/RT/Action/NotifyGroup.pm | 209 + rt/lib/RT/Action/NotifyGroupAsComment.pm | 91 + rt/lib/RT/Action/RecordComment.pm | 9 +- rt/lib/RT/Action/RecordCorrespondence.pm | 9 +- rt/lib/RT/Action/ResolveMembers.pm | 9 +- rt/lib/RT/Action/SendEmail.pm | 1055 +- rt/lib/RT/Action/SetPriority.pm | 9 +- rt/lib/RT/Action/UserDefined.pm | 9 +- rt/lib/RT/Approval.pm | 74 + rt/lib/RT/Approval/Rule.pm | 85 + rt/lib/RT/Approval/Rule/Created.pm | 71 + rt/lib/RT/Approval/Rule/NewPending.pm | 97 + rt/lib/RT/Approval/Rule/Passed.pm | 110 + rt/lib/RT/Approval/Rule/Rejected.pm | 114 + rt/lib/RT/Attachment.pm | 5 +- rt/lib/RT/Attachment_Overlay.pm | 604 +- rt/lib/RT/Attachments.pm | 5 +- rt/lib/RT/Attachments_Overlay.pm | 195 +- rt/lib/RT/Attribute.pm | 5 +- rt/lib/RT/Attribute_Overlay.pm | 55 +- rt/lib/RT/Attributes.pm | 5 +- rt/lib/RT/Attributes_Overlay.pm | 43 +- rt/lib/RT/Base.pm | 15 +- rt/lib/RT/CachedGroupMember.pm | 5 +- rt/lib/RT/CachedGroupMember_Overlay.pm | 13 +- rt/lib/RT/CachedGroupMembers.pm | 5 +- rt/lib/RT/CachedGroupMembers_Overlay.pm | 10 +- rt/lib/RT/Condition.pm | 233 + rt/lib/RT/Condition/AnyTransaction.pm | 9 +- rt/lib/RT/Condition/BeforeDue.pm | 10 +- rt/lib/RT/Condition/CloseTicket.pm | 84 + rt/lib/RT/Condition/Generic.pm | 181 +- rt/lib/RT/Condition/Overdue.pm | 11 +- rt/lib/RT/Condition/OwnerChange.pm | 54 +- rt/lib/RT/Condition/PriorityChange.pm | 11 +- rt/lib/RT/Condition/PriorityExceeds.pm | 12 +- rt/lib/RT/Condition/QueueChange.pm | 12 +- rt/lib/RT/Condition/ReopenTicket.pm | 89 + rt/lib/RT/Condition/StatusChange.pm | 11 +- rt/lib/RT/Condition/UserDefined.pm | 12 +- rt/lib/RT/Config.pm | 894 ++ rt/lib/RT/Crypt/GnuPG.pm | 2450 +++++ rt/lib/RT/CurrentUser.pm | 326 +- rt/lib/RT/CustomField.pm | 19 +- rt/lib/RT/CustomFieldValue.pm | 5 +- rt/lib/RT/CustomFieldValue_Overlay.pm | 107 +- rt/lib/RT/CustomFieldValues.pm | 5 +- rt/lib/RT/CustomFieldValues/External.pm | 235 + rt/lib/RT/CustomFieldValues/Groups.pm | 88 + rt/lib/RT/CustomFieldValues_Overlay.pm | 16 +- rt/lib/RT/CustomField_Overlay.pm | 765 +- rt/lib/RT/CustomFields.pm | 5 +- rt/lib/RT/CustomFields_Overlay.pm | 55 +- rt/lib/RT/Dashboard.pm | 358 + rt/lib/RT/Date.pm | 1026 +- rt/lib/RT/EmailParser.pm | 358 +- rt/lib/RT/Graph/Tickets.pm | 358 + rt/lib/RT/Group.pm | 5 +- rt/lib/RT/GroupMember.pm | 5 +- rt/lib/RT/GroupMember_Overlay.pm | 20 +- rt/lib/RT/GroupMembers.pm | 5 +- rt/lib/RT/GroupMembers_Overlay.pm | 10 +- rt/lib/RT/Group_Overlay.pm | 339 +- rt/lib/RT/Groups.pm | 7 +- rt/lib/RT/Groups_Overlay.pm | 165 +- rt/lib/RT/Handle.pm | 1009 +- rt/lib/RT/I18N.pm | 91 +- rt/lib/RT/I18N/ar.po | 7509 +++++++++++++ rt/lib/RT/I18N/bg.po | 7461 +++++++++++++ rt/lib/RT/I18N/cs.pm | 5 +- rt/lib/RT/I18N/cs.po | 5539 +++++++--- rt/lib/RT/I18N/da.po | 6184 +++++++---- rt/lib/RT/I18N/de.po | 5708 +++++++--- rt/lib/RT/I18N/en.po | 92 +- rt/lib/RT/I18N/es.po | 5797 +++++++--- rt/lib/RT/I18N/fi.po | 6330 +++++++---- rt/lib/RT/I18N/fr.po | 5754 +++++++--- rt/lib/RT/I18N/he.po | 5365 ++++++--- rt/lib/RT/I18N/hr.po | 8617 +++++++++++++++ rt/lib/RT/I18N/hu.po | 5441 ++++++--- rt/lib/RT/I18N/i_default.pm | 9 +- rt/lib/RT/I18N/id.po | 5364 ++++++--- rt/lib/RT/I18N/it.po | 5546 +++++++--- rt/lib/RT/I18N/ja.po | 7374 +++++++------ rt/lib/RT/I18N/nb.po | 9073 +++++++++++++++ rt/lib/RT/I18N/nl.po | 5394 ++++++--- rt/lib/RT/I18N/pl.po | 5441 ++++++--- rt/lib/RT/I18N/pt.po | 7869 +++++++++++++ rt/lib/RT/I18N/pt_BR.po | 9249 ++++++++++++++++ rt/lib/RT/I18N/rt.pot | 7460 +++++++++++++ rt/lib/RT/I18N/ru.pm | 74 + rt/lib/RT/I18N/ru.po | 6876 +++++++----- rt/lib/RT/I18N/sv.po | 6160 +++++++---- rt/lib/RT/I18N/tr.po | 5403 ++++++--- rt/lib/RT/I18N/zh_CN.po | 10953 ++++++++++++++++++ rt/lib/RT/I18N/zh_TW.po | 10985 +++++++++++++++++++ rt/lib/RT/Installer.pm | 340 + rt/lib/RT/Interface/CLI.pm | 24 +- rt/lib/RT/Interface/Email.pm | 1205 +- rt/lib/RT/Interface/Email/Auth/GnuPG.pm | 211 +- rt/lib/RT/Interface/Email/Auth/MailFrom.pm | 99 +- rt/lib/RT/Interface/Email/Filter/SpamAssassin.pm | 8 +- rt/lib/RT/Interface/REST.pm | 58 +- rt/lib/RT/Interface/Web.pm | 1849 ++-- rt/lib/RT/Interface/Web/Handler.pm | 78 +- rt/lib/RT/Interface/Web/Menu.pm | 5 +- rt/lib/RT/Interface/Web/Menu/Item.pm | 5 +- rt/lib/RT/Interface/Web/QueryBuilder.pm | 5 +- rt/lib/RT/Interface/Web/QueryBuilder/Tree.pm | 180 +- rt/lib/RT/Interface/Web/Request.pm | 207 + rt/lib/RT/Interface/Web/Session.pm | 285 + rt/lib/RT/Interface/Web/Standalone.pm | 49 +- rt/lib/RT/Interface/Web/Standalone/PreFork.pm | 103 + rt/lib/RT/Link.pm | 5 +- rt/lib/RT/Link_Overlay.pm | 117 +- rt/lib/RT/Links.pm | 5 +- rt/lib/RT/Links_Overlay.pm | 14 +- rt/lib/RT/ObjectCustomField.pm | 5 +- rt/lib/RT/ObjectCustomFieldValue.pm | 5 +- rt/lib/RT/ObjectCustomFieldValue_Overlay.pm | 189 +- rt/lib/RT/ObjectCustomFieldValues.pm | 5 +- rt/lib/RT/ObjectCustomFieldValues_Overlay.pm | 81 +- rt/lib/RT/ObjectCustomField_Overlay.pm | 69 +- rt/lib/RT/ObjectCustomFields.pm | 5 +- rt/lib/RT/ObjectCustomFields_Overlay.pm | 29 +- rt/lib/RT/Plugin.pm | 141 + rt/lib/RT/Principal.pm | 5 +- rt/lib/RT/Principal_Overlay.pm | 105 +- rt/lib/RT/Principals.pm | 5 +- rt/lib/RT/Principals_Overlay.pm | 10 +- rt/lib/RT/Queue.pm | 5 +- rt/lib/RT/Queue_Overlay.pm | 330 +- rt/lib/RT/Queues.pm | 5 +- rt/lib/RT/Queues_Overlay.pm | 10 +- rt/lib/RT/Record.pm | 545 +- rt/lib/RT/Reminders.pm | 10 +- rt/lib/RT/Report/Tickets.pm | 307 +- rt/lib/RT/Report/Tickets/Entry.pm | 14 +- rt/lib/RT/Rule.pm | 118 + rt/lib/RT/Ruleset.pm | 94 + rt/lib/RT/SQL.pm | 302 + rt/lib/RT/SavedSearch.pm | 276 +- rt/lib/RT/SavedSearches.pm | 16 +- rt/lib/RT/Scrip.pm | 5 +- rt/lib/RT/ScripAction.pm | 5 +- rt/lib/RT/ScripAction_Overlay.pm | 10 +- rt/lib/RT/ScripActions.pm | 5 +- rt/lib/RT/ScripActions_Overlay.pm | 10 +- rt/lib/RT/ScripCondition.pm | 5 +- rt/lib/RT/ScripCondition_Overlay.pm | 10 +- rt/lib/RT/ScripConditions.pm | 5 +- rt/lib/RT/ScripConditions_Overlay.pm | 10 +- rt/lib/RT/Scrip_Overlay.pm | 71 +- rt/lib/RT/Scrips.pm | 5 +- rt/lib/RT/Scrips_Overlay.pm | 30 +- rt/lib/RT/Search.pm | 148 + rt/lib/RT/Search/ActiveTicketsInQueue.pm | 12 +- rt/lib/RT/Search/FromSQL.pm | 12 +- rt/lib/RT/Search/Generic.pm | 98 +- rt/lib/RT/Search/Googleish.pm | 51 +- rt/lib/RT/SearchBuilder.pm | 94 +- rt/lib/RT/SharedSetting.pm | 458 + rt/lib/RT/Shredder.pm | 868 ++ rt/lib/RT/Shredder/ACE.pm | 101 + rt/lib/RT/Shredder/Attachment.pm | 136 + rt/lib/RT/Shredder/CachedGroupMember.pm | 151 + rt/lib/RT/Shredder/Constants.pm | 141 + rt/lib/RT/Shredder/CustomField.pm | 126 + rt/lib/RT/Shredder/CustomFieldValue.pm | 94 + rt/lib/RT/Shredder/Dependencies.pm | 149 + rt/lib/RT/Shredder/Dependency.pm | 112 + rt/lib/RT/Shredder/Exceptions.pm | 113 + rt/lib/RT/Shredder/Group.pm | 185 + rt/lib/RT/Shredder/GroupMember.pm | 183 + rt/lib/RT/Shredder/Link.pm | 140 + rt/lib/RT/Shredder/ObjectCustomFieldValue.pm | 116 + rt/lib/RT/Shredder/POD.pm | 131 + rt/lib/RT/Shredder/Plugin.pm | 249 + rt/lib/RT/Shredder/Plugin/Attachments.pm | 141 + rt/lib/RT/Shredder/Plugin/Base.pm | 188 + rt/lib/RT/Shredder/Plugin/Base/Dump.pm | 70 + rt/lib/RT/Shredder/Plugin/Base/Search.pm | 142 + rt/lib/RT/Shredder/Plugin/Objects.pm | 107 + rt/lib/RT/Shredder/Plugin/SQLDump.pm | 96 + rt/lib/RT/Shredder/Plugin/Summary.pm | 188 + rt/lib/RT/Shredder/Plugin/Tickets.pm | 153 + rt/lib/RT/Shredder/Plugin/Users.pm | 260 + rt/lib/RT/Shredder/Principal.pm | 127 + rt/lib/RT/Shredder/Queue.pm | 106 + rt/lib/RT/Shredder/Record.pm | 273 + rt/lib/RT/Shredder/Scrip.pm | 130 + rt/lib/RT/Shredder/ScripAction.pm | 100 + rt/lib/RT/Shredder/ScripCondition.pm | 101 + rt/lib/RT/Shredder/Template.pm | 120 + rt/lib/RT/Shredder/Ticket.pm | 126 + rt/lib/RT/Shredder/Transaction.pm | 115 + rt/lib/RT/Shredder/User.pm | 191 + rt/lib/RT/StyleGuide.pod | 24 +- rt/lib/RT/System.pm | 103 +- rt/lib/RT/Template.pm | 5 +- rt/lib/RT/Template_Overlay.pm | 289 +- rt/lib/RT/Templates.pm | 5 +- rt/lib/RT/Templates_Overlay.pm | 10 +- rt/lib/RT/Test.pm | 1293 +++ rt/lib/RT/Test/Email.pm | 131 + rt/lib/RT/Test/Web.pm | 192 + rt/lib/RT/Ticket.pm | 67 +- rt/lib/RT/Ticket_Overlay.pm | 1412 +-- rt/lib/RT/Tickets.pm | 5 +- rt/lib/RT/Tickets_Overlay.pm | 1133 +- rt/lib/RT/Tickets_Overlay_SQL.pm | 534 +- rt/lib/RT/Transaction.pm | 5 +- rt/lib/RT/Transaction_Overlay.pm | 482 +- rt/lib/RT/Transactions.pm | 5 +- rt/lib/RT/Transactions_Overlay.pm | 36 +- rt/lib/RT/URI.pm | 40 +- rt/lib/RT/URI/base.pm | 5 +- rt/lib/RT/URI/fsck_com_rt.pm | 39 +- rt/lib/RT/URI/t.pm | 22 +- rt/lib/RT/User.pm | 5 +- rt/lib/RT/User_Overlay.pm | 1050 +- rt/lib/RT/Users.pm | 5 +- rt/lib/RT/Users_Overlay.pm | 84 +- rt/lib/RT/Util.pm | 89 + rt/m4/rt_layout.m4 | 5 +- rt/sbin/extract-message-catalog | 141 +- rt/sbin/factory | 17 +- rt/sbin/license_tag | 51 +- rt/sbin/merge-rosetta.pl | 102 + rt/sbin/rt-attributes-viewer | 110 + rt/sbin/rt-attributes-viewer.in | 110 + rt/sbin/rt-clean-sessions | 190 + rt/sbin/rt-clean-sessions.in | 190 + rt/sbin/rt-dump-database | 32 +- rt/sbin/rt-dump-database.in | 32 +- rt/sbin/rt-email-dashboards | 568 + rt/sbin/rt-email-dashboards.in | 568 + rt/sbin/rt-email-digest | 337 + rt/sbin/rt-email-digest.in | 337 + rt/sbin/rt-email-group-admin | 508 + rt/sbin/rt-email-group-admin.in | 508 + rt/sbin/rt-server | 129 + rt/sbin/rt-server.in | 129 + rt/sbin/rt-setup-database | 861 +- rt/sbin/rt-setup-database.in | 861 +- rt/sbin/rt-shredder | 323 + rt/sbin/rt-shredder.in | 323 + rt/sbin/rt-test-dependencies | 273 +- rt/sbin/rt-test-dependencies.in | 273 +- rt/sbin/rt-validator | 1118 ++ rt/sbin/rt-validator.in | 1118 ++ rt/sbin/tweak-template-locstring | 55 + rt/share/html/Admin/CustomFields/GroupRights.html | 114 + rt/share/html/Admin/CustomFields/Modify.html | 249 + rt/share/html/Admin/CustomFields/Objects.html | 153 + rt/share/html/Admin/CustomFields/UserRights.html | 110 + rt/share/html/Admin/CustomFields/index.html | 108 + rt/share/html/Admin/Elements/AddCustomFieldValue | 86 + rt/share/html/Admin/Elements/ConfigureMyRT | 82 + rt/share/html/Admin/Elements/CreateUserCalled | 50 + rt/share/html/Admin/Elements/CustomFieldTabs | 118 + rt/share/html/Admin/Elements/EditCustomField | 159 + rt/share/html/Admin/Elements/EditCustomFieldValues | 104 + .../Admin/Elements/EditCustomFieldValuesSource | 82 + rt/share/html/Admin/Elements/EditCustomFields | 205 + rt/share/html/Admin/Elements/EditQueueWatchers | 78 + rt/share/html/Admin/Elements/EditScrip | 197 + rt/share/html/Admin/Elements/EditScrips | 124 + rt/share/html/Admin/Elements/EditTemplates | 127 + rt/share/html/Admin/Elements/EditUserComments | 56 + rt/share/html/Admin/Elements/GlobalCustomFieldTabs | 105 + rt/share/html/Admin/Elements/GroupTabs | 102 + rt/share/html/Admin/Elements/Header | 52 + .../html/Admin/Elements/ListGlobalCustomFields | 61 + rt/share/html/Admin/Elements/ListGlobalScrips | 71 + rt/share/html/Admin/Elements/ModifyTemplate | 84 + rt/share/html/Admin/Elements/ObjectCustomFields | 111 + rt/share/html/Admin/Elements/PickCustomFields | 98 + rt/share/html/Admin/Elements/PickObjects | 81 + rt/share/html/Admin/Elements/QueueRightsForUser | 64 + rt/share/html/Admin/Elements/QueueTabs | 123 + rt/share/html/Admin/Elements/SelectCustomField | 71 + .../Admin/Elements/SelectCustomFieldLookupType | 60 + rt/share/html/Admin/Elements/SelectCustomFieldType | 60 + rt/share/html/Admin/Elements/SelectGroups | 62 + rt/share/html/Admin/Elements/SelectModifyGroup | 57 + rt/share/html/Admin/Elements/SelectModifyQueue | 57 + rt/share/html/Admin/Elements/SelectModifyUser | 73 + rt/share/html/Admin/Elements/SelectNewGroupMembers | 108 + rt/share/html/Admin/Elements/SelectRights | 121 + rt/share/html/Admin/Elements/SelectScrip | 72 + rt/share/html/Admin/Elements/SelectScripAction | 73 + rt/share/html/Admin/Elements/SelectScripCondition | 72 + .../html/Admin/Elements/SelectSingleOrMultiple | 67 + rt/share/html/Admin/Elements/SelectStage | 75 + rt/share/html/Admin/Elements/SelectTemplate | 87 + rt/share/html/Admin/Elements/SelectUsers | 66 + rt/share/html/Admin/Elements/ShowKeyInfo | 91 + rt/share/html/Admin/Elements/SystemTabs | 102 + rt/share/html/Admin/Elements/Tabs | 95 + rt/share/html/Admin/Elements/ToolTabs | 82 + rt/share/html/Admin/Elements/UserTabs | 116 + .../html/Admin/Global/CustomFields/Groups.html | 58 + .../Admin/Global/CustomFields/Queue-Tickets.html | 58 + .../Global/CustomFields/Queue-Transactions.html | 58 + .../html/Admin/Global/CustomFields/Queues.html | 58 + rt/share/html/Admin/Global/CustomFields/Users.html | 58 + rt/share/html/Admin/Global/CustomFields/index.html | 99 + rt/share/html/Admin/Global/GroupRights.html | 123 + rt/share/html/Admin/Global/MyRT.html | 112 + rt/share/html/Admin/Global/Scrip.html | 86 + rt/share/html/Admin/Global/Scrips.html | 77 + rt/share/html/Admin/Global/Template.html | 123 + rt/share/html/Admin/Global/Templates.html | 77 + rt/share/html/Admin/Global/UserRights.html | 99 + rt/share/html/Admin/Global/index.html | 86 + rt/share/html/Admin/Groups/CustomFields.html | 48 + rt/share/html/Admin/Groups/GroupRights.html | 119 + rt/share/html/Admin/Groups/History.html | 68 + rt/share/html/Admin/Groups/Members.html | 159 + rt/share/html/Admin/Groups/Modify.html | 178 + rt/share/html/Admin/Groups/UserRights.html | 112 + rt/share/html/Admin/Groups/index.html | 129 + rt/share/html/Admin/Queues/CustomField.html | 87 + rt/share/html/Admin/Queues/CustomFields.html | 72 + rt/share/html/Admin/Queues/GroupRights.html | 134 + rt/share/html/Admin/Queues/History.html | 68 + rt/share/html/Admin/Queues/Modify.html | 214 + rt/share/html/Admin/Queues/People.html | 215 + rt/share/html/Admin/Queues/Scrip.html | 99 + rt/share/html/Admin/Queues/Scrips.html | 87 + rt/share/html/Admin/Queues/Template.html | 132 + rt/share/html/Admin/Queues/Templates.html | 81 + rt/share/html/Admin/Queues/UserRights.html | 112 + rt/share/html/Admin/Queues/index.html | 126 + rt/share/html/Admin/Tools/Configuration.html | 170 + rt/share/html/Admin/Tools/Shredder/Dumps/dhandler | 68 + .../Admin/Tools/Shredder/Elements/DumpFileLink | 61 + .../Admin/Tools/Shredder/Elements/Error/NoRights | 55 + .../Admin/Tools/Shredder/Elements/Error/NoStorage | 59 + .../Tools/Shredder/Elements/Object/RT--Attachment | 53 + .../Tools/Shredder/Elements/Object/RT--Ticket | 53 + .../Admin/Tools/Shredder/Elements/Object/RT--User | 53 + .../Admin/Tools/Shredder/Elements/ObjectCheckBox | 66 + .../Admin/Tools/Shredder/Elements/PluginArguments | 62 + .../html/Admin/Tools/Shredder/Elements/PluginHelp | 75 + .../Admin/Tools/Shredder/Elements/SelectObjects | 67 + .../Admin/Tools/Shredder/Elements/SelectPlugin | 73 + rt/share/html/Admin/Tools/Shredder/autohandler | 60 + rt/share/html/Admin/Tools/Shredder/index.html | 186 + rt/share/html/Admin/Tools/index.html | 55 + rt/share/html/Admin/Users/CustomFields.html | 71 + rt/share/html/Admin/Users/GnuPG.html | 112 + rt/share/html/Admin/Users/History.html | 68 + rt/share/html/Admin/Users/Memberships.html | 143 + rt/share/html/Admin/Users/Modify.html | 437 + rt/share/html/Admin/Users/MyRT.html | 134 + rt/share/html/Admin/Users/index.html | 135 + rt/share/html/Admin/autohandler | 55 + rt/share/html/Admin/index.html | 95 + rt/share/html/Approvals/Display.html | 72 + rt/share/html/Approvals/Elements/Approve | 94 + rt/share/html/Approvals/Elements/PendingMyApproval | 111 + rt/share/html/Approvals/Elements/ShowDependency | 109 + rt/share/html/Approvals/Elements/Tabs | 58 + rt/share/html/Approvals/autohandler | 53 + rt/share/html/Approvals/index.html | 90 + .../html/Dashboards/Elements/DashboardsForObject | 81 + .../html/Dashboards/Elements/DashboardsForObjects | 81 + rt/share/html/Dashboards/Elements/Deleted | 62 + rt/share/html/Dashboards/Elements/HiddenSearches | 79 + rt/share/html/Dashboards/Elements/ListOfDashboards | 19 + rt/share/html/Dashboards/Elements/SelectPrivacy | 64 + rt/share/html/Dashboards/Elements/ShowDashboards | 112 + .../html/Dashboards/Elements/ShowPortlet/component | 54 + .../html/Dashboards/Elements/ShowPortlet/dashboard | 89 + .../html/Dashboards/Elements/ShowPortlet/search | 63 + rt/share/html/Dashboards/Elements/ShowSubscription | 75 + rt/share/html/Dashboards/Elements/Tabs | 113 + rt/share/html/Dashboards/Modify.html | 168 + rt/share/html/Dashboards/Queries.html | 280 + rt/share/html/Dashboards/Render.html | 143 + rt/share/html/Dashboards/Subscription.html | 292 + rt/share/html/Dashboards/dhandler | 56 + rt/share/html/Dashboards/index.html | 107 + rt/share/html/Download/CustomFieldValue/dhandler | 77 + rt/share/html/Download/Tabular/dhandler | 76 + rt/share/html/Elements/BevelBoxRaisedEnd | 50 + rt/share/html/Elements/BevelBoxRaisedStart | 50 + rt/share/html/Elements/Callback | 53 + rt/share/html/Elements/Checkbox | 63 + rt/share/html/Elements/CollectionAsTable/Header | 142 + .../html/Elements/CollectionAsTable/ParseFormat | 108 + rt/share/html/Elements/CollectionAsTable/Row | 143 + rt/share/html/Elements/CollectionList | 175 + rt/share/html/Elements/CollectionListPaging | 108 + rt/share/html/Elements/ColumnMap | 151 + rt/share/html/Elements/CreateTicket | 50 + rt/share/html/Elements/DashboardTabs | 52 + rt/share/html/Elements/Dashboards | 66 + rt/share/html/Elements/EditCustomField | 116 + rt/share/html/Elements/EditCustomFieldAutocomplete | 88 + rt/share/html/Elements/EditCustomFieldBinary | 62 + rt/share/html/Elements/EditCustomFieldCombobox | 69 + rt/share/html/Elements/EditCustomFieldFreeform | 71 + rt/share/html/Elements/EditCustomFieldImage | 62 + rt/share/html/Elements/EditCustomFieldSelect | 161 + rt/share/html/Elements/EditCustomFieldText | 67 + rt/share/html/Elements/EditCustomFieldWikitext | 67 + rt/share/html/Elements/EditLinks | 178 + rt/share/html/Elements/EditTimeValue | 65 + rt/share/html/Elements/EmailInput | 53 + rt/share/html/Elements/Error | 90 + rt/share/html/Elements/Footer | 84 + rt/share/html/Elements/GnuPG/KeyIssues | 92 + .../html/Elements/GnuPG/SelectKeyForEncryption | 81 + rt/share/html/Elements/GnuPG/SelectKeyForSigning | 64 + rt/share/html/Elements/GnuPG/SignEncryptWidget | 184 + rt/share/html/Elements/GotoTicket | 50 + rt/share/html/Elements/Header | 118 + rt/share/html/Elements/HeaderJavascript | 122 + rt/share/html/Elements/ListActions | 94 + rt/share/html/Elements/ListMenu | 58 + rt/share/html/Elements/Login | 139 + rt/share/html/Elements/Logo | 66 + rt/share/html/Elements/Logout | 65 + rt/share/html/Elements/MakeClicky | 207 + rt/share/html/Elements/Menu | 139 + rt/share/html/Elements/MessageBox | 82 + rt/share/html/Elements/MyAdminQueues | 54 + rt/share/html/Elements/MyRT | 106 + rt/share/html/Elements/MyReminders | 74 + rt/share/html/Elements/MyRequests | 49 + rt/share/html/Elements/MySupportQueues | 60 + rt/share/html/Elements/MyTickets | 49 + rt/share/html/Elements/PageLayout | 251 + rt/share/html/Elements/PersonalQuickbar | 16 + rt/share/html/Elements/QueryString | 64 + rt/share/html/Elements/QueueSummary | 108 + rt/share/html/Elements/QuickCreate | 77 + rt/share/html/Elements/Quicksearch | 65 + rt/share/html/Elements/RT__Group/ColumnMap | 92 + rt/share/html/Elements/RT__Queue/ColumnMap | 115 + rt/share/html/Elements/RT__Scrip/ColumnMap | 103 + rt/share/html/Elements/RT__Template/ColumnMap | 76 + rt/share/html/Elements/RT__Ticket/ColumnMap | 355 + rt/share/html/Elements/RT__User/ColumnMap | 141 + rt/share/html/Elements/Refresh | 69 + rt/share/html/Elements/RefreshHomepage | 54 + rt/share/html/Elements/ScrubHTML | 77 + rt/share/html/Elements/Section | 51 + rt/share/html/Elements/SelectAttachmentField | 56 + rt/share/html/Elements/SelectBoolean | 71 + rt/share/html/Elements/SelectCustomFieldOperator | 64 + rt/share/html/Elements/SelectCustomFieldValue | 65 + rt/share/html/Elements/SelectDate | 77 + rt/share/html/Elements/SelectDateRelation | 60 + rt/share/html/Elements/SelectDateType | 60 + rt/share/html/Elements/SelectEqualityOperator | 64 + rt/share/html/Elements/SelectGroups | 68 + rt/share/html/Elements/SelectLang | 80 + rt/share/html/Elements/SelectLinkType | 57 + rt/share/html/Elements/SelectMatch | 82 + rt/share/html/Elements/SelectNewTicketQueue | 50 + rt/share/html/Elements/SelectOwner | 133 + rt/share/html/Elements/SelectPriority | 8 + rt/share/html/Elements/SelectQueue | 106 + rt/share/html/Elements/SelectResultsPerPage | 68 + rt/share/html/Elements/SelectSortOrder | 65 + rt/share/html/Elements/SelectStatus | 67 + rt/share/html/Elements/SelectTicketSortBy | 62 + rt/share/html/Elements/SelectTicketTypes | 58 + rt/share/html/Elements/SelectTimeUnits | 62 + rt/share/html/Elements/SelectTimezone | 84 + rt/share/html/Elements/SelectUsers | 68 + rt/share/html/Elements/SelectWatcherType | 71 + rt/share/html/Elements/SetupSessionCookie | 57 + rt/share/html/Elements/ShowCustomFieldBinary | 51 + rt/share/html/Elements/ShowCustomFieldImage | 53 + rt/share/html/Elements/ShowCustomFieldText | 56 + rt/share/html/Elements/ShowCustomFieldWikitext | 58 + rt/share/html/Elements/ShowCustomFields | 123 + rt/share/html/Elements/ShowLink | 69 + rt/share/html/Elements/ShowLinks | 160 + rt/share/html/Elements/ShowMemberships | 88 + rt/share/html/Elements/ShowSearch | 151 + rt/share/html/Elements/ShowUser | 69 + rt/share/html/Elements/ShowUserConcise | 52 + rt/share/html/Elements/ShowUserEmailFrequency | 56 + rt/share/html/Elements/ShowUserVerbose | 73 + rt/share/html/Elements/SimpleSearch | 53 + rt/share/html/Elements/Submit | 103 + rt/share/html/Elements/Tabs | 128 + rt/share/html/Elements/TicketList | 70 + rt/share/html/Elements/TitleBox | 51 + rt/share/html/Elements/TitleBoxEnd | 51 + rt/share/html/Elements/TitleBoxStart | 51 + rt/share/html/Elements/ValidateCustomFields | 100 + .../html/Helpers/Autocomplete/CustomFieldValues | 81 + rt/share/html/Helpers/CalPopup.html | 133 + rt/share/html/Helpers/Toggle/TicketBookmark | 54 + rt/share/html/Install/Basics.html | 104 + rt/share/html/Install/DatabaseDetails.html | 201 + rt/share/html/Install/DatabaseType.html | 90 + rt/share/html/Install/Elements/Errors | 63 + rt/share/html/Install/Elements/Wrapper | 66 + rt/share/html/Install/Finish.html | 95 + rt/share/html/Install/Global.html | 121 + rt/share/html/Install/Initialize.html | 142 + rt/share/html/Install/Sendmail.html | 107 + rt/share/html/Install/autohandler | 67 + rt/share/html/Install/index.html | 142 + rt/share/html/NoAuth/Logout.html | 79 + rt/share/html/NoAuth/Reminder.html | 50 + .../editor/_source/classes/fckcontextmenu.js | 223 + .../editor/_source/classes/fckdataprocessor.js | 119 + .../_source/classes/fckdocumentfragment_gecko.js | 53 + .../_source/classes/fckdocumentfragment_ie.js | 58 + .../editor/_source/classes/fckdomrange.js | 935 ++ .../editor/_source/classes/fckdomrange_gecko.js | 104 + .../editor/_source/classes/fckdomrange_ie.js | 199 + .../editor/_source/classes/fckdomrangeiterator.js | 327 + .../editor/_source/classes/fckeditingarea.js | 368 + .../editor/_source/classes/fckelementpath.js | 89 + .../editor/_source/classes/fckenterkey.js | 708 ++ .../FCKeditor/editor/_source/classes/fckevents.js | 71 + .../editor/_source/classes/fckhtmliterator.js | 142 + .../FCKeditor/editor/_source/classes/fckicon.js | 103 + .../editor/_source/classes/fckiecleanup.js | 68 + .../editor/_source/classes/fckimagepreloader.js | 64 + .../editor/_source/classes/fckkeystrokehandler.js | 141 + .../editor/_source/classes/fckmenublock.js | 153 + .../editor/_source/classes/fckmenublockpanel.js | 54 + .../editor/_source/classes/fckmenuitem.js | 161 + .../FCKeditor/editor/_source/classes/fckpanel.js | 463 + .../FCKeditor/editor/_source/classes/fckplugin.js | 56 + .../editor/_source/classes/fckspecialcombo.js | 376 + .../FCKeditor/editor/_source/classes/fckstyle.js | 1500 +++ .../FCKeditor/editor/_source/classes/fcktoolbar.js | 103 + .../_source/classes/fcktoolbarbreak_gecko.js | 36 + .../editor/_source/classes/fcktoolbarbreak_ie.js | 38 + .../editor/_source/classes/fcktoolbarbutton.js | 81 + .../editor/_source/classes/fcktoolbarbuttonui.js | 198 + .../_source/classes/fcktoolbarfontformatcombo.js | 139 + .../editor/_source/classes/fcktoolbarfontscombo.js | 98 + .../_source/classes/fcktoolbarfontsizecombo.js | 76 + .../_source/classes/fcktoolbarpanelbutton.js | 103 + .../_source/classes/fcktoolbarspecialcombo.js | 146 + .../editor/_source/classes/fcktoolbarstylecombo.js | 200 + .../editor/_source/classes/fckw3crange.js | 451 + .../FCKeditor/editor/_source/classes/fckxml.js | 108 + .../editor/_source/classes/fckxml_gecko.js | 106 + .../FCKeditor/editor/_source/classes/fckxml_ie.js | 93 + .../_source/commandclasses/fck_othercommands.js | 634 ++ .../_source/commandclasses/fckblockquotecommand.js | 281 + .../_source/commandclasses/fckcorestylecommand.js | 61 + .../editor/_source/commandclasses/fckfitwindow.js | 213 + .../_source/commandclasses/fckindentcommands.js | 282 + .../_source/commandclasses/fckjustifycommands.js | 173 + .../_source/commandclasses/fcklistcommands.js | 382 + .../_source/commandclasses/fcknamedcommand.js | 39 + .../commandclasses/fckpasteplaintextcommand.js | 40 + .../_source/commandclasses/fckpastewordcommand.js | 40 + .../commandclasses/fckremoveformatcommand.js | 45 + .../editor/_source/commandclasses/fckshowblocks.js | 94 + .../commandclasses/fckspellcheckcommand_gecko.js | 49 + .../commandclasses/fckspellcheckcommand_ie.js | 72 + .../_source/commandclasses/fckstylecommand.js | 60 + .../_source/commandclasses/fcktablecommand.js | 106 + .../_source/commandclasses/fcktextcolorcommand.js | 201 + .../FCKeditor/editor/_source/fckconstants.js | 56 + .../FCKeditor/editor/_source/fckeditorapi.js | 179 + .../editor/_source/fckjscoreextensions.js | 159 + .../FCKeditor/editor/_source/fckscriptloader.js | 122 + .../FCKeditor/editor/_source/internals/fck.js | 1256 +++ .../editor/_source/internals/fck_contextmenu.js | 345 + .../editor/_source/internals/fck_gecko.js | 497 + .../FCKeditor/editor/_source/internals/fck_ie.js | 456 + .../editor/_source/internals/fckbrowserinfo.js | 61 + .../editor/_source/internals/fckcodeformatter.js | 100 + .../editor/_source/internals/fckcommands.js | 172 + .../editor/_source/internals/fckconfig.js | 237 + .../FCKeditor/editor/_source/internals/fckdebug.js | 59 + .../editor/_source/internals/fckdebug_empty.js | 31 + .../editor/_source/internals/fckdialog.js | 239 + .../_source/internals/fckdocumentprocessor.js | 270 + .../editor/_source/internals/fckdomtools.js | 1057 ++ .../editor/_source/internals/fcklanguagemanager.js | 165 + .../editor/_source/internals/fcklisthandler.js | 152 + .../editor/_source/internals/fcklistslib.js | 63 + .../editor/_source/internals/fckplugins.js | 46 + .../editor/_source/internals/fckregexlib.js | 100 + .../editor/_source/internals/fckselection.js | 42 + .../editor/_source/internals/fckselection_gecko.js | 228 + .../editor/_source/internals/fckselection_ie.js | 287 + .../editor/_source/internals/fckstyles.js | 381 + .../editor/_source/internals/fcktablehandler.js | 863 ++ .../_source/internals/fcktablehandler_gecko.js | 56 + .../editor/_source/internals/fcktablehandler_ie.js | 64 + .../editor/_source/internals/fcktoolbaritems.js | 124 + .../editor/_source/internals/fcktoolbarset.js | 399 + .../FCKeditor/editor/_source/internals/fcktools.js | 749 ++ .../editor/_source/internals/fcktools_gecko.js | 282 + .../editor/_source/internals/fcktools_ie.js | 234 + .../FCKeditor/editor/_source/internals/fckundo.js | 223 + .../editor/_source/internals/fckurlparams.js | 39 + .../FCKeditor/editor/_source/internals/fckxhtml.js | 534 + .../editor/_source/internals/fckxhtml_gecko.js | 114 + .../editor/_source/internals/fckxhtml_ie.js | 213 + .../editor/_source/internals/fckxhtmlentities.js | 357 + .../editor/css/behaviors/disablehandles.htc | 15 + .../editor/css/behaviors/showtableborders.htc | 36 + .../FCKeditor/editor/css/fck_editorarea.css | 110 + .../RichText/FCKeditor/editor/css/fck_internal.css | 199 + .../editor/css/fck_showtableborders_gecko.css | 49 + .../FCKeditor/editor/css/images/block_address.png | Bin 0 -> 288 bytes .../editor/css/images/block_blockquote.png | Bin 0 -> 293 bytes .../FCKeditor/editor/css/images/block_div.png | Bin 0 -> 229 bytes .../FCKeditor/editor/css/images/block_h1.png | Bin 0 -> 218 bytes .../FCKeditor/editor/css/images/block_h2.png | Bin 0 -> 220 bytes .../FCKeditor/editor/css/images/block_h3.png | Bin 0 -> 219 bytes .../FCKeditor/editor/css/images/block_h4.png | Bin 0 -> 229 bytes .../FCKeditor/editor/css/images/block_h5.png | Bin 0 -> 236 bytes .../FCKeditor/editor/css/images/block_h6.png | Bin 0 -> 216 bytes .../FCKeditor/editor/css/images/block_p.png | Bin 0 -> 205 bytes .../FCKeditor/editor/css/images/block_pre.png | Bin 0 -> 223 bytes .../FCKeditor/editor/css/images/fck_anchor.gif | Bin 0 -> 184 bytes .../FCKeditor/editor/css/images/fck_flashlogo.gif | Bin 0 -> 599 bytes .../editor/css/images/fck_hiddenfield.gif | Bin 0 -> 105 bytes .../FCKeditor/editor/css/images/fck_pagebreak.gif | Bin 0 -> 54 bytes .../FCKeditor/editor/css/images/fck_plugin.gif | Bin 0 -> 1709 bytes .../editor/dialog/common/fck_dialog_common.css | 85 + .../editor/dialog/common/fck_dialog_common.js | 347 + .../editor/dialog/common/images/locked.gif | Bin 0 -> 74 bytes .../editor/dialog/common/images/reset.gif | Bin 0 -> 104 bytes .../editor/dialog/common/images/unlocked.gif | Bin 0 -> 75 bytes .../FCKeditor/editor/dialog/fck_about.html | 161 + .../editor/dialog/fck_about/logo_fckeditor.gif | Bin 0 -> 2044 bytes .../editor/dialog/fck_about/logo_fredck.gif | Bin 0 -> 920 bytes .../dialog/fck_about/sponsors/spellchecker_net.gif | Bin 0 -> 1447 bytes .../FCKeditor/editor/dialog/fck_anchor.html | 220 + .../FCKeditor/editor/dialog/fck_button.html | 104 + .../FCKeditor/editor/dialog/fck_checkbox.html | 104 + .../FCKeditor/editor/dialog/fck_colorselector.html | 172 + .../RichText/FCKeditor/editor/dialog/fck_div.html | 396 + .../FCKeditor/editor/dialog/fck_docprops.html | 600 + .../dialog/fck_docprops/fck_document_preview.html | 113 + .../FCKeditor/editor/dialog/fck_flash.html | 152 + .../FCKeditor/editor/dialog/fck_flash/fck_flash.js | 300 + .../editor/dialog/fck_flash/fck_flash_preview.html | 50 + .../RichText/FCKeditor/editor/dialog/fck_form.html | 109 + .../FCKeditor/editor/dialog/fck_hiddenfield.html | 115 + .../FCKeditor/editor/dialog/fck_image.html | 258 + .../FCKeditor/editor/dialog/fck_image/fck_image.js | 512 + .../editor/dialog/fck_image/fck_image_preview.html | 72 + .../RichText/FCKeditor/editor/dialog/fck_link.html | 295 + .../FCKeditor/editor/dialog/fck_link/fck_link.js | 893 ++ .../FCKeditor/editor/dialog/fck_listprop.html | 120 + .../FCKeditor/editor/dialog/fck_paste.html | 347 + .../FCKeditor/editor/dialog/fck_radiobutton.html | 104 + .../FCKeditor/editor/dialog/fck_replace.html | 650 ++ .../FCKeditor/editor/dialog/fck_select.html | 180 + .../editor/dialog/fck_select/fck_select.js | 194 + .../FCKeditor/editor/dialog/fck_smiley.html | 111 + .../FCKeditor/editor/dialog/fck_source.html | 68 + .../FCKeditor/editor/dialog/fck_specialchar.html | 121 + .../FCKeditor/editor/dialog/fck_spellerpages.html | 70 + .../fck_spellerpages/spellerpages/blank.html | 0 .../fck_spellerpages/spellerpages/controlWindow.js | 87 + .../fck_spellerpages/spellerpages/controls.html | 153 + .../spellerpages/server-scripts/spellchecker.cfm | 148 + .../spellerpages/server-scripts/spellchecker.php | 199 + .../spellerpages/server-scripts/spellchecker.pl | 181 + .../fck_spellerpages/spellerpages/spellChecker.js | 461 + .../spellerpages/spellchecker.html | 71 + .../fck_spellerpages/spellerpages/spellerStyle.css | 49 + .../fck_spellerpages/spellerpages/wordWindow.js | 272 + .../FCKeditor/editor/dialog/fck_table.html | 439 + .../FCKeditor/editor/dialog/fck_tablecell.html | 293 + .../FCKeditor/editor/dialog/fck_template.html | 242 + .../dialog/fck_template/images/template1.gif | Bin 0 -> 375 bytes .../dialog/fck_template/images/template2.gif | Bin 0 -> 333 bytes .../dialog/fck_template/images/template3.gif | Bin 0 -> 422 bytes .../FCKeditor/editor/dialog/fck_textarea.html | 94 + .../FCKeditor/editor/dialog/fck_textfield.html | 136 + .../FCKeditor/editor/dtd/fck_dtd_test.html | 41 + .../FCKeditor/editor/dtd/fck_xhtml10strict.js | 116 + .../editor/dtd/fck_xhtml10transitional.js | 140 + .../NoAuth/RichText/FCKeditor/editor/fckdebug.html | 153 + .../RichText/FCKeditor/editor/fckdialog.html | 819 ++ .../RichText/FCKeditor/editor/fckeditor.html | 317 + .../FCKeditor/editor/fckeditor.original.html | 424 + .../RichText/FCKeditor/editor/images/anchor.gif | Bin 0 -> 184 bytes .../RichText/FCKeditor/editor/images/arrow_ltr.gif | Bin 0 -> 49 bytes .../RichText/FCKeditor/editor/images/arrow_rtl.gif | Bin 0 -> 49 bytes .../editor/images/smiley/msn/angel_smile.gif | Bin 0 -> 445 bytes .../editor/images/smiley/msn/angry_smile.gif | Bin 0 -> 453 bytes .../editor/images/smiley/msn/broken_heart.gif | Bin 0 -> 423 bytes .../FCKeditor/editor/images/smiley/msn/cake.gif | Bin 0 -> 453 bytes .../editor/images/smiley/msn/confused_smile.gif | Bin 0 -> 322 bytes .../editor/images/smiley/msn/cry_smile.gif | Bin 0 -> 473 bytes .../editor/images/smiley/msn/devil_smile.gif | Bin 0 -> 444 bytes .../editor/images/smiley/msn/embaressed_smile.gif | Bin 0 -> 1077 bytes .../editor/images/smiley/msn/envelope.gif | Bin 0 -> 1030 bytes .../FCKeditor/editor/images/smiley/msn/heart.gif | Bin 0 -> 1012 bytes .../FCKeditor/editor/images/smiley/msn/kiss.gif | Bin 0 -> 978 bytes .../editor/images/smiley/msn/lightbulb.gif | Bin 0 -> 303 bytes .../editor/images/smiley/msn/omg_smile.gif | Bin 0 -> 342 bytes .../editor/images/smiley/msn/regular_smile.gif | Bin 0 -> 1036 bytes .../editor/images/smiley/msn/sad_smile.gif | Bin 0 -> 1039 bytes .../editor/images/smiley/msn/shades_smile.gif | Bin 0 -> 1059 bytes .../editor/images/smiley/msn/teeth_smile.gif | Bin 0 -> 1064 bytes .../editor/images/smiley/msn/thumbs_down.gif | Bin 0 -> 992 bytes .../editor/images/smiley/msn/thumbs_up.gif | Bin 0 -> 989 bytes .../editor/images/smiley/msn/tounge_smile.gif | Bin 0 -> 1055 bytes .../smiley/msn/whatchutalkingabout_smile.gif | Bin 0 -> 1034 bytes .../editor/images/smiley/msn/wink_smile.gif | Bin 0 -> 1041 bytes .../RichText/FCKeditor/editor/images/spacer.gif | Bin 0 -> 43 bytes .../RichText/FCKeditor/editor/js/fckadobeair.js | 176 + .../FCKeditor/editor/js/fckeditorcode_gecko.js | 108 + .../FCKeditor/editor/js/fckeditorcode_ie.js | 109 + .../FCKeditor/editor/lang/_translationstatus.txt | 79 + .../NoAuth/RichText/FCKeditor/editor/lang/af.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/ar.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/bg.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/bn.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/bs.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/ca.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/cs.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/da.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/de.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/el.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/en-au.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/en-ca.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/en-uk.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/en.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/eo.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/es.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/et.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/eu.js | 535 + .../NoAuth/RichText/FCKeditor/editor/lang/fa.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/fi.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/fo.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/fr-ca.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/fr.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/gl.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/gu.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/he.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/hi.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/hr.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/hu.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/is.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/it.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/ja.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/km.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/ko.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/lt.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/lv.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/mn.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/ms.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/nb.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/nl.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/no.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/pl.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/pt-br.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/pt.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/ro.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/ru.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/sk.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/sl.js | 534 + .../RichText/FCKeditor/editor/lang/sr-latn.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/sr.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/sv.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/th.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/tr.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/uk.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/vi.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/zh-cn.js | 534 + .../NoAuth/RichText/FCKeditor/editor/lang/zh.js | 534 + .../FCKeditor/editor/plugins/autogrow/fckplugin.js | 111 + .../editor/plugins/bbcode/_sample/sample.config.js | 26 + .../editor/plugins/bbcode/_sample/sample.html | 67 + .../FCKeditor/editor/plugins/bbcode/fckplugin.js | 123 + .../editor/plugins/dragresizetable/fckplugin.js | 529 + .../plugins/placeholder/fck_placeholder.html | 105 + .../editor/plugins/placeholder/fckplugin.js | 187 + .../editor/plugins/placeholder/lang/de.js | 27 + .../editor/plugins/placeholder/lang/en.js | 27 + .../editor/plugins/placeholder/lang/es.js | 27 + .../editor/plugins/placeholder/lang/fr.js | 27 + .../editor/plugins/placeholder/lang/it.js | 27 + .../editor/plugins/placeholder/lang/pl.js | 27 + .../editor/plugins/placeholder/placeholder.gif | Bin 0 -> 96 bytes .../editor/plugins/simplecommands/fckplugin.js | 29 + .../editor/plugins/tablecommands/fckplugin.js | 33 + .../FCKeditor/editor/skins/_fckviewstrips.html | 121 + .../FCKeditor/editor/skins/default/fck_dialog.css | 402 + .../editor/skins/default/fck_dialog_ie6.js | 110 + .../FCKeditor/editor/skins/default/fck_editor.css | 464 + .../FCKeditor/editor/skins/default/fck_strip.gif | Bin 0 -> 5175 bytes .../editor/skins/default/images/dialog.sides.gif | Bin 0 -> 48 bytes .../editor/skins/default/images/dialog.sides.png | Bin 0 -> 178 bytes .../skins/default/images/dialog.sides.rtl.png | Bin 0 -> 181 bytes .../editor/skins/default/images/sprites.gif | Bin 0 -> 959 bytes .../editor/skins/default/images/sprites.png | Bin 0 -> 3250 bytes .../skins/default/images/toolbar.arrowright.gif | Bin 0 -> 53 bytes .../skins/default/images/toolbar.buttonarrow.gif | Bin 0 -> 46 bytes .../skins/default/images/toolbar.collapse.gif | Bin 0 -> 152 bytes .../editor/skins/default/images/toolbar.end.gif | Bin 0 -> 43 bytes .../editor/skins/default/images/toolbar.expand.gif | Bin 0 -> 152 bytes .../skins/default/images/toolbar.separator.gif | Bin 0 -> 58 bytes .../editor/skins/default/images/toolbar.start.gif | Bin 0 -> 105 bytes .../editor/skins/office2003/fck_dialog.css | 402 + .../editor/skins/office2003/fck_dialog_ie6.js | 110 + .../editor/skins/office2003/fck_editor.css | 476 + .../editor/skins/office2003/fck_strip.gif | Bin 0 -> 9668 bytes .../skins/office2003/images/dialog.sides.gif | Bin 0 -> 48 bytes .../skins/office2003/images/dialog.sides.png | Bin 0 -> 203 bytes .../skins/office2003/images/dialog.sides.rtl.png | Bin 0 -> 205 bytes .../editor/skins/office2003/images/sprites.gif | Bin 0 -> 959 bytes .../editor/skins/office2003/images/sprites.png | Bin 0 -> 3305 bytes .../skins/office2003/images/toolbar.arrowright.gif | Bin 0 -> 53 bytes .../editor/skins/office2003/images/toolbar.bg.gif | Bin 0 -> 73 bytes .../office2003/images/toolbar.buttonarrow.gif | Bin 0 -> 46 bytes .../skins/office2003/images/toolbar.collapse.gif | Bin 0 -> 152 bytes .../editor/skins/office2003/images/toolbar.end.gif | Bin 0 -> 124 bytes .../skins/office2003/images/toolbar.expand.gif | Bin 0 -> 152 bytes .../skins/office2003/images/toolbar.separator.gif | Bin 0 -> 67 bytes .../skins/office2003/images/toolbar.start.gif | Bin 0 -> 99 bytes .../FCKeditor/editor/skins/silver/fck_dialog.css | 402 + .../editor/skins/silver/fck_dialog_ie6.js | 110 + .../FCKeditor/editor/skins/silver/fck_editor.css | 473 + .../FCKeditor/editor/skins/silver/fck_strip.gif | Bin 0 -> 5175 bytes .../editor/skins/silver/images/dialog.sides.gif | Bin 0 -> 48 bytes .../editor/skins/silver/images/dialog.sides.png | Bin 0 -> 198 bytes .../skins/silver/images/dialog.sides.rtl.png | Bin 0 -> 200 bytes .../editor/skins/silver/images/sprites.gif | Bin 0 -> 959 bytes .../editor/skins/silver/images/sprites.png | Bin 0 -> 3278 bytes .../skins/silver/images/toolbar.arrowright.gif | Bin 0 -> 53 bytes .../skins/silver/images/toolbar.buttonarrow.gif | Bin 0 -> 46 bytes .../skins/silver/images/toolbar.buttonbg.gif | Bin 0 -> 829 bytes .../skins/silver/images/toolbar.collapse.gif | Bin 0 -> 152 bytes .../editor/skins/silver/images/toolbar.end.gif | Bin 0 -> 43 bytes .../editor/skins/silver/images/toolbar.expand.gif | Bin 0 -> 152 bytes .../skins/silver/images/toolbar.separator.gif | Bin 0 -> 58 bytes .../editor/skins/silver/images/toolbar.start.gif | Bin 0 -> 105 bytes .../RichText/FCKeditor/editor/wsc/ciframe.html | 65 + .../RichText/FCKeditor/editor/wsc/tmpFrameset.html | 67 + .../NoAuth/RichText/FCKeditor/editor/wsc/w.html | 227 + .../html/NoAuth/RichText/FCKeditor/fckconfig.js | 327 + .../html/NoAuth/RichText/FCKeditor/fckeditor.js | 330 + .../html/NoAuth/RichText/FCKeditor/fckpackager.xml | 262 + .../html/NoAuth/RichText/FCKeditor/fckstyles.xml | 111 + .../NoAuth/RichText/FCKeditor/fcktemplates.xml | 103 + .../html/NoAuth/RichText/FCKeditor/license.txt | 1246 +++ rt/share/html/NoAuth/RichText/dhandler | 69 + rt/share/html/NoAuth/css/3.4-compat/body.css | 75 + rt/share/html/NoAuth/css/3.4-compat/footer.css | 61 + rt/share/html/NoAuth/css/3.4-compat/forms.css | 110 + rt/share/html/NoAuth/css/3.4-compat/header.css | 88 + rt/share/html/NoAuth/css/3.4-compat/login.css | 54 + rt/share/html/NoAuth/css/3.4-compat/main.css | 75 + rt/share/html/NoAuth/css/3.4-compat/misc.css | 78 + rt/share/html/NoAuth/css/3.4-compat/nav.css | 106 + rt/share/html/NoAuth/css/3.4-compat/quickbar.css | 82 + rt/share/html/NoAuth/css/3.4-compat/ticket.css | 50 + rt/share/html/NoAuth/css/3.4-compat/titlebox.css | 103 + .../html/NoAuth/css/3.4-compat/transactions.css | 83 + rt/share/html/NoAuth/css/3.5-default/approvals.css | 97 + rt/share/html/NoAuth/css/3.5-default/body.css | 81 + rt/share/html/NoAuth/css/3.5-default/footer.css | 91 + rt/share/html/NoAuth/css/3.5-default/forms.css | 148 + rt/share/html/NoAuth/css/3.5-default/header.css | 152 + rt/share/html/NoAuth/css/3.5-default/local.css | 50 + rt/share/html/NoAuth/css/3.5-default/login.css | 85 + rt/share/html/NoAuth/css/3.5-default/logo.css | 60 + rt/share/html/NoAuth/css/3.5-default/main.css | 67 + rt/share/html/NoAuth/css/3.5-default/misc.css | 125 + rt/share/html/NoAuth/css/3.5-default/nav-left.css | 86 + rt/share/html/NoAuth/css/3.5-default/nav.css | 163 + rt/share/html/NoAuth/css/3.5-default/quickbar.css | 98 + .../html/NoAuth/css/3.5-default/ticket-search.css | 86 + rt/share/html/NoAuth/css/3.5-default/ticket.css | 57 + rt/share/html/NoAuth/css/3.5-default/titlebox.css | 162 + .../html/NoAuth/css/3.5-default/transactions.css | 146 + rt/share/html/NoAuth/css/autohandler | 59 + rt/share/html/NoAuth/css/dhandler | 77 + rt/share/html/NoAuth/css/print.css | 94 + rt/share/html/NoAuth/css/web2/InHeader | 54 + rt/share/html/NoAuth/css/web2/admin.css | 60 + rt/share/html/NoAuth/css/web2/base.css | 70 + rt/share/html/NoAuth/css/web2/boxes.css | 192 + rt/share/html/NoAuth/css/web2/forms.css | 242 + rt/share/html/NoAuth/css/web2/images/dhandler | 8 + .../css/web2/images/source/background-gradient.png | Bin 0 -> 394 bytes rt/share/html/NoAuth/css/web2/layout.css | 234 + rt/share/html/NoAuth/css/web2/login.css | 82 + rt/share/html/NoAuth/css/web2/main.css | 68 + rt/share/html/NoAuth/css/web2/misc.css | 108 + rt/share/html/NoAuth/css/web2/msie.css | 239 + rt/share/html/NoAuth/css/web2/msie6.css | 88 + rt/share/html/NoAuth/css/web2/nav.css | 203 + rt/share/html/NoAuth/css/web2/portlets.css | 65 + rt/share/html/NoAuth/css/web2/ticket-lists.css | 172 + rt/share/html/NoAuth/css/web2/ticket-search.css | 199 + rt/share/html/NoAuth/css/web2/ticket.css | 230 + rt/share/html/NoAuth/css/web2/tools.css | 56 + rt/share/html/NoAuth/css/web2/yui-fonts.css | 7 + rt/share/html/NoAuth/iCal/dhandler | 122 + rt/share/html/NoAuth/images/autohandler | 7 + rt/share/html/NoAuth/images/bplogo.gif | Bin 0 -> 755 bytes rt/share/html/NoAuth/images/css/cb-light.gif | Bin 0 -> 186 bytes rt/share/html/NoAuth/images/css/cb.gif | Bin 0 -> 163 bytes rt/share/html/NoAuth/images/css/cbr-b2g.gif | Bin 0 -> 135 bytes rt/share/html/NoAuth/images/css/cbr-b2lb.gif | Bin 0 -> 137 bytes rt/share/html/NoAuth/images/css/cbr-gray.gif | Bin 0 -> 137 bytes rt/share/html/NoAuth/images/css/cbr-trans.gif | Bin 0 -> 183 bytes rt/share/html/NoAuth/images/css/cbr.gif | Bin 0 -> 188 bytes rt/share/html/NoAuth/images/css/ct-light.gif | Bin 0 -> 162 bytes rt/share/html/NoAuth/images/css/ct.gif | Bin 0 -> 162 bytes rt/share/html/NoAuth/images/css/ctr-b2g.gif | Bin 0 -> 136 bytes rt/share/html/NoAuth/images/css/ctr-b2lb.gif | Bin 0 -> 114 bytes rt/share/html/NoAuth/images/css/ctr-gray.gif | Bin 0 -> 138 bytes rt/share/html/NoAuth/images/css/ctr-trans.gif | Bin 0 -> 182 bytes rt/share/html/NoAuth/images/css/ctr.gif | Bin 0 -> 188 bytes rt/share/html/NoAuth/images/css/dark-arrow-up.png | Bin 0 -> 346 bytes rt/share/html/NoAuth/images/css/dark-arrow.png | Bin 0 -> 337 bytes .../NoAuth/images/css/fieldbg-autocomplete.gif | Bin 0 -> 1164 bytes rt/share/html/NoAuth/images/css/light-arrow-up.png | Bin 0 -> 348 bytes rt/share/html/NoAuth/images/css/light-arrow.png | Bin 0 -> 340 bytes rt/share/html/NoAuth/images/css/rolldown-arrow.gif | Bin 0 -> 83 bytes rt/share/html/NoAuth/images/css/rolldown-arrow.png | Bin 0 -> 259 bytes rt/share/html/NoAuth/images/css/rollup-arrow.gif | Bin 0 -> 82 bytes rt/share/html/NoAuth/images/empty_star.gif | Bin 0 -> 914 bytes rt/share/html/NoAuth/images/favicon.png | Bin 0 -> 335 bytes rt/share/html/NoAuth/images/star.gif | Bin 0 -> 161 bytes rt/share/html/NoAuth/images/test.png | 2 + rt/share/html/NoAuth/js/IE7/IE7.js | 2 + rt/share/html/NoAuth/js/IE7/IE8.js | 2 + rt/share/html/NoAuth/js/IE7/blank.gif | Bin 0 -> 48 bytes rt/share/html/NoAuth/js/IE7/ie7-recalc.js | 2 + rt/share/html/NoAuth/js/IE7/ie7-squish.js | 36 + rt/share/html/NoAuth/js/ahah.js | 48 + rt/share/html/NoAuth/js/autohandler | 61 + rt/share/html/NoAuth/js/cascaded.js | 102 + rt/share/html/NoAuth/js/class.js | 15 + rt/share/html/NoAuth/js/combobox.js | 262 + rt/share/html/NoAuth/js/list.js | 112 + rt/share/html/NoAuth/js/prototype/prototype.js | 4320 ++++++++ rt/share/html/NoAuth/js/scriptaculous/controls.js | 971 ++ rt/share/html/NoAuth/js/scriptaculous/effects.js | 1130 ++ .../html/NoAuth/js/scriptaculous/scriptaculous.js | 60 + rt/share/html/NoAuth/js/titlebox-state.js | 83 + rt/share/html/NoAuth/js/util.js | 317 + rt/share/html/Prefs/Elements/Tabs | 76 + rt/share/html/Prefs/MyRT.html | 157 + rt/share/html/Prefs/Other.html | 111 + rt/share/html/Prefs/Quicksearch.html | 97 + rt/share/html/Prefs/Search.html | 111 + rt/share/html/Prefs/SearchOptions.html | 111 + rt/share/html/REST/1.0/Forms/attachment/default | 102 + rt/share/html/REST/1.0/Forms/group/customfields | 96 + rt/share/html/REST/1.0/Forms/group/default | 203 + rt/share/html/REST/1.0/Forms/group/ns | 62 + rt/share/html/REST/1.0/Forms/queue/customfields | 96 + rt/share/html/REST/1.0/Forms/queue/default | 186 + rt/share/html/REST/1.0/Forms/queue/ns | 62 + .../html/REST/1.0/Forms/queue/ticketcustomfields | 97 + rt/share/html/REST/1.0/Forms/ticket/attachments | 135 + rt/share/html/REST/1.0/Forms/ticket/comment | 152 + rt/share/html/REST/1.0/Forms/ticket/default | 447 + rt/share/html/REST/1.0/Forms/ticket/history | 205 + rt/share/html/REST/1.0/Forms/ticket/links | 172 + rt/share/html/REST/1.0/Forms/ticket/merge | 96 + rt/share/html/REST/1.0/Forms/ticket/take | 135 + rt/share/html/REST/1.0/Forms/transaction/default | 143 + rt/share/html/REST/1.0/Forms/user/default | 188 + rt/share/html/REST/1.0/Forms/user/ns | 65 + rt/share/html/REST/1.0/NoAuth/mail-gateway | 84 + rt/share/html/REST/1.0/autohandler | 56 + rt/share/html/REST/1.0/dhandler | 326 + rt/share/html/REST/1.0/logout | 51 + rt/share/html/REST/1.0/search/dhandler | 56 + rt/share/html/REST/1.0/search/ticket | 159 + rt/share/html/REST/1.0/ticket/comment | 177 + rt/share/html/REST/1.0/ticket/link | 123 + rt/share/html/REST/1.0/ticket/merge | 102 + rt/share/html/Search/Build.html | 323 + rt/share/html/Search/Bulk.html | 446 + rt/share/html/Search/Chart | 186 + rt/share/html/Search/Chart.html | 98 + rt/share/html/Search/Edit.html | 99 + rt/share/html/Search/Elements/BuildFormatString | 250 + rt/share/html/Search/Elements/Chart | 147 + rt/share/html/Search/Elements/ConditionRow | 97 + rt/share/html/Search/Elements/DisplayOptions | 134 + rt/share/html/Search/Elements/EditFormat | 113 + rt/share/html/Search/Elements/EditQuery | 243 + rt/share/html/Search/Elements/EditSearches | 296 + rt/share/html/Search/Elements/Graph | 50 + rt/share/html/Search/Elements/NewListActions | 66 + rt/share/html/Search/Elements/PickBasics | 214 + rt/share/html/Search/Elements/PickCFs | 104 + rt/share/html/Search/Elements/PickCriteria | 72 + rt/share/html/Search/Elements/ResultViews | 80 + rt/share/html/Search/Elements/SearchPrivacy | 55 + rt/share/html/Search/Elements/SearchesForObject | 65 + rt/share/html/Search/Elements/SelectAndOr | 53 + rt/share/html/Search/Elements/SelectChartType | 58 + rt/share/html/Search/Elements/SelectGroup | 67 + rt/share/html/Search/Elements/SelectGroupBy | 63 + rt/share/html/Search/Elements/SelectLinks | 66 + rt/share/html/Search/Elements/SelectPersonType | 84 + rt/share/html/Search/Elements/SelectSearchObject | 68 + .../html/Search/Elements/SelectSearchesForObjects | 69 + rt/share/html/Search/Graph.html | 50 + rt/share/html/Search/Results.html | 201 + rt/share/html/Search/Results.rdf | 103 + rt/share/html/Search/Results.tsv | 162 + rt/share/html/Search/Simple.html | 106 + rt/share/html/SelfService/Attachment/dhandler | 51 + rt/share/html/SelfService/Closed.html | 60 + rt/share/html/SelfService/Create.html | 149 + rt/share/html/SelfService/CreateTicketInQueue.html | 63 + rt/share/html/SelfService/Display.html | 233 + rt/share/html/SelfService/Elements/GotoTicket | 50 + rt/share/html/SelfService/Elements/Header | 49 + rt/share/html/SelfService/Elements/MyRequests | 87 + rt/share/html/SelfService/Elements/Tabs | 113 + rt/share/html/SelfService/Error.html | 71 + rt/share/html/SelfService/Prefs.html | 92 + rt/share/html/SelfService/Update.html | 133 + rt/share/html/SelfService/index.html | 57 + .../html/Ticket/Attachment/WithHeaders/dhandler | 80 + rt/share/html/Ticket/Attachment/dhandler | 93 + rt/share/html/Ticket/Create.html | 430 + rt/share/html/Ticket/Display.html | 207 + rt/share/html/Ticket/Elements/AddWatchers | 140 + rt/share/html/Ticket/Elements/Bookmark | 93 + rt/share/html/Ticket/Elements/BulkLinks | 195 + rt/share/html/Ticket/Elements/EditBasics | 130 + rt/share/html/Ticket/Elements/EditCustomFields | 108 + rt/share/html/Ticket/Elements/EditDates | 77 + rt/share/html/Ticket/Elements/EditPeople | 93 + .../Ticket/Elements/EditTransactionCustomFields | 81 + rt/share/html/Ticket/Elements/EditWatchers | 79 + rt/share/html/Ticket/Elements/FindAttachments | 95 + rt/share/html/Ticket/Elements/LoadTextAttachments | 93 + rt/share/html/Ticket/Elements/PreviewScrips | 235 + rt/share/html/Ticket/Elements/Reminders | 178 + rt/share/html/Ticket/Elements/ShowAttachments | 104 + rt/share/html/Ticket/Elements/ShowBasics | 87 + rt/share/html/Ticket/Elements/ShowCustomFields | 51 + rt/share/html/Ticket/Elements/ShowDates | 91 + rt/share/html/Ticket/Elements/ShowDependencies | 65 + rt/share/html/Ticket/Elements/ShowGnuPGStatus | 177 + rt/share/html/Ticket/Elements/ShowGroupMembers | 65 + rt/share/html/Ticket/Elements/ShowHistory | 171 + rt/share/html/Ticket/Elements/ShowMembers | 69 + rt/share/html/Ticket/Elements/ShowMessageHeaders | 91 + rt/share/html/Ticket/Elements/ShowMessageStanza | 110 + rt/share/html/Ticket/Elements/ShowParents | 63 + rt/share/html/Ticket/Elements/ShowPeople | 72 + rt/share/html/Ticket/Elements/ShowPriority | 51 + rt/share/html/Ticket/Elements/ShowQueue | 56 + rt/share/html/Ticket/Elements/ShowRequestor | 105 + rt/share/html/Ticket/Elements/ShowSummary | 117 + rt/share/html/Ticket/Elements/ShowTime | 59 + rt/share/html/Ticket/Elements/ShowTransaction | 216 + .../Ticket/Elements/ShowTransactionAttachments | 252 + rt/share/html/Ticket/Elements/ShowUpdateStatus | 64 + rt/share/html/Ticket/Elements/ShowUserEntry | 56 + rt/share/html/Ticket/Elements/Tabs | 341 + rt/share/html/Ticket/Elements/UpdateCc | 80 + rt/share/html/Ticket/Forward.html | 123 + rt/share/html/Ticket/GnuPG.html | 104 + .../Ticket/Graphs/Elements/EditGraphProperties | 167 + rt/share/html/Ticket/Graphs/Elements/ShowGraph | 71 + rt/share/html/Ticket/Graphs/Elements/ShowLegends | 73 + rt/share/html/Ticket/Graphs/dhandler | 80 + rt/share/html/Ticket/Graphs/index.html | 114 + rt/share/html/Ticket/History.html | 91 + rt/share/html/Ticket/Modify.html | 95 + rt/share/html/Ticket/ModifyAll.html | 255 + rt/share/html/Ticket/ModifyDates.html | 79 + rt/share/html/Ticket/ModifyLinks.html | 85 + rt/share/html/Ticket/ModifyPeople.html | 94 + rt/share/html/Ticket/Reminders.html | 74 + rt/share/html/Ticket/ShowEmailRecord.html | 96 + rt/share/html/Ticket/Update.html | 269 + rt/share/html/Tools/Elements/Tabs | 90 + rt/share/html/Tools/MyDay.html | 114 + rt/share/html/Tools/Offline.html | 168 + rt/share/html/Tools/Reports/CreatedByDates.html | 94 + rt/share/html/Tools/Reports/Elements/Tabs | 89 + rt/share/html/Tools/Reports/ResolvedByDates.html | 95 + rt/share/html/Tools/Reports/ResolvedByOwner.html | 70 + rt/share/html/Tools/Reports/index.html | 76 + rt/share/html/Tools/index.html | 81 + rt/share/html/User/Delegation.html | 107 + rt/share/html/User/Elements/DelegateRights | 110 + rt/share/html/User/Elements/GroupTabs | 84 + rt/share/html/User/Elements/Tabs | 95 + rt/share/html/User/Groups/Members.html | 160 + rt/share/html/User/Groups/Modify.html | 157 + rt/share/html/User/Groups/index.html | 67 + rt/share/html/User/Prefs.html | 314 + rt/share/html/Widgets/BulkEdit | 67 + rt/share/html/Widgets/BulkProcess | 73 + rt/share/html/Widgets/ComboBox | 77 + rt/share/html/Widgets/FinalizeWidgetArguments | 64 + rt/share/html/Widgets/Form/Boolean | 103 + rt/share/html/Widgets/Form/Integer | 99 + rt/share/html/Widgets/Form/Select | 165 + rt/share/html/Widgets/Form/String | 108 + rt/share/html/Widgets/SavedSearch | 189 + rt/share/html/Widgets/SelectionBox | 254 + rt/share/html/Widgets/TitleBox | 54 + rt/share/html/Widgets/TitleBoxEnd | 59 + rt/share/html/Widgets/TitleBoxStart | 97 + rt/share/html/autohandler | 61 + rt/share/html/dhandler | 54 + rt/share/html/index.html | 143 + rt/share/html/l | 52 + rt/t/00-compile.t | 58 + rt/t/00-mason-syntax.t | 44 + rt/t/api/ace.t | 238 + rt/t/api/action-createtickets.t | 240 + rt/t/api/attachment.t | 45 + rt/t/api/attribute-tests.t | 86 + rt/t/api/attribute.t | 42 + rt/t/api/cf.t | 224 + rt/t/api/cf_combo_casacade.t | 46 + rt/t/api/cf_external.t | 56 + rt/t/api/cf_pattern.t | 53 + rt/t/api/cf_single_values.t | 38 + rt/t/api/cf_transaction.t | 60 + rt/t/api/condition-ownerchange.t | 51 + rt/t/api/condition-reject.t | 45 + rt/t/api/currentuser.t | 32 + rt/t/api/customfield.t | 74 + rt/t/api/date.t | 564 + rt/t/api/emailparser.t | 32 + rt/t/api/group.t | 99 + rt/t/api/groups.t | 139 + rt/t/api/i18n.t | 30 + rt/t/api/link.t | 24 + rt/t/api/queue.t | 92 + rt/t/api/record.t | 70 + rt/t/api/reminders.t | 88 + rt/t/api/rights.t | 142 + rt/t/api/rt.t | 18 + rt/t/api/scrip.t | 49 + rt/t/api/scrip_order.t | 56 + rt/t/api/searchbuilder.t | 40 + rt/t/api/system.t | 33 + rt/t/api/template-insert.t | 26 + rt/t/api/template.t | 26 + rt/t/api/ticket.t | 257 + rt/t/api/tickets.t | 104 + rt/t/api/tickets_overlay_sql.t | 73 + rt/t/api/uri-fsck_com_rt.t | 28 + rt/t/api/uri-t.t | 21 + rt/t/api/user.t | 339 + rt/t/api/users.t | 80 + rt/t/approval/basic.t | 218 + rt/t/clicky.t | 119 + rt/t/cron.t | 90 + rt/t/customfields/access_via_queue.t | 160 + rt/t/customfields/sort_order.t | 92 + rt/t/data/configs/apache2.2+fastcgi.conf | 44 + rt/t/data/configs/apache2.2+fastcgi.conf.in | 44 + rt/t/data/configs/apache2.2+mod_perl.conf | 40 + rt/t/data/configs/apache2.2+mod_perl.conf.in | 40 + rt/t/data/emails/8859-15-message-series/dir | 356 + rt/t/data/emails/8859-15-message-series/msg1 | 36 + rt/t/data/emails/8859-15-message-series/msg2 | 36 + rt/t/data/emails/8859-15-message-series/msg3 | 35 + rt/t/data/emails/8859-15-message-series/msg4 | 35 + rt/t/data/emails/8859-15-message-series/msg5 | 35 + rt/t/data/emails/8859-15-message-series/msg6 | 35 + rt/t/data/emails/8859-15-message-series/msg7 | 36 + rt/t/data/emails/crashes-file-based-parser | 193 + rt/t/data/emails/lorem-ipsum | 5 + rt/t/data/emails/multipart-alternative-with-umlaut | 62 + rt/t/data/emails/multipart-report | 66 + rt/t/data/emails/nested-mime-sample | 396 + rt/t/data/emails/nested-rfc-822 | 253 + rt/t/data/emails/new-ticket-from-iso-8859-1 | 31 + rt/t/data/emails/new-ticket-from-iso-8859-1-full | 38 + rt/t/data/emails/notes-uuencoded | 2368 ++++ rt/t/data/emails/rt-send-cc | 5 + rt/t/data/emails/russian-subject-no-content-type | 42 + rt/t/data/emails/subject-with-folding-ws | 10 + rt/t/data/emails/text-html-in-russian | 87 + rt/t/data/emails/text-html-with-umlaut | 35 + rt/t/data/emails/very-long-subject | 12 + rt/t/data/gnupg/emails/1-signed-MIME-plain.txt | 38 + .../gnupg/emails/10-encrypted-inline-plain.txt | 31 + .../emails/11-encrypted-inline-attachment.txt | 80 + .../gnupg/emails/12-encrypted-inline-binary.txt | 86 + .../emails/13-signed-encrypted-MIME-plain.txt | 49 + .../emails/14-signed-encrypted-MIME-attachment.txt | 51 + .../emails/15-signed-encrypted-MIME-binary.txt | 60 + .../emails/16-signed-encrypted-inline-plain.txt | 33 + .../17-signed-encrypted-inline-attachment.txt | 84 + .../emails/18-signed-encrypted-inline-binary.txt | 89 + .../gnupg/emails/19-signed-inline-plain-nested.txt | 34 + .../emails/2-signed-MIME-plain-with-attachment.txt | 48 + .../emails/3-signed-MIME-plain-with-binary.txt | 55 + rt/t/data/gnupg/emails/4-signed-inline-plain.txt | 24 + .../emails/5-signed-inline-with-attachment.txt | 48 + .../gnupg/emails/6-signed-inline-with-binary.txt | 55 + rt/t/data/gnupg/emails/7-encrypted-MIME-plain.txt | 47 + .../emails/8-encrypted-MIME-with-attachment.txt | 49 + .../gnupg/emails/9-encrypted-MIME-with-binary.txt | 57 + rt/t/data/gnupg/emails/README | 28 + rt/t/data/gnupg/keyrings/pubring.gpg | Bin 0 -> 4651 bytes rt/t/data/gnupg/keyrings/secring.gpg | Bin 0 -> 5095 bytes .../keyrings/signed_old_style_with_attachment.eml | 48 + rt/t/data/gnupg/keyrings/trustdb.gpg | Bin 0 -> 1520 bytes .../gnupg/keys/general-at-example.com.2.public.key | 30 + .../gnupg/keys/general-at-example.com.2.secret.key | 33 + .../gnupg/keys/general-at-example.com.public.key | 30 + .../gnupg/keys/general-at-example.com.secret.key | 31 + .../gnupg/keys/recipient-at-example.com.public.key | 30 + .../gnupg/keys/recipient-at-example.com.secret.key | 33 + .../keys/rt-recipient-at-example.com.public.key | 30 + .../keys/rt-recipient-at-example.com.secret.key | 33 + .../gnupg/keys/rt-test-at-example.com.2.public.key | 30 + .../gnupg/keys/rt-test-at-example.com.2.secret.key | 33 + .../gnupg/keys/rt-test-at-example.com.public.key | 30 + .../gnupg/keys/rt-test-at-example.com.secret.key | 33 + rt/t/delegation/cleanup_stalled.t | 458 + rt/t/delegation/revocation.t | 135 + rt/t/i18n/default.t | 19 + rt/t/mail/charsets-outgoing.t | 306 + rt/t/mail/crypt-gnupg.t | 312 + rt/t/mail/extractsubjecttag.t | 98 + rt/t/mail/gateway.t | 802 ++ rt/t/mail/gnupg-bad.t | 58 + rt/t/mail/gnupg-incoming.t | 320 + rt/t/mail/gnupg-realmail.t | 184 + rt/t/mail/gnupg-reverification.t | 92 + rt/t/mail/mime_decoding.t | 59 + rt/t/mail/sendmail.t | 538 + rt/t/mail/verp.t | 8 + rt/t/maildigest/attributes.t | 168 + rt/t/pod.t | 7 + rt/t/rtname.t | 34 + rt/t/savedsearch.t | 185 + rt/t/shredder/00load.t | 29 + rt/t/shredder/00skeleton.t | 25 + rt/t/shredder/01basics.t | 32 + rt/t/shredder/01ticket.t | 86 + rt/t/shredder/02group_member.t | 103 + rt/t/shredder/02queue.t | 125 + rt/t/shredder/02template.t | 76 + rt/t/shredder/02user.t | 62 + rt/t/shredder/03plugin.t | 46 + rt/t/shredder/03plugin_summary.t | 23 + rt/t/shredder/03plugin_tickets.t | 150 + rt/t/shredder/03plugin_users.t | 40 + rt/t/shredder/utils.pl | 435 + rt/t/ticket/action_linear_escalate.t | 100 + rt/t/ticket/add-watchers.t | 167 + rt/t/ticket/badlinks.t | 38 + rt/t/ticket/batch-upload-csv.t | 48 + rt/t/ticket/cfsort-freeform-multiple.t | 137 + rt/t/ticket/cfsort-freeform-single.t | 191 + rt/t/ticket/deferred_owner.t | 120 + rt/t/ticket/link_search.t | 246 + rt/t/ticket/linking.t | 385 + rt/t/ticket/merge.t | 92 + rt/t/ticket/quicksearch.t | 41 + rt/t/ticket/requestor-order.t | 142 + rt/t/ticket/scrips_batch.t | 100 + rt/t/ticket/search.t | 278 + rt/t/ticket/search_by_cf_freeform_multiple.t | 153 + rt/t/ticket/search_by_cf_freeform_single.t | 142 + rt/t/ticket/search_by_links.t | 132 + rt/t/ticket/search_by_txn.t | 35 + rt/t/ticket/search_by_watcher.t | 280 + rt/t/ticket/search_long_cf_values.t | 79 + rt/t/ticket/sort-by-custom-ownership.t | 103 + rt/t/ticket/sort-by-queue.t | 100 + rt/t/ticket/sort-by-user.t | 152 + rt/t/ticket/sort_by_cf.t | 172 + rt/t/validator/group_members.t | 178 + rt/t/web/attachments.t | 47 + rt/t/web/basic.t | 146 + rt/t/web/cf_access.t | 191 + rt/t/web/cf_onqueue.t | 66 + rt/t/web/cf_select_one.t | 159 + rt/t/web/command_line.t | 544 + rt/t/web/command_line_with_unknown_field.t | 34 + rt/t/web/compilation_errors.t | 68 + rt/t/web/config_tab_right.t | 41 + rt/t/web/crypt-gnupg.t | 446 + rt/t/web/custom_frontpage.t | 61 + rt/t/web/custom_search.t | 84 + rt/t/web/dashboard_with_deleted_saved_search.t | 89 + rt/t/web/dashboards-groups.t | 102 + rt/t/web/dashboards-permissions.t | 38 + rt/t/web/dashboards.t | 250 + rt/t/web/gnupg-outgoing.t | 363 + rt/t/web/gnupg-select-keys-on-create.t | 325 + rt/t/web/gnupg-select-keys-on-update.t | 344 + rt/t/web/offline_messages_utf8.t | 67 + rt/t/web/offline_utf8.t | 54 + rt/t/web/query_builder.t | 249 + rt/t/web/quicksearch.t | 51 + rt/t/web/rest-non-ascii-subject.t | 55 + rt/t/web/rest.t | 71 + rt/t/web/rights.t | 85 + rt/t/web/rights1.t | 134 + rt/t/web/saved_search_chart.t | 86 + rt/t/web/saved_search_permissions.t | 34 + rt/t/web/search_bulk_update_links.t | 147 + rt/t/web/ticket-create-utf8.t | 83 + rt/t/web/ticket_owner.t | 356 + rt/t/web/ticket_seen.t | 80 + rt/t/web/ticket_update_without_content.t | 52 + rt/t/web/unlimited_search.t | 41 + 1425 files changed, 347299 insertions(+), 41516 deletions(-) create mode 100644 rt/UPGRADING.mysql create mode 100644 rt/docs/creating_external_custom_fields.pod create mode 100644 rt/docs/design_docs/gnupg_details_on_output_formats create mode 100644 rt/docs/extending_clickable_links.pod create mode 100644 rt/docs/gnupg_integration.pod create mode 100644 rt/docs/porting.windows create mode 100644 rt/docs/queue_subject_tag.pod create mode 100644 rt/docs/templates.pod create mode 100644 rt/docs/using_forms_widgets.pod create mode 100755 rt/etc/schema.mysql-4.0 create mode 100755 rt/etc/schema.mysql-4.1 create mode 100644 rt/etc/upgrade/3.7.1/content create mode 100644 rt/etc/upgrade/3.7.10/content create mode 100644 rt/etc/upgrade/3.7.15/content create mode 100644 rt/etc/upgrade/3.7.19/content create mode 100644 rt/etc/upgrade/3.7.3/schema.Oracle create mode 100644 rt/etc/upgrade/3.7.3/schema.Pg create mode 100644 rt/etc/upgrade/3.7.3/schema.mysql create mode 100644 rt/etc/upgrade/3.7.81/schema.Oracle create mode 100644 rt/etc/upgrade/3.7.81/schema.mysql create mode 100644 rt/etc/upgrade/3.7.82/content create mode 100644 rt/etc/upgrade/3.7.85/content create mode 100644 rt/etc/upgrade/3.7.86/content create mode 100644 rt/etc/upgrade/3.7.87/content create mode 100755 rt/etc/upgrade/3.8-branded-queues-extension create mode 100755 rt/etc/upgrade/3.8-branded-queues-extension.in create mode 100755 rt/etc/upgrade/3.8-ical-extension create mode 100644 rt/etc/upgrade/3.8-ical-extension.in create mode 100644 rt/etc/upgrade/3.8.0/content create mode 100644 rt/etc/upgrade/3.8.1/content create mode 100644 rt/etc/upgrade/3.8.2/content create mode 100644 rt/etc/upgrade/3.8.3/content create mode 100644 rt/etc/upgrade/3.8.3/schema.Pg create mode 100644 rt/etc/upgrade/3.8.4/content create mode 100644 rt/etc/upgrade/3.8.6/content create mode 100644 rt/etc/upgrade/shrink_cgm_table.pl create mode 100755 rt/etc/upgrade/split-out-cf-categories create mode 100644 rt/etc/upgrade/split-out-cf-categories.in create mode 100755 rt/etc/upgrade/upgrade-mysql-schema.pl create mode 100755 rt/lib/RT/Action.pm create mode 100644 rt/lib/RT/Action/ExtractSubjectTag.pm create mode 100755 rt/lib/RT/Action/LinearEscalate.pm create mode 100644 rt/lib/RT/Action/NotifyGroup.pm create mode 100644 rt/lib/RT/Action/NotifyGroupAsComment.pm create mode 100644 rt/lib/RT/Approval.pm create mode 100644 rt/lib/RT/Approval/Rule.pm create mode 100644 rt/lib/RT/Approval/Rule/Created.pm create mode 100644 rt/lib/RT/Approval/Rule/NewPending.pm create mode 100644 rt/lib/RT/Approval/Rule/Passed.pm create mode 100644 rt/lib/RT/Approval/Rule/Rejected.pm create mode 100755 rt/lib/RT/Condition.pm create mode 100644 rt/lib/RT/Condition/CloseTicket.pm create mode 100644 rt/lib/RT/Condition/ReopenTicket.pm create mode 100644 rt/lib/RT/Config.pm create mode 100644 rt/lib/RT/Crypt/GnuPG.pm create mode 100644 rt/lib/RT/CustomFieldValues/External.pm create mode 100644 rt/lib/RT/CustomFieldValues/Groups.pm create mode 100644 rt/lib/RT/Dashboard.pm create mode 100644 rt/lib/RT/Graph/Tickets.pm create mode 100644 rt/lib/RT/I18N/ar.po create mode 100644 rt/lib/RT/I18N/bg.po create mode 100644 rt/lib/RT/I18N/hr.po create mode 100755 rt/lib/RT/I18N/nb.po create mode 100644 rt/lib/RT/I18N/pt.po create mode 100755 rt/lib/RT/I18N/pt_BR.po create mode 100644 rt/lib/RT/I18N/rt.pot create mode 100755 rt/lib/RT/I18N/ru.pm create mode 100644 rt/lib/RT/I18N/zh_CN.po create mode 100644 rt/lib/RT/I18N/zh_TW.po create mode 100644 rt/lib/RT/Installer.pm create mode 100644 rt/lib/RT/Interface/Web/Request.pm create mode 100644 rt/lib/RT/Interface/Web/Session.pm create mode 100644 rt/lib/RT/Interface/Web/Standalone/PreFork.pm create mode 100644 rt/lib/RT/Plugin.pm create mode 100644 rt/lib/RT/Rule.pm create mode 100644 rt/lib/RT/Ruleset.pm create mode 100644 rt/lib/RT/SQL.pm create mode 100755 rt/lib/RT/Search.pm create mode 100644 rt/lib/RT/SharedSetting.pm create mode 100644 rt/lib/RT/Shredder.pm create mode 100644 rt/lib/RT/Shredder/ACE.pm create mode 100644 rt/lib/RT/Shredder/Attachment.pm create mode 100644 rt/lib/RT/Shredder/CachedGroupMember.pm create mode 100644 rt/lib/RT/Shredder/Constants.pm create mode 100644 rt/lib/RT/Shredder/CustomField.pm create mode 100644 rt/lib/RT/Shredder/CustomFieldValue.pm create mode 100644 rt/lib/RT/Shredder/Dependencies.pm create mode 100644 rt/lib/RT/Shredder/Dependency.pm create mode 100644 rt/lib/RT/Shredder/Exceptions.pm create mode 100644 rt/lib/RT/Shredder/Group.pm create mode 100644 rt/lib/RT/Shredder/GroupMember.pm create mode 100644 rt/lib/RT/Shredder/Link.pm create mode 100644 rt/lib/RT/Shredder/ObjectCustomFieldValue.pm create mode 100644 rt/lib/RT/Shredder/POD.pm create mode 100644 rt/lib/RT/Shredder/Plugin.pm create mode 100644 rt/lib/RT/Shredder/Plugin/Attachments.pm create mode 100644 rt/lib/RT/Shredder/Plugin/Base.pm create mode 100644 rt/lib/RT/Shredder/Plugin/Base/Dump.pm create mode 100644 rt/lib/RT/Shredder/Plugin/Base/Search.pm create mode 100644 rt/lib/RT/Shredder/Plugin/Objects.pm create mode 100644 rt/lib/RT/Shredder/Plugin/SQLDump.pm create mode 100644 rt/lib/RT/Shredder/Plugin/Summary.pm create mode 100644 rt/lib/RT/Shredder/Plugin/Tickets.pm create mode 100644 rt/lib/RT/Shredder/Plugin/Users.pm create mode 100644 rt/lib/RT/Shredder/Principal.pm create mode 100644 rt/lib/RT/Shredder/Queue.pm create mode 100644 rt/lib/RT/Shredder/Record.pm create mode 100644 rt/lib/RT/Shredder/Scrip.pm create mode 100644 rt/lib/RT/Shredder/ScripAction.pm create mode 100644 rt/lib/RT/Shredder/ScripCondition.pm create mode 100644 rt/lib/RT/Shredder/Template.pm create mode 100644 rt/lib/RT/Shredder/Ticket.pm create mode 100644 rt/lib/RT/Shredder/Transaction.pm create mode 100644 rt/lib/RT/Shredder/User.pm create mode 100644 rt/lib/RT/Test.pm create mode 100644 rt/lib/RT/Test/Email.pm create mode 100644 rt/lib/RT/Test/Web.pm create mode 100644 rt/lib/RT/Util.pm create mode 100644 rt/sbin/merge-rosetta.pl create mode 100755 rt/sbin/rt-attributes-viewer create mode 100644 rt/sbin/rt-attributes-viewer.in create mode 100755 rt/sbin/rt-clean-sessions create mode 100644 rt/sbin/rt-clean-sessions.in create mode 100755 rt/sbin/rt-email-dashboards create mode 100644 rt/sbin/rt-email-dashboards.in create mode 100755 rt/sbin/rt-email-digest create mode 100644 rt/sbin/rt-email-digest.in create mode 100755 rt/sbin/rt-email-group-admin create mode 100755 rt/sbin/rt-email-group-admin.in create mode 100755 rt/sbin/rt-server create mode 100644 rt/sbin/rt-server.in create mode 100755 rt/sbin/rt-shredder create mode 100755 rt/sbin/rt-shredder.in create mode 100755 rt/sbin/rt-validator create mode 100644 rt/sbin/rt-validator.in create mode 100644 rt/sbin/tweak-template-locstring create mode 100644 rt/share/html/Admin/CustomFields/GroupRights.html create mode 100644 rt/share/html/Admin/CustomFields/Modify.html create mode 100644 rt/share/html/Admin/CustomFields/Objects.html create mode 100644 rt/share/html/Admin/CustomFields/UserRights.html create mode 100644 rt/share/html/Admin/CustomFields/index.html create mode 100755 rt/share/html/Admin/Elements/AddCustomFieldValue create mode 100644 rt/share/html/Admin/Elements/ConfigureMyRT create mode 100755 rt/share/html/Admin/Elements/CreateUserCalled create mode 100644 rt/share/html/Admin/Elements/CustomFieldTabs create mode 100755 rt/share/html/Admin/Elements/EditCustomField create mode 100755 rt/share/html/Admin/Elements/EditCustomFieldValues create mode 100644 rt/share/html/Admin/Elements/EditCustomFieldValuesSource create mode 100755 rt/share/html/Admin/Elements/EditCustomFields create mode 100755 rt/share/html/Admin/Elements/EditQueueWatchers create mode 100755 rt/share/html/Admin/Elements/EditScrip create mode 100755 rt/share/html/Admin/Elements/EditScrips create mode 100755 rt/share/html/Admin/Elements/EditTemplates create mode 100755 rt/share/html/Admin/Elements/EditUserComments create mode 100755 rt/share/html/Admin/Elements/GlobalCustomFieldTabs create mode 100755 rt/share/html/Admin/Elements/GroupTabs create mode 100755 rt/share/html/Admin/Elements/Header create mode 100755 rt/share/html/Admin/Elements/ListGlobalCustomFields create mode 100755 rt/share/html/Admin/Elements/ListGlobalScrips create mode 100755 rt/share/html/Admin/Elements/ModifyTemplate create mode 100644 rt/share/html/Admin/Elements/ObjectCustomFields create mode 100644 rt/share/html/Admin/Elements/PickCustomFields create mode 100644 rt/share/html/Admin/Elements/PickObjects create mode 100755 rt/share/html/Admin/Elements/QueueRightsForUser create mode 100755 rt/share/html/Admin/Elements/QueueTabs create mode 100644 rt/share/html/Admin/Elements/SelectCustomField create mode 100644 rt/share/html/Admin/Elements/SelectCustomFieldLookupType create mode 100755 rt/share/html/Admin/Elements/SelectCustomFieldType create mode 100755 rt/share/html/Admin/Elements/SelectGroups create mode 100755 rt/share/html/Admin/Elements/SelectModifyGroup create mode 100755 rt/share/html/Admin/Elements/SelectModifyQueue create mode 100755 rt/share/html/Admin/Elements/SelectModifyUser create mode 100755 rt/share/html/Admin/Elements/SelectNewGroupMembers create mode 100755 rt/share/html/Admin/Elements/SelectRights create mode 100755 rt/share/html/Admin/Elements/SelectScrip create mode 100755 rt/share/html/Admin/Elements/SelectScripAction create mode 100755 rt/share/html/Admin/Elements/SelectScripCondition create mode 100755 rt/share/html/Admin/Elements/SelectSingleOrMultiple create mode 100644 rt/share/html/Admin/Elements/SelectStage create mode 100755 rt/share/html/Admin/Elements/SelectTemplate create mode 100755 rt/share/html/Admin/Elements/SelectUsers create mode 100644 rt/share/html/Admin/Elements/ShowKeyInfo create mode 100755 rt/share/html/Admin/Elements/SystemTabs create mode 100755 rt/share/html/Admin/Elements/Tabs create mode 100755 rt/share/html/Admin/Elements/ToolTabs create mode 100755 rt/share/html/Admin/Elements/UserTabs create mode 100644 rt/share/html/Admin/Global/CustomFields/Groups.html create mode 100755 rt/share/html/Admin/Global/CustomFields/Queue-Tickets.html create mode 100755 rt/share/html/Admin/Global/CustomFields/Queue-Transactions.html create mode 100644 rt/share/html/Admin/Global/CustomFields/Queues.html create mode 100644 rt/share/html/Admin/Global/CustomFields/Users.html create mode 100644 rt/share/html/Admin/Global/CustomFields/index.html create mode 100755 rt/share/html/Admin/Global/GroupRights.html create mode 100644 rt/share/html/Admin/Global/MyRT.html create mode 100755 rt/share/html/Admin/Global/Scrip.html create mode 100755 rt/share/html/Admin/Global/Scrips.html create mode 100755 rt/share/html/Admin/Global/Template.html create mode 100755 rt/share/html/Admin/Global/Templates.html create mode 100755 rt/share/html/Admin/Global/UserRights.html create mode 100755 rt/share/html/Admin/Global/index.html create mode 100644 rt/share/html/Admin/Groups/CustomFields.html create mode 100755 rt/share/html/Admin/Groups/GroupRights.html create mode 100644 rt/share/html/Admin/Groups/History.html create mode 100755 rt/share/html/Admin/Groups/Members.html create mode 100755 rt/share/html/Admin/Groups/Modify.html create mode 100755 rt/share/html/Admin/Groups/UserRights.html create mode 100755 rt/share/html/Admin/Groups/index.html create mode 100755 rt/share/html/Admin/Queues/CustomField.html create mode 100755 rt/share/html/Admin/Queues/CustomFields.html create mode 100755 rt/share/html/Admin/Queues/GroupRights.html create mode 100644 rt/share/html/Admin/Queues/History.html create mode 100755 rt/share/html/Admin/Queues/Modify.html create mode 100755 rt/share/html/Admin/Queues/People.html create mode 100755 rt/share/html/Admin/Queues/Scrip.html create mode 100755 rt/share/html/Admin/Queues/Scrips.html create mode 100755 rt/share/html/Admin/Queues/Template.html create mode 100755 rt/share/html/Admin/Queues/Templates.html create mode 100755 rt/share/html/Admin/Queues/UserRights.html create mode 100755 rt/share/html/Admin/Queues/index.html create mode 100644 rt/share/html/Admin/Tools/Configuration.html create mode 100644 rt/share/html/Admin/Tools/Shredder/Dumps/dhandler create mode 100644 rt/share/html/Admin/Tools/Shredder/Elements/DumpFileLink create mode 100644 rt/share/html/Admin/Tools/Shredder/Elements/Error/NoRights create mode 100644 rt/share/html/Admin/Tools/Shredder/Elements/Error/NoStorage create mode 100644 rt/share/html/Admin/Tools/Shredder/Elements/Object/RT--Attachment create mode 100644 rt/share/html/Admin/Tools/Shredder/Elements/Object/RT--Ticket create mode 100644 rt/share/html/Admin/Tools/Shredder/Elements/Object/RT--User create mode 100644 rt/share/html/Admin/Tools/Shredder/Elements/ObjectCheckBox create mode 100644 rt/share/html/Admin/Tools/Shredder/Elements/PluginArguments create mode 100644 rt/share/html/Admin/Tools/Shredder/Elements/PluginHelp create mode 100644 rt/share/html/Admin/Tools/Shredder/Elements/SelectObjects create mode 100644 rt/share/html/Admin/Tools/Shredder/Elements/SelectPlugin create mode 100644 rt/share/html/Admin/Tools/Shredder/autohandler create mode 100644 rt/share/html/Admin/Tools/Shredder/index.html create mode 100644 rt/share/html/Admin/Tools/index.html create mode 100644 rt/share/html/Admin/Users/CustomFields.html create mode 100644 rt/share/html/Admin/Users/GnuPG.html create mode 100644 rt/share/html/Admin/Users/History.html create mode 100644 rt/share/html/Admin/Users/Memberships.html create mode 100755 rt/share/html/Admin/Users/Modify.html create mode 100644 rt/share/html/Admin/Users/MyRT.html create mode 100755 rt/share/html/Admin/Users/index.html create mode 100644 rt/share/html/Admin/autohandler create mode 100755 rt/share/html/Admin/index.html create mode 100755 rt/share/html/Approvals/Display.html create mode 100755 rt/share/html/Approvals/Elements/Approve create mode 100755 rt/share/html/Approvals/Elements/PendingMyApproval create mode 100755 rt/share/html/Approvals/Elements/ShowDependency create mode 100755 rt/share/html/Approvals/Elements/Tabs create mode 100644 rt/share/html/Approvals/autohandler create mode 100755 rt/share/html/Approvals/index.html create mode 100644 rt/share/html/Dashboards/Elements/DashboardsForObject create mode 100644 rt/share/html/Dashboards/Elements/DashboardsForObjects create mode 100644 rt/share/html/Dashboards/Elements/Deleted create mode 100644 rt/share/html/Dashboards/Elements/HiddenSearches create mode 100644 rt/share/html/Dashboards/Elements/ListOfDashboards create mode 100644 rt/share/html/Dashboards/Elements/SelectPrivacy create mode 100644 rt/share/html/Dashboards/Elements/ShowDashboards create mode 100644 rt/share/html/Dashboards/Elements/ShowPortlet/component create mode 100644 rt/share/html/Dashboards/Elements/ShowPortlet/dashboard create mode 100644 rt/share/html/Dashboards/Elements/ShowPortlet/search create mode 100644 rt/share/html/Dashboards/Elements/ShowSubscription create mode 100755 rt/share/html/Dashboards/Elements/Tabs create mode 100755 rt/share/html/Dashboards/Modify.html create mode 100644 rt/share/html/Dashboards/Queries.html create mode 100644 rt/share/html/Dashboards/Render.html create mode 100644 rt/share/html/Dashboards/Subscription.html create mode 100644 rt/share/html/Dashboards/dhandler create mode 100644 rt/share/html/Dashboards/index.html create mode 100644 rt/share/html/Download/CustomFieldValue/dhandler create mode 100644 rt/share/html/Download/Tabular/dhandler create mode 100755 rt/share/html/Elements/BevelBoxRaisedEnd create mode 100755 rt/share/html/Elements/BevelBoxRaisedStart create mode 100755 rt/share/html/Elements/Callback create mode 100755 rt/share/html/Elements/Checkbox create mode 100644 rt/share/html/Elements/CollectionAsTable/Header create mode 100644 rt/share/html/Elements/CollectionAsTable/ParseFormat create mode 100644 rt/share/html/Elements/CollectionAsTable/Row create mode 100644 rt/share/html/Elements/CollectionList create mode 100644 rt/share/html/Elements/CollectionListPaging create mode 100644 rt/share/html/Elements/ColumnMap create mode 100755 rt/share/html/Elements/CreateTicket create mode 100644 rt/share/html/Elements/DashboardTabs create mode 100644 rt/share/html/Elements/Dashboards create mode 100644 rt/share/html/Elements/EditCustomField create mode 100644 rt/share/html/Elements/EditCustomFieldAutocomplete create mode 100644 rt/share/html/Elements/EditCustomFieldBinary create mode 100644 rt/share/html/Elements/EditCustomFieldCombobox create mode 100644 rt/share/html/Elements/EditCustomFieldFreeform create mode 100644 rt/share/html/Elements/EditCustomFieldImage create mode 100644 rt/share/html/Elements/EditCustomFieldSelect create mode 100644 rt/share/html/Elements/EditCustomFieldText create mode 100644 rt/share/html/Elements/EditCustomFieldWikitext create mode 100755 rt/share/html/Elements/EditLinks create mode 100644 rt/share/html/Elements/EditTimeValue create mode 100644 rt/share/html/Elements/EmailInput create mode 100755 rt/share/html/Elements/Error create mode 100755 rt/share/html/Elements/Footer create mode 100644 rt/share/html/Elements/GnuPG/KeyIssues create mode 100644 rt/share/html/Elements/GnuPG/SelectKeyForEncryption create mode 100644 rt/share/html/Elements/GnuPG/SelectKeyForSigning create mode 100644 rt/share/html/Elements/GnuPG/SignEncryptWidget create mode 100755 rt/share/html/Elements/GotoTicket create mode 100755 rt/share/html/Elements/Header create mode 100644 rt/share/html/Elements/HeaderJavascript create mode 100755 rt/share/html/Elements/ListActions create mode 100644 rt/share/html/Elements/ListMenu create mode 100755 rt/share/html/Elements/Login create mode 100644 rt/share/html/Elements/Logo create mode 100644 rt/share/html/Elements/Logout create mode 100644 rt/share/html/Elements/MakeClicky create mode 100755 rt/share/html/Elements/Menu create mode 100755 rt/share/html/Elements/MessageBox create mode 100644 rt/share/html/Elements/MyAdminQueues create mode 100644 rt/share/html/Elements/MyRT create mode 100755 rt/share/html/Elements/MyReminders create mode 100755 rt/share/html/Elements/MyRequests create mode 100644 rt/share/html/Elements/MySupportQueues create mode 100755 rt/share/html/Elements/MyTickets create mode 100755 rt/share/html/Elements/PageLayout create mode 100644 rt/share/html/Elements/PersonalQuickbar create mode 100644 rt/share/html/Elements/QueryString create mode 100644 rt/share/html/Elements/QueueSummary create mode 100644 rt/share/html/Elements/QuickCreate create mode 100755 rt/share/html/Elements/Quicksearch create mode 100644 rt/share/html/Elements/RT__Group/ColumnMap create mode 100644 rt/share/html/Elements/RT__Queue/ColumnMap create mode 100644 rt/share/html/Elements/RT__Scrip/ColumnMap create mode 100644 rt/share/html/Elements/RT__Template/ColumnMap create mode 100644 rt/share/html/Elements/RT__Ticket/ColumnMap create mode 100644 rt/share/html/Elements/RT__User/ColumnMap create mode 100755 rt/share/html/Elements/Refresh create mode 100644 rt/share/html/Elements/RefreshHomepage create mode 100644 rt/share/html/Elements/ScrubHTML create mode 100755 rt/share/html/Elements/Section create mode 100755 rt/share/html/Elements/SelectAttachmentField create mode 100755 rt/share/html/Elements/SelectBoolean create mode 100755 rt/share/html/Elements/SelectCustomFieldOperator create mode 100755 rt/share/html/Elements/SelectCustomFieldValue create mode 100755 rt/share/html/Elements/SelectDate create mode 100755 rt/share/html/Elements/SelectDateRelation create mode 100755 rt/share/html/Elements/SelectDateType create mode 100755 rt/share/html/Elements/SelectEqualityOperator create mode 100755 rt/share/html/Elements/SelectGroups create mode 100755 rt/share/html/Elements/SelectLang create mode 100755 rt/share/html/Elements/SelectLinkType create mode 100755 rt/share/html/Elements/SelectMatch create mode 100755 rt/share/html/Elements/SelectNewTicketQueue create mode 100755 rt/share/html/Elements/SelectOwner create mode 100644 rt/share/html/Elements/SelectPriority create mode 100755 rt/share/html/Elements/SelectQueue create mode 100755 rt/share/html/Elements/SelectResultsPerPage create mode 100755 rt/share/html/Elements/SelectSortOrder create mode 100755 rt/share/html/Elements/SelectStatus create mode 100755 rt/share/html/Elements/SelectTicketSortBy create mode 100755 rt/share/html/Elements/SelectTicketTypes create mode 100755 rt/share/html/Elements/SelectTimeUnits create mode 100644 rt/share/html/Elements/SelectTimezone create mode 100755 rt/share/html/Elements/SelectUsers create mode 100755 rt/share/html/Elements/SelectWatcherType create mode 100755 rt/share/html/Elements/SetupSessionCookie create mode 100644 rt/share/html/Elements/ShowCustomFieldBinary create mode 100644 rt/share/html/Elements/ShowCustomFieldImage create mode 100644 rt/share/html/Elements/ShowCustomFieldText create mode 100644 rt/share/html/Elements/ShowCustomFieldWikitext create mode 100644 rt/share/html/Elements/ShowCustomFields create mode 100644 rt/share/html/Elements/ShowLink create mode 100755 rt/share/html/Elements/ShowLinks create mode 100644 rt/share/html/Elements/ShowMemberships create mode 100644 rt/share/html/Elements/ShowSearch create mode 100644 rt/share/html/Elements/ShowUser create mode 100644 rt/share/html/Elements/ShowUserConcise create mode 100644 rt/share/html/Elements/ShowUserEmailFrequency create mode 100644 rt/share/html/Elements/ShowUserVerbose create mode 100755 rt/share/html/Elements/SimpleSearch create mode 100755 rt/share/html/Elements/Submit create mode 100755 rt/share/html/Elements/Tabs create mode 100644 rt/share/html/Elements/TicketList create mode 100644 rt/share/html/Elements/TitleBox create mode 100644 rt/share/html/Elements/TitleBoxEnd create mode 100644 rt/share/html/Elements/TitleBoxStart create mode 100644 rt/share/html/Elements/ValidateCustomFields create mode 100644 rt/share/html/Helpers/Autocomplete/CustomFieldValues create mode 100644 rt/share/html/Helpers/CalPopup.html create mode 100644 rt/share/html/Helpers/Toggle/TicketBookmark create mode 100644 rt/share/html/Install/Basics.html create mode 100644 rt/share/html/Install/DatabaseDetails.html create mode 100644 rt/share/html/Install/DatabaseType.html create mode 100644 rt/share/html/Install/Elements/Errors create mode 100644 rt/share/html/Install/Elements/Wrapper create mode 100644 rt/share/html/Install/Finish.html create mode 100644 rt/share/html/Install/Global.html create mode 100644 rt/share/html/Install/Initialize.html create mode 100644 rt/share/html/Install/Sendmail.html create mode 100644 rt/share/html/Install/autohandler create mode 100644 rt/share/html/Install/index.html create mode 100755 rt/share/html/NoAuth/Logout.html create mode 100755 rt/share/html/NoAuth/Reminder.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckcontextmenu.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckdataprocessor.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckdocumentfragment_gecko.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckdocumentfragment_ie.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckdomrange.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckdomrange_gecko.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckdomrange_ie.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckdomrangeiterator.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckeditingarea.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckelementpath.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckenterkey.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckevents.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckhtmliterator.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckicon.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckiecleanup.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckimagepreloader.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckkeystrokehandler.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckmenublock.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckmenublockpanel.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckmenuitem.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckpanel.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckplugin.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckspecialcombo.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckstyle.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fcktoolbar.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fcktoolbarbreak_gecko.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fcktoolbarbreak_ie.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fcktoolbarbutton.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fcktoolbarbuttonui.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fcktoolbarfontformatcombo.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fcktoolbarfontscombo.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fcktoolbarfontsizecombo.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fcktoolbarpanelbutton.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fcktoolbarspecialcombo.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fcktoolbarstylecombo.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckw3crange.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckxml.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckxml_gecko.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/classes/fckxml_ie.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fck_othercommands.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fckblockquotecommand.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fckcorestylecommand.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fckfitwindow.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fckindentcommands.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fckjustifycommands.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fcklistcommands.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fcknamedcommand.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fckpasteplaintextcommand.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fckpastewordcommand.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fckremoveformatcommand.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fckshowblocks.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fckspellcheckcommand_gecko.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fckspellcheckcommand_ie.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fckstylecommand.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fcktablecommand.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/commandclasses/fcktextcolorcommand.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/fckconstants.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/fckeditorapi.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/fckjscoreextensions.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/fckscriptloader.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fck.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fck_contextmenu.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fck_gecko.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fck_ie.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckbrowserinfo.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckcodeformatter.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckcommands.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckconfig.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckdebug.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckdebug_empty.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckdialog.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckdocumentprocessor.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckdomtools.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fcklanguagemanager.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fcklisthandler.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fcklistslib.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckplugins.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckregexlib.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckselection.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckselection_gecko.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckselection_ie.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckstyles.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fcktablehandler.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fcktablehandler_gecko.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fcktablehandler_ie.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fcktoolbaritems.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fcktoolbarset.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fcktools.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fcktools_gecko.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fcktools_ie.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckundo.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckurlparams.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckxhtml.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckxhtml_gecko.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckxhtml_ie.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/_source/internals/fckxhtmlentities.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/css/behaviors/disablehandles.htc create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/css/behaviors/showtableborders.htc create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/css/fck_editorarea.css create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/css/fck_internal.css create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/css/fck_showtableborders_gecko.css create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/css/images/block_address.png create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/css/images/block_blockquote.png create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/css/images/block_div.png create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/css/images/block_h1.png create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/css/images/block_h2.png create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/css/images/block_h3.png create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/css/images/block_h4.png create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/css/images/block_h5.png create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/css/images/block_h6.png create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/css/images/block_p.png create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/css/images/block_pre.png create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/css/images/fck_anchor.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/css/images/fck_flashlogo.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/css/images/fck_hiddenfield.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/css/images/fck_pagebreak.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/css/images/fck_plugin.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/common/fck_dialog_common.css create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/common/fck_dialog_common.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/common/images/locked.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/common/images/reset.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/common/images/unlocked.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_about.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_about/logo_fckeditor.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_about/logo_fredck.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_about/sponsors/spellchecker_net.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_anchor.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_button.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_checkbox.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_colorselector.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_div.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_docprops.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_docprops/fck_document_preview.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_flash.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_flash/fck_flash.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_flash/fck_flash_preview.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_form.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_hiddenfield.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_image.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_image/fck_image.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_image/fck_image_preview.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_link.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_link/fck_link.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_listprop.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_paste.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_radiobutton.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_replace.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_select.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_select/fck_select.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_smiley.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_source.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_specialchar.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_spellerpages.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_spellerpages/spellerpages/blank.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_spellerpages/spellerpages/controlWindow.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_spellerpages/spellerpages/controls.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_spellerpages/spellerpages/server-scripts/spellchecker.cfm create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_spellerpages/spellerpages/server-scripts/spellchecker.php create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_spellerpages/spellerpages/server-scripts/spellchecker.pl create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_spellerpages/spellerpages/spellChecker.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_spellerpages/spellerpages/spellchecker.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_spellerpages/spellerpages/spellerStyle.css create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_spellerpages/spellerpages/wordWindow.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_table.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_tablecell.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_template.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_template/images/template1.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_template/images/template2.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_template/images/template3.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_textarea.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dialog/fck_textfield.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dtd/fck_dtd_test.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dtd/fck_xhtml10strict.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/dtd/fck_xhtml10transitional.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/fckdebug.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/fckdialog.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/fckeditor.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/fckeditor.original.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/images/anchor.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/images/arrow_ltr.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/images/arrow_rtl.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/angel_smile.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/angry_smile.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/broken_heart.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/cake.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/confused_smile.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/cry_smile.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/devil_smile.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/embaressed_smile.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/envelope.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/heart.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/kiss.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/lightbulb.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/omg_smile.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/regular_smile.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/sad_smile.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/shades_smile.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/teeth_smile.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/thumbs_down.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/thumbs_up.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/tounge_smile.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/whatchutalkingabout_smile.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/images/smiley/msn/wink_smile.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/images/spacer.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/js/fckadobeair.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/js/fckeditorcode_gecko.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/js/fckeditorcode_ie.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/_translationstatus.txt create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/af.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/ar.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/bg.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/bn.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/bs.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/ca.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/cs.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/da.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/de.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/el.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/en-au.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/en-ca.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/en-uk.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/en.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/eo.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/es.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/et.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/eu.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/fa.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/fi.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/fo.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/fr-ca.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/fr.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/gl.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/gu.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/he.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/hi.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/hr.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/hu.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/is.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/it.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/ja.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/km.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/ko.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/lt.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/lv.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/mn.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/ms.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/nb.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/nl.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/no.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/pl.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/pt-br.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/pt.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/ro.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/ru.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/sk.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/sl.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/sr-latn.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/sr.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/sv.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/th.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/tr.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/uk.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/vi.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/zh-cn.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/lang/zh.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/plugins/autogrow/fckplugin.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/plugins/bbcode/_sample/sample.config.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/plugins/bbcode/_sample/sample.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/plugins/bbcode/fckplugin.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/plugins/dragresizetable/fckplugin.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/plugins/placeholder/fck_placeholder.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/plugins/placeholder/fckplugin.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/plugins/placeholder/lang/de.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/plugins/placeholder/lang/en.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/plugins/placeholder/lang/es.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/plugins/placeholder/lang/fr.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/plugins/placeholder/lang/it.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/plugins/placeholder/lang/pl.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/plugins/placeholder/placeholder.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/plugins/simplecommands/fckplugin.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/plugins/tablecommands/fckplugin.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/_fckviewstrips.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/default/fck_dialog.css create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/default/fck_dialog_ie6.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/default/fck_editor.css create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/default/fck_strip.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/default/images/dialog.sides.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/default/images/dialog.sides.png create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/default/images/dialog.sides.rtl.png create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/default/images/sprites.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/default/images/sprites.png create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/default/images/toolbar.arrowright.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/default/images/toolbar.buttonarrow.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/default/images/toolbar.collapse.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/default/images/toolbar.end.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/default/images/toolbar.expand.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/default/images/toolbar.separator.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/default/images/toolbar.start.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/fck_dialog.css create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/fck_dialog_ie6.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/fck_editor.css create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/fck_strip.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/images/dialog.sides.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/images/dialog.sides.png create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/images/dialog.sides.rtl.png create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/images/sprites.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/images/sprites.png create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/images/toolbar.arrowright.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/images/toolbar.bg.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/images/toolbar.buttonarrow.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/images/toolbar.collapse.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/images/toolbar.end.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/images/toolbar.expand.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/images/toolbar.separator.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/office2003/images/toolbar.start.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/fck_dialog.css create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/fck_dialog_ie6.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/fck_editor.css create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/fck_strip.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/images/dialog.sides.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/images/dialog.sides.png create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/images/dialog.sides.rtl.png create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/images/sprites.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/images/sprites.png create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/images/toolbar.arrowright.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/images/toolbar.buttonarrow.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/images/toolbar.buttonbg.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/images/toolbar.collapse.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/images/toolbar.end.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/images/toolbar.expand.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/images/toolbar.separator.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/skins/silver/images/toolbar.start.gif create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/wsc/ciframe.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/wsc/tmpFrameset.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/editor/wsc/w.html create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/fckconfig.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/fckeditor.js create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/fckpackager.xml create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/fckstyles.xml create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/fcktemplates.xml create mode 100644 rt/share/html/NoAuth/RichText/FCKeditor/license.txt create mode 100644 rt/share/html/NoAuth/RichText/dhandler create mode 100644 rt/share/html/NoAuth/css/3.4-compat/body.css create mode 100644 rt/share/html/NoAuth/css/3.4-compat/footer.css create mode 100644 rt/share/html/NoAuth/css/3.4-compat/forms.css create mode 100644 rt/share/html/NoAuth/css/3.4-compat/header.css create mode 100644 rt/share/html/NoAuth/css/3.4-compat/login.css create mode 100644 rt/share/html/NoAuth/css/3.4-compat/main.css create mode 100644 rt/share/html/NoAuth/css/3.4-compat/misc.css create mode 100644 rt/share/html/NoAuth/css/3.4-compat/nav.css create mode 100644 rt/share/html/NoAuth/css/3.4-compat/quickbar.css create mode 100644 rt/share/html/NoAuth/css/3.4-compat/ticket.css create mode 100644 rt/share/html/NoAuth/css/3.4-compat/titlebox.css create mode 100644 rt/share/html/NoAuth/css/3.4-compat/transactions.css create mode 100644 rt/share/html/NoAuth/css/3.5-default/approvals.css create mode 100755 rt/share/html/NoAuth/css/3.5-default/body.css create mode 100644 rt/share/html/NoAuth/css/3.5-default/footer.css create mode 100755 rt/share/html/NoAuth/css/3.5-default/forms.css create mode 100644 rt/share/html/NoAuth/css/3.5-default/header.css create mode 100644 rt/share/html/NoAuth/css/3.5-default/local.css create mode 100644 rt/share/html/NoAuth/css/3.5-default/login.css create mode 100644 rt/share/html/NoAuth/css/3.5-default/logo.css create mode 100644 rt/share/html/NoAuth/css/3.5-default/main.css create mode 100755 rt/share/html/NoAuth/css/3.5-default/misc.css create mode 100644 rt/share/html/NoAuth/css/3.5-default/nav-left.css create mode 100644 rt/share/html/NoAuth/css/3.5-default/nav.css create mode 100644 rt/share/html/NoAuth/css/3.5-default/quickbar.css create mode 100644 rt/share/html/NoAuth/css/3.5-default/ticket-search.css create mode 100644 rt/share/html/NoAuth/css/3.5-default/ticket.css create mode 100644 rt/share/html/NoAuth/css/3.5-default/titlebox.css create mode 100755 rt/share/html/NoAuth/css/3.5-default/transactions.css create mode 100644 rt/share/html/NoAuth/css/autohandler create mode 100644 rt/share/html/NoAuth/css/dhandler create mode 100644 rt/share/html/NoAuth/css/print.css create mode 100644 rt/share/html/NoAuth/css/web2/InHeader create mode 100644 rt/share/html/NoAuth/css/web2/admin.css create mode 100644 rt/share/html/NoAuth/css/web2/base.css create mode 100644 rt/share/html/NoAuth/css/web2/boxes.css create mode 100755 rt/share/html/NoAuth/css/web2/forms.css create mode 100644 rt/share/html/NoAuth/css/web2/images/dhandler create mode 100644 rt/share/html/NoAuth/css/web2/images/source/background-gradient.png create mode 100644 rt/share/html/NoAuth/css/web2/layout.css create mode 100644 rt/share/html/NoAuth/css/web2/login.css create mode 100644 rt/share/html/NoAuth/css/web2/main.css create mode 100644 rt/share/html/NoAuth/css/web2/misc.css create mode 100644 rt/share/html/NoAuth/css/web2/msie.css create mode 100644 rt/share/html/NoAuth/css/web2/msie6.css create mode 100644 rt/share/html/NoAuth/css/web2/nav.css create mode 100644 rt/share/html/NoAuth/css/web2/portlets.css create mode 100644 rt/share/html/NoAuth/css/web2/ticket-lists.css create mode 100644 rt/share/html/NoAuth/css/web2/ticket-search.css create mode 100644 rt/share/html/NoAuth/css/web2/ticket.css create mode 100644 rt/share/html/NoAuth/css/web2/tools.css create mode 100644 rt/share/html/NoAuth/css/web2/yui-fonts.css create mode 100644 rt/share/html/NoAuth/iCal/dhandler create mode 100644 rt/share/html/NoAuth/images/autohandler create mode 100755 rt/share/html/NoAuth/images/bplogo.gif create mode 100644 rt/share/html/NoAuth/images/css/cb-light.gif create mode 100644 rt/share/html/NoAuth/images/css/cb.gif create mode 100644 rt/share/html/NoAuth/images/css/cbr-b2g.gif create mode 100644 rt/share/html/NoAuth/images/css/cbr-b2lb.gif create mode 100644 rt/share/html/NoAuth/images/css/cbr-gray.gif create mode 100644 rt/share/html/NoAuth/images/css/cbr-trans.gif create mode 100644 rt/share/html/NoAuth/images/css/cbr.gif create mode 100644 rt/share/html/NoAuth/images/css/ct-light.gif create mode 100644 rt/share/html/NoAuth/images/css/ct.gif create mode 100644 rt/share/html/NoAuth/images/css/ctr-b2g.gif create mode 100644 rt/share/html/NoAuth/images/css/ctr-b2lb.gif create mode 100644 rt/share/html/NoAuth/images/css/ctr-gray.gif create mode 100644 rt/share/html/NoAuth/images/css/ctr-trans.gif create mode 100644 rt/share/html/NoAuth/images/css/ctr.gif create mode 100644 rt/share/html/NoAuth/images/css/dark-arrow-up.png create mode 100644 rt/share/html/NoAuth/images/css/dark-arrow.png create mode 100644 rt/share/html/NoAuth/images/css/fieldbg-autocomplete.gif create mode 100644 rt/share/html/NoAuth/images/css/light-arrow-up.png create mode 100644 rt/share/html/NoAuth/images/css/light-arrow.png create mode 100644 rt/share/html/NoAuth/images/css/rolldown-arrow.gif create mode 100644 rt/share/html/NoAuth/images/css/rolldown-arrow.png create mode 100644 rt/share/html/NoAuth/images/css/rollup-arrow.gif create mode 100644 rt/share/html/NoAuth/images/empty_star.gif create mode 100755 rt/share/html/NoAuth/images/favicon.png create mode 100644 rt/share/html/NoAuth/images/star.gif create mode 100644 rt/share/html/NoAuth/images/test.png create mode 100644 rt/share/html/NoAuth/js/IE7/IE7.js create mode 100644 rt/share/html/NoAuth/js/IE7/IE8.js create mode 100644 rt/share/html/NoAuth/js/IE7/blank.gif create mode 100644 rt/share/html/NoAuth/js/IE7/ie7-recalc.js create mode 100644 rt/share/html/NoAuth/js/IE7/ie7-squish.js create mode 100644 rt/share/html/NoAuth/js/ahah.js create mode 100644 rt/share/html/NoAuth/js/autohandler create mode 100644 rt/share/html/NoAuth/js/cascaded.js create mode 100644 rt/share/html/NoAuth/js/class.js create mode 100644 rt/share/html/NoAuth/js/combobox.js create mode 100644 rt/share/html/NoAuth/js/list.js create mode 100644 rt/share/html/NoAuth/js/prototype/prototype.js create mode 100644 rt/share/html/NoAuth/js/scriptaculous/controls.js create mode 100644 rt/share/html/NoAuth/js/scriptaculous/effects.js create mode 100644 rt/share/html/NoAuth/js/scriptaculous/scriptaculous.js create mode 100644 rt/share/html/NoAuth/js/titlebox-state.js create mode 100644 rt/share/html/NoAuth/js/util.js create mode 100644 rt/share/html/Prefs/Elements/Tabs create mode 100644 rt/share/html/Prefs/MyRT.html create mode 100644 rt/share/html/Prefs/Other.html create mode 100644 rt/share/html/Prefs/Quicksearch.html create mode 100644 rt/share/html/Prefs/Search.html create mode 100644 rt/share/html/Prefs/SearchOptions.html create mode 100644 rt/share/html/REST/1.0/Forms/attachment/default create mode 100644 rt/share/html/REST/1.0/Forms/group/customfields create mode 100644 rt/share/html/REST/1.0/Forms/group/default create mode 100644 rt/share/html/REST/1.0/Forms/group/ns create mode 100644 rt/share/html/REST/1.0/Forms/queue/customfields create mode 100755 rt/share/html/REST/1.0/Forms/queue/default create mode 100755 rt/share/html/REST/1.0/Forms/queue/ns create mode 100644 rt/share/html/REST/1.0/Forms/queue/ticketcustomfields create mode 100755 rt/share/html/REST/1.0/Forms/ticket/attachments create mode 100755 rt/share/html/REST/1.0/Forms/ticket/comment create mode 100755 rt/share/html/REST/1.0/Forms/ticket/default create mode 100755 rt/share/html/REST/1.0/Forms/ticket/history create mode 100755 rt/share/html/REST/1.0/Forms/ticket/links create mode 100755 rt/share/html/REST/1.0/Forms/ticket/merge create mode 100755 rt/share/html/REST/1.0/Forms/ticket/take create mode 100644 rt/share/html/REST/1.0/Forms/transaction/default create mode 100755 rt/share/html/REST/1.0/Forms/user/default create mode 100755 rt/share/html/REST/1.0/Forms/user/ns create mode 100755 rt/share/html/REST/1.0/NoAuth/mail-gateway create mode 100755 rt/share/html/REST/1.0/autohandler create mode 100755 rt/share/html/REST/1.0/dhandler create mode 100755 rt/share/html/REST/1.0/logout create mode 100755 rt/share/html/REST/1.0/search/dhandler create mode 100755 rt/share/html/REST/1.0/search/ticket create mode 100755 rt/share/html/REST/1.0/ticket/comment create mode 100755 rt/share/html/REST/1.0/ticket/link create mode 100755 rt/share/html/REST/1.0/ticket/merge create mode 100644 rt/share/html/Search/Build.html create mode 100755 rt/share/html/Search/Bulk.html create mode 100644 rt/share/html/Search/Chart create mode 100644 rt/share/html/Search/Chart.html create mode 100755 rt/share/html/Search/Edit.html create mode 100644 rt/share/html/Search/Elements/BuildFormatString create mode 100644 rt/share/html/Search/Elements/Chart create mode 100644 rt/share/html/Search/Elements/ConditionRow create mode 100644 rt/share/html/Search/Elements/DisplayOptions create mode 100644 rt/share/html/Search/Elements/EditFormat create mode 100644 rt/share/html/Search/Elements/EditQuery create mode 100644 rt/share/html/Search/Elements/EditSearches create mode 100644 rt/share/html/Search/Elements/Graph create mode 100644 rt/share/html/Search/Elements/NewListActions create mode 100644 rt/share/html/Search/Elements/PickBasics create mode 100644 rt/share/html/Search/Elements/PickCFs create mode 100644 rt/share/html/Search/Elements/PickCriteria create mode 100644 rt/share/html/Search/Elements/ResultViews create mode 100644 rt/share/html/Search/Elements/SearchPrivacy create mode 100644 rt/share/html/Search/Elements/SearchesForObject create mode 100644 rt/share/html/Search/Elements/SelectAndOr create mode 100644 rt/share/html/Search/Elements/SelectChartType create mode 100644 rt/share/html/Search/Elements/SelectGroup create mode 100644 rt/share/html/Search/Elements/SelectGroupBy create mode 100644 rt/share/html/Search/Elements/SelectLinks create mode 100644 rt/share/html/Search/Elements/SelectPersonType create mode 100644 rt/share/html/Search/Elements/SelectSearchObject create mode 100644 rt/share/html/Search/Elements/SelectSearchesForObjects create mode 100644 rt/share/html/Search/Graph.html create mode 100755 rt/share/html/Search/Results.html create mode 100644 rt/share/html/Search/Results.rdf create mode 100644 rt/share/html/Search/Results.tsv create mode 100644 rt/share/html/Search/Simple.html create mode 100755 rt/share/html/SelfService/Attachment/dhandler create mode 100755 rt/share/html/SelfService/Closed.html create mode 100755 rt/share/html/SelfService/Create.html create mode 100755 rt/share/html/SelfService/CreateTicketInQueue.html create mode 100755 rt/share/html/SelfService/Display.html create mode 100755 rt/share/html/SelfService/Elements/GotoTicket create mode 100755 rt/share/html/SelfService/Elements/Header create mode 100755 rt/share/html/SelfService/Elements/MyRequests create mode 100755 rt/share/html/SelfService/Elements/Tabs create mode 100755 rt/share/html/SelfService/Error.html create mode 100755 rt/share/html/SelfService/Prefs.html create mode 100755 rt/share/html/SelfService/Update.html create mode 100755 rt/share/html/SelfService/index.html create mode 100644 rt/share/html/Ticket/Attachment/WithHeaders/dhandler create mode 100755 rt/share/html/Ticket/Attachment/dhandler create mode 100755 rt/share/html/Ticket/Create.html create mode 100755 rt/share/html/Ticket/Display.html create mode 100755 rt/share/html/Ticket/Elements/AddWatchers create mode 100644 rt/share/html/Ticket/Elements/Bookmark create mode 100755 rt/share/html/Ticket/Elements/BulkLinks create mode 100755 rt/share/html/Ticket/Elements/EditBasics create mode 100755 rt/share/html/Ticket/Elements/EditCustomFields create mode 100755 rt/share/html/Ticket/Elements/EditDates create mode 100755 rt/share/html/Ticket/Elements/EditPeople create mode 100644 rt/share/html/Ticket/Elements/EditTransactionCustomFields create mode 100755 rt/share/html/Ticket/Elements/EditWatchers create mode 100644 rt/share/html/Ticket/Elements/FindAttachments create mode 100644 rt/share/html/Ticket/Elements/LoadTextAttachments create mode 100755 rt/share/html/Ticket/Elements/PreviewScrips create mode 100644 rt/share/html/Ticket/Elements/Reminders create mode 100755 rt/share/html/Ticket/Elements/ShowAttachments create mode 100755 rt/share/html/Ticket/Elements/ShowBasics create mode 100755 rt/share/html/Ticket/Elements/ShowCustomFields create mode 100755 rt/share/html/Ticket/Elements/ShowDates create mode 100755 rt/share/html/Ticket/Elements/ShowDependencies create mode 100644 rt/share/html/Ticket/Elements/ShowGnuPGStatus create mode 100644 rt/share/html/Ticket/Elements/ShowGroupMembers create mode 100755 rt/share/html/Ticket/Elements/ShowHistory create mode 100755 rt/share/html/Ticket/Elements/ShowMembers create mode 100755 rt/share/html/Ticket/Elements/ShowMessageHeaders create mode 100755 rt/share/html/Ticket/Elements/ShowMessageStanza create mode 100644 rt/share/html/Ticket/Elements/ShowParents create mode 100755 rt/share/html/Ticket/Elements/ShowPeople create mode 100644 rt/share/html/Ticket/Elements/ShowPriority create mode 100644 rt/share/html/Ticket/Elements/ShowQueue create mode 100755 rt/share/html/Ticket/Elements/ShowRequestor create mode 100755 rt/share/html/Ticket/Elements/ShowSummary create mode 100644 rt/share/html/Ticket/Elements/ShowTime create mode 100755 rt/share/html/Ticket/Elements/ShowTransaction create mode 100644 rt/share/html/Ticket/Elements/ShowTransactionAttachments create mode 100644 rt/share/html/Ticket/Elements/ShowUpdateStatus create mode 100644 rt/share/html/Ticket/Elements/ShowUserEntry create mode 100755 rt/share/html/Ticket/Elements/Tabs create mode 100644 rt/share/html/Ticket/Elements/UpdateCc create mode 100644 rt/share/html/Ticket/Forward.html create mode 100644 rt/share/html/Ticket/GnuPG.html create mode 100644 rt/share/html/Ticket/Graphs/Elements/EditGraphProperties create mode 100644 rt/share/html/Ticket/Graphs/Elements/ShowGraph create mode 100644 rt/share/html/Ticket/Graphs/Elements/ShowLegends create mode 100644 rt/share/html/Ticket/Graphs/dhandler create mode 100644 rt/share/html/Ticket/Graphs/index.html create mode 100755 rt/share/html/Ticket/History.html create mode 100755 rt/share/html/Ticket/Modify.html create mode 100755 rt/share/html/Ticket/ModifyAll.html create mode 100755 rt/share/html/Ticket/ModifyDates.html create mode 100755 rt/share/html/Ticket/ModifyLinks.html create mode 100755 rt/share/html/Ticket/ModifyPeople.html create mode 100755 rt/share/html/Ticket/Reminders.html create mode 100644 rt/share/html/Ticket/ShowEmailRecord.html create mode 100755 rt/share/html/Ticket/Update.html create mode 100644 rt/share/html/Tools/Elements/Tabs create mode 100644 rt/share/html/Tools/MyDay.html create mode 100644 rt/share/html/Tools/Offline.html create mode 100644 rt/share/html/Tools/Reports/CreatedByDates.html create mode 100644 rt/share/html/Tools/Reports/Elements/Tabs create mode 100644 rt/share/html/Tools/Reports/ResolvedByDates.html create mode 100644 rt/share/html/Tools/Reports/ResolvedByOwner.html create mode 100644 rt/share/html/Tools/Reports/index.html create mode 100644 rt/share/html/Tools/index.html create mode 100755 rt/share/html/User/Delegation.html create mode 100755 rt/share/html/User/Elements/DelegateRights create mode 100755 rt/share/html/User/Elements/GroupTabs create mode 100755 rt/share/html/User/Elements/Tabs create mode 100755 rt/share/html/User/Groups/Members.html create mode 100755 rt/share/html/User/Groups/Modify.html create mode 100755 rt/share/html/User/Groups/index.html create mode 100755 rt/share/html/User/Prefs.html create mode 100644 rt/share/html/Widgets/BulkEdit create mode 100644 rt/share/html/Widgets/BulkProcess create mode 100644 rt/share/html/Widgets/ComboBox create mode 100644 rt/share/html/Widgets/FinalizeWidgetArguments create mode 100644 rt/share/html/Widgets/Form/Boolean create mode 100644 rt/share/html/Widgets/Form/Integer create mode 100644 rt/share/html/Widgets/Form/Select create mode 100644 rt/share/html/Widgets/Form/String create mode 100644 rt/share/html/Widgets/SavedSearch create mode 100644 rt/share/html/Widgets/SelectionBox create mode 100644 rt/share/html/Widgets/TitleBox create mode 100755 rt/share/html/Widgets/TitleBoxEnd create mode 100755 rt/share/html/Widgets/TitleBoxStart create mode 100755 rt/share/html/autohandler create mode 100644 rt/share/html/dhandler create mode 100755 rt/share/html/index.html create mode 100755 rt/share/html/l create mode 100644 rt/t/00-compile.t create mode 100644 rt/t/00-mason-syntax.t create mode 100644 rt/t/api/ace.t create mode 100644 rt/t/api/action-createtickets.t create mode 100644 rt/t/api/attachment.t create mode 100644 rt/t/api/attribute-tests.t create mode 100644 rt/t/api/attribute.t create mode 100644 rt/t/api/cf.t create mode 100644 rt/t/api/cf_combo_casacade.t create mode 100644 rt/t/api/cf_external.t create mode 100644 rt/t/api/cf_pattern.t create mode 100644 rt/t/api/cf_single_values.t create mode 100644 rt/t/api/cf_transaction.t create mode 100644 rt/t/api/condition-ownerchange.t create mode 100644 rt/t/api/condition-reject.t create mode 100644 rt/t/api/currentuser.t create mode 100644 rt/t/api/customfield.t create mode 100644 rt/t/api/date.t create mode 100644 rt/t/api/emailparser.t create mode 100644 rt/t/api/group.t create mode 100644 rt/t/api/groups.t create mode 100644 rt/t/api/i18n.t create mode 100644 rt/t/api/link.t create mode 100644 rt/t/api/queue.t create mode 100644 rt/t/api/record.t create mode 100644 rt/t/api/reminders.t create mode 100644 rt/t/api/rights.t create mode 100644 rt/t/api/rt.t create mode 100644 rt/t/api/scrip.t create mode 100644 rt/t/api/scrip_order.t create mode 100644 rt/t/api/searchbuilder.t create mode 100644 rt/t/api/system.t create mode 100644 rt/t/api/template-insert.t create mode 100644 rt/t/api/template.t create mode 100644 rt/t/api/ticket.t create mode 100644 rt/t/api/tickets.t create mode 100644 rt/t/api/tickets_overlay_sql.t create mode 100644 rt/t/api/uri-fsck_com_rt.t create mode 100644 rt/t/api/uri-t.t create mode 100644 rt/t/api/user.t create mode 100644 rt/t/api/users.t create mode 100644 rt/t/approval/basic.t create mode 100644 rt/t/clicky.t create mode 100644 rt/t/cron.t create mode 100644 rt/t/customfields/access_via_queue.t create mode 100644 rt/t/customfields/sort_order.t create mode 100644 rt/t/data/configs/apache2.2+fastcgi.conf create mode 100644 rt/t/data/configs/apache2.2+fastcgi.conf.in create mode 100644 rt/t/data/configs/apache2.2+mod_perl.conf create mode 100644 rt/t/data/configs/apache2.2+mod_perl.conf.in create mode 100755 rt/t/data/emails/8859-15-message-series/dir create mode 100755 rt/t/data/emails/8859-15-message-series/msg1 create mode 100755 rt/t/data/emails/8859-15-message-series/msg2 create mode 100755 rt/t/data/emails/8859-15-message-series/msg3 create mode 100755 rt/t/data/emails/8859-15-message-series/msg4 create mode 100755 rt/t/data/emails/8859-15-message-series/msg5 create mode 100755 rt/t/data/emails/8859-15-message-series/msg6 create mode 100755 rt/t/data/emails/8859-15-message-series/msg7 create mode 100755 rt/t/data/emails/crashes-file-based-parser create mode 100644 rt/t/data/emails/lorem-ipsum create mode 100755 rt/t/data/emails/multipart-alternative-with-umlaut create mode 100755 rt/t/data/emails/multipart-report create mode 100755 rt/t/data/emails/nested-mime-sample create mode 100755 rt/t/data/emails/nested-rfc-822 create mode 100755 rt/t/data/emails/new-ticket-from-iso-8859-1 create mode 100755 rt/t/data/emails/new-ticket-from-iso-8859-1-full create mode 100755 rt/t/data/emails/notes-uuencoded create mode 100644 rt/t/data/emails/rt-send-cc create mode 100755 rt/t/data/emails/russian-subject-no-content-type create mode 100644 rt/t/data/emails/subject-with-folding-ws create mode 100755 rt/t/data/emails/text-html-in-russian create mode 100755 rt/t/data/emails/text-html-with-umlaut create mode 100644 rt/t/data/emails/very-long-subject create mode 100644 rt/t/data/gnupg/emails/1-signed-MIME-plain.txt create mode 100644 rt/t/data/gnupg/emails/10-encrypted-inline-plain.txt create mode 100644 rt/t/data/gnupg/emails/11-encrypted-inline-attachment.txt create mode 100644 rt/t/data/gnupg/emails/12-encrypted-inline-binary.txt create mode 100644 rt/t/data/gnupg/emails/13-signed-encrypted-MIME-plain.txt create mode 100644 rt/t/data/gnupg/emails/14-signed-encrypted-MIME-attachment.txt create mode 100644 rt/t/data/gnupg/emails/15-signed-encrypted-MIME-binary.txt create mode 100644 rt/t/data/gnupg/emails/16-signed-encrypted-inline-plain.txt create mode 100644 rt/t/data/gnupg/emails/17-signed-encrypted-inline-attachment.txt create mode 100644 rt/t/data/gnupg/emails/18-signed-encrypted-inline-binary.txt create mode 100644 rt/t/data/gnupg/emails/19-signed-inline-plain-nested.txt create mode 100755 rt/t/data/gnupg/emails/2-signed-MIME-plain-with-attachment.txt create mode 100755 rt/t/data/gnupg/emails/3-signed-MIME-plain-with-binary.txt create mode 100644 rt/t/data/gnupg/emails/4-signed-inline-plain.txt create mode 100644 rt/t/data/gnupg/emails/5-signed-inline-with-attachment.txt create mode 100644 rt/t/data/gnupg/emails/6-signed-inline-with-binary.txt create mode 100644 rt/t/data/gnupg/emails/7-encrypted-MIME-plain.txt create mode 100644 rt/t/data/gnupg/emails/8-encrypted-MIME-with-attachment.txt create mode 100644 rt/t/data/gnupg/emails/9-encrypted-MIME-with-binary.txt create mode 100644 rt/t/data/gnupg/emails/README create mode 100644 rt/t/data/gnupg/keyrings/pubring.gpg create mode 100644 rt/t/data/gnupg/keyrings/secring.gpg create mode 100644 rt/t/data/gnupg/keyrings/signed_old_style_with_attachment.eml create mode 100644 rt/t/data/gnupg/keyrings/trustdb.gpg create mode 100644 rt/t/data/gnupg/keys/general-at-example.com.2.public.key create mode 100644 rt/t/data/gnupg/keys/general-at-example.com.2.secret.key create mode 100644 rt/t/data/gnupg/keys/general-at-example.com.public.key create mode 100644 rt/t/data/gnupg/keys/general-at-example.com.secret.key create mode 100644 rt/t/data/gnupg/keys/recipient-at-example.com.public.key create mode 100644 rt/t/data/gnupg/keys/recipient-at-example.com.secret.key create mode 100644 rt/t/data/gnupg/keys/rt-recipient-at-example.com.public.key create mode 100644 rt/t/data/gnupg/keys/rt-recipient-at-example.com.secret.key create mode 100644 rt/t/data/gnupg/keys/rt-test-at-example.com.2.public.key create mode 100644 rt/t/data/gnupg/keys/rt-test-at-example.com.2.secret.key create mode 100644 rt/t/data/gnupg/keys/rt-test-at-example.com.public.key create mode 100644 rt/t/data/gnupg/keys/rt-test-at-example.com.secret.key create mode 100644 rt/t/delegation/cleanup_stalled.t create mode 100644 rt/t/delegation/revocation.t create mode 100644 rt/t/i18n/default.t create mode 100644 rt/t/mail/charsets-outgoing.t create mode 100644 rt/t/mail/crypt-gnupg.t create mode 100644 rt/t/mail/extractsubjecttag.t create mode 100644 rt/t/mail/gateway.t create mode 100644 rt/t/mail/gnupg-bad.t create mode 100644 rt/t/mail/gnupg-incoming.t create mode 100644 rt/t/mail/gnupg-realmail.t create mode 100644 rt/t/mail/gnupg-reverification.t create mode 100644 rt/t/mail/mime_decoding.t create mode 100644 rt/t/mail/sendmail.t create mode 100644 rt/t/mail/verp.t create mode 100644 rt/t/maildigest/attributes.t create mode 100644 rt/t/pod.t create mode 100644 rt/t/rtname.t create mode 100644 rt/t/savedsearch.t create mode 100644 rt/t/shredder/00load.t create mode 100644 rt/t/shredder/00skeleton.t create mode 100644 rt/t/shredder/01basics.t create mode 100644 rt/t/shredder/01ticket.t create mode 100644 rt/t/shredder/02group_member.t create mode 100644 rt/t/shredder/02queue.t create mode 100644 rt/t/shredder/02template.t create mode 100644 rt/t/shredder/02user.t create mode 100644 rt/t/shredder/03plugin.t create mode 100644 rt/t/shredder/03plugin_summary.t create mode 100644 rt/t/shredder/03plugin_tickets.t create mode 100644 rt/t/shredder/03plugin_users.t create mode 100644 rt/t/shredder/utils.pl create mode 100644 rt/t/ticket/action_linear_escalate.t create mode 100644 rt/t/ticket/add-watchers.t create mode 100644 rt/t/ticket/badlinks.t create mode 100644 rt/t/ticket/batch-upload-csv.t create mode 100644 rt/t/ticket/cfsort-freeform-multiple.t create mode 100644 rt/t/ticket/cfsort-freeform-single.t create mode 100644 rt/t/ticket/deferred_owner.t create mode 100644 rt/t/ticket/link_search.t create mode 100644 rt/t/ticket/linking.t create mode 100644 rt/t/ticket/merge.t create mode 100644 rt/t/ticket/quicksearch.t create mode 100644 rt/t/ticket/requestor-order.t create mode 100644 rt/t/ticket/scrips_batch.t create mode 100644 rt/t/ticket/search.t create mode 100644 rt/t/ticket/search_by_cf_freeform_multiple.t create mode 100644 rt/t/ticket/search_by_cf_freeform_single.t create mode 100644 rt/t/ticket/search_by_links.t create mode 100644 rt/t/ticket/search_by_txn.t create mode 100644 rt/t/ticket/search_by_watcher.t create mode 100644 rt/t/ticket/search_long_cf_values.t create mode 100644 rt/t/ticket/sort-by-custom-ownership.t create mode 100644 rt/t/ticket/sort-by-queue.t create mode 100644 rt/t/ticket/sort-by-user.t create mode 100644 rt/t/ticket/sort_by_cf.t create mode 100644 rt/t/validator/group_members.t create mode 100644 rt/t/web/attachments.t create mode 100644 rt/t/web/basic.t create mode 100644 rt/t/web/cf_access.t create mode 100644 rt/t/web/cf_onqueue.t create mode 100644 rt/t/web/cf_select_one.t create mode 100644 rt/t/web/command_line.t create mode 100644 rt/t/web/command_line_with_unknown_field.t create mode 100644 rt/t/web/compilation_errors.t create mode 100644 rt/t/web/config_tab_right.t create mode 100644 rt/t/web/crypt-gnupg.t create mode 100644 rt/t/web/custom_frontpage.t create mode 100644 rt/t/web/custom_search.t create mode 100644 rt/t/web/dashboard_with_deleted_saved_search.t create mode 100644 rt/t/web/dashboards-groups.t create mode 100644 rt/t/web/dashboards-permissions.t create mode 100644 rt/t/web/dashboards.t create mode 100644 rt/t/web/gnupg-outgoing.t create mode 100644 rt/t/web/gnupg-select-keys-on-create.t create mode 100644 rt/t/web/gnupg-select-keys-on-update.t create mode 100644 rt/t/web/offline_messages_utf8.t create mode 100644 rt/t/web/offline_utf8.t create mode 100644 rt/t/web/query_builder.t create mode 100644 rt/t/web/quicksearch.t create mode 100644 rt/t/web/rest-non-ascii-subject.t create mode 100644 rt/t/web/rest.t create mode 100644 rt/t/web/rights.t create mode 100644 rt/t/web/rights1.t create mode 100644 rt/t/web/saved_search_chart.t create mode 100644 rt/t/web/saved_search_permissions.t create mode 100644 rt/t/web/search_bulk_update_links.t create mode 100644 rt/t/web/ticket-create-utf8.t create mode 100644 rt/t/web/ticket_owner.t create mode 100644 rt/t/web/ticket_seen.t create mode 100644 rt/t/web/ticket_update_without_content.t create mode 100644 rt/t/web/unlimited_search.t diff --git a/rt/Makefile b/rt/Makefile index 87d6fd74c..e89a69ac5 100644 --- a/rt/Makefile +++ b/rt/Makefile @@ -1,8 +1,8 @@ # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: -# -# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -51,19 +51,22 @@ # -PERL = /usr/bin/perl +PERL = /usr/bin/perl +INSTALL = install-sh + +RT_LAYOUT = relative CONFIG_FILE_PATH = /opt/rt3/etc -CONFIG_FILE = $(CONFIG_FILE_PATH)/RT_Config.pm -SITE_CONFIG_FILE = $(CONFIG_FILE_PATH)/RT_SiteConfig.pm +CONFIG_FILE = $(CONFIG_FILE_PATH)/RT_Config.pm +SITE_CONFIG_FILE = $(CONFIG_FILE_PATH)/RT_SiteConfig.pm RT_VERSION_MAJOR = 3 -RT_VERSION_MINOR = 6 -RT_VERSION_PATCH = 10 +RT_VERSION_MINOR = 8 +RT_VERSION_PATCH = 7 -RT_VERSION = $(RT_VERSION_MAJOR).$(RT_VERSION_MINOR).$(RT_VERSION_PATCH) -TAG = rt-$(RT_VERSION_MAJOR)-$(RT_VERSION_MINOR)-$(RT_VERSION_PATCH) +RT_VERSION = $(RT_VERSION_MAJOR).$(RT_VERSION_MINOR).$(RT_VERSION_PATCH) +TAG = rt-$(RT_VERSION_MAJOR)-$(RT_VERSION_MINOR)-$(RT_VERSION_PATCH) # This is the group that all of the installed files will be chgrp'ed to. @@ -88,11 +91,13 @@ APACHECTL = /usr/sbin/apachectl # {{{ Files and directories # DESTDIR allows you to specify that RT be installed somewhere other than -# where it will eventually reside +# where it will eventually reside. DESTDIR _must_ have a trailing slash +# if it's defined. DESTDIR = + RT_PATH = /opt/rt3 RT_ETC_PATH = /opt/rt3/etc RT_BIN_PATH = /opt/rt3/bin @@ -102,6 +107,7 @@ RT_MAN_PATH = /opt/rt3/man RT_VAR_PATH = /opt/rt3/var RT_DOC_PATH = /opt/rt3/share/doc RT_LOCAL_PATH = /opt/rt3/local +LOCAL_PLUGIN_PATH = /opt/rt3/local/plugins LOCAL_ETC_PATH = /opt/rt3/local/etc LOCAL_LIB_PATH = /opt/rt3/local/lib LOCAL_LEXICON_PATH = /opt/rt3/local/po @@ -109,7 +115,7 @@ MASON_HTML_PATH = /opt/rt3/share/html MASON_LOCAL_HTML_PATH = /opt/rt3/local/html MASON_DATA_PATH = /opt/rt3/var/mason_data MASON_SESSION_PATH = /opt/rt3/var/session_data -RT_LOG_PATH = /opt/rt3/var/log +RT_LOG_PATH = /opt/rt3/var/log # RT_READABLE_DIR_MODE is the mode of directories that are generally meant # to be accessable @@ -121,34 +127,69 @@ RT_READABLE_DIR_MODE = 0755 # {{{ all these define the places that RT's binaries should get installed # RT_MODPERL_HANDLER is the mason handler script for mod_perl -RT_MODPERL_HANDLER = $(RT_BIN_PATH)/webmux.pl +RT_MODPERL_HANDLER = webmux.pl # RT_STANDALONE_SERVER is a stand-alone HTTP server -RT_STANDALONE_SERVER = $(RT_BIN_PATH)/standalone_httpd +RT_STANDALONE_SERVER = standalone_httpd # RT_SPEEDYCGI_HANDLER is the mason handler script for SpeedyCGI -RT_SPEEDYCGI_HANDLER = $(RT_BIN_PATH)/mason_handler.scgi +RT_SPEEDYCGI_HANDLER = mason_handler.scgi # RT_FASTCGI_HANDLER is the mason handler script for FastCGI -RT_FASTCGI_HANDLER = $(RT_BIN_PATH)/mason_handler.fcgi +RT_FASTCGI_HANDLER = mason_handler.fcgi # RT_WIN32_FASTCGI_HANDLER is the mason handler script for FastCGI -RT_WIN32_FASTCGI_HANDLER = $(RT_BIN_PATH)/mason_handler.svc +RT_WIN32_FASTCGI_HANDLER = mason_handler.svc # RT's CLI -RT_CLI_BIN = $(RT_BIN_PATH)/rt +RT_CLI_BIN = rt # RT's mail gateway -RT_MAILGATE_BIN = $(RT_BIN_PATH)/rt-mailgate +RT_MAILGATE_BIN = rt-mailgate # RT's cron tool -RT_CRON_BIN = $(RT_BIN_PATH)/rt-crontool +RT_CRON_BIN = rt-crontool # }}} -BINARIES = $(DESTDIR)/$(RT_MODPERL_HANDLER) \ - $(DESTDIR)/$(RT_MAILGATE_BIN) \ - $(DESTDIR)/$(RT_CLI_BIN) \ - $(DESTDIR)/$(RT_CRON_BIN) \ - $(DESTDIR)/$(RT_STANDALONE_SERVER) \ - $(DESTDIR)/$(RT_SPEEDYCGI_HANDLER) \ - $(DESTDIR)/$(RT_FASTCGI_HANDLER) \ - $(DESTDIR)/$(RT_WIN32_FASTCGI_HANDLER) -SYSTEM_BINARIES = $(DESTDIR)/$(RT_SBIN_PATH)/ +BINARIES = $(RT_MODPERL_HANDLER) \ + $(RT_MAILGATE_BIN) \ + $(RT_CLI_BIN) \ + $(RT_CRON_BIN) \ + $(RT_STANDALONE_SERVER) \ + $(RT_SPEEDYCGI_HANDLER) \ + $(RT_FASTCGI_HANDLER) \ + $(RT_WIN32_FASTCGI_HANDLER) + + + + + +SYSTEM_BINARIES = rt-dump-database \ + rt-setup-database \ + rt-email-digest \ + rt-email-dashboards \ + rt-email-group-admin \ + rt-server \ + rt-test-dependencies \ + rt-clean-sessions \ + rt-shredder \ + rt-validator + + +ETC_FILES = acl.Informix \ + acl.Pg \ + acl.Oracle \ + acl.mysql \ + acl.Sybase \ + schema.Informix \ + schema.Pg \ + schema.Oracle \ + schema.mysql-4.0 \ + schema.mysql-4.1 \ + schema.Sybase \ + schema.SQLite \ + initialdata + +# }}} + +# {{{ Web frontend + +WEB_HANDLER = standalone # }}} @@ -205,18 +246,21 @@ DB_RT_PASS = rt_pass # }}} +TEST_FILES = t/*.t t/*/*.t +TEST_VERBOSE = 0 + + #################################################################### all: default default: - @echo "Please read RT's readme before installing. Not doing so could" - @echo "be dangerous." + @echo "Please read RT's README before beginning your installation." instruct: - @echo "Congratulations. RT has been installed. " + @echo "Congratulations. RT is now installed." @echo "" @echo "" @echo "You must now configure RT by editing $(SITE_CONFIG_FILE)." @@ -230,244 +274,214 @@ instruct: @echo "After that, you need to initialize RT's database by running" @echo " 'make initialize-database'" -# @echo " or by executing " -# @echo " '$(RT_SBIN_PATH)/rt-setup-database --action init \ " -# @echo " --dba $(DB_DBA) --prompt-for-dba-password'" - - upgrade-instruct: - @echo "Congratulations. RT has been upgraded. You should now check-over" + @echo "Congratulations. RT has been upgraded. You should now check over" @echo "$(CONFIG_FILE) for any necessary site customization. Additionally," @echo "you should update RT's system database objects by running " - @echo " ls etc/upgrade" - @echo "" - @echo "For each item in that directory whose name is greater than" - @echo "your previously installed RT version, run:" - @echo " $(RT_SBIN_PATH)/rt-setup-database --dba $(DB_DBA) --prompt-for-dba-password --action schema --datadir etc/upgrade/" - @echo " $(RT_SBIN_PATH)/rt-setup-database --dba $(DB_DBA) --prompt-for-dba-password --action acl --datadir etc/upgrade/" - @echo " $(RT_SBIN_PATH)/rt-setup-database --dba $(DB_DBA) --prompt-for-dba-password --action insert --datadir etc/upgrade/" + @echo " $(RT_SBIN_PATH)/rt-setup-database --dba $(DB_DBA) --prompt-for-dba-password --action upgrade" -upgrade: config-install dirs files-install fixperms upgrade-instruct +upgrade: testdeps config-install dirs files-install fixperms upgrade-instruct -upgrade-noclobber: config-install libs-install html-install bin-install local-install doc-install fixperms +upgrade-noclobber: config-install dirs libs-install html-install bin-install local-install doc-install fixperms # {{{ dependencies + +my_with_web_handlers= $(shell $(PERL) -e 'print join " ", map "--with-$$_", grep defined && length, split /,/, "$(WEB_HANDLER)"') testdeps: - $(PERL) ./sbin/rt-test-dependencies --verbose --with-$(DB_TYPE) + $(PERL) ./sbin/rt-test-dependencies --verbose --with-$(DB_TYPE) $(my_with_web_handlers) depends: fixdeps fixdeps: - $(PERL) ./sbin/rt-test-dependencies --verbose --install --with-$(DB_TYPE) + $(PERL) ./sbin/rt-test-dependencies --verbose --install --with-$(DB_TYPE) $(my_with_web_handlers) #}}} # {{{ fixperms fixperms: # Make the libraries readable - chmod $(RT_READABLE_DIR_MODE) $(DESTDIR)/$(RT_PATH) - chown -R $(LIBS_OWNER) $(DESTDIR)/$(RT_LIB_PATH) - chgrp -R $(LIBS_GROUP) $(DESTDIR)/$(RT_LIB_PATH) - chmod -R u+rwX,go-w,go+rX $(DESTDIR)/$(RT_LIB_PATH) + chmod $(RT_READABLE_DIR_MODE) $(DESTDIR)$(RT_PATH) + chown -R $(LIBS_OWNER) $(DESTDIR)$(RT_LIB_PATH) + chgrp -R $(LIBS_GROUP) $(DESTDIR)$(RT_LIB_PATH) + chmod -R u+rwX,go-w,go+rX $(DESTDIR)$(RT_LIB_PATH) - chmod $(RT_READABLE_DIR_MODE) $(DESTDIR)/$(RT_BIN_PATH) - chmod $(RT_READABLE_DIR_MODE) $(DESTDIR)/$(RT_BIN_PATH) + chmod $(RT_READABLE_DIR_MODE) $(DESTDIR)$(RT_BIN_PATH) - chmod 0755 $(DESTDIR)/$(RT_ETC_PATH) - chmod 0500 $(DESTDIR)/$(RT_ETC_PATH)/* + chmod 0755 $(DESTDIR)$(RT_ETC_PATH) + cd $(DESTDIR)$(RT_ETC_PATH) && chmod 0400 $(ETC_FILES) #TODO: the config file should probably be able to have its # owner set separately from the binaries. - chown -R $(BIN_OWNER) $(DESTDIR)/$(RT_ETC_PATH) - chgrp -R $(RTGROUP) $(DESTDIR)/$(RT_ETC_PATH) + chown -R $(BIN_OWNER) $(DESTDIR)$(RT_ETC_PATH) + chgrp -R $(RTGROUP) $(DESTDIR)$(RT_ETC_PATH) + + chmod 0440 $(DESTDIR)$(CONFIG_FILE) + chmod 0640 $(DESTDIR)$(SITE_CONFIG_FILE) - chmod 0550 $(DESTDIR)/$(CONFIG_FILE) - chmod 0550 $(DESTDIR)/$(SITE_CONFIG_FILE) + # Make the system binaries + cd $(DESTDIR)$(RT_BIN_PATH) && ( chmod 0755 $(BINARIES) ; chown $(BIN_OWNER) $(BINARIES); chgrp $(RTGROUP) $(BINARIES)) - # Make the interfaces executable - chown $(BIN_OWNER) $(BINARIES) - chgrp $(RTGROUP) $(BINARIES) - chmod 0755 $(BINARIES) + # Make the system binaries executable also + cd $(DESTDIR)$(RT_SBIN_PATH) && ( chmod 0755 $(SYSTEM_BINARIES) ; chown $(BIN_OWNER) $(SYSTEM_BINARIES); chgrp $(RTGROUP) $(SYSTEM_BINARIES)) # Make the web ui readable by all. - chmod -R u+rwX,go-w,go+rX $(DESTDIR)/$(MASON_HTML_PATH) \ - $(DESTDIR)/$(MASON_LOCAL_HTML_PATH) \ - $(DESTDIR)/$(LOCAL_LEXICON_PATH) - chown -R $(LIBS_OWNER) $(DESTDIR)/$(MASON_HTML_PATH) \ - $(DESTDIR)/$(MASON_LOCAL_HTML_PATH) - chgrp -R $(LIBS_GROUP) $(DESTDIR)/$(MASON_HTML_PATH) \ - $(DESTDIR)/$(MASON_LOCAL_HTML_PATH) + chmod -R u+rwX,go-w,go+rX $(DESTDIR)$(MASON_HTML_PATH) \ + $(DESTDIR)$(MASON_LOCAL_HTML_PATH) \ + $(DESTDIR)$(LOCAL_LEXICON_PATH) + chown -R $(LIBS_OWNER) $(DESTDIR)$(MASON_HTML_PATH) \ + $(DESTDIR)$(MASON_LOCAL_HTML_PATH) + chgrp -R $(LIBS_GROUP) $(DESTDIR)$(MASON_HTML_PATH) \ + $(DESTDIR)$(MASON_LOCAL_HTML_PATH) # Make the web ui's data dir writable - chmod 0770 $(DESTDIR)/$(MASON_DATA_PATH) \ - $(DESTDIR)/$(MASON_SESSION_PATH) - chown -R $(WEB_USER) $(DESTDIR)/$(MASON_DATA_PATH) \ - $(DESTDIR)/$(MASON_SESSION_PATH) - chgrp -R $(WEB_GROUP) $(DESTDIR)/$(MASON_DATA_PATH) \ - $(DESTDIR)/$(MASON_SESSION_PATH) + chmod 0770 $(DESTDIR)$(MASON_DATA_PATH) \ + $(DESTDIR)$(MASON_SESSION_PATH) + chown -R $(WEB_USER) $(DESTDIR)$(MASON_DATA_PATH) \ + $(DESTDIR)$(MASON_SESSION_PATH) + chgrp -R $(WEB_GROUP) $(DESTDIR)$(MASON_DATA_PATH) \ + $(DESTDIR)$(MASON_SESSION_PATH) # }}} # {{{ dirs dirs: - mkdir -p $(DESTDIR)/$(RT_LOG_PATH) - mkdir -p $(DESTDIR)/$(MASON_DATA_PATH) - mkdir -p $(DESTDIR)/$(MASON_DATA_PATH)/cache - mkdir -p $(DESTDIR)/$(MASON_DATA_PATH)/etc - mkdir -p $(DESTDIR)/$(MASON_DATA_PATH)/obj - mkdir -p $(DESTDIR)/$(MASON_SESSION_PATH) - mkdir -p $(DESTDIR)/$(MASON_HTML_PATH) - mkdir -p $(DESTDIR)/$(MASON_LOCAL_HTML_PATH) - mkdir -p $(DESTDIR)/$(LOCAL_ETC_PATH) - mkdir -p $(DESTDIR)/$(LOCAL_LIB_PATH) - mkdir -p $(DESTDIR)/$(LOCAL_LEXICON_PATH) + $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_LOG_PATH) + $(INSTALL) -m 0770 -d $(DESTDIR)$(MASON_DATA_PATH) + $(INSTALL) -m 0770 -d $(DESTDIR)$(MASON_DATA_PATH)/cache + $(INSTALL) -m 0770 -d $(DESTDIR)$(MASON_DATA_PATH)/etc + $(INSTALL) -m 0770 -d $(DESTDIR)$(MASON_DATA_PATH)/obj + $(INSTALL) -m 0770 -d $(DESTDIR)$(MASON_SESSION_PATH) + $(INSTALL) -m 0755 -d $(DESTDIR)$(MASON_HTML_PATH) + $(INSTALL) -m 0755 -d $(DESTDIR)$(MASON_LOCAL_HTML_PATH) + $(INSTALL) -m 0755 -d $(DESTDIR)$(LOCAL_ETC_PATH) + $(INSTALL) -m 0755 -d $(DESTDIR)$(LOCAL_LIB_PATH) + $(INSTALL) -m 0755 -d $(DESTDIR)$(LOCAL_PLUGIN_PATH) + $(INSTALL) -m 0755 -d $(DESTDIR)$(LOCAL_LEXICON_PATH) # }}} -install: config-install dirs files-install fixperms instruct +install: testdeps config-install dirs files-install fixperms instruct -files-install: libs-install etc-install bin-install sbin-install html-install local-install doc-install +files-install: libs-install etc-install config-install bin-install sbin-install html-install local-install doc-install config-install: - mkdir -p $(DESTDIR)/$(CONFIG_FILE_PATH) - -cp etc/RT_Config.pm $(DESTDIR)/$(CONFIG_FILE) - [ -f $(DESTDIR)/$(SITE_CONFIG_FILE) ] || cp etc/RT_SiteConfig.pm $(DESTDIR)/$(SITE_CONFIG_FILE) - - chgrp $(RTGROUP) $(DESTDIR)/$(CONFIG_FILE) - chown $(BIN_OWNER) $(DESTDIR)/$(CONFIG_FILE) - - chgrp $(RTGROUP) $(DESTDIR)/$(SITE_CONFIG_FILE) - chown $(BIN_OWNER) $(DESTDIR)/$(SITE_CONFIG_FILE) - - @echo "Installed configuration. about to install rt in $(RT_PATH)" + $(INSTALL) -m 0755 -o $(BIN_OWNER) -g $(RTGROUP) -d $(DESTDIR)$(CONFIG_FILE_PATH) + -$(INSTALL) -m 0440 -o $(BIN_OWNER) -g $(RTGROUP) etc/RT_Config.pm $(DESTDIR)$(CONFIG_FILE) + [ -f $(DESTDIR)$(SITE_CONFIG_FILE) ] || $(INSTALL) -m 0640 -o $(BIN_OWNER) -g $(RTGROUP) etc/RT_SiteConfig.pm $(DESTDIR)$(SITE_CONFIG_FILE) + @echo "Installed configuration. About to install RT in $(RT_PATH)" test: - $(PERL) -Ilib lib/t/00smoke.t - -regression-install: config-install - $(PERL) -pi -e 's/Set\(\$$DatabaseName.*\);/Set\(\$$DatabaseName, "rt3regression"\);/' $(DESTDIR)/$(CONFIG_FILE) + $(PERL) "-MExtUtils::Command::MM" -e "test_harness($(TEST_VERBOSE), 'lib')" $(TEST_FILES) -regression: regression-install dirs files-install libs-install sbin-install bin-install regression-instruct regression-reset-db testify-pods fixperms apachectl run-regression +parallel-test: test-parallel -run-regression: - prove -Ilib lib/t/setup_regression.t lib/t/autogen/ lib/t/regression/ - - -regression-noapache: regression-install dirs files-install libs-install sbin-install bin-install regression-instruct regression-reset-db testify-pods fixperms start-httpd run-regression - -regression-quiet: - $(PERL) sbin/regression_harness - -regression-instruct: - @echo "About to wipe your database for a regression test. ABORT NOW with Control-C" +test-parallel: + RT_TEST_PARALLEL=1 HARNESS_OPTIONS="j4" $(PERL) "-MExtUtils::Command::MM" -e "test_harness($(TEST_VERBOSE), 'lib')" $(TEST_FILES) +regression-install: config-install + $(PERL) -pi -e 's/Set\(\$$DatabaseName.*\);/Set\(\$$DatabaseName, "rt3regression"\);/' $(DESTDIR)$(CONFIG_FILE) # {{{ database-installation -regression-reset-db: - $(PERL) $(DESTDIR)/$(RT_SBIN_PATH)/rt-setup-database --action drop --dba $(DB_DBA) --dba-password '' --force - $(PERL) $(DESTDIR)/$(RT_SBIN_PATH)/rt-setup-database --action init --dba $(DB_DBA) --dba-password '' +regression-reset-db: force-dropdb + $(PERL) -I$(LOCAL_LIB_PATH) -I$(RT_LIB_PATH) sbin/rt-setup-database --action init --dba $(DB_DBA) --dba-password '' initdb :: initialize-database initialize-database: - $(PERL) $(DESTDIR)/$(RT_SBIN_PATH)/rt-setup-database --action init --dba $(DB_DBA) --prompt-for-dba-password + $(PERL) -I$(LOCAL_LIB_PATH) -I$(RT_LIB_PATH) sbin/rt-setup-database --action init --dba $(DB_DBA) --prompt-for-dba-password dropdb: - $(PERL) $(DESTDIR)/$(RT_SBIN_PATH)/rt-setup-database --action drop --dba $(DB_DBA) --prompt-for-dba-password + $(PERL) -I$(LOCAL_LIB_PATH) -I$(RT_LIB_PATH) sbin/rt-setup-database --action drop --dba $(DB_DBA) --prompt-for-dba-password + +force-dropdb: + $(PERL) -I$(LOCAL_LIB_PATH) -I$(RT_LIB_PATH) sbin/rt-setup-database --action drop --dba $(DB_DBA) --dba-password '' --force -insert-approval-data: - $(PERL) $(DESTDIR)/$(RT_SBIN_PATH)/insert_approval_scrips # }}} # {{{ libs-install libs-install: - [ -d $(DESTDIR)/$(RT_LIB_PATH) ] || mkdir -p $(DESTDIR)/$(RT_LIB_PATH) - -cp -rp lib/* $(DESTDIR)/$(RT_LIB_PATH) + [ -d $(DESTDIR)$(RT_LIB_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_LIB_PATH) + -( cd lib && find . -type d -print ) | while read dir ; do \ + $(INSTALL) -m 0755 -d "$(DESTDIR)$(RT_LIB_PATH)/$$dir" ; \ + done + -( cd lib && find . -type f -print ) | while read file ; do \ + $(INSTALL) -m 0644 "lib/$$file" "$(DESTDIR)$(RT_LIB_PATH)/$$file" ; \ + done # }}} # {{{ html-install html-install: - [ -d $(DESTDIR)/$(MASON_HTML_PATH) ] || mkdir -p $(DESTDIR)/$(MASON_HTML_PATH) - -cp -rp ./html/* $(DESTDIR)/$(MASON_HTML_PATH) + [ -d $(DESTDIR)$(MASON_HTML_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(MASON_HTML_PATH) + -( cd share/html && find . -type d -print ) | while read dir ; do \ + $(INSTALL) -m 0755 -d "$(DESTDIR)$(MASON_HTML_PATH)/$$dir" ; \ + done + -( cd share/html && find . -type f -print ) | while read file ; do \ + $(INSTALL) -m 0644 "share/html/$$file" "$(DESTDIR)$(MASON_HTML_PATH)/$$file" ; \ + done # }}} # {{{ doc-install doc-install: # RT 3.0.0 - RT 3.0.2 would accidentally create a file instead of a dir - -[ -f $(DESTDIR)/$(RT_DOC_PATH) ] && rm $(DESTDIR)/$(RT_DOC_PATH) - [ -d $(DESTDIR)/$(RT_DOC_PATH) ] || mkdir -p $(DESTDIR)/$(RT_DOC_PATH) - -cp -rp ./README $(DESTDIR)/$(RT_DOC_PATH) + -[ -f $(DESTDIR)$(RT_DOC_PATH) ] && rm $(DESTDIR)$(RT_DOC_PATH) + [ -d $(DESTDIR)$(RT_DOC_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_DOC_PATH) + -$(INSTALL) -m 0644 ./README $(DESTDIR)$(RT_DOC_PATH)/ # }}} # {{{ etc-install etc-install: - mkdir -p $(DESTDIR)/$(RT_ETC_PATH) - -cp -rp \ - etc/acl.* \ - etc/initialdata \ - etc/schema.* \ - $(DESTDIR)/$(RT_ETC_PATH) + [ -d $(DESTDIR)$(RT_ETC_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_ETC_PATH) + for file in $(ETC_FILES) ; do \ + $(INSTALL) -m 0644 "etc/$$file" "$(DESTDIR)$(RT_ETC_PATH)/" ; \ + done # }}} # {{{ sbin-install sbin-install: - mkdir -p $(DESTDIR)/$(RT_SBIN_PATH) - chmod +x \ - sbin/rt-dump-database \ - sbin/rt-setup-database \ - sbin/rt-test-dependencies - -cp -rp \ - sbin/rt-dump-database \ - sbin/rt-setup-database \ - sbin/rt-test-dependencies \ - $(DESTDIR)/$(RT_SBIN_PATH) + $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_SBIN_PATH) + for file in $(SYSTEM_BINARIES) ; do \ + $(INSTALL) -o $(BIN_OWNER) -g $(RTGROUP) -m 0755 "sbin/$$file" "$(DESTDIR)$(RT_SBIN_PATH)/" ; \ + done # }}} # {{{ bin-install bin-install: - mkdir -p $(DESTDIR)/$(RT_BIN_PATH) - chmod +x bin/rt-mailgate \ - bin/rt-crontool - -cp -rp \ - bin/rt-mailgate \ - bin/mason_handler.fcgi \ - bin/mason_handler.scgi \ - bin/standalone_httpd \ - bin/mason_handler.svc \ - bin/rt \ - bin/webmux.pl \ - bin/rt-crontool \ - $(DESTDIR)/$(RT_BIN_PATH) -# }}} + $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_BIN_PATH) + for file in $(BINARIES) ; do \ + $(INSTALL) -o $(BIN_OWNER) -g $(RTGROUP) -m 0755 "bin/$$file" "$(DESTDIR)$(RT_BIN_PATH)/" ; \ + done # {{{ local-install local-install: - -cp -rp ./local/html/* $(DESTDIR)/$(MASON_LOCAL_HTML_PATH) - -cp -rp ./local/po/* $(DESTDIR)/$(LOCAL_LEXICON_PATH) - -cp -rp ./local/etc/* $(DESTDIR)/$(LOCAL_ETC_PATH) + -( cd local/html && find . -type d -print ) | while read dir ; do \ + $(INSTALL) -m 0755 -d "$(DESTDIR)$(MASON_LOCAL_HTML_PATH)/$$dir" ; \ + done + -( cd local/html && find . -type f -print ) | while read file ; do \ + $(INSTALL) -m 0644 "local/html/$$file" "$(DESTDIR)$(MASON_LOCAL_HTML_PATH)/$$file" ; \ + done + -( cd local/po && find . -type d -print ) | while read dir ; do \ + $(INSTALL) -m 0755 -d "$(DESTDIR)$(LOCAL_LEXICON_PATH)/$$dir" ; \ + done + -( cd local/po && find . -type f -print ) | while read file ; do \ + $(INSTALL) -m 0644 "local/po/$$file" "$(DESTDIR)$(LOCAL_LEXICON_PATH)/$$file" ; \ + done + -( cd local/etc && find . -type d -print ) | while read dir ; do \ + $(INSTALL) -m 0755 -d "$(DESTDIR)$(LOCAL_ETC_PATH)/$$dir" ; \ + done + -( cd local/etc && find . -type f -print ) | while read file ; do \ + $(INSTALL) -m 0644 "etc/$$file" "$(DESTDIR)$(LOCAL_ETC_PATH)/$$file" ; \ + done # }}} # {{{ Best Practical Build targets -- no user servicable parts inside - -POD2TEST_EXE = sbin/extract_pod_tests - -testify-pods: - [ -d lib/t/autogen ] || mkdir lib/t/autogen - find lib -name \*pm | grep -v \*.in |xargs -n 1 $(PERL) $(POD2TEST_EXE) - find bin -type f | grep -v \~ | grep -v "\.in" | xargs -n 1 $(PERL) $(POD2TEST_EXE) - find lib -name \*pm | grep -v \*.in |xargs -n 1 $(PERL) $(POD2TEST_EXE) - find bin -type f | grep -v \~ | grep -v "\.in" | xargs -n 1 $(PERL) $(POD2TEST_EXE) - - - regenerate-catalogs: $(PERL) sbin/extract-message-catalog @@ -486,9 +500,20 @@ reconfigure: start-httpd: $(PERL) bin/standalone_httpd & +start-server: + $(PERL) sbin/rt-server & + apachectl: $(APACHECTL) stop sleep 10 $(APACHECTL) start sleep 5 + +SNAPSHOT=$(shell git describe --tags) +snapshot: + git archive --prefix "$(SNAPSHOT)/" HEAD | tar -xf - + ( cd $(SNAPSHOT) && autoconf && ./configure ) + tar -czf "$(SNAPSHOT).tar.gz" "$(SNAPSHOT)/" + rm -fr "$(SNAPSHOT)/" + # }}} diff --git a/rt/Makefile.in b/rt/Makefile.in index 33a5dae1b..19a5f6849 100644 --- a/rt/Makefile.in +++ b/rt/Makefile.in @@ -1,8 +1,8 @@ # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: -# -# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -51,19 +51,22 @@ # -PERL = @PERL@ +PERL = @PERL@ +INSTALL = @INSTALL@ + +RT_LAYOUT = @rt_layout_name@ -CONFIG_FILE_PATH = @CONFIG_FILE_PATH@ -CONFIG_FILE = $(CONFIG_FILE_PATH)/RT_Config.pm -SITE_CONFIG_FILE = $(CONFIG_FILE_PATH)/RT_SiteConfig.pm +CONFIG_FILE_PATH = @CONFIG_FILE_PATH_R@ +CONFIG_FILE = $(CONFIG_FILE_PATH)/RT_Config.pm +SITE_CONFIG_FILE = $(CONFIG_FILE_PATH)/RT_SiteConfig.pm RT_VERSION_MAJOR = @RT_VERSION_MAJOR@ RT_VERSION_MINOR = @RT_VERSION_MINOR@ RT_VERSION_PATCH = @RT_VERSION_PATCH@ -RT_VERSION = $(RT_VERSION_MAJOR).$(RT_VERSION_MINOR).$(RT_VERSION_PATCH) -TAG = rt-$(RT_VERSION_MAJOR)-$(RT_VERSION_MINOR)-$(RT_VERSION_PATCH) +RT_VERSION = $(RT_VERSION_MAJOR).$(RT_VERSION_MINOR).$(RT_VERSION_PATCH) +TAG = rt-$(RT_VERSION_MAJOR)-$(RT_VERSION_MINOR)-$(RT_VERSION_PATCH) # This is the group that all of the installed files will be chgrp'ed to. @@ -88,28 +91,31 @@ APACHECTL = @APACHECTL@ # {{{ Files and directories # DESTDIR allows you to specify that RT be installed somewhere other than -# where it will eventually reside +# where it will eventually reside. DESTDIR _must_ have a trailing slash +# if it's defined. DESTDIR = -RT_PATH = @RT_PATH@ -RT_ETC_PATH = @RT_ETC_PATH@ -RT_BIN_PATH = @RT_BIN_PATH@ -RT_SBIN_PATH = @RT_SBIN_PATH@ -RT_LIB_PATH = @RT_LIB_PATH@ -RT_MAN_PATH = @RT_MAN_PATH@ -RT_VAR_PATH = @RT_VAR_PATH@ -RT_DOC_PATH = @RT_DOC_PATH@ -RT_LOCAL_PATH = @RT_LOCAL_PATH@ -LOCAL_ETC_PATH = @LOCAL_ETC_PATH@ -LOCAL_LIB_PATH = @LOCAL_LIB_PATH@ -LOCAL_LEXICON_PATH = @LOCAL_LEXICON_PATH@ -MASON_HTML_PATH = @MASON_HTML_PATH@ -MASON_LOCAL_HTML_PATH = @MASON_LOCAL_HTML_PATH@ -MASON_DATA_PATH = @MASON_DATA_PATH@ -MASON_SESSION_PATH = @MASON_SESSION_PATH@ -RT_LOG_PATH = @RT_LOG_PATH@ + +RT_PATH = @RT_PATH_R@ +RT_ETC_PATH = @RT_ETC_PATH_R@ +RT_BIN_PATH = @RT_BIN_PATH_R@ +RT_SBIN_PATH = @RT_SBIN_PATH_R@ +RT_LIB_PATH = @RT_LIB_PATH_R@ +RT_MAN_PATH = @RT_MAN_PATH_R@ +RT_VAR_PATH = @RT_VAR_PATH_R@ +RT_DOC_PATH = @RT_DOC_PATH_R@ +RT_LOCAL_PATH = @RT_LOCAL_PATH_R@ +LOCAL_PLUGIN_PATH = @RT_LOCAL_PATH_R@/plugins +LOCAL_ETC_PATH = @LOCAL_ETC_PATH_R@ +LOCAL_LIB_PATH = @LOCAL_LIB_PATH_R@ +LOCAL_LEXICON_PATH = @LOCAL_LEXICON_PATH_R@ +MASON_HTML_PATH = @MASON_HTML_PATH_R@ +MASON_LOCAL_HTML_PATH = @MASON_LOCAL_HTML_PATH_R@ +MASON_DATA_PATH = @MASON_DATA_PATH_R@ +MASON_SESSION_PATH = @MASON_SESSION_PATH_R@ +RT_LOG_PATH = @RT_LOG_PATH_R@ # RT_READABLE_DIR_MODE is the mode of directories that are generally meant # to be accessable @@ -121,34 +127,69 @@ RT_READABLE_DIR_MODE = 0755 # {{{ all these define the places that RT's binaries should get installed # RT_MODPERL_HANDLER is the mason handler script for mod_perl -RT_MODPERL_HANDLER = $(RT_BIN_PATH)/webmux.pl +RT_MODPERL_HANDLER = webmux.pl # RT_STANDALONE_SERVER is a stand-alone HTTP server -RT_STANDALONE_SERVER = $(RT_BIN_PATH)/standalone_httpd +RT_STANDALONE_SERVER = standalone_httpd # RT_SPEEDYCGI_HANDLER is the mason handler script for SpeedyCGI -RT_SPEEDYCGI_HANDLER = $(RT_BIN_PATH)/mason_handler.scgi +RT_SPEEDYCGI_HANDLER = mason_handler.scgi # RT_FASTCGI_HANDLER is the mason handler script for FastCGI -RT_FASTCGI_HANDLER = $(RT_BIN_PATH)/mason_handler.fcgi +RT_FASTCGI_HANDLER = mason_handler.fcgi # RT_WIN32_FASTCGI_HANDLER is the mason handler script for FastCGI -RT_WIN32_FASTCGI_HANDLER = $(RT_BIN_PATH)/mason_handler.svc +RT_WIN32_FASTCGI_HANDLER = mason_handler.svc # RT's CLI -RT_CLI_BIN = $(RT_BIN_PATH)/rt +RT_CLI_BIN = rt # RT's mail gateway -RT_MAILGATE_BIN = $(RT_BIN_PATH)/rt-mailgate +RT_MAILGATE_BIN = rt-mailgate # RT's cron tool -RT_CRON_BIN = $(RT_BIN_PATH)/rt-crontool +RT_CRON_BIN = rt-crontool # }}} -BINARIES = $(DESTDIR)/$(RT_MODPERL_HANDLER) \ - $(DESTDIR)/$(RT_MAILGATE_BIN) \ - $(DESTDIR)/$(RT_CLI_BIN) \ - $(DESTDIR)/$(RT_CRON_BIN) \ - $(DESTDIR)/$(RT_STANDALONE_SERVER) \ - $(DESTDIR)/$(RT_SPEEDYCGI_HANDLER) \ - $(DESTDIR)/$(RT_FASTCGI_HANDLER) \ - $(DESTDIR)/$(RT_WIN32_FASTCGI_HANDLER) -SYSTEM_BINARIES = $(DESTDIR)/$(RT_SBIN_PATH)/ +BINARIES = $(RT_MODPERL_HANDLER) \ + $(RT_MAILGATE_BIN) \ + $(RT_CLI_BIN) \ + $(RT_CRON_BIN) \ + $(RT_STANDALONE_SERVER) \ + $(RT_SPEEDYCGI_HANDLER) \ + $(RT_FASTCGI_HANDLER) \ + $(RT_WIN32_FASTCGI_HANDLER) + + + + + +SYSTEM_BINARIES = rt-dump-database \ + rt-setup-database \ + rt-email-digest \ + rt-email-dashboards \ + rt-email-group-admin \ + rt-server \ + rt-test-dependencies \ + rt-clean-sessions \ + rt-shredder \ + rt-validator + + +ETC_FILES = acl.Informix \ + acl.Pg \ + acl.Oracle \ + acl.mysql \ + acl.Sybase \ + schema.Informix \ + schema.Pg \ + schema.Oracle \ + schema.mysql-4.0 \ + schema.mysql-4.1 \ + schema.Sybase \ + schema.SQLite \ + initialdata + +# }}} + +# {{{ Web frontend + +WEB_HANDLER = @WEB_HANDLER@ # }}} @@ -205,18 +246,21 @@ DB_RT_PASS = @DB_RT_PASS@ # }}} +TEST_FILES = t/*.t t/*/*.t +TEST_VERBOSE = 0 + + #################################################################### all: default default: - @echo "Please read RT's readme before installing. Not doing so could" - @echo "be dangerous." + @echo "Please read RT's README before beginning your installation." instruct: - @echo "Congratulations. RT has been installed. " + @echo "Congratulations. RT is now installed." @echo "" @echo "" @echo "You must now configure RT by editing $(SITE_CONFIG_FILE)." @@ -230,244 +274,214 @@ instruct: @echo "After that, you need to initialize RT's database by running" @echo " 'make initialize-database'" -# @echo " or by executing " -# @echo " '$(RT_SBIN_PATH)/rt-setup-database --action init \ " -# @echo " --dba $(DB_DBA) --prompt-for-dba-password'" - - upgrade-instruct: - @echo "Congratulations. RT has been upgraded. You should now check-over" + @echo "Congratulations. RT has been upgraded. You should now check over" @echo "$(CONFIG_FILE) for any necessary site customization. Additionally," @echo "you should update RT's system database objects by running " - @echo " ls etc/upgrade" - @echo "" - @echo "For each item in that directory whose name is greater than" - @echo "your previously installed RT version, run:" - @echo " $(RT_SBIN_PATH)/rt-setup-database --dba $(DB_DBA) --prompt-for-dba-password --action schema --datadir etc/upgrade/" - @echo " $(RT_SBIN_PATH)/rt-setup-database --dba $(DB_DBA) --prompt-for-dba-password --action acl --datadir etc/upgrade/" - @echo " $(RT_SBIN_PATH)/rt-setup-database --dba $(DB_DBA) --prompt-for-dba-password --action insert --datadir etc/upgrade/" + @echo " $(RT_SBIN_PATH)/rt-setup-database --dba $(DB_DBA) --prompt-for-dba-password --action upgrade" -upgrade: config-install dirs files-install fixperms upgrade-instruct +upgrade: testdeps config-install dirs files-install fixperms upgrade-instruct -upgrade-noclobber: config-install libs-install html-install bin-install local-install doc-install fixperms +upgrade-noclobber: config-install dirs libs-install html-install bin-install local-install doc-install fixperms # {{{ dependencies + +my_with_web_handlers= $(shell $(PERL) -e 'print join " ", map "--with-$$_", grep defined && length, split /,/, "$(WEB_HANDLER)"') testdeps: - $(PERL) ./sbin/rt-test-dependencies --verbose --with-$(DB_TYPE) + $(PERL) ./sbin/rt-test-dependencies --verbose --with-$(DB_TYPE) $(my_with_web_handlers) depends: fixdeps fixdeps: - $(PERL) ./sbin/rt-test-dependencies --verbose --install --with-$(DB_TYPE) + $(PERL) ./sbin/rt-test-dependencies --verbose --install --with-$(DB_TYPE) $(my_with_web_handlers) #}}} # {{{ fixperms fixperms: # Make the libraries readable - chmod $(RT_READABLE_DIR_MODE) $(DESTDIR)/$(RT_PATH) - chown -R $(LIBS_OWNER) $(DESTDIR)/$(RT_LIB_PATH) - chgrp -R $(LIBS_GROUP) $(DESTDIR)/$(RT_LIB_PATH) - chmod -R u+rwX,go-w,go+rX $(DESTDIR)/$(RT_LIB_PATH) + chmod $(RT_READABLE_DIR_MODE) $(DESTDIR)$(RT_PATH) + chown -R $(LIBS_OWNER) $(DESTDIR)$(RT_LIB_PATH) + chgrp -R $(LIBS_GROUP) $(DESTDIR)$(RT_LIB_PATH) + chmod -R u+rwX,go-w,go+rX $(DESTDIR)$(RT_LIB_PATH) - chmod $(RT_READABLE_DIR_MODE) $(DESTDIR)/$(RT_BIN_PATH) - chmod $(RT_READABLE_DIR_MODE) $(DESTDIR)/$(RT_BIN_PATH) + chmod $(RT_READABLE_DIR_MODE) $(DESTDIR)$(RT_BIN_PATH) - chmod 0755 $(DESTDIR)/$(RT_ETC_PATH) - chmod 0500 $(DESTDIR)/$(RT_ETC_PATH)/* + chmod 0755 $(DESTDIR)$(RT_ETC_PATH) + cd $(DESTDIR)$(RT_ETC_PATH) && chmod 0400 $(ETC_FILES) #TODO: the config file should probably be able to have its # owner set separately from the binaries. - chown -R $(BIN_OWNER) $(DESTDIR)/$(RT_ETC_PATH) - chgrp -R $(RTGROUP) $(DESTDIR)/$(RT_ETC_PATH) + chown -R $(BIN_OWNER) $(DESTDIR)$(RT_ETC_PATH) + chgrp -R $(RTGROUP) $(DESTDIR)$(RT_ETC_PATH) + + chmod 0440 $(DESTDIR)$(CONFIG_FILE) + chmod 0640 $(DESTDIR)$(SITE_CONFIG_FILE) - chmod 0550 $(DESTDIR)/$(CONFIG_FILE) - chmod 0550 $(DESTDIR)/$(SITE_CONFIG_FILE) + # Make the system binaries + cd $(DESTDIR)$(RT_BIN_PATH) && ( chmod 0755 $(BINARIES) ; chown $(BIN_OWNER) $(BINARIES); chgrp $(RTGROUP) $(BINARIES)) - # Make the interfaces executable - chown $(BIN_OWNER) $(BINARIES) - chgrp $(RTGROUP) $(BINARIES) - chmod 0755 $(BINARIES) + # Make the system binaries executable also + cd $(DESTDIR)$(RT_SBIN_PATH) && ( chmod 0755 $(SYSTEM_BINARIES) ; chown $(BIN_OWNER) $(SYSTEM_BINARIES); chgrp $(RTGROUP) $(SYSTEM_BINARIES)) # Make the web ui readable by all. - chmod -R u+rwX,go-w,go+rX $(DESTDIR)/$(MASON_HTML_PATH) \ - $(DESTDIR)/$(MASON_LOCAL_HTML_PATH) \ - $(DESTDIR)/$(LOCAL_LEXICON_PATH) - chown -R $(LIBS_OWNER) $(DESTDIR)/$(MASON_HTML_PATH) \ - $(DESTDIR)/$(MASON_LOCAL_HTML_PATH) - chgrp -R $(LIBS_GROUP) $(DESTDIR)/$(MASON_HTML_PATH) \ - $(DESTDIR)/$(MASON_LOCAL_HTML_PATH) + chmod -R u+rwX,go-w,go+rX $(DESTDIR)$(MASON_HTML_PATH) \ + $(DESTDIR)$(MASON_LOCAL_HTML_PATH) \ + $(DESTDIR)$(LOCAL_LEXICON_PATH) + chown -R $(LIBS_OWNER) $(DESTDIR)$(MASON_HTML_PATH) \ + $(DESTDIR)$(MASON_LOCAL_HTML_PATH) + chgrp -R $(LIBS_GROUP) $(DESTDIR)$(MASON_HTML_PATH) \ + $(DESTDIR)$(MASON_LOCAL_HTML_PATH) # Make the web ui's data dir writable - chmod 0770 $(DESTDIR)/$(MASON_DATA_PATH) \ - $(DESTDIR)/$(MASON_SESSION_PATH) - chown -R $(WEB_USER) $(DESTDIR)/$(MASON_DATA_PATH) \ - $(DESTDIR)/$(MASON_SESSION_PATH) - chgrp -R $(WEB_GROUP) $(DESTDIR)/$(MASON_DATA_PATH) \ - $(DESTDIR)/$(MASON_SESSION_PATH) + chmod 0770 $(DESTDIR)$(MASON_DATA_PATH) \ + $(DESTDIR)$(MASON_SESSION_PATH) + chown -R $(WEB_USER) $(DESTDIR)$(MASON_DATA_PATH) \ + $(DESTDIR)$(MASON_SESSION_PATH) + chgrp -R $(WEB_GROUP) $(DESTDIR)$(MASON_DATA_PATH) \ + $(DESTDIR)$(MASON_SESSION_PATH) # }}} # {{{ dirs dirs: - mkdir -p $(DESTDIR)/$(RT_LOG_PATH) - mkdir -p $(DESTDIR)/$(MASON_DATA_PATH) - mkdir -p $(DESTDIR)/$(MASON_DATA_PATH)/cache - mkdir -p $(DESTDIR)/$(MASON_DATA_PATH)/etc - mkdir -p $(DESTDIR)/$(MASON_DATA_PATH)/obj - mkdir -p $(DESTDIR)/$(MASON_SESSION_PATH) - mkdir -p $(DESTDIR)/$(MASON_HTML_PATH) - mkdir -p $(DESTDIR)/$(MASON_LOCAL_HTML_PATH) - mkdir -p $(DESTDIR)/$(LOCAL_ETC_PATH) - mkdir -p $(DESTDIR)/$(LOCAL_LIB_PATH) - mkdir -p $(DESTDIR)/$(LOCAL_LEXICON_PATH) + $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_LOG_PATH) + $(INSTALL) -m 0770 -d $(DESTDIR)$(MASON_DATA_PATH) + $(INSTALL) -m 0770 -d $(DESTDIR)$(MASON_DATA_PATH)/cache + $(INSTALL) -m 0770 -d $(DESTDIR)$(MASON_DATA_PATH)/etc + $(INSTALL) -m 0770 -d $(DESTDIR)$(MASON_DATA_PATH)/obj + $(INSTALL) -m 0770 -d $(DESTDIR)$(MASON_SESSION_PATH) + $(INSTALL) -m 0755 -d $(DESTDIR)$(MASON_HTML_PATH) + $(INSTALL) -m 0755 -d $(DESTDIR)$(MASON_LOCAL_HTML_PATH) + $(INSTALL) -m 0755 -d $(DESTDIR)$(LOCAL_ETC_PATH) + $(INSTALL) -m 0755 -d $(DESTDIR)$(LOCAL_LIB_PATH) + $(INSTALL) -m 0755 -d $(DESTDIR)$(LOCAL_PLUGIN_PATH) + $(INSTALL) -m 0755 -d $(DESTDIR)$(LOCAL_LEXICON_PATH) # }}} -install: config-install dirs files-install fixperms instruct +install: testdeps config-install dirs files-install fixperms instruct -files-install: libs-install etc-install bin-install sbin-install html-install local-install doc-install +files-install: libs-install etc-install config-install bin-install sbin-install html-install local-install doc-install config-install: - mkdir -p $(DESTDIR)/$(CONFIG_FILE_PATH) - -cp etc/RT_Config.pm $(DESTDIR)/$(CONFIG_FILE) - [ -f $(DESTDIR)/$(SITE_CONFIG_FILE) ] || cp etc/RT_SiteConfig.pm $(DESTDIR)/$(SITE_CONFIG_FILE) - - chgrp $(RTGROUP) $(DESTDIR)/$(CONFIG_FILE) - chown $(BIN_OWNER) $(DESTDIR)/$(CONFIG_FILE) - - chgrp $(RTGROUP) $(DESTDIR)/$(SITE_CONFIG_FILE) - chown $(BIN_OWNER) $(DESTDIR)/$(SITE_CONFIG_FILE) - - @echo "Installed configuration. about to install rt in $(RT_PATH)" +@COMMENT_INPLACE_LAYOUT@ $(INSTALL) -m 0755 -o $(BIN_OWNER) -g $(RTGROUP) -d $(DESTDIR)$(CONFIG_FILE_PATH) +@COMMENT_INPLACE_LAYOUT@ -$(INSTALL) -m 0440 -o $(BIN_OWNER) -g $(RTGROUP) etc/RT_Config.pm $(DESTDIR)$(CONFIG_FILE) +@COMMENT_INPLACE_LAYOUT@ [ -f $(DESTDIR)$(SITE_CONFIG_FILE) ] || $(INSTALL) -m 0640 -o $(BIN_OWNER) -g $(RTGROUP) etc/RT_SiteConfig.pm $(DESTDIR)$(SITE_CONFIG_FILE) +@COMMENT_INPLACE_LAYOUT@ @echo "Installed configuration. About to install RT in $(RT_PATH)" test: - $(PERL) -Ilib lib/t/00smoke.t - -regression-install: config-install - $(PERL) -pi -e 's/Set\(\$$DatabaseName.*\);/Set\(\$$DatabaseName, "rt3regression"\);/' $(DESTDIR)/$(CONFIG_FILE) + $(PERL) "-MExtUtils::Command::MM" -e "test_harness($(TEST_VERBOSE), 'lib')" $(TEST_FILES) -regression: regression-install dirs files-install libs-install sbin-install bin-install regression-instruct regression-reset-db testify-pods fixperms apachectl run-regression +parallel-test: test-parallel -run-regression: - prove -Ilib lib/t/setup_regression.t lib/t/autogen/ lib/t/regression/ - - -regression-noapache: regression-install dirs files-install libs-install sbin-install bin-install regression-instruct regression-reset-db testify-pods fixperms start-httpd run-regression - -regression-quiet: - $(PERL) sbin/regression_harness - -regression-instruct: - @echo "About to wipe your database for a regression test. ABORT NOW with Control-C" +test-parallel: + RT_TEST_PARALLEL=1 HARNESS_OPTIONS="j4" $(PERL) "-MExtUtils::Command::MM" -e "test_harness($(TEST_VERBOSE), 'lib')" $(TEST_FILES) +regression-install: config-install + $(PERL) -pi -e 's/Set\(\$$DatabaseName.*\);/Set\(\$$DatabaseName, "rt3regression"\);/' $(DESTDIR)$(CONFIG_FILE) # {{{ database-installation -regression-reset-db: - $(PERL) $(DESTDIR)/$(RT_SBIN_PATH)/rt-setup-database --action drop --dba $(DB_DBA) --dba-password '' --force - $(PERL) $(DESTDIR)/$(RT_SBIN_PATH)/rt-setup-database --action init --dba $(DB_DBA) --dba-password '' +regression-reset-db: force-dropdb + $(PERL) -I$(LOCAL_LIB_PATH) -I$(RT_LIB_PATH) sbin/rt-setup-database --action init --dba $(DB_DBA) --dba-password '' initdb :: initialize-database initialize-database: - $(PERL) $(DESTDIR)/$(RT_SBIN_PATH)/rt-setup-database --action init --dba $(DB_DBA) --prompt-for-dba-password + $(PERL) -I$(LOCAL_LIB_PATH) -I$(RT_LIB_PATH) sbin/rt-setup-database --action init --dba $(DB_DBA) --prompt-for-dba-password dropdb: - $(PERL) $(DESTDIR)/$(RT_SBIN_PATH)/rt-setup-database --action drop --dba $(DB_DBA) --prompt-for-dba-password + $(PERL) -I$(LOCAL_LIB_PATH) -I$(RT_LIB_PATH) sbin/rt-setup-database --action drop --dba $(DB_DBA) --prompt-for-dba-password + +force-dropdb: + $(PERL) -I$(LOCAL_LIB_PATH) -I$(RT_LIB_PATH) sbin/rt-setup-database --action drop --dba $(DB_DBA) --dba-password '' --force -insert-approval-data: - $(PERL) $(DESTDIR)/$(RT_SBIN_PATH)/insert_approval_scrips # }}} # {{{ libs-install libs-install: - [ -d $(DESTDIR)/$(RT_LIB_PATH) ] || mkdir -p $(DESTDIR)/$(RT_LIB_PATH) - -cp -rp lib/* $(DESTDIR)/$(RT_LIB_PATH) +@COMMENT_INPLACE_LAYOUT@ [ -d $(DESTDIR)$(RT_LIB_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_LIB_PATH) +@COMMENT_INPLACE_LAYOUT@ -( cd lib && find . -type d -print ) | while read dir ; do \ +@COMMENT_INPLACE_LAYOUT@ $(INSTALL) -m 0755 -d "$(DESTDIR)$(RT_LIB_PATH)/$$dir" ; \ +@COMMENT_INPLACE_LAYOUT@ done +@COMMENT_INPLACE_LAYOUT@ -( cd lib && find . -type f -print ) | while read file ; do \ +@COMMENT_INPLACE_LAYOUT@ $(INSTALL) -m 0644 "lib/$$file" "$(DESTDIR)$(RT_LIB_PATH)/$$file" ; \ +@COMMENT_INPLACE_LAYOUT@ done # }}} # {{{ html-install html-install: - [ -d $(DESTDIR)/$(MASON_HTML_PATH) ] || mkdir -p $(DESTDIR)/$(MASON_HTML_PATH) - -cp -rp ./html/* $(DESTDIR)/$(MASON_HTML_PATH) +@COMMENT_INPLACE_LAYOUT@ [ -d $(DESTDIR)$(MASON_HTML_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(MASON_HTML_PATH) +@COMMENT_INPLACE_LAYOUT@ -( cd share/html && find . -type d -print ) | while read dir ; do \ +@COMMENT_INPLACE_LAYOUT@ $(INSTALL) -m 0755 -d "$(DESTDIR)$(MASON_HTML_PATH)/$$dir" ; \ +@COMMENT_INPLACE_LAYOUT@ done +@COMMENT_INPLACE_LAYOUT@ -( cd share/html && find . -type f -print ) | while read file ; do \ +@COMMENT_INPLACE_LAYOUT@ $(INSTALL) -m 0644 "share/html/$$file" "$(DESTDIR)$(MASON_HTML_PATH)/$$file" ; \ +@COMMENT_INPLACE_LAYOUT@ done # }}} # {{{ doc-install doc-install: - # RT 3.0.0 - RT 3.0.2 would accidentally create a file instead of a dir - -[ -f $(DESTDIR)/$(RT_DOC_PATH) ] && rm $(DESTDIR)/$(RT_DOC_PATH) - [ -d $(DESTDIR)/$(RT_DOC_PATH) ] || mkdir -p $(DESTDIR)/$(RT_DOC_PATH) - -cp -rp ./README $(DESTDIR)/$(RT_DOC_PATH) +@COMMENT_INPLACE_LAYOUT@ # RT 3.0.0 - RT 3.0.2 would accidentally create a file instead of a dir +@COMMENT_INPLACE_LAYOUT@ -[ -f $(DESTDIR)$(RT_DOC_PATH) ] && rm $(DESTDIR)$(RT_DOC_PATH) +@COMMENT_INPLACE_LAYOUT@ [ -d $(DESTDIR)$(RT_DOC_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_DOC_PATH) +@COMMENT_INPLACE_LAYOUT@ -$(INSTALL) -m 0644 ./README $(DESTDIR)$(RT_DOC_PATH)/ # }}} # {{{ etc-install etc-install: - mkdir -p $(DESTDIR)/$(RT_ETC_PATH) - -cp -rp \ - etc/acl.* \ - etc/initialdata \ - etc/schema.* \ - $(DESTDIR)/$(RT_ETC_PATH) +@COMMENT_INPLACE_LAYOUT@ [ -d $(DESTDIR)$(RT_ETC_PATH) ] || $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_ETC_PATH) +@COMMENT_INPLACE_LAYOUT@ for file in $(ETC_FILES) ; do \ +@COMMENT_INPLACE_LAYOUT@ $(INSTALL) -m 0644 "etc/$$file" "$(DESTDIR)$(RT_ETC_PATH)/" ; \ +@COMMENT_INPLACE_LAYOUT@ done # }}} # {{{ sbin-install sbin-install: - mkdir -p $(DESTDIR)/$(RT_SBIN_PATH) - chmod +x \ - sbin/rt-dump-database \ - sbin/rt-setup-database \ - sbin/rt-test-dependencies - -cp -rp \ - sbin/rt-dump-database \ - sbin/rt-setup-database \ - sbin/rt-test-dependencies \ - $(DESTDIR)/$(RT_SBIN_PATH) +@COMMENT_INPLACE_LAYOUT@ $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_SBIN_PATH) +@COMMENT_INPLACE_LAYOUT@ for file in $(SYSTEM_BINARIES) ; do \ +@COMMENT_INPLACE_LAYOUT@ $(INSTALL) -o $(BIN_OWNER) -g $(RTGROUP) -m 0755 "sbin/$$file" "$(DESTDIR)$(RT_SBIN_PATH)/" ; \ +@COMMENT_INPLACE_LAYOUT@ done # }}} # {{{ bin-install bin-install: - mkdir -p $(DESTDIR)/$(RT_BIN_PATH) - chmod +x bin/rt-mailgate \ - bin/rt-crontool - -cp -rp \ - bin/rt-mailgate \ - bin/mason_handler.fcgi \ - bin/mason_handler.scgi \ - bin/standalone_httpd \ - bin/mason_handler.svc \ - bin/rt \ - bin/webmux.pl \ - bin/rt-crontool \ - $(DESTDIR)/$(RT_BIN_PATH) -# }}} +@COMMENT_INPLACE_LAYOUT@ $(INSTALL) -m 0755 -d $(DESTDIR)$(RT_BIN_PATH) +@COMMENT_INPLACE_LAYOUT@ for file in $(BINARIES) ; do \ +@COMMENT_INPLACE_LAYOUT@ $(INSTALL) -o $(BIN_OWNER) -g $(RTGROUP) -m 0755 "bin/$$file" "$(DESTDIR)$(RT_BIN_PATH)/" ; \ +@COMMENT_INPLACE_LAYOUT@ done # {{{ local-install local-install: - -cp -rp ./local/html/* $(DESTDIR)/$(MASON_LOCAL_HTML_PATH) - -cp -rp ./local/po/* $(DESTDIR)/$(LOCAL_LEXICON_PATH) - -cp -rp ./local/etc/* $(DESTDIR)/$(LOCAL_ETC_PATH) + -( cd local/html && find . -type d -print ) | while read dir ; do \ + $(INSTALL) -m 0755 -d "$(DESTDIR)$(MASON_LOCAL_HTML_PATH)/$$dir" ; \ + done + -( cd local/html && find . -type f -print ) | while read file ; do \ + $(INSTALL) -m 0644 "local/html/$$file" "$(DESTDIR)$(MASON_LOCAL_HTML_PATH)/$$file" ; \ + done + -( cd local/po && find . -type d -print ) | while read dir ; do \ + $(INSTALL) -m 0755 -d "$(DESTDIR)$(LOCAL_LEXICON_PATH)/$$dir" ; \ + done + -( cd local/po && find . -type f -print ) | while read file ; do \ + $(INSTALL) -m 0644 "local/po/$$file" "$(DESTDIR)$(LOCAL_LEXICON_PATH)/$$file" ; \ + done + -( cd local/etc && find . -type d -print ) | while read dir ; do \ + $(INSTALL) -m 0755 -d "$(DESTDIR)$(LOCAL_ETC_PATH)/$$dir" ; \ + done + -( cd local/etc && find . -type f -print ) | while read file ; do \ + $(INSTALL) -m 0644 "etc/$$file" "$(DESTDIR)$(LOCAL_ETC_PATH)/$$file" ; \ + done # }}} # {{{ Best Practical Build targets -- no user servicable parts inside - -POD2TEST_EXE = sbin/extract_pod_tests - -testify-pods: - [ -d lib/t/autogen ] || mkdir lib/t/autogen - find lib -name \*pm | grep -v \*.in |xargs -n 1 $(PERL) $(POD2TEST_EXE) - find bin -type f | grep -v \~ | grep -v "\.in" | xargs -n 1 $(PERL) $(POD2TEST_EXE) - find lib -name \*pm | grep -v \*.in |xargs -n 1 $(PERL) $(POD2TEST_EXE) - find bin -type f | grep -v \~ | grep -v "\.in" | xargs -n 1 $(PERL) $(POD2TEST_EXE) - - - regenerate-catalogs: $(PERL) sbin/extract-message-catalog @@ -486,9 +500,20 @@ reconfigure: start-httpd: $(PERL) bin/standalone_httpd & +start-server: + $(PERL) sbin/rt-server & + apachectl: $(APACHECTL) stop sleep 10 $(APACHECTL) start sleep 5 + +SNAPSHOT=$(shell git describe --tags) +snapshot: + git archive --prefix "$(SNAPSHOT)/" HEAD | tar -xf - + ( cd $(SNAPSHOT) && autoconf && ./configure ) + tar -czf "$(SNAPSHOT).tar.gz" "$(SNAPSHOT)/" + rm -fr "$(SNAPSHOT)/" + # }}} diff --git a/rt/README b/rt/README index 398d8c422..1f78515a6 100755 --- a/rt/README +++ b/rt/README @@ -15,14 +15,13 @@ us at sales@bestpractical.com. March, 2005 -REQUIRED PACKAGES: ------------------- +REQUIRED PACKAGES +----------------- -o Perl 5.8.3 or later (http://www.perl.com). +o Perl 5.8.3 or later (http://www.perl.org). - Perl versions prior to 5.8.3 contain bugs that could result - in data corruption. We recommend strongly that you use 5.8.3 - or newer. + Perl versions prior to 5.8.3 contain bugs that could result + in data corruption. RT won't start on older versions. o A supported SQL database @@ -31,25 +30,21 @@ o A supported SQL database Oracle 9iR2 or later. SQLite 3.0. (Not recommended for production) -o Apache version 1.3.x or 2.x (http://httpd.apache.org) - with mod_perl -- (http://perl.apache.org ) - or a webserver with FastCGI support (www.fastcgi.com) - - Compiling mod_perl on Apache 1.3.x as a DSO has been known - to have massive stability problems and is not recommended. - - mod_perl 1.x must be built with EVERYTHING=1 +o Apache version 1.3.x or 2.x (http://httpd.apache.org) + with mod_perl -- (http://perl.apache.org ) + or with FastCGI -- (www.fastcgi.com) + or other webserver with FastCGI support RT's FastCGI handler needs to access RT's configuration file. -o Various and sundry perl modules - A tool included with RT takes care of the installation of - most of these automatically during the install process. +o Various and sundry perl modules + A tool included with RT takes care of the installation of + most of these automatically during the install process. - The tool supplied with RT uses Perl's CPAN system - (http://www.cpan.org) to install modules. Some operating - systems package all or some of the modules required, and - you may be better off installing the modules that way. + The tool supplied with RT uses Perl's CPAN system + (http://www.cpan.org) to install modules. Some operating + systems package all or some of the modules required, and + you may be better off installing the modules that way. GENERAL INSTALLATION @@ -66,20 +61,27 @@ want to read a more comprehensive installation guide at: tar xzvf rt.tar.gz -C /tmp -2 Run the "configure" script. +2 Run the "configure" script. ./configure --help to see the list of options ./configure (with the flags you want) RT defaults to installing in /opt/rt3 with MySQL as its database. It tries to guess which of www-data, www, apache or nobody your webserver - will run as, but you can override that behavior. + will run as, but you can override that behavior. Note that the + default install directory in /opt/rt3 does not work under SELinux's + default configuration. + + If you're upgrading RT then it worth to read UPGRADING document at this + moment. Some extension you're using may have been integrated into + core. It's recommended to use new clean directory when you're + upgrading to new major release (for example from 3.6.x to 3.8.x). 3 Make sure that RT has everything it needs to run. Check for missing dependencies by running: - make testdeps + make testdeps 4 If the script reports any missing dependencies, install them by hand or run the following command as a user who has permission to install perl @@ -87,65 +89,66 @@ want to read a more comprehensive installation guide at: make fixdeps + Some modules require user input or environment variables to install correctly, + so it may be necessary to install them manually. + 5 Check to make sure everything was installed properly. - + make testdeps It might sometimes be necessary to run "make fixdeps" several times to install all necessary perl modules. 6 If this is a new installation: - + As a user with permission to install RT in your chosen directory, type: - make install - + make install + Set up etc/RT_SiteConfig.pm in your RT installation directory. - You'll need to add any values you need to change from the defaults + You'll need to add any values you need to change from the defaults in etc/RT_Config.pm As a user with permission to read RT's configuration file, type: - - make initialize-database + + make initialize-database If the make fails, type: - - make dropdb + + make dropdb and start over from step 6 7 If you're upgrading from RT 3.0 or newer: - Read through the UPGRADING document included in this distribution. - + Read through the UPGRADING document included in this distribution. If + you're using MySQL, read through UPGRADING.mysql as well. + It includes special upgrade instructions that will help you get this new version of RT up and running smoothly. As a user with permission to install RT in your chosen installation - directory, type: + directory, type: - make upgrade + make upgrade This will install new binaries, config files and libraries without - overwriting your RT database. + overwriting your RT database. Update etc/RT_SiteConfig.pm in your RT installation directory. - You'll need to add any new values you need to change from the defaults + You'll need to add any new values you need to change from the defaults in etc/RT_Config.pm - You may also need to update RT's database. To find out, type: + You may also need to update RT's database. You can do this with + the rt-setup-database tool. Replace root with the name of the dba + user on your database (root is the default for MySQL). - ls etc/upgrade + You will be prompted for your previous version of RT (such as 3.6.4) + so that we can calculate which database updates to apply - For each item in that directory whose name is greater than - your previously installed RT version, run: + You should back up your database before running this command. - /opt/rt3/sbin/rt-setup-database --action schema \ - --datadir etc/upgrade/ - /opt/rt3/sbin/rt-setup-database --action acl \ - --datadir etc/upgrade/ - /opt/rt3/sbin/rt-setup-database --action insert \ - --datadir etc/upgrade/ + /opt/rt3/sbin/rt-setup-database --dba root --prompt-for-dba-password --action upgrade Clear mason cache dir: @@ -156,57 +159,122 @@ want to read a more comprehensive installation guide at: 8 If you're upgrading from RT 2.0: - Please upgrade from RT 2.0 to RT 3.2 and then follow the instructions - for section 7. + Use the RT::Extension::RT2toRT3 module to upgrade to the current RT + release. You can download it from CPAN here: + http://search.cpan.org/dist/RT-Extension-RT2toRT3/ -9 Configure the email and web gateways, as described below. +9 Configure the email and web gateways, as described below. - NOTE: root's password for the web interface is "password" + NOTE: root's password for the web interface is "password" (without the quotes). Not changing this is a SECURITY risk! - -10 Set up users, groups, queues, scrips and access control. + +10 Set up automated recurring tasks (cronjobs): + + To generate email digest messages, you must arrange for the provided + utility to be run once daily, and once weekly. You may also want to + arrange for the rt-email-dashboards utility to be run hourly. + For example, if your task scheduler is cron, you can configure it as + follows: + + crontab -e # as the RT administrator (probably root) + # insert the following lines: + 0 0 * * * /opt/rt3/sbin/rt-email-digest -m daily + 0 0 * * 0 /opt/rt3/sbin/rt-email-digest -m weekly + 0 * * * * /opt/rt3/sbin/rt-email-dashboards + + +11 Set up users, groups, queues, scrips and access control. Until you do this, RT will not be able to send or receive email, nor will it be more than marginally functional. This is not an optional step. + + SETTING UP THE WEB INTERFACE ---------------------------- RT's web interface is based around HTML::Mason, which works well with -the mod_perl perl interpreter within Apache httpd and FastCGI +the mod_perl perl interpreter within Apache httpd and FastCGI. + +Once you've set up the web interface, consider setting up automatic +logout for inactive sessions. For more information about how to do that, +run + perldoc /path/to/rt/sbin/rt-clean-sessions + + +mod_perl 1.xx +------------- + +WARNING: mod_perl 1.99_xx is not supported. -mod_perl --------- +See below configuration instructions for mod_perl 2.x -To install RT with mod_perl, you'll need to install the -apache database connection cache. To make sure it's installed, run +To install RT with mod_perl 1.x, you'll need to install the +apache database connection cache. To make sure it's installed, run the following command: - perl -MCPAN -e'install Apache::DBI' + perl -MCPAN -e'install "Apache::DBI"' -Next, add a few lines to your Apache configuration file, so that +Next, add a few lines to your Apache 1.3.xx configuration file, so that it knows where to find RT: ServerName your.rt.server.hostname + DocumentRoot /opt/rt3/share/html AddDefaultCharset UTF-8 + # optional apache logs for RT + # ErrorLog /opt/rt3/var/log/apache.error + # TransferLog /opt/rt3/var/log/apache.access + PerlModule Apache::DBI PerlRequire /opt/rt3/bin/webmux.pl + + SetHandler default + + + SetHandler perl-script + PerlHandler RT::Mason + + + +mod_perl 2.xx +------------- + +WARNING: mod_perl 1.99_xx is not supported. + +Add a few lines to your Apache 2.xx configuration file, so that +it knows where to find RT: + + + ServerName your.rt.server.hostname + + DocumentRoot /opt/rt3/share/html + AddDefaultCharset UTF-8 + + # optional apache logs for RT + # ErrorLog /opt/rt3/var/log/apache2.error + # TransferLog /opt/rt3/var/log/apache2.access + + PerlRequire "/opt/rt3/bin/webmux.pl" + + + SetHandler default + - SetHandler perl-script - PerlHandler RT::Mason + SetHandler perl-script + PerlResponseHandler RT::Mason FastCGI ------- -Installation with FastCGI is a little bit more complex and is documented +Installation with FastCGI is a little bit more complex and is documented in detail at http://wiki.bestpractical.com/index.cgi?FastCGIConfiguration In the most basic configuration, you can set up your webserver to run @@ -215,7 +283,7 @@ can read RT's configuration file. It's important to understand the security implications of this configuration, which are discussed in the document mentioned above. -To install RT with FastCGI, you'll need to add a few lines to your +To install RT with FastCGI, you'll need to add a few lines to your Apache configuration file telling it about RT: @@ -225,18 +293,18 @@ FastCgiIpcDir /tmp FastCgiServer /opt/rt3/bin/mason_handler.fcgi -idle-timeout 120 + ServerName your.rt.server.hostname # Pass through requests to display images Alias /NoAuth/images/ /opt/rt3/share/html/NoAuth/images/ AddHandler fastcgi-script fcgi ScriptAlias / /opt/rt3/bin/mason_handler.fcgi/ - -SETTING UP THE MAIL GATEWAY +SETTING UP THE MAIL GATEWAY --------------------------- To let email flow to your RT server, you need to add a few lines of @@ -265,7 +333,7 @@ are available at http://www.bestpractical.com or by writing to . If you're interested in having RT extended or customized or would like more -information about commercial support options, please send email to +information about commercial support options, please send email to to discuss rates and availability. @@ -273,21 +341,21 @@ information about commercial support options, please send email to RT WEBSITE ---------- -For current information about RT, check out the RT website at - http://www.bestpractical.com/ +For current information about RT, check out the RT website at + http://www.bestpractical.com/ -You'll find screenshots, a pointer to the current version of RT, contributed +You'll find screenshots, a pointer to the current version of RT, contributed patches, and lots of other great stuff. RT-USERS MAILING LIST --------------------- +--------------------- To keep up to date on the latest RT tips, techniques and extensions, you probably want to join the rt-users mailing list. Send a message to: - rt-users-request@lists.bestpractical.com + rt-users-request@lists.bestpractical.com with the body of the message consisting of only the word: @@ -319,8 +387,8 @@ To report a bug, send email to rt-bugs@fsck.com. # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: -# -# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) diff --git a/rt/README.Oracle b/rt/README.Oracle index 41bec822c..9835b93e6 100644 --- a/rt/README.Oracle +++ b/rt/README.Oracle @@ -1,37 +1,41 @@ -In order to install RT with Oracle, the database must first be -prepared. Ports of RT to other databases will automatically create -the RT schema. This is not done for Oracle because most sites wishing -to deploy RT on Oracle will have choose to make specific configuration -of the RT user, for example to select the appropriate tablespace or to -set up a resource profile. The RT user must have appropriate -privileges similar to the resource and connect roles and must have the -"query rewrite" system privilege. - Here is an example of commands to create an RT user called "RT" with -a password of "rt". - - create user rt identified by rt default tablespace users temporary - tablespace temp; - grant resource, connect, query rewrite to rt; - - -RT should not run its schema creation as the Oracle DBA; instead the -schema creation should be run as the RT user. To accomplish this set -the --with-rt-dba configuration parameter to the RT user, not to the -Oracle DBA. As an example, the following might be appropriate to -configure RT for the example.com Oracle database. - - ./configure --prefix /usr/local/rt --with-db-type=Oracle \ - --with-db-dba=rt --with-db-database=example.com \ - --with-db-rt-user=rt \ - --with-db-rt-pass=rt - - -As with all databases it is important to analyze the Schema and get +Since RT 3.8.2 RT deployment on Oracle database has been simplified. + +You don't need to create anything beforehead. During installation +an user is created and all RT's objects are created in his schema. +User is created with the following parameters: + + CREATE USER IDENTIFIED BY + DEFAULT TABLESPACE USERS TEMPORARY TABLESPACE TEMP + QUOTA UNLIMITED ON USERS + +And as well granted with 'CONNECT' and 'RESOURCE'. It's up to you +to do decide how to manage users, change quotas, table spaces and +other. + +RT has option DatabaseName which is used to define SID of +the Oracle database. You don't have to setup TWO_TASK environment +variable or other helpers files for connection. + +Example: + + ./configure \ + --with-db-type=Oracle \ + --with-db-database=XE \ + --with-db-host=192.168.0.1 \ + --with-db-dba=system \ + --with-db-rt-user=rtdb1 \ + --with-db-rt-pass=rtdb1secret \ + ... other configure options ... + +This's all specifics of deployment on Oracle you should be aware +of. To complete installation you must follow general instructions +in the README file. + +As with all databases it is important to analyze the schema and get current statistics after any significant dataset change. Oracle's cost-based optimizer can provide particularly bad performance when the schema statistics are significantly inaccurate. To analyze the schema -of a user called rt, execute the following from withing Sqlplus. +of a user called rt, execute the following from within sqlplus: execute dbms_utility.analyze_schema( 'RT', 'estimate'); - diff --git a/rt/UPGRADING b/rt/UPGRADING index aca9bb079..1aaccdb3c 100644 --- a/rt/UPGRADING +++ b/rt/UPGRADING @@ -14,7 +14,149 @@ Before making any changes to your database, always ensure that you have a complete current backup. If you don't have a current backup, you could accidentally damage your database and lose data or worse. +If you are using MySQL, please read the instructions in UPGRADING.mysql as +well. + ******* +UPGRADING FROM 3.8.6 and earlier - Changes: + +For MySQL and Oracle users: +If you upgraded from a version of RT earlier than 3.7.81 you should +already have a CachedGroupMembers3 index on your CachedGroupMembers table. +If you did a clean install of RT somewhere in the 3.8 release series, you +most likely don't have this index. You can add it manually with + + CREATE INDEX CachedGroupMembers3 on CachedGroupMembers (MemberId, ImmediateParentId); + +UPGRADING FROM 3.8.5 and earlier - Changes: + +You can now forward an entire Ticket history (in addition to specific transactions) +but this requires a new Template called forward ticket. This template will be added +when you run. + +/opt/rt3/sbin/rt-setup-database --dba root --prompt-for-dba-password --action upgrade + +Custom fields with categories can optionally be split out into +hierarchical custom fields. If you wish to convert your old +category-based custom fields, run: + + perl etc/upgrade/split-out-cf-categories + +It will prompt you for each custom field with categories that it +finds, and the name of the custom field to create to store the +categories. + +If you were using the LocalizedDateTime RT::Date formatter from code +and passing a DateFormat or TimeFormat argument, you need to switch from +the strftime methods to the cldr methods (ie full_date_format becomes date_format_full) +You may have done this from your RT_SiteConfig.pm by using +Set($DateTimeFormat, { Format => 'LocalizedDateTime', DateFormat => 'medium_date_format' ); + +UPGRADING FROM 3.8.3 and earlier - Changes: + +Arguments to the NotifyGroup Scrip Action need +to be corrected in the database using + +/opt/rt3/sbin/rt-setup-database --dba root --prompt-for-dba-password --action upgrade + + +UPGRADING FROM 3.8.2 and earlier - Changes: + +New scrip condition 'On Reject'. + +UPGRADING FROM 3.8.1 and earlier - Changes: + += Oracle configuration = + +$DatabaseName is used as SID, so RT can connect without environment variables +or tnsnames.ora file. Because of this change your RT instance may loose ability +to connect to your DB, you have to update options and restart your web server. +Example configuration: + + Set($DatabaseType, 'Oracle'); + Set($DatabaseHost, '192.168.0.1'); + # undefined port => will try both 1526 and 1521 + Set($DatabasePort, undef); + # ORACLE SID + Set($DatabaseName, 'XE'); + # user for RT in Oracle, RT's tables in his schema + Set($DatabaseUser, 'test'); + # above user's password + Set($DatabasePassword, 'test'); + += Rights changes = + +Now, if you want any user to be able to access the Approvals tools (a.k.a. the +Approvals tab), you must grant that user the "ShowApprovalsTab" right. + +UPGRADING FROM 3.8.0 and earlier - Changes: + +Searches for bookmarked tickets have been reimplemented and syntax has +been changed a little. Database upgrade script handles global 'Bookmarked Tickets' +search only. New Ticket SQL "id = '__Bookmarked__'" is more flexible than +old "__Bookmarks__". Old version is not valid Ticket SQL query, so people +can not use it in the query builder and as well admins couldn't not edit +format and other properties of the global saved search. Old version's been +left for backwards compatibility. + + +UPGRADING FROM 3.7.85 and earlier - Changes: + +We've proved that it's possible to delete pretty big set of records +from CachedGroupMembers table without losing functionality. To delete +record run the following script: + + perl -I /opt/rt3/local/lib -I /opt/rt3/lib etc/upgrade/shrink_cgm_table.pl + +UPGRADING FROM 3.7.81 and earlier - Changes: + +RT::Extension::BrandedQueues has been integrated into core, so you MUST read +upgrading instructions docs/queue_subject_tag.pod EVEN IF you have not used +that extension. + +RT::Action::LinearEscalate extension has been integrated into core, +so you MUST uninstall it before upgrading. + +RT::Extension::iCal has been integrated into core, so you MUST uninstall +it before upgrading. In addition, you must run etc/upgrade/3.8-ical-extension +script to convert old data. + +UPGRADING FROM 3.7.80 and earlier - Changes: + +Added indexes to CachedGroupMembers for MySQL and Oracle. +If you have previously installed RTx-Shredder, you may already +have these indexes. You can see the indexes by looking at +etc/upgrade/3.7.81/schema.* + +These indexes may take a very long time to create. + +UPGRADING FROM 3.6.X and earlier - Changes: + +There are a lot of changes all over the code, so it's highly recommended to +use fresh directory and then reinstalling your customizations. + +New schema for mysql 4.1 and greater, read more in UPGRADING.mysql. + +Config format has been made stricter. All options MUST be set using Set +function, no more "@XXX = (...) unless @XXX;". Use "Set(@XXX, ...);" instead. + +RTx::Shredder extension has been integrated into core and features have been +added, so you MUST uninstall it before upgrading or use a fresh directory for +installation. + +New interface for making links in text clickable and doing other replacements +has been integrated into RT. +Read more in `perldoc docs/extending_clickable_links.pod`. + +New feature that allow users to forward messages. There is a new option in +the config ($ForwardFromUser), new rights and a template. + +New global templates with "Error: " prefix in the name to make it possible +to configure error messages sent to users. + +Read about GnuPG integration in `perldoc docs/gnupg_integration.pod`. + +New scrip conditions 'On Close' and 'On Reopen'. UPGRADING FROM 3.5.7 and earlier - Changes: @@ -65,7 +207,7 @@ UPGRADING FROM 3.0.x - Changes: = Installation = We recommend you move your existing /opt/rt3 tree completely out -of the way before installating the newversion of RT, to make sure +of the way before installing the new version of RT, to make sure that you don't inadvertently leave old files hanging around. = Rights changes = @@ -85,7 +227,7 @@ It's worth checking out that resource if these instructions don't work right for you -RT 3.2 includes a signficant change to the FastCGI handler. It is +RT 3.2 includes a significant change to the FastCGI handler. It is no longer "setgid" to the RT group. Perl's setid support has been deprecated for the last several releases and a number of platforms don't bundle the "sperl" or "suidperl" executable by default. @@ -220,3 +362,7 @@ Apache 1.3 on a Debian Linux server. +UPGRADING FROM 2.x: + +See http://search.cpan.org/dist/RT-Extension-RT2toRT3/ + diff --git a/rt/UPGRADING.mysql b/rt/UPGRADING.mysql new file mode 100644 index 000000000..accaa979a --- /dev/null +++ b/rt/UPGRADING.mysql @@ -0,0 +1,85 @@ +MySQL 4.1 and greater implemented changes in character set handling +that may result in RT failures: multiple login requests, binary attachments +breakage, image custom fields breakage and more. + +In order to resolve this issue we've changed our schema for MySQL 4.1 and +greater versions. + +If you're installing a new RT then you can skip this file. + +If you're migrating from MySQL 4.0 to MySQL 4.1 and newer then you MUST follow +instructions at the bottom of this file. + +If you're upgrading RT from versions prior to 3.8.0 then you MUST follow +instructions below even if your old RT was installed on MySQL 4.1 or newer. + +=== Upgrading RT from versions prior to 3.8.0 === + +1) Backup RT's database. Test that you can restore from this backup. + +2) Follow instructions in the README file to step 7. + +3) Apply changes described in step 7, but only up to version 3.7.87. + +4) Apply the RT 3.8 schema upgrades. Included in RT is the script +etc/upgrade/upgrade-mysql-schema.pl that generates SQL queries to +upgrade the database's schema. Run it: + + perl etc/upgrade/upgrade-mysql-schema.pl db user pass > queries.sql + +If your mysql database is on a remote host, you can run the script +like this instead + + perl etc/upgrade/upgrade-mysql-schema.pl db:host user pass > queries.sql + +5) Check sanity of the SQL queries yourself or consult with your DBA. + +6) Apply the queries. Note that this step can take a while. It may require +additional space on your hard drive comparable with size of your tables. + + mysql -u root -p rt3 < queries.sql + +NOTE that 'rt3' is the default name of the RT database, change it in the +command above if you're using a different name. + +This step should not produce any errors or warnings. If you see any, restore +your database from the backup you made at step 1) and send a report to the +rt-users@lists.bestpractical.com mailing list. + +7) Continue from step 7 in the README and apply other upgrades and +follow the remaining steps. + +8) Test everything. The most important parts you have to test: +* binary attachments, like docs, PDFs, and images +* binary custom fields +* everything that may contain characters other than ASCII + +=== Migrating from MySQL 4.0 to MySQL 4.1 and newer === + +Upgrading both MySQL and RT at the same time is a bad idea. The process becomes +more complicated, more time consuming, greater chance to fail, and much harder +to debug. + +It's better to perform the upgrade in two steps. First upgrade MySQL from 4.0 +to 4.1 or newer. Remember the following: + +* Don't use utf8 as MySQL's character set. This is the default in some + Linux distributions. +* import/export MySQL dumps using binary character set. + +When you're sure that everything is fine, you may upgrade RT using the +instructions above. + +If you still want to upgrade MySQL and RT simultaneously, then you can +do the following: + +1) Install a new RT on MySQL 4.1 or newer. +2) Test that this new clean RT works on this new database. +3) Dump the database from MySQL 4.0. +4) Configure MySQL 4.1 and newer to use Latin1 as default character set + everywhere. +5) Import the dump into the new MySQL server, replacing your empty database you + created at step 1. +6) At this point you have RT 3.8.x code base using an old database. You can + upgrade RT using the instructions above. + diff --git a/rt/aclocal.m4 b/rt/aclocal.m4 index 9c6b64134..0e041b6e9 100644 --- a/rt/aclocal.m4 +++ b/rt/aclocal.m4 @@ -26,11 +26,11 @@ dnl AC_DEFUN([RT_ENABLE_LAYOUT],[ AC_ARG_ENABLE(layout, AC_HELP_STRING([--enable-layout=LAYOUT], - [Use a specific directory layout (Default: RT3)]), + [Use a specific directory layout (Default: relative)]), LAYOUT=$enableval) if test "x$LAYOUT" = "x"; then - LAYOUT="RT3" + LAYOUT="relative" fi RT_LAYOUT($srcdir/config.layout, $LAYOUT) AC_MSG_CHECKING(for chosen layout) @@ -45,6 +45,11 @@ else AC_SUBST(rt_layout_name) AC_MSG_RESULT($rt_layout_name) fi +if test "x$rt_layout_name" != "xinplace" ; then + AC_SUBST([COMMENT_INPLACE_LAYOUT], [""]) +else + AC_SUBST([COMMENT_INPLACE_LAYOUT], [# ]) +fi ]) dnl @@ -111,6 +116,7 @@ AC_DEFUN([RT_LAYOUT],[ RT_SUBST_EXPANDED_ARG(datadir) RT_SUBST_EXPANDED_ARG(htmldir) RT_SUBST_EXPANDED_ARG(manualdir) + RT_SUBST_EXPANDED_ARG(plugindir) RT_SUBST_EXPANDED_ARG(localstatedir) RT_SUBST_EXPANDED_ARG(logfiledir) RT_SUBST_EXPANDED_ARG(masonstatedir) diff --git a/rt/bin/mason_handler.fcgi b/rt/bin/mason_handler.fcgi index 9bef9a89f..4fe888a93 100755 --- a/rt/bin/mason_handler.fcgi +++ b/rt/bin/mason_handler.fcgi @@ -2,8 +2,8 @@ # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: -# -# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -51,12 +51,17 @@ package RT::Mason; use strict; use vars '$Handler'; use File::Basename; -require ('/opt/rt3/bin/webmux.pl'); + +require (dirname(__FILE__) . '/webmux.pl'); # Enter CGI::Fast mode, which should also work as a vanilla CGI script. require CGI::Fast; RT::Init(); +$Handler ||= RT::Interface::Web::Handler->new( + RT->Config->Get('MasonParameters') +); + while ( my $cgi = CGI::Fast->new ) { # the whole point of fastcgi requires the env to get reset here.. @@ -67,7 +72,7 @@ while ( my $cgi = CGI::Fast->new ) { $ENV{'ENV'} = '' if defined $ENV{'ENV'}; $ENV{'IFS'} = '' if defined $ENV{'IFS'}; - Module::Refresh->refresh if $RT::DevelMode; + Module::Refresh->refresh if RT->Config->Get('DevelMode'); RT::ConnectToDatabase(); if ( ( !$Handler->interp->comp_exists( $cgi->path_info ) ) diff --git a/rt/bin/mason_handler.fcgi.in b/rt/bin/mason_handler.fcgi.in index 26842d31e..48155f257 100644 --- a/rt/bin/mason_handler.fcgi.in +++ b/rt/bin/mason_handler.fcgi.in @@ -2,8 +2,8 @@ # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: -# -# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -51,12 +51,17 @@ package RT::Mason; use strict; use vars '$Handler'; use File::Basename; -require ('@RT_BIN_PATH@/webmux.pl'); + +require (dirname(__FILE__) . '/webmux.pl'); # Enter CGI::Fast mode, which should also work as a vanilla CGI script. require CGI::Fast; RT::Init(); +$Handler ||= RT::Interface::Web::Handler->new( + RT->Config->Get('MasonParameters') +); + while ( my $cgi = CGI::Fast->new ) { # the whole point of fastcgi requires the env to get reset here.. @@ -67,7 +72,7 @@ while ( my $cgi = CGI::Fast->new ) { $ENV{'ENV'} = '' if defined $ENV{'ENV'}; $ENV{'IFS'} = '' if defined $ENV{'IFS'}; - Module::Refresh->refresh if $RT::DevelMode; + Module::Refresh->refresh if RT->Config->Get('DevelMode'); RT::ConnectToDatabase(); if ( ( !$Handler->interp->comp_exists( $cgi->path_info ) ) diff --git a/rt/bin/mason_handler.scgi b/rt/bin/mason_handler.scgi index 6a3404ea5..248c57ca2 100755 --- a/rt/bin/mason_handler.scgi +++ b/rt/bin/mason_handler.scgi @@ -2,8 +2,8 @@ # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: -# -# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -50,11 +50,17 @@ package RT::Mason; use strict; use vars '$Handler'; -require ('/opt/rt3/bin/webmux.pl'); +use File::Basename; + +require (dirname(__FILE__) . '/webmux.pl'); require CGI; RT::Init(); +$Handler ||= RT::Interface::Web::Handler->new( + RT->Config->Get('MasonParameters') +); + my $cgi = CGI->new; if ( ( !$Handler->interp->comp_exists( $cgi->path_info ) ) diff --git a/rt/bin/mason_handler.scgi.in b/rt/bin/mason_handler.scgi.in index 2d77e8f82..a853529d0 100644 --- a/rt/bin/mason_handler.scgi.in +++ b/rt/bin/mason_handler.scgi.in @@ -2,8 +2,8 @@ # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: -# -# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -50,11 +50,17 @@ package RT::Mason; use strict; use vars '$Handler'; -require ('@RT_BIN_PATH@/webmux.pl'); +use File::Basename; + +require (dirname(__FILE__) . '/webmux.pl'); require CGI; RT::Init(); +$Handler ||= RT::Interface::Web::Handler->new( + RT->Config->Get('MasonParameters') +); + my $cgi = CGI->new; if ( ( !$Handler->interp->comp_exists( $cgi->path_info ) ) diff --git a/rt/bin/mason_handler.svc b/rt/bin/mason_handler.svc index 86adfae28..3cde20cd1 100644 --- a/rt/bin/mason_handler.svc +++ b/rt/bin/mason_handler.svc @@ -2,8 +2,8 @@ # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: -# -# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -111,7 +111,7 @@ BEGIN { $Win32::TieRegistry::Registry->{ 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\\'. 'W3SVC\Parameters\Virtual Roots\\' - }->{$RT::WebPath || '/'} = "$path,,205"; + }->{RT->Config->Get('WebPath') || '/'} = "$path,,205"; $Win32::TieRegistry::Registry->{ 'HKEY_LOCAL_MACHINE\Software\FASTCGI\.html\\' @@ -225,13 +225,18 @@ warn "Begin listening on $ENV{'FCGI_SOCKET_PATH'}\n"; require CGI::Fast; RT::Init(); +$Handler ||= RT::Interface::Web::Handler->new( + RT->Config->Get('MasonParameters') +); + # Response loop while( my $cgi = CGI::Fast->new ) { my $comp = $ENV{'PATH_INFO'}; $comp = $1 if ($comp =~ /^(.*)$/); - $comp =~ s|^$RT::WebPath\b||i; + my $web_path = RT->Config->Get('WebPath'); + $comp =~ s|^\Q$web_path\E\b||i; $comp .= "index.html" if ($comp =~ /\/$/); $comp =~ s/.pl$/.html/g; diff --git a/rt/bin/mason_handler.svc.in b/rt/bin/mason_handler.svc.in index 3bf851c7a..d7e68b3a2 100644 --- a/rt/bin/mason_handler.svc.in +++ b/rt/bin/mason_handler.svc.in @@ -2,8 +2,8 @@ # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: -# -# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -111,7 +111,7 @@ BEGIN { $Win32::TieRegistry::Registry->{ 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\\'. 'W3SVC\Parameters\Virtual Roots\\' - }->{$RT::WebPath || '/'} = "$path,,205"; + }->{RT->Config->Get('WebPath') || '/'} = "$path,,205"; $Win32::TieRegistry::Registry->{ 'HKEY_LOCAL_MACHINE\Software\FASTCGI\.html\\' @@ -225,13 +225,18 @@ warn "Begin listening on $ENV{'FCGI_SOCKET_PATH'}\n"; require CGI::Fast; RT::Init(); +$Handler ||= RT::Interface::Web::Handler->new( + RT->Config->Get('MasonParameters') +); + # Response loop while( my $cgi = CGI::Fast->new ) { my $comp = $ENV{'PATH_INFO'}; $comp = $1 if ($comp =~ /^(.*)$/); - $comp =~ s|^$RT::WebPath\b||i; + my $web_path = RT->Config->Get('WebPath'); + $comp =~ s|^\Q$web_path\E\b||i; $comp .= "index.html" if ($comp =~ /\/$/); $comp =~ s/.pl$/.html/g; diff --git a/rt/bin/rt b/rt/bin/rt index 3440d9e57..9554a932c 100755 --- a/rt/bin/rt +++ b/rt/bin/rt @@ -2,8 +2,8 @@ # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: -# -# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -58,7 +58,19 @@ use Cwd; use LWP; use Text::ParseWords; use HTTP::Request::Common; +use HTTP::Headers; use Term::ReadLine; +use Time::Local; # used in prettyshow + +# strong (GSSAPI based) authentication is supported if the server does provide +# it and the perl modules GSSAPI and LWP::Authen::Negotiate are installed +# it can be suppressed by setting externalauth=0 (default is undef) +eval { require GSSAPI }; +my $no_strong_auth = 'missing perl module GSSAPI'; +if ( ! $@ ) { + eval {require LWP::Authen::Negotiate}; + $no_strong_auth = $@ ? 'missing perl module LWP::Authen::Negotiate' : 0; +} # We derive configuration information from hardwired defaults, dotfiles, # and the RT* environment variables (in increasing order of precedence). @@ -70,18 +82,27 @@ my $HOME = eval{(getpwuid($<))[7]} || "."; my %config = ( ( - debug => 0, - user => eval{(getpwuid($<))[0]} || $ENV{USER} || $ENV{USERNAME}, - passwd => undef, - server => 'http://localhost/', - query => undef, - orderby => undef, + debug => 0, + user => eval{(getpwuid($<))[0]} || $ENV{USER} || $ENV{USERNAME}, + passwd => undef, + server => 'http://localhost/', + query => "Status!='resolved' and Status!='rejected'", + orderby => 'id', + queue => undef, +# to protect against unlimited searches a better choice would be +# queue => 'Unknown_Queue', +# setting externalauth => undef will try GSSAPI auth if the corresponding perl +# modules are installed, externalauth => 0 is the backward compatible choice + externalauth => 0, ), config_from_file($ENV{RTCONFIG} || ".rtrc"), config_from_env() ); my $session = new Session("$HOME/.rt_sessions"); my $REST = "$config{server}/REST/1.0"; +$no_strong_auth = 'switched off by externalauth=0' + if defined $config{externalauth}; + my $prompt = 'rt> '; @@ -91,11 +112,12 @@ sub DEBUG { warn @_ if $config{debug} >= shift } # These regexes are used by command handlers to parse arguments. # (XXX: Ask Autrijus how i18n changes these definitions.) -my $name = '[\w.-]+'; -my $field = '(?:[a-zA-Z](?:[a-zA-Z0-9_-]|\s+)*)'; -my $label = '[a-zA-Z0-9@_.+-]+'; -my $labels = "(?:$label,)*$label"; -my $idlist = '(?:(?:\d+-)?\d+,)*(?:\d+-)?\d+'; +my $name = '[\w.-]+'; +my $CF_name = '[\sa-z0-9_ :()/-]+'; +my $field = '(?i:[a-z][a-z0-9_-]*|C(?:ustom)?F(?:ield)?-'.$CF_name.'|CF\.\{'.$CF_name.'\})'; +my $label = '[a-zA-Z0-9@_.+-]+'; +my $labels = "(?:$label,)*$label"; +my $idlist = '(?:(?:\d+-)?\d+,)*(?:\d+-)?\d+'; # Our command line looks like this: # @@ -119,6 +141,8 @@ my %handlers = ( grant => ["grant", "revoke"], take => ["take", "steal", "untake"], quit => ["quit", "exit"], + setcommand => ["del", "delete", "give", "res", "resolve", + "subject"], ); my %actions; @@ -137,16 +161,16 @@ sub handler { shift @ARGV if ($ARGV[0] eq 'rt'); # ignore a leading 'rt' if (@ARGV && exists $actions{$ARGV[0]}) { $action = shift @ARGV; - $actions{$action}->($action); + return $actions{$action}->($action); } else { print STDERR "rt: Unknown command '@ARGV'.\n"; print STDERR "rt: For help, run 'rt help'.\n"; + return 1; } } -handler(); -exit; +exit handler(); # Handler functions. # ------------------ @@ -166,10 +190,12 @@ sub shell { sub version { print "rt $VERSION\n"; + return 0; } sub logout { submit("$REST/logout") if defined $session->cookie; + return 0; } sub quit { @@ -179,7 +205,8 @@ sub quit { my %help; sub help { - my ($action, $type) = @_; + my ($action, $type, $rv) = @_; + $rv = defined $rv ? $rv : 0; my $key; # What help topics do we know about? @@ -228,6 +255,7 @@ sub help { } print STDERR $help{$key}, "\n\n"; + return $rv; } # Displays a list of objects that match some specified condition. @@ -240,6 +268,9 @@ sub list { $data{orderby} = $config{orderby}; } my $bad = 0; + my $rawprint = 0; + my $reverse_sort = 0; + my $queue = $config{queue}; while (@ARGV) { $_ = shift @ARGV; @@ -255,6 +286,13 @@ sub list { } elsif (/^-([isl])$/) { $data{format} = $1; + $rawprint = 1; + } + elsif (/^-q$/) { + $queue = shift @ARGV; + } + elsif (/^-r$/) { + $reverse_sort = 1; } elsif (/^-f$/) { if ($ARGV[0] !~ /^(?:(?:$field,)*$field)$/) { @@ -262,6 +300,8 @@ sub list { $bad = 1; last; } $data{fields} = shift @ARGV; + $data{format} = 's' if ! $data{format}; + $rawprint = 1; } elsif (!defined $q && !/^-/) { $q = $_; @@ -272,10 +312,35 @@ sub list { $bad = 1; last; } } + if ( ! $rawprint and ! exists $data{format} ) { + $data{format} = 'l'; + } + if ( $reverse_sort and $data{orderby} =~ /^-/ ) { + $data{orderby} =~ s/^-/+/; + } elsif ($reverse_sort) { + $data{orderby} =~ s/^\+?(.*)/-$1/; + } + if (!defined $q) { $q = $config{query}; } + $q =~ s/^#//; # get rid of leading hash + if ($q =~ /^\d+$/) { + # only digits, must be an id, formulate a correct query + $q = "id=$q" if $q =~ /^\d+$/; + } else { + # a string only, take it as an owner or requestor (quoting done later) + $q = "(Owner=$q or Requestor like $q) and $config{query}" + if $q =~ /^[\w\-]+$/; + # always add a query for a specific queue or (comma separated) queues + $queue =~ s/,/ or Queue=/g if $queue; + $q .= " and (Queue=$queue)" if $queue and $q and $q !~ /Queue\s*=/i + and $q !~ /id\s*=/i; + } + # correctly quote strings in a query + $q =~ s/(=|like\s)\s*([^'\d\s]\S*)\b/$1\'$2\'/g; + $type ||= "ticket"; unless ($type && defined $q) { my $item = $type ? "query string" : "object type"; @@ -283,10 +348,17 @@ sub list { $bad = 1; } #return help("list", $type) if $bad; - return suggest_help("list", $type) if $bad; + return suggest_help("list", $type, $bad) if $bad; + print "Query:$q\n" if ! $rawprint; my $r = submit("$REST/search/$type", { query => $q, %data }); - print $r->content; + if ( $rawprint ) { + print $r->content; + } else { + my $forms = Form::parse($r->content); + prettylist ($forms); + } + return 0; } # Displays selected information about a single object. @@ -295,10 +367,12 @@ sub show { my ($type, @objects, %data); my $slurped = 0; my $bad = 0; + my $rawprint = 0; + my $histspec; while (@ARGV) { $_ = shift @ARGV; - + s/^#// if /^#\d+/; # get rid of leading hash if (/^-t$/) { $bad = 1, last unless defined($type = get_type_argument()); } @@ -307,6 +381,7 @@ sub show { } elsif (/^-([isl])$/) { $data{format} = $1; + $rawprint = 1; } elsif (/^-$/ && !$slurped) { chomp(my @lines = ); @@ -325,9 +400,21 @@ sub show { $bad = 1; last; } $data{fields} = shift @ARGV; + # option f requires short raw listing format + $data{format} = 's'; + $rawprint = 1; + } + elsif (/^\d+$/ and my $spc2 = is_object_spec("ticket/$_", $type)) { + push @objects, $spc2; + $histspec = is_object_spec("ticket/$_/history", $type); + } + elsif (/^\d+\// and my $spc3 = is_object_spec("ticket/$_", $type)) { + push @objects, $spc3; + $rawprint = 1 if $_ =~ /\/content$/; } elsif (my $spec = is_object_spec($_, $type)) { push @objects, $spec; + $rawprint = 1 if $_ =~ /\/content$/ or $_ !~ /^ticket/; } else { my $datum = /^-/ ? "option" : "argument"; @@ -335,13 +422,17 @@ sub show { $bad = 1; last; } } + if ( ! $rawprint ) { + push @objects, $histspec if $histspec; + $data{format} = 'l' if ! exists $data{format}; + } unless (@objects) { whine "No objects specified."; $bad = 1; } #return help("show", $type) if $bad; - return suggest_help("show", $type) if $bad; + return suggest_help("show", $type, $bad) if $bad; my $r = submit("$REST/show", { id => \@objects, %data }); my $c = $r->content; @@ -350,8 +441,17 @@ sub show { # show ticket/id/attachments/id/content > foo.tar.gz if ($r->content_type !~ /^text\//) { chomp($c); + $rawprint = 1; + } + if ( $rawprint ) { + print $c; + } else { + # I do not know how to get more than one form correctly returned + $c =~ s!^RT/[\d\.]+ 200 Ok$!--!mg; + my $forms = Form::parse($c); + prettyshow ($forms); } - print $c; + return 0; } # To create a new object, we ask the server for a form with the defaults @@ -373,6 +473,7 @@ sub edit { while (@ARGV) { $_ = shift @ARGV; + s/^#// if /^#\d+/; # get rid of leading hash if (/^-e$/) { $edit = 1 } elsif (/^-i$/) { $input = 1 } @@ -397,7 +498,7 @@ sub edit { elsif (/^set$/i) { my $vars = 0; - while (@ARGV && $ARGV[0] =~ /^($field)([+-]?=)(.*)$/) { + while (@ARGV && $ARGV[0] =~ /^($field)([+-]?=)(.*)$/s) { my ($key, $op, $val) = ($1, $2, $3); my $hash = ($op eq '=') ? \%set : ($op =~ /^\+/) ? \%add : \%del; @@ -415,7 +516,7 @@ sub edit { my $vars = 0; my $hash = ($_ eq "add") ? \%add : \%del; - while (@ARGV && $ARGV[0] =~ /^($field)=(.*)$/) { + while (@ARGV && $ARGV[0] =~ /^($field)=(.*)$/s) { my ($key, $val) = ($1, $2); vpush($hash, lc $key, $val); @@ -428,6 +529,9 @@ sub edit { } $cl = $vars; } + elsif (/^\d+$/ and my $spc2 = is_object_spec("ticket/$_", $type)) { + push @objects, $spc2; + } elsif (my $spec = is_object_spec($_, $type)) { push @objects, $spec; } @@ -453,10 +557,10 @@ sub edit { whine "What type of object do you want to create?"; $bad = 1; } - @objects = ("$type/new"); + @objects = ("$type/new") if defined($type); } #return help($action, $type) if $bad; - return suggest_help($action, $type) if $bad; + return suggest_help($action, $type, $bad) if $bad; # We need a form to make changes to. We usually ask the server for # one, but we can avoid that if we are fed one on STDIN, or if the @@ -540,7 +644,7 @@ sub edit { if ($output) { print $text; - return; + return 0; } my $synerr = 0; @@ -566,11 +670,60 @@ EDIT: } else { print $r->content; - return; + return 0; } } print $r->content; } + return 0; +} + +# handler for special edit commands. A valid edit command is constructed and +# further work is delegated to the edit handler + +sub setcommand { + my ($action) = @_; + my ($id, $bad, $what); + if ( @ARGV ) { + $_ = shift @ARGV; + $id = $1 if (m|^(?:ticket/)?($idlist)$|); + } + if ( ! $id ) { + $bad = 1; + whine "No ticket number specified."; + } + if ( @ARGV ) { + if ($action eq 'subject') { + my $subject = '"'.join (" ", @ARGV).'"'; + @ARGV = (); + $what = "subject=$subject"; + } elsif ($action eq 'give') { + my $owner = shift @ARGV; + $what = "owner=$owner"; + } + } else { + if ( $action eq 'delete' or $action eq 'del' ) { + $what = "status=deleted"; + } elsif ($action eq 'resolve' or $action eq 'res' ) { + $what = "status=resolved"; + } elsif ($action eq 'take' ) { + $what = "owner=$config{user}"; + } elsif ($action eq 'untake') { + $what = "owner=Nobody"; + } + } + if (@ARGV) { + $bad = 1; + whine "Extraneous arguments for action $action: @ARGV."; + } + if ( ! $what ) { + $bad = 1; + whine "unrecognized action $action."; + } + return help("edit", undef, $bad) if $bad; + @ARGV = ( $id, "set", $what ); + print "Executing: rt edit @ARGV\n"; + return edit("edit"); } # We roll "comment" and "correspond" into the same handler. @@ -595,7 +748,7 @@ sub comment { if (/-a/) { unless (-f $ARGV[0] && -r $ARGV[0]) { whine "Cannot read attachment: '$ARGV[0]'."; - return; + return 0; } push @files, shift @ARGV; } @@ -665,7 +818,7 @@ sub comment { goto NEXT; } elsif (!@$o) { - return; + return 0; } @files = @{ vsplit($k->{Attachment}) }; @@ -683,6 +836,7 @@ sub comment { my $r = submit("$REST/ticket/$id/comment", \%data); print $r->content; + return 0; } # Merge one ticket into another. @@ -693,6 +847,7 @@ sub merge { while (@ARGV) { $_ = shift @ARGV; + s/^#// if /^#\d+/; # get rid of leading hash if (/^\d+$/) { push @id, $_; @@ -709,16 +864,19 @@ sub merge { $bad = 1; } #return help("merge", "ticket") if $bad; - return suggest_help("merge", "ticket") if $bad; + return suggest_help("merge", "ticket", $bad) if $bad; my $r = submit("$REST/ticket/$id[0]/merge/$id[1]"); print $r->content; + return 0; } # Link one ticket to another. sub link { my ($bad, $del, %data) = (0, 0, ()); + my $type; + my %ltypes = map { lc $_ => $_ } qw(DependsOn DependedOnBy RefersTo ReferredToBy HasMember MemberOf); @@ -728,21 +886,26 @@ sub link { if (/^-d$/) { $del = 1; } + elsif (/^-t$/) { + $bad = 1, last unless defined($type = get_type_argument()); + } else { whine "Unrecognised option: '$_'."; $bad = 1; last; } } - + + $type = "ticket" unless $type; # default type to tickets + if (@ARGV == 3) { my ($from, $rel, $to) = @ARGV; if ($from !~ /^\d+$/ || $to !~ /^\d+$/) { my $bad = $from =~ /^\d+$/ ? $to : $from; - whine "Invalid ticket ID '$bad' specified."; + whine "Invalid $type ID '$bad' specified."; $bad = 1; } - unless (exists $ltypes{lc $rel}) { - whine "Invalid link '$rel' specified."; + if (($type eq "ticket") && ( ! exists $ltypes{lc $rel})) { + whine "Invalid link '$rel' for type $type specified."; $bad = 1; } %data = (id => $from, rel => $rel, to => $to, del => $del); @@ -752,11 +915,11 @@ sub link { whine "Too $bad arguments specified."; $bad = 1; } - #return help("link", "ticket") if $bad; - return suggest_help("link", "ticket") if $bad; - - my $r = submit("$REST/ticket/link", \%data); + return suggest_help("link", $type, $bad) if $bad; + + my $r = submit("$REST/$type/link", \%data); print $r->content; + return 0; } # Take/steal a ticket @@ -791,10 +954,11 @@ sub take { whine "Too $bad arguments specified."; $bad = 1; } - return suggest_help("take", "ticket") if $bad; + return suggest_help("take", "ticket", $bad) if $bad; my $r = submit("$REST/ticket/$id/take", \%data); print $r->content; + return 0; } # Grant/revoke a user's rights. @@ -807,6 +971,7 @@ sub grant { } $revoke = 1 if $cmd->{action} eq 'revoke'; + return 0; } # Client <-> Server communication. @@ -820,6 +985,7 @@ sub submit { my ($uri, $content) = @_; my ($req, $data); my $ua = new LWP::UserAgent(agent => "RT/3.0b", env_proxy => 1); + my $h = HTTP::Headers->new; # Did the caller specify any data to send with the request? $data = []; @@ -845,9 +1011,22 @@ sub submit { } # Should we send authentication information to start a new session? - if (!defined $session->cookie) { - push @$data, ( user => $config{user} ); - push @$data, ( pass => $config{passwd} || read_passwd() ); + my $how = $config{server} =~ /^https/ ? 'over SSL' : 'unencrypted'; + (my $server = $config{server}) =~ s/^.*\/\/([^\/]+)\/?/$1/; + if ($config{externalauth}) { + $h->authorization_basic($config{user}, $config{passwd} || read_passwd() ); + print " Password will be sent to $server $how\n", + " Press CTRL-C now if you do not want to continue\n" + if ! $config{passwd}; + } elsif ( $no_strong_auth ) { + if (!defined $session->cookie) { + print " Strong encryption not available, $no_strong_auth\n", + " Password will be sent to $server $how\n", + " Press CTRL-C now if you do not want to continue\n" + if ! $config{passwd}; + push @$data, ( user => $config{user} ); + push @$data, ( pass => $config{passwd} || read_passwd() ); + } } # Now, we construct the request. @@ -858,6 +1037,9 @@ sub submit { $req = GET($uri); } $session->add_cookie_header($req); + if ($config{externalauth}) { + $req->header(%$h); + } # Then we send the request and parse the response. DEBUG(3, $req->as_string); @@ -874,7 +1056,7 @@ sub submit { $text =~ s/\n*$/\n/ if ($text); # "RT/3.0.1 401 Credentials required" - if ($status !~ m#^RT/\d+(?:\S+) (\d+) ([\w\s]+)$#) { + if ($status !~ m#^RT/\d+(?:\S+) (\d+) ([\w\s]+)$#) { warn "rt: Malformed RT response from $config{server}.\n"; warn "(Rerun with RTDEBUG=3 for details.)\n" if $config{debug} < 3; exit -1; @@ -1043,7 +1225,7 @@ sub submit { sub Form::parse { my $state = 0; my @forms = (); - my @lines = split /\n/, $_[0]; + my @lines = split /\n/, $_[0] if $_[0]; my ($c, $o, $k, $e) = ("", [], {}, ""); LINE: @@ -1199,7 +1381,8 @@ sub Form::compose { sub config_from_env { my %env; - foreach my $k ("DEBUG", "USER", "PASSWD", "SERVER", "QUERY", "ORDERBY") { + foreach my $k (qw(EXTERNALAUTH DEBUG USER PASSWD SERVER QUERY ORDERBY)) { + if (exists $ENV{"RT$k"}) { $env{lc $k} = $ENV{"RT$k"}; } @@ -1251,7 +1434,7 @@ sub parse_config_file { chomp; next if (/^#/ || /^\s*$/); - if (/^(user|passwd|server|query|orderby)\s+(.*)\s?$/) { + if (/^(externalauth|user|passwd|server|query|orderby|queue)\s+(.*)\s?$/) { $cfg{$1} = $2; } else { @@ -1270,7 +1453,7 @@ sub whine { my $sub = (caller(1))[3]; $sub =~ s/^main:://; warn "rt: $sub: @_\n"; - return; + return 0; } sub read_passwd { @@ -1331,7 +1514,37 @@ sub vsplit { # XXX: This should become a real parser, à la Text::ParseWords. $line =~ s/^\s+//; $line =~ s/\s+$//; - push @words, split /\s*,\s*/, $line; + my ( $a, $b ) = split /,/, $line, 2; + + while ($a) { + no warnings 'uninitialized'; + if ( $a =~ /^'/ ) { + my $s = $a; + while ( $a !~ /'$/ || ( $a !~ /(\\\\)+'$/ + && $a =~ /(\\)+'$/ )) { + ( $a, $b ) = split /,/, $b, 2; + $s .= ',' . $a; + } + push @words, $s; + } + elsif ( $a =~ /^q{/ ) { + my $s = $a; + while ( $a !~ /}$/ ) { + ( $a, $b ) = + split /,/, $b, 2; + $s .= ',' . $a; + } + $s =~ s/^q{/'/; + $s =~ s/}/'/; + push @words, $s; + } + else { + push @words, $a; + } + ( $a, $b ) = split /,/, $b, 2; + } + + } return \@words; @@ -1406,14 +1619,130 @@ sub is_object_spec { $spec =~ s|^(?:$type/)?|$type/| if defined $type; return $spec if ($spec =~ m{^$name/(?:$idlist|$labels)(?:/.*)?$}o); - return; + return 0; } sub suggest_help { - my ($action, $type) = @_; + my ($action, $type, $rv) = @_; print STDERR "rt: For help, run 'rt help $action'.\n" if defined $action; print STDERR "rt: For help, run 'rt help $type'.\n" if defined $type; + return $rv; +} + +sub str2time { + # simplified procedure for parsing date, avoid loading Date::Parse + my %month = (Jan => 0, Feb => 1, Mar => 2, Apr => 3, May => 4, Jun => 5, + Jul => 6, Aug => 7, Sep => 8, Oct => 9, Nov => 10, Dec => 11); + $_ = shift; + my ($mon, $day, $hr, $min, $sec, $yr, $monstr); + if ( /(\w{3})\s+(\d\d?)\s+(\d\d):(\d\d):(\d\d)\s+(\d{4})/ ) { + ($monstr, $day, $hr, $min, $sec, $yr) = ($1, $2, $3, $4, $5, $6); + $mon = $month{$monstr} if exists $month{$monstr}; + } elsif ( /(\d{4})-(\d\d)-(\d\d)\s+(\d\d):(\d\d):(\d\d)/ ) { + ($yr, $mon, $day, $hr, $min, $sec) = ($1, $2-1, $3, $4, $5, $6); + } + if ( $yr and defined $mon and $day and defined $hr and defined $sec ) { + return timelocal($sec,$min,$hr,$day,$mon,$yr); + } else { + print "Unknown date format in parsedate: $_\n"; + return undef; + } +} + +sub date_diff { + my ($old, $new) = @_; + $new = time() if ! $new; + $old = str2time($old) if $old !~ /^\d+$/; + $new = str2time($new) if $new !~ /^\d+$/; + return "???" if ! $old or ! $new; + + my %seconds = (min => 60, + hr => 60*60, + day => 60*60*24, + wk => 60*60*24*7, + mth => 60*60*24*30, + yr => 60*60*24*365); + + my $diff = $new - $old; + my $what = 'sec'; + my $howmuch = $diff; + for ( sort {$seconds{$a} <=> $seconds{$b}} keys %seconds) { + last if $diff < $seconds{$_}; + $what = $_; + $howmuch = int($diff/$seconds{$_}); + } + return "$howmuch $what"; +} + +sub prettyshow { + my $forms = shift; + my ($form) = grep { exists $_->[2]->{Queue} } @$forms; + my $k = $form->[2]; + # dates are in local time zone + if ( $k ) { + print "Date: $k->{Created}\n"; + print "From: $k->{Requestors}\n"; + print "Cc: $k->{Cc}\n" if $k->{Cc}; + print "X-AdminCc: $k->{AdminCc}\n" if $k->{AdminCc}; + print "X-Queue: $k->{Queue}\n"; + print "Subject: [rt #$k->{id}] $k->{Subject}\n\n"; + } + # dates in these attributes are in GMT and will be converted + foreach my $form (@$forms) { + my ($c, $o, $k, $e) = @$form; + next if ! $k->{id} or exists $k->{Queue}; + if ( exists $k->{Created} ) { + my ($y,$m,$d,$hh,$mm,$ss) = ($k->{Created} =~ /(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)/); + $m--; + my $created = localtime(timegm($ss,$mm,$hh,$d,$m,$y)); + if ( exists $k->{Description} ) { + print "===> $k->{Description} on $created\n"; + } + } + print "$k->{Content}\n" if exists $k->{Content} and + $k->{Content} !~ /to have no content$/ and + $k->{Type} ne 'EmailRecord'; + print "$k->{Attachments}\n" if exists $k->{Attachments} and + $k->{Attachments}; + } +} + +sub prettylist { + my $forms = shift; + my $heading = "Ticket Owner Queue Age Told Status Requestor Subject\n"; + $heading .= '-' x 80 . "\n"; + my (@open, @me); + foreach my $form (@$forms) { + my ($c, $o, $k, $e) = @$form; + next if ! $k->{id}; + print $heading if $heading; + $heading = ''; + my $id = $k->{id}; + $id =~ s!^ticket/!!; + my $owner = $k->{Owner} eq 'Nobody' ? '' : $k->{Owner}; + $owner = substr($owner, 0, 5); + my $queue = substr($k->{Queue}, 0, 5); + my $subject = substr($k->{Subject}, 0, 30); + my $age = date_diff($k->{Created}); + my $told = $k->{Told} eq 'Not set' ? '' : date_diff($k->{Told}); + my $status = substr($k->{Status}, 0, 6); + my $requestor = substr($k->{Requestors}, 0, 9); + my $line = sprintf "%6s %5s %5s %6s %6s %-6s %-9s %-30s\n", + $id, $owner, $queue, $age, $told, $status, $requestor, $subject; + if ( $k->{Owner} eq 'Nobody' ) { + push @open, $line; + } elsif ($k->{Owner} eq $config{user} ) { + push @me, $line; + } else { + print $line; + } + } + print "No matches found\n" if $heading; + printf "========== my %2d open tickets ==========\n", scalar @me if @me; + print @me if @me; + printf "========== %2d unowned tickets ==========\n", scalar @open if @open; + print @open if @open; } __DATA__ @@ -1511,9 +1840,21 @@ Text: - passwd RT user's password. - query Default RT Query for list action - orderby Default RT order for list action + - queue Default RT Queue for list action + - externalauth <0|1> Use HTTP Basic authentication + explicitely setting externalauth to 0 inhibits also GSSAPI based + authentication, if LWP::Authen::Negotiate (and GSSAPI) is installed Blank and #-commented lines are ignored. + Sample configuration file contents: + + server https://rt.somewhere.com/ + # more than one queue can be given (by adding a query expression) + queue helpdesk or queue=support + query Status != resolved and Owner=myaccount + + Environment variables: The following environment variables override any corresponding @@ -1521,6 +1862,7 @@ Text: - RTUSER - RTPASSWD + - RTEXTERNALAUTH - RTSERVER - RTDEBUG Numeric debug level. (Set to 3 for full logs.) - RTCONFIG Specifies a name other than ".rtrc" for the @@ -1552,8 +1894,12 @@ Text: "user/root,1-3,5,7-10,ams" is a list of ten users; the same list can also be written as "user/ams,root,1,2,3,5,7,8-10". + If just a number is given as object specification it will be + interpreted as ticket/ + Examples: + 1 # the same as ticket/1 ticket/1 ticket/1/attachments ticket/1/attachments/3 @@ -1591,6 +1937,22 @@ Text: - rt help (action-specific details) - rt help types (a list of possible types) + The following actions on tickets are also possible: + + - comment Add comments to a ticket + - correspond Add comments to a ticket + - merge Merge one ticket into another + - link Link one ticket to another + - take Take a ticket (steal and untake are possible as well) + + For several edit set subcommands that are frequently used abbreviations + have been introduced. These abbreviations are: + + - delete or del delete a ticket (edit set status=deleted) + - resolve or res resolve a ticket (edit set status=resolved) + - subject change subject of ticket (edit set subject=string) + - give give a ticket to somebody (edit set owner=user) + -- Title: types @@ -1629,6 +1991,13 @@ Text: - merge - comment - correspond + - take + - steal + - untake + - give + - resolve + - delete + - subject Attributes: @@ -1687,6 +2056,83 @@ Text: -- +Title: subject +Text: + + Syntax: + + rt subject + + Change the subject of a ticket whose ticket id is given. + +-- + +Title: give +Text: + + Syntax: + + rt give + + Give a ticket whose ticket id is given to another user. + +-- + +Title: steal +Text: + + rt steal + + Steal a ticket whose ticket id is given, i.e. set the owner to myself. + +-- + +Title: take +Text: + + Syntax: + + rt take + + Take a ticket whose ticket id is given, i.e. set the owner to myself. + +-- + +Title: untake +Text: + + Syntax: + + rt untake + + Untake a ticket whose ticket id is given, i.e. set the owner to Nobody. + +-- + +Title: resolve +Title: res +Text: + + Syntax: + + rt resolve + + Resolves a ticket whose ticket id is given. + +-- + +Title: delete +Title: del +Text: + + Syntax: + + rt delete + + Deletes a ticket whose ticket id is given. + +-- + Title: logout Text: @@ -1725,24 +2171,30 @@ Text: The following options control how much information is displayed about each matching object: - -i Numeric IDs only. (Useful for |rt edit -; see examples.) - -s Short description. - -l Longer description. + -i Numeric IDs only. (Useful for |rt edit -; see examples.) + -s Short description. + -l Longer description. + -f Orders the returned list by the specified field. - -S var=val Submits the specified variable with the request. - -t type Specifies the type of object to look for. (The - default is "ticket".) + -o +/- Orders the returned list by the specified field. + -r reversed order (useful if a default was given) + -q queue[s] restricts the query to the queue[s] given + multiple queues are separated by comma + -S var=val Submits the specified variable with the request. + -t type Specifies the type of object to look for. (The + default is "ticket".) Examples: - rt ls "Priority > 5 and Status='new'" - rt ls -o +Subject "Priority > 5 and Status='new'" - rt ls -o -Created "Priority > 5 and Status='new'" + rt ls "Priority > 5 and Status=new" + rt ls -o +Subject "Priority > 5 and Status=new" + rt ls -o -Created "Priority > 5 and Status=new" rt ls -i "Priority > 5"|rt edit - set status=resolved rt ls -t ticket "Subject like '[PATCH]%'" + rt ls -q systems + rt ls -f owner,subject -- @@ -1760,16 +2212,28 @@ Text: that refers to the links for tickets 1-3). Consult "rt help " and "rt help objects" for further details. + If only a number is given it will be interpreted as the objects + ticket/number and ticket/number/history + This command writes a set of forms representing the requested object data to STDOUT. Options: + The following options control how much information is displayed + about each matching object: + + Without any formatting options prettyprinted output is generated. + Giving any of the two options below reverts to raw output. + -s Short description (history and attachments only). + -l Longer description (history and attachments only). + + In addition, - Read IDs from STDIN instead of the command-line. -t type Specifies object type. -f a,b,c Restrict the display to the specified fields. -S var=val Submits the specified variable with the request. - -v Verbose display + Examples: rt show -t ticket -f id,subject,status 1-3 @@ -1777,8 +2241,9 @@ Text: rt show ticket/3/attachments/29/content rt show ticket/1-3/links rt show ticket/3/history - rt show -v ticket/3/history + rt show -l ticket/3/history rt show -t user 2 + rt show 2 -- @@ -1795,6 +2260,8 @@ Text: Edits information corresponding to the specified objects. + A purely numeric object id nnn is translated into ticket/nnn + If, instead of "edit", an action of "new" or "create" is specified, then a new object is created. In this case, no numeric object IDs may be specified, but the syntax and behaviour remain otherwise @@ -1834,7 +2301,7 @@ Text: rt create -t ticket # Non-interactive. - rt edit ticket/1-3 add cc=foo@example.com set priority=3 + rt edit ticket/1-3 add cc=foo@example.com set priority=3 due=tomorrow rt ls -t tickets -i 'Priority > 5' | rt edit - set status=resolved rt edit ticket/4 set priority=3 owner=bar@example.com \ add cc=foo@example.com bcc=quux@example.net @@ -1930,6 +2397,35 @@ Text: (XXX: I'm going to have to write it, aren't I?) + Until it exists here a short description of important constructs: + + The two simple forms of query expressions are the constructs + Attribute like Value and + Attribute = Value or Attribute != Value + + Whether attributes can be matched using like or using = is built into RT. + The attributes id, Queue, Owner Priority and Status require the = or != + tests. + + If Value is a string it must be quoted and may contain the wildcard + character %. If the string does not contain white space, the quoting + may however be omitted, it will be added automatically when parsing + the input. + + Simple query expressions can be combined using and, or and parentheses + can be used to group expressions. + + As a special case a standalone string (which would not form a correct + query) is transformed into (Owner='string' or Requestor like 'string%') + and added to the default query, i.e. the query is narrowed down. + + If no Queue=name clause is contained in the query, a default clause + Queue=$config{queue} is added. + + Examples: + Status!='resolved' and Status!='rejected' + (Owner='myaccount' or Requestor like 'myaccount%') and Status!='resolved' + -- Title: form @@ -1984,10 +2480,43 @@ Title: example Title: examples Text: - This section will be filled in with useful examples, once it becomes - more clear what examples may be useful. - - For the moment, please consult examples provided with each action. + some useful examples + + All the following list requests will be restricted to the default queue. + That can be changed by adding the option -q queuename + + List all tickets that are not rejected/resolved + rt ls + List all tickets that are new and do not have an owner + rt ls "status=new and owner=nobody" + List all tickets which I have sent or of which I am the owner + rt ls myaccount + List all attributes for the ticket 6977 (ls -l instead of ls) + rt ls -l 6977 + Show the content of ticket 6977 + rt show 6977 + Show all attributes in the ticket and in the history of the ticket + rt show -l 6977 + Comment a ticket (mail is sent to all queue watchers, i.e. AdminCc's) + rt comment 6977 + This will open an editor and lets you add text (attribute Text:) + Other attributes may be changed as well, but usually don't do that. + Correspond a ticket (like comment, but mail is also sent to requestors) + rt correspond 6977 + Edit a ticket (generic change, interactive using the editor) + rt edit 6977 + Change the owner of a ticket non interactively + rt edit 6977 set owner=myaccount + or + rt give 6977 account + or + rt take 6977 + Change the status of a ticket + rt edit 6977 set status=resolved + or + rt resolve 6977 + Change the status of all tickets I own to resolved !!! + rt ls -i owner=myaccount | rt edit - set status=resolved -- diff --git a/rt/bin/rt-crontool b/rt/bin/rt-crontool index 3171d115c..563a272fa 100644 --- a/rt/bin/rt-crontool +++ b/rt/bin/rt-crontool @@ -2,8 +2,8 @@ # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: -# -# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -49,9 +49,32 @@ use strict; use Carp; -use lib ("/opt/rt3/local/lib", "/opt/rt3/lib"); +# fix lib paths, some may be relative +BEGIN { + require File::Spec; + my @libs = ("lib", "local/lib"); + my $bin_path; + + for my $lib (@libs) { + unless ( File::Spec->file_name_is_absolute($lib) ) { + unless ($bin_path) { + if ( File::Spec->file_name_is_absolute(__FILE__) ) { + $bin_path = ( File::Spec->splitpath(__FILE__) )[1]; + } + else { + require FindBin; + no warnings "once"; + $bin_path = $FindBin::Bin; + } + } + $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib ); + } + unshift @INC, $lib; + } -package RT; +} + +use RT; use Getopt::Long; @@ -62,54 +85,66 @@ use RT::Template; #Clean out all the nasties from the environment CleanEnv(); +my ( $search, $condition, $action, $search_arg, $condition_arg, $action_arg, + $template, $template_id, $transaction, $transaction_type, $help, $log, $verbose ); +GetOptions( + "search=s" => \$search, + "search-arg=s" => \$search_arg, + "condition=s" => \$condition, + "condition-arg=s" => \$condition_arg, + "action-arg=s" => \$action_arg, + "action=s" => \$action, + "template=s" => \$template, + "template-id=s" => \$template_id, + "transaction=s" => \$transaction, + "transaction-type=s" => \$transaction_type, + "log=s" => \$log, + "verbose|v" => \$verbose, + "help" => \$help, +); + # Load the config file RT::LoadConfig(); +# adjust logging to the screen according to options +RT->Config->Set( LogToScreen => $log ) if $log; + #Connect to the database and get RT::SystemUser and RT::Nobody loaded RT::Init(); #Get the current user all loaded my $CurrentUser = GetCurrentUser(); +# show help even if there is no current user +help() if $help; + unless ( $CurrentUser->Id ) { print loc("No RT user found. Please consult your RT administrator.\n"); exit(1); } -my ( $search, $condition, $action, $search_arg, $condition_arg, $action_arg, - $template_id, $transaction, $transaction_type, $help, $verbose ); -GetOptions( "search=s" => \$search, - "search-arg=s" => \$search_arg, - "condition=s" => \$condition, - "condition-arg=s" => \$condition_arg, - "action-arg=s" => \$action_arg, - "action=s" => \$action, - "template-id=s" => \$template_id, - "transaction=s" => \$transaction, - "transaction-type=s" => \$transaction_type, - "help" => \$help, - "verbose|v" => \$verbose ); - -help() if $help or not $search or not $action; - -$transaction ||= 'first'; -unless ( $transaction =~ /^(first|last)$/i ) { - print STDERR loc("--transaction argument could be only 'first' or 'last'"); +help() unless $search && $action; + +$transaction = lc( $transaction||'' ); +if ( $transaction && $transaction !~ /^(first|all|last)$/i ) { + print STDERR loc("--transaction argument could be only 'first', 'last' or 'all'"); + exit 1; +} + +if ( $template && $template_id ) { + print STDERR loc("--template-id is deprecated argument and can not be used with --template"); exit 1; } -$transaction = lc($transaction) eq 'first'? 'ASC': 'DESC'; +elsif ( $template_id ) { +# don't warn + $template = $template_id; +} # We _must_ have a search object load_module($search); load_module($action) if ($action); load_module($condition) if ($condition); -# load template if specified -my $template_obj; -if ($template_id) { - $template_obj = RT::Template->new($CurrentUser); - $template_obj->Load($template_id); -} my $void_scrip = RT::Scrip->new( $CurrentUser ); my $void_scrip_action = RT::ScripAction->new( $CurrentUser ); @@ -132,9 +167,31 @@ my $tickets = $search->TicketsObj; while ( my $ticket = $tickets->Next() ) { print $ticket->Id() . ": " if ($verbose); - my $transaction = get_transaction($ticket); - print loc("Using transaction #[_1]...", $transaction->id) - if $verbose && $transaction; + my $template_obj = get_template( $ticket ); + + if ( $transaction ) { + my $txns = get_transactions($ticket); + my $found = 0; + while ( my $txn = $txns->Next ) { + print loc("Using transaction #[_1]...", $txn->id) + if $verbose; + process($ticket, $txn, $template_obj); + $found = 1; + } + print loc("Couldn't find suitable transaction, skipping") + if $verbose && !$found; + } else { + print loc("Processing without transaction, some conditions and actions may fail. Consider using --transaction argument") + if $verbose; + + process($ticket, undef, $template_obj); + } +} + +sub process { + my $ticket = shift; + my $transaction = shift; + my $template_obj = shift; # perform some more advanced check if ($condition) { @@ -149,8 +206,8 @@ while ( my $ticket = $tickets->Next() ) { # if the condition doesn't apply, get out of here - next unless ( $condition_obj->IsApplicable ); - print loc("Condition matches...") if ($verbose); + return unless $condition_obj->IsApplicable; + print loc("Condition matches...") if $verbose; } #prepare our action @@ -165,34 +222,76 @@ while ( my $ticket = $tickets->Next() ) { ); #if our preparation, move onto the next ticket - next unless ( $action_obj->Prepare ); - print loc("Action prepared...") if ($verbose); + return unless $action_obj->Prepare; + print loc("Action prepared...") if $verbose; #commit our action. - next unless ( $action_obj->Commit ); - print loc("Action committed.\n") if ($verbose); + return unless $action_obj->Commit; + print loc("Action committed.\n") if $verbose; } -=head2 get_transaction +=head2 get_transactions -Takes ticket and returns its transaction acording to command -line arguments C<--transaction> and <--transaction-type>. +Takes ticket and returns L object with transactions +of the ticket according to command line arguments C<--transaction> +and <--transaction-type>. =cut -sub get_transaction { +sub get_transactions { my $ticket = shift; my $txns = $ticket->Transactions; + my $order = $transaction eq 'last'? 'DESC': 'ASC'; $txns->OrderByCols( - { FIELD => 'Created', ORDER => $transaction }, - { FIELD => 'id', ORDER => $transaction }, + { FIELD => 'Created', ORDER => $order }, + { FIELD => 'id', ORDER => $order }, ); - $txns->Limit( FIELD => 'Type', VALUE => $transaction_type ) - if $transaction_type; - $txns->RowsPerPage(1); - return $txns->First; + if ( $transaction_type ) { + $transaction_type =~ s/^\s+//; + $transaction_type =~ s/\s+$//; + foreach my $type ( split /\s*,\s*/, $transaction_type ) { + $txns->Limit( FIELD => 'Type', VALUE => $type, ENTRYAGGREGATOR => 'OR' ); + } + } + $txns->RowsPerPage(1) unless $transaction eq 'all'; + return $txns; } +=head2 get_template + +Takes a ticket and returns a template according to command line options. + +=cut + +{ my $cache = undef; +sub get_template { + my $ticket = shift; + return undef unless $template; + + unless ( $template =~ /\D/ ) { + # by id + return $cache if $cache; + + my $cache = RT::Template->new( $RT::SystemUser ); + $cache->Load( $template ); + die "Failed to load template '$template'" + unless $cache->id; + return $cache; + } + + my $queue = $ticket->Queue; + return $cache->{ $queue } if $cache->{ $queue }; + + my $res = RT::Template->new( $RT::SystemUser ); + $res->LoadQueueTemplate( Queue => $queue, Name => $template ); + unless ( $res->id ) { + $res->LoadGlobalTemplate( $template ); + die "Failed to load template '$template', either for queue #$queue or global" + unless $res->id; + } + return $cache->{ $queue } = $res; +} } + # {{{ load_module =head2 load_module @@ -236,31 +335,33 @@ sub help { . loc( "[_1] - Specify the search module you want to use", "--search" ) . "\n"; print " " - . loc( "[_1] - An argument to pass to [_2]", "--search-argument", "--search" ) + . loc( "[_1] - An argument to pass to [_2]", "--search-arg", "--search" ) . "\n"; print " " . loc( "[_1] - Specify the condition module you want to use", "--condition" ) . "\n"; print " " - . loc( "[_1] - An argument to pass to [_2]", "--condition-argument", "--condition" ) + . loc( "[_1] - An argument to pass to [_2]", "--condition-arg", "--condition" ) . "\n"; print " " . loc( "[_1] - Specify the action module you want to use", "--action" ) . "\n"; print " " - . loc( "[_1] - An argument to pass to [_2]", "--action-argument", "--action" ) + . loc( "[_1] - An argument to pass to [_2]", "--action-arg", "--action" ) . "\n"; print " " - . loc( "[_1] - Specify id of the template you want to use", "--template-id" ) + . loc( "[_1] - Specify name or id of template(s) you want to use", "--template" ) . "\n"; print " " - . loc( "[_1] - Specify if you want to use either 'first' or 'last' transaction", "--transaction" ) + . loc( "[_1] - Specify if you want to use either 'first', 'last' or 'all' transactions", "--transaction" ) . "\n"; print " " - . loc( "[_1] - Specify the type of a transaction you want to use", "--transaction-type" ) + . loc( "[_1] - Specify the comma separated list of transactions' types you want to use", "--transaction-type" ) . "\n"; print " " + . loc( "[_1] - Adjust LogToScreen config option", "--log" ) . "\n"; + print " " . loc( "[_1] - Output status updates to STDOUT", "--verbose" ) . "\n"; print "\n"; print "\n"; diff --git a/rt/bin/rt-crontool.in b/rt/bin/rt-crontool.in index 07e7a8b52..8401acab3 100644 --- a/rt/bin/rt-crontool.in +++ b/rt/bin/rt-crontool.in @@ -2,8 +2,8 @@ # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: -# -# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -49,9 +49,32 @@ use strict; use Carp; -use lib ("@LOCAL_LIB_PATH@", "@RT_LIB_PATH@"); +# fix lib paths, some may be relative +BEGIN { + require File::Spec; + my @libs = ("@RT_LIB_PATH@", "@LOCAL_LIB_PATH@"); + my $bin_path; + + for my $lib (@libs) { + unless ( File::Spec->file_name_is_absolute($lib) ) { + unless ($bin_path) { + if ( File::Spec->file_name_is_absolute(__FILE__) ) { + $bin_path = ( File::Spec->splitpath(__FILE__) )[1]; + } + else { + require FindBin; + no warnings "once"; + $bin_path = $FindBin::Bin; + } + } + $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib ); + } + unshift @INC, $lib; + } -package RT; +} + +use RT; use Getopt::Long; @@ -62,54 +85,66 @@ use RT::Template; #Clean out all the nasties from the environment CleanEnv(); +my ( $search, $condition, $action, $search_arg, $condition_arg, $action_arg, + $template, $template_id, $transaction, $transaction_type, $help, $log, $verbose ); +GetOptions( + "search=s" => \$search, + "search-arg=s" => \$search_arg, + "condition=s" => \$condition, + "condition-arg=s" => \$condition_arg, + "action-arg=s" => \$action_arg, + "action=s" => \$action, + "template=s" => \$template, + "template-id=s" => \$template_id, + "transaction=s" => \$transaction, + "transaction-type=s" => \$transaction_type, + "log=s" => \$log, + "verbose|v" => \$verbose, + "help" => \$help, +); + # Load the config file RT::LoadConfig(); +# adjust logging to the screen according to options +RT->Config->Set( LogToScreen => $log ) if $log; + #Connect to the database and get RT::SystemUser and RT::Nobody loaded RT::Init(); #Get the current user all loaded my $CurrentUser = GetCurrentUser(); +# show help even if there is no current user +help() if $help; + unless ( $CurrentUser->Id ) { print loc("No RT user found. Please consult your RT administrator.\n"); exit(1); } -my ( $search, $condition, $action, $search_arg, $condition_arg, $action_arg, - $template_id, $transaction, $transaction_type, $help, $verbose ); -GetOptions( "search=s" => \$search, - "search-arg=s" => \$search_arg, - "condition=s" => \$condition, - "condition-arg=s" => \$condition_arg, - "action-arg=s" => \$action_arg, - "action=s" => \$action, - "template-id=s" => \$template_id, - "transaction=s" => \$transaction, - "transaction-type=s" => \$transaction_type, - "help" => \$help, - "verbose|v" => \$verbose ); - -help() if $help or not $search or not $action; - -$transaction ||= 'first'; -unless ( $transaction =~ /^(first|last)$/i ) { - print STDERR loc("--transaction argument could be only 'first' or 'last'"); +help() unless $search && $action; + +$transaction = lc( $transaction||'' ); +if ( $transaction && $transaction !~ /^(first|all|last)$/i ) { + print STDERR loc("--transaction argument could be only 'first', 'last' or 'all'"); + exit 1; +} + +if ( $template && $template_id ) { + print STDERR loc("--template-id is deprecated argument and can not be used with --template"); exit 1; } -$transaction = lc($transaction) eq 'first'? 'ASC': 'DESC'; +elsif ( $template_id ) { +# don't warn + $template = $template_id; +} # We _must_ have a search object load_module($search); load_module($action) if ($action); load_module($condition) if ($condition); -# load template if specified -my $template_obj; -if ($template_id) { - $template_obj = RT::Template->new($CurrentUser); - $template_obj->Load($template_id); -} my $void_scrip = RT::Scrip->new( $CurrentUser ); my $void_scrip_action = RT::ScripAction->new( $CurrentUser ); @@ -132,9 +167,31 @@ my $tickets = $search->TicketsObj; while ( my $ticket = $tickets->Next() ) { print $ticket->Id() . ": " if ($verbose); - my $transaction = get_transaction($ticket); - print loc("Using transaction #[_1]...", $transaction->id) - if $verbose && $transaction; + my $template_obj = get_template( $ticket ); + + if ( $transaction ) { + my $txns = get_transactions($ticket); + my $found = 0; + while ( my $txn = $txns->Next ) { + print loc("Using transaction #[_1]...", $txn->id) + if $verbose; + process($ticket, $txn, $template_obj); + $found = 1; + } + print loc("Couldn't find suitable transaction, skipping") + if $verbose && !$found; + } else { + print loc("Processing without transaction, some conditions and actions may fail. Consider using --transaction argument") + if $verbose; + + process($ticket, undef, $template_obj); + } +} + +sub process { + my $ticket = shift; + my $transaction = shift; + my $template_obj = shift; # perform some more advanced check if ($condition) { @@ -149,8 +206,8 @@ while ( my $ticket = $tickets->Next() ) { # if the condition doesn't apply, get out of here - next unless ( $condition_obj->IsApplicable ); - print loc("Condition matches...") if ($verbose); + return unless $condition_obj->IsApplicable; + print loc("Condition matches...") if $verbose; } #prepare our action @@ -165,34 +222,76 @@ while ( my $ticket = $tickets->Next() ) { ); #if our preparation, move onto the next ticket - next unless ( $action_obj->Prepare ); - print loc("Action prepared...") if ($verbose); + return unless $action_obj->Prepare; + print loc("Action prepared...") if $verbose; #commit our action. - next unless ( $action_obj->Commit ); - print loc("Action committed.\n") if ($verbose); + return unless $action_obj->Commit; + print loc("Action committed.\n") if $verbose; } -=head2 get_transaction +=head2 get_transactions -Takes ticket and returns its transaction acording to command -line arguments C<--transaction> and <--transaction-type>. +Takes ticket and returns L object with transactions +of the ticket according to command line arguments C<--transaction> +and <--transaction-type>. =cut -sub get_transaction { +sub get_transactions { my $ticket = shift; my $txns = $ticket->Transactions; + my $order = $transaction eq 'last'? 'DESC': 'ASC'; $txns->OrderByCols( - { FIELD => 'Created', ORDER => $transaction }, - { FIELD => 'id', ORDER => $transaction }, + { FIELD => 'Created', ORDER => $order }, + { FIELD => 'id', ORDER => $order }, ); - $txns->Limit( FIELD => 'Type', VALUE => $transaction_type ) - if $transaction_type; - $txns->RowsPerPage(1); - return $txns->First; + if ( $transaction_type ) { + $transaction_type =~ s/^\s+//; + $transaction_type =~ s/\s+$//; + foreach my $type ( split /\s*,\s*/, $transaction_type ) { + $txns->Limit( FIELD => 'Type', VALUE => $type, ENTRYAGGREGATOR => 'OR' ); + } + } + $txns->RowsPerPage(1) unless $transaction eq 'all'; + return $txns; } +=head2 get_template + +Takes a ticket and returns a template according to command line options. + +=cut + +{ my $cache = undef; +sub get_template { + my $ticket = shift; + return undef unless $template; + + unless ( $template =~ /\D/ ) { + # by id + return $cache if $cache; + + my $cache = RT::Template->new( $RT::SystemUser ); + $cache->Load( $template ); + die "Failed to load template '$template'" + unless $cache->id; + return $cache; + } + + my $queue = $ticket->Queue; + return $cache->{ $queue } if $cache->{ $queue }; + + my $res = RT::Template->new( $RT::SystemUser ); + $res->LoadQueueTemplate( Queue => $queue, Name => $template ); + unless ( $res->id ) { + $res->LoadGlobalTemplate( $template ); + die "Failed to load template '$template', either for queue #$queue or global" + unless $res->id; + } + return $cache->{ $queue } = $res; +} } + # {{{ load_module =head2 load_module @@ -236,31 +335,33 @@ sub help { . loc( "[_1] - Specify the search module you want to use", "--search" ) . "\n"; print " " - . loc( "[_1] - An argument to pass to [_2]", "--search-argument", "--search" ) + . loc( "[_1] - An argument to pass to [_2]", "--search-arg", "--search" ) . "\n"; print " " . loc( "[_1] - Specify the condition module you want to use", "--condition" ) . "\n"; print " " - . loc( "[_1] - An argument to pass to [_2]", "--condition-argument", "--condition" ) + . loc( "[_1] - An argument to pass to [_2]", "--condition-arg", "--condition" ) . "\n"; print " " . loc( "[_1] - Specify the action module you want to use", "--action" ) . "\n"; print " " - . loc( "[_1] - An argument to pass to [_2]", "--action-argument", "--action" ) + . loc( "[_1] - An argument to pass to [_2]", "--action-arg", "--action" ) . "\n"; print " " - . loc( "[_1] - Specify id of the template you want to use", "--template-id" ) + . loc( "[_1] - Specify name or id of template(s) you want to use", "--template" ) . "\n"; print " " - . loc( "[_1] - Specify if you want to use either 'first' or 'last' transaction", "--transaction" ) + . loc( "[_1] - Specify if you want to use either 'first', 'last' or 'all' transactions", "--transaction" ) . "\n"; print " " - . loc( "[_1] - Specify the type of a transaction you want to use", "--transaction-type" ) + . loc( "[_1] - Specify the comma separated list of transactions' types you want to use", "--transaction-type" ) . "\n"; print " " + . loc( "[_1] - Adjust LogToScreen config option", "--log" ) . "\n"; + print " " . loc( "[_1] - Output status updates to STDOUT", "--verbose" ) . "\n"; print "\n"; print "\n"; diff --git a/rt/bin/rt-mailgate b/rt/bin/rt-mailgate index a578b4bc6..abe731196 100755 --- a/rt/bin/rt-mailgate +++ b/rt/bin/rt-mailgate @@ -2,8 +2,8 @@ # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: -# -# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -52,30 +52,34 @@ rt-mailgate - Mail interface to RT3. =cut - use strict; use warnings; + use Getopt::Long; use LWP::UserAgent; +use HTTP::Request::Common qw($DYNAMIC_FILE_UPLOAD); +$DYNAMIC_FILE_UPLOAD = 1; use constant EX_TEMPFAIL => 75; +use constant BUFFER_SIZE => 8192; my %opts; GetOptions( \%opts, "queue=s", "action=s", "url=s", "jar=s", "help", "debug", "extension=s", "timeout=i" ); -if ( $opts{help} ) { +if ( $opts{'help'} ) { require Pod::Usage; import Pod::Usage; pod2usage("RT Mail Gateway\n"); exit 1; # Don't want to succeed if this is really an email! } -for (qw(url)) { - die "$0 invoked improperly\n\nNo $_ provided to mail gateway!\n" unless $opts{$_}; +unless ( $opts{'url'} ) { + print STDERR "$0 invoked improperly\n\nNo 'url' provided to mail gateway!\n"; + exit 1; } -my $ua = LWP::UserAgent->new(); -$ua->cookie_jar( { file => $opts{jar} } ); +my $ua = new LWP::UserAgent; +$ua->cookie_jar( { file => $opts{'jar'} } ) if $opts{'jar'}; my %args = ( SessionType => 'REST', # Surpress login box @@ -84,37 +88,54 @@ foreach ( qw(queue action) ) { $args{$_} = $opts{$_} if defined $opts{$_}; }; -# Read the message in from STDIN -$args{'message'} = do { local (@ARGV, $/); <> }; - -unless ( $args{message} =~ /\S/ ) { - print STDERR "$0: no message passed on STDIN!\n"; - exit 0; +if ( ($opts{'extension'} || '') =~ /^(?:action|queue|ticket)$/i ) { + $args{ lc $opts{'extension'} } = $ENV{'EXTENSION'} || $opts{$opts{'extension'}}; +} elsif ( $opts{'extension'} && $ENV{'EXTENSION'} ) { + print STDERR "Value of the --extension argument is not action, queue or ticket" + .", but environment variable EXTENSION is also defined. The former is ignored.\n"; } -if ($opts{'extension'}) { - $args{$opts{'extension'}} = $ENV{'EXTENSION'}; +# add ENV{'EXTENSION'} as X-RT-MailExtension to the message header +if ( my $value = ( $ENV{'EXTENSION'} || $opts{'extension'} ) ) { + # prepare value to avoid MIME format breakage + # strip trailing newline symbols + $value =~ s/(\r*\n)+$//; + # make a correct multiline header field, + # with tabs in the beginning of each line + $value =~ s/(\r*\n)/$1\t/g; + $opts{'headers'} .= "X-RT-Mail-Extension: $value\n"; } -# Set up cookie here. +# Read the message in from STDIN +my %message = write_down_message(); +unless( $message{'filename'} ) { + $args{'message'} = [ + undef, '', + 'Content-Type' => 'application/octet-stream', + Content => ${ $message{'content'} }, + ]; +} else { + $args{'message'} = [ + $message{'filename'}, '', + 'Content-Type' => 'application/octet-stream', + ]; +} my $full_url = $opts{'url'}. "/REST/1.0/NoAuth/mail-gateway"; -warn "Connecting to $full_url" if $opts{'debug'}; +print STDERR "$0: connecting to $full_url\n" if $opts{'debug'}; - - -$ua->timeout(exists($opts{'timeout'}) ? $opts{'timeout'} : 180); -my $r = $ua->post( $full_url, {%args} ); +$ua->timeout( exists( $opts{'timeout'} )? $opts{'timeout'}: 180 ); +my $r = $ua->post( $full_url, \%args, Content_Type => 'form-data' ); check_failure($r); my $content = $r->content; -warn $content if ($opts{debug}); +print STDERR $content ."\n" if $opts{'debug'}; if ( $content !~ /^(ok|not ok)/ ) { # It's not the server's fault if the mail is bogus. We just want to know that # *something* came out of the server. - warn <is_success(); + return if $r->is_success; # This ordinarily oughtn't to be able to happen, suggests a bug in RT. # So only load these heavy modules when they're needed. @@ -140,17 +164,64 @@ sub check_failure { require HTML::FormatText; my $error = $r->error_as_HTML; - my $tree = HTML::TreeBuilder->new->parse($error); + my $tree = HTML::TreeBuilder->new->parse( $error ); $tree->eof; # It'll be a cold day in hell before RT sends out bounces in HTML - my $formatter = HTML::FormatText->new( leftmargin => 0, - rightmargin => 50 ); - warn $formatter->format($tree); - warn "This is $0 exiting because of an undefined server error" if ($opts{debug}); + my $formatter = HTML::FormatText->new( + leftmargin => 0, + rightmargin => 50, + ); + print STDERR $formatter->format( $tree ); + print STDERR "\n$0: undefined server error\n" if $opts{'debug'}; exit EX_TEMPFAIL; } +sub write_down_message { + use File::Temp qw(tempfile); + + local $@; + my ($fh, $filename) = eval { tempfile() }; + if ( !$fh || $@ ) { + print STDERR "$0: Couldn't create temp file, using memory\n"; + print STDERR "error: $@\n" if $@; + + my $message = \do { local (@ARGV, $/); <> }; + unless ( $$message =~ /\S/ ) { + print STDERR "$0: no message passed on STDIN\n"; + exit 0; + } + $$message = $opts{'headers'} . $$message if $opts{'headers'}; + return ( content => $message ); + } + + binmode $fh; + binmode \*STDIN; + + print $fh $opts{'headers'} if $opts{'headers'}; + + my $buf; my $empty = 1; + while(1) { + my $status = read \*STDIN, $buf, BUFFER_SIZE; + unless ( defined $status ) { + print STDERR "$0: couldn't read message: $!\n"; + exit EX_TEMPFAIL; + } elsif ( !$status ) { + last; + } + $empty = 0 if $buf =~ /\S/; + print $fh $buf; + }; + close $fh; + + if ( $empty ) { + print STDERR "$0: no message passed on STDIN\n"; + exit 0; + } + print STDERR "$0: temp file is '$filename'\n" if $opts{'debug'}; + return (filename => $filename); +} + =head1 SYNOPSIS @@ -166,8 +237,6 @@ Usual invocation (from MTA): -See C for more. - =head1 OPTIONS =over 3 @@ -178,7 +247,7 @@ Specifies what happens to email sent to this alias. The avaliable basic actions are: C, C. -If you've set the RT configuration variable B<$RT::UnsafeEmailCommands>, +If you've set the RT configuration variable B<< C >>, C and C are also available. You can execute two or more actions on a single message using a C<-> separated list. RT will execute the actions in the listed order. For example you can use C, @@ -259,13 +328,13 @@ there are situations in which you will want to authenticate users before allowing them to communicate with the system. You can do this via a plug-in mechanism in the RT configuration. -You can set the array C<@RT::MailPlugins> to be a list of plugins. The +You can set the array C<@MailPlugins> to be a list of plugins. The default plugin, if this is not given, is C - that is, authentication of the person is done based on the C header of the email. If you have additional filters or authentication mechanisms, you can list them here and they will be called in order: - @RT::MailPlugins = ( + Set( @MailPlugins => "Filter::SpamAssassin", "Auth::LDAP", # ... @@ -273,12 +342,12 @@ can list them here and they will be called in order: See the documentation for any additional plugins you have. -You may also put Perl subroutines into the C<@RT::MailPlugins> array, if +You may also put Perl subroutines into the C<@MailPlugins> array, if they behave as described below. =head1 WRITING PLUGINS -What's actually going on in the above is that C<@RT::MailPlugins> is a +What's actually going on in the above is that C<@MailPlugins> is a list of Perl modules; RT prepends C to the name, to form a package name, and then C's this module. The module is expected to provide a C subroutine, which takes a hash of @@ -319,5 +388,22 @@ the correspondent) or one, which is the normal mode of operation. Additionally, if C<-1> is returned, then the processing of the plug-ins stops immediately and the message is ignored. +=head1 ENVIRONMENT + +=over 4 + +=item EXTENSION + +Some MTAs will route mail sent to user-foo@host or user+foo@host to user@host +and present "foo" in the environment variable C. Mailgate adds value +of this variable to message in the C field of the message +header. + +See also C<--extension> option. Note that value of the environment variable is +always added to the message header when it's not empty even if C<--extension> +option is not provided. + +=back 4 + =cut diff --git a/rt/bin/rt-mailgate.in b/rt/bin/rt-mailgate.in index 49c4facfe..b2343a0f5 100644 --- a/rt/bin/rt-mailgate.in +++ b/rt/bin/rt-mailgate.in @@ -2,8 +2,8 @@ # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: -# -# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -52,30 +52,34 @@ rt-mailgate - Mail interface to RT3. =cut - use strict; use warnings; + use Getopt::Long; use LWP::UserAgent; +use HTTP::Request::Common qw($DYNAMIC_FILE_UPLOAD); +$DYNAMIC_FILE_UPLOAD = 1; use constant EX_TEMPFAIL => 75; +use constant BUFFER_SIZE => 8192; my %opts; GetOptions( \%opts, "queue=s", "action=s", "url=s", "jar=s", "help", "debug", "extension=s", "timeout=i" ); -if ( $opts{help} ) { +if ( $opts{'help'} ) { require Pod::Usage; import Pod::Usage; pod2usage("RT Mail Gateway\n"); exit 1; # Don't want to succeed if this is really an email! } -for (qw(url)) { - die "$0 invoked improperly\n\nNo $_ provided to mail gateway!\n" unless $opts{$_}; +unless ( $opts{'url'} ) { + print STDERR "$0 invoked improperly\n\nNo 'url' provided to mail gateway!\n"; + exit 1; } -my $ua = LWP::UserAgent->new(); -$ua->cookie_jar( { file => $opts{jar} } ); +my $ua = new LWP::UserAgent; +$ua->cookie_jar( { file => $opts{'jar'} } ) if $opts{'jar'}; my %args = ( SessionType => 'REST', # Surpress login box @@ -84,37 +88,54 @@ foreach ( qw(queue action) ) { $args{$_} = $opts{$_} if defined $opts{$_}; }; -# Read the message in from STDIN -$args{'message'} = do { local (@ARGV, $/); <> }; - -unless ( $args{message} =~ /\S/ ) { - print STDERR "$0: no message passed on STDIN!\n"; - exit 0; +if ( ($opts{'extension'} || '') =~ /^(?:action|queue|ticket)$/i ) { + $args{ lc $opts{'extension'} } = $ENV{'EXTENSION'} || $opts{$opts{'extension'}}; +} elsif ( $opts{'extension'} && $ENV{'EXTENSION'} ) { + print STDERR "Value of the --extension argument is not action, queue or ticket" + .", but environment variable EXTENSION is also defined. The former is ignored.\n"; } -if ($opts{'extension'}) { - $args{$opts{'extension'}} = $ENV{'EXTENSION'}; +# add ENV{'EXTENSION'} as X-RT-MailExtension to the message header +if ( my $value = ( $ENV{'EXTENSION'} || $opts{'extension'} ) ) { + # prepare value to avoid MIME format breakage + # strip trailing newline symbols + $value =~ s/(\r*\n)+$//; + # make a correct multiline header field, + # with tabs in the beginning of each line + $value =~ s/(\r*\n)/$1\t/g; + $opts{'headers'} .= "X-RT-Mail-Extension: $value\n"; } -# Set up cookie here. +# Read the message in from STDIN +my %message = write_down_message(); +unless( $message{'filename'} ) { + $args{'message'} = [ + undef, '', + 'Content-Type' => 'application/octet-stream', + Content => ${ $message{'content'} }, + ]; +} else { + $args{'message'} = [ + $message{'filename'}, '', + 'Content-Type' => 'application/octet-stream', + ]; +} my $full_url = $opts{'url'}. "/REST/1.0/NoAuth/mail-gateway"; -warn "Connecting to $full_url" if $opts{'debug'}; +print STDERR "$0: connecting to $full_url\n" if $opts{'debug'}; - - -$ua->timeout(exists($opts{'timeout'}) ? $opts{'timeout'} : 180); -my $r = $ua->post( $full_url, {%args} ); +$ua->timeout( exists( $opts{'timeout'} )? $opts{'timeout'}: 180 ); +my $r = $ua->post( $full_url, \%args, Content_Type => 'form-data' ); check_failure($r); my $content = $r->content; -warn $content if ($opts{debug}); +print STDERR $content ."\n" if $opts{'debug'}; if ( $content !~ /^(ok|not ok)/ ) { # It's not the server's fault if the mail is bogus. We just want to know that # *something* came out of the server. - warn <is_success(); + return if $r->is_success; # This ordinarily oughtn't to be able to happen, suggests a bug in RT. # So only load these heavy modules when they're needed. @@ -140,17 +164,64 @@ sub check_failure { require HTML::FormatText; my $error = $r->error_as_HTML; - my $tree = HTML::TreeBuilder->new->parse($error); + my $tree = HTML::TreeBuilder->new->parse( $error ); $tree->eof; # It'll be a cold day in hell before RT sends out bounces in HTML - my $formatter = HTML::FormatText->new( leftmargin => 0, - rightmargin => 50 ); - warn $formatter->format($tree); - warn "This is $0 exiting because of an undefined server error" if ($opts{debug}); + my $formatter = HTML::FormatText->new( + leftmargin => 0, + rightmargin => 50, + ); + print STDERR $formatter->format( $tree ); + print STDERR "\n$0: undefined server error\n" if $opts{'debug'}; exit EX_TEMPFAIL; } +sub write_down_message { + use File::Temp qw(tempfile); + + local $@; + my ($fh, $filename) = eval { tempfile() }; + if ( !$fh || $@ ) { + print STDERR "$0: Couldn't create temp file, using memory\n"; + print STDERR "error: $@\n" if $@; + + my $message = \do { local (@ARGV, $/); <> }; + unless ( $$message =~ /\S/ ) { + print STDERR "$0: no message passed on STDIN\n"; + exit 0; + } + $$message = $opts{'headers'} . $$message if $opts{'headers'}; + return ( content => $message ); + } + + binmode $fh; + binmode \*STDIN; + + print $fh $opts{'headers'} if $opts{'headers'}; + + my $buf; my $empty = 1; + while(1) { + my $status = read \*STDIN, $buf, BUFFER_SIZE; + unless ( defined $status ) { + print STDERR "$0: couldn't read message: $!\n"; + exit EX_TEMPFAIL; + } elsif ( !$status ) { + last; + } + $empty = 0 if $buf =~ /\S/; + print $fh $buf; + }; + close $fh; + + if ( $empty ) { + print STDERR "$0: no message passed on STDIN\n"; + exit 0; + } + print STDERR "$0: temp file is '$filename'\n" if $opts{'debug'}; + return (filename => $filename); +} + =head1 SYNOPSIS @@ -166,8 +237,6 @@ Usual invocation (from MTA): -See C for more. - =head1 OPTIONS =over 3 @@ -178,7 +247,7 @@ Specifies what happens to email sent to this alias. The avaliable basic actions are: C, C. -If you've set the RT configuration variable B<$RT::UnsafeEmailCommands>, +If you've set the RT configuration variable B<< C >>, C and C are also available. You can execute two or more actions on a single message using a C<-> separated list. RT will execute the actions in the listed order. For example you can use C, @@ -259,13 +328,13 @@ there are situations in which you will want to authenticate users before allowing them to communicate with the system. You can do this via a plug-in mechanism in the RT configuration. -You can set the array C<@RT::MailPlugins> to be a list of plugins. The +You can set the array C<@MailPlugins> to be a list of plugins. The default plugin, if this is not given, is C - that is, authentication of the person is done based on the C header of the email. If you have additional filters or authentication mechanisms, you can list them here and they will be called in order: - @RT::MailPlugins = ( + Set( @MailPlugins => "Filter::SpamAssassin", "Auth::LDAP", # ... @@ -273,12 +342,12 @@ can list them here and they will be called in order: See the documentation for any additional plugins you have. -You may also put Perl subroutines into the C<@RT::MailPlugins> array, if +You may also put Perl subroutines into the C<@MailPlugins> array, if they behave as described below. =head1 WRITING PLUGINS -What's actually going on in the above is that C<@RT::MailPlugins> is a +What's actually going on in the above is that C<@MailPlugins> is a list of Perl modules; RT prepends C to the name, to form a package name, and then C's this module. The module is expected to provide a C subroutine, which takes a hash of @@ -319,5 +388,22 @@ the correspondent) or one, which is the normal mode of operation. Additionally, if C<-1> is returned, then the processing of the plug-ins stops immediately and the message is ignored. +=head1 ENVIRONMENT + +=over 4 + +=item EXTENSION + +Some MTAs will route mail sent to user-foo@host or user+foo@host to user@host +and present "foo" in the environment variable C. Mailgate adds value +of this variable to message in the C field of the message +header. + +See also C<--extension> option. Note that value of the environment variable is +always added to the message header when it's not empty even if C<--extension> +option is not provided. + +=back 4 + =cut diff --git a/rt/bin/rt.in b/rt/bin/rt.in index 9731acf7d..aa3ac33de 100644 --- a/rt/bin/rt.in +++ b/rt/bin/rt.in @@ -2,8 +2,8 @@ # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: -# -# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -58,7 +58,19 @@ use Cwd; use LWP; use Text::ParseWords; use HTTP::Request::Common; +use HTTP::Headers; use Term::ReadLine; +use Time::Local; # used in prettyshow + +# strong (GSSAPI based) authentication is supported if the server does provide +# it and the perl modules GSSAPI and LWP::Authen::Negotiate are installed +# it can be suppressed by setting externalauth=0 (default is undef) +eval { require GSSAPI }; +my $no_strong_auth = 'missing perl module GSSAPI'; +if ( ! $@ ) { + eval {require LWP::Authen::Negotiate}; + $no_strong_auth = $@ ? 'missing perl module LWP::Authen::Negotiate' : 0; +} # We derive configuration information from hardwired defaults, dotfiles, # and the RT* environment variables (in increasing order of precedence). @@ -70,18 +82,27 @@ my $HOME = eval{(getpwuid($<))[7]} || "."; my %config = ( ( - debug => 0, - user => eval{(getpwuid($<))[0]} || $ENV{USER} || $ENV{USERNAME}, - passwd => undef, - server => 'http://localhost/', - query => undef, - orderby => undef, + debug => 0, + user => eval{(getpwuid($<))[0]} || $ENV{USER} || $ENV{USERNAME}, + passwd => undef, + server => 'http://localhost/', + query => "Status!='resolved' and Status!='rejected'", + orderby => 'id', + queue => undef, +# to protect against unlimited searches a better choice would be +# queue => 'Unknown_Queue', +# setting externalauth => undef will try GSSAPI auth if the corresponding perl +# modules are installed, externalauth => 0 is the backward compatible choice + externalauth => 0, ), config_from_file($ENV{RTCONFIG} || ".rtrc"), config_from_env() ); my $session = new Session("$HOME/.rt_sessions"); my $REST = "$config{server}/REST/1.0"; +$no_strong_auth = 'switched off by externalauth=0' + if defined $config{externalauth}; + my $prompt = 'rt> '; @@ -91,11 +112,12 @@ sub DEBUG { warn @_ if $config{debug} >= shift } # These regexes are used by command handlers to parse arguments. # (XXX: Ask Autrijus how i18n changes these definitions.) -my $name = '[\w.-]+'; -my $field = '(?:[a-zA-Z](?:[a-zA-Z0-9_-]|\s+)*)'; -my $label = '[a-zA-Z0-9@_.+-]+'; -my $labels = "(?:$label,)*$label"; -my $idlist = '(?:(?:\d+-)?\d+,)*(?:\d+-)?\d+'; +my $name = '[\w.-]+'; +my $CF_name = '[\sa-z0-9_ :()/-]+'; +my $field = '(?i:[a-z][a-z0-9_-]*|C(?:ustom)?F(?:ield)?-'.$CF_name.'|CF\.\{'.$CF_name.'\})'; +my $label = '[a-zA-Z0-9@_.+-]+'; +my $labels = "(?:$label,)*$label"; +my $idlist = '(?:(?:\d+-)?\d+,)*(?:\d+-)?\d+'; # Our command line looks like this: # @@ -119,6 +141,8 @@ my %handlers = ( grant => ["grant", "revoke"], take => ["take", "steal", "untake"], quit => ["quit", "exit"], + setcommand => ["del", "delete", "give", "res", "resolve", + "subject"], ); my %actions; @@ -137,16 +161,16 @@ sub handler { shift @ARGV if ($ARGV[0] eq 'rt'); # ignore a leading 'rt' if (@ARGV && exists $actions{$ARGV[0]}) { $action = shift @ARGV; - $actions{$action}->($action); + return $actions{$action}->($action); } else { print STDERR "rt: Unknown command '@ARGV'.\n"; print STDERR "rt: For help, run 'rt help'.\n"; + return 1; } } -handler(); -exit; +exit handler(); # Handler functions. # ------------------ @@ -166,10 +190,12 @@ sub shell { sub version { print "rt $VERSION\n"; + return 0; } sub logout { submit("$REST/logout") if defined $session->cookie; + return 0; } sub quit { @@ -179,7 +205,8 @@ sub quit { my %help; sub help { - my ($action, $type) = @_; + my ($action, $type, $rv) = @_; + $rv = defined $rv ? $rv : 0; my $key; # What help topics do we know about? @@ -228,6 +255,7 @@ sub help { } print STDERR $help{$key}, "\n\n"; + return $rv; } # Displays a list of objects that match some specified condition. @@ -240,6 +268,9 @@ sub list { $data{orderby} = $config{orderby}; } my $bad = 0; + my $rawprint = 0; + my $reverse_sort = 0; + my $queue = $config{queue}; while (@ARGV) { $_ = shift @ARGV; @@ -255,6 +286,13 @@ sub list { } elsif (/^-([isl])$/) { $data{format} = $1; + $rawprint = 1; + } + elsif (/^-q$/) { + $queue = shift @ARGV; + } + elsif (/^-r$/) { + $reverse_sort = 1; } elsif (/^-f$/) { if ($ARGV[0] !~ /^(?:(?:$field,)*$field)$/) { @@ -262,6 +300,8 @@ sub list { $bad = 1; last; } $data{fields} = shift @ARGV; + $data{format} = 's' if ! $data{format}; + $rawprint = 1; } elsif (!defined $q && !/^-/) { $q = $_; @@ -272,10 +312,35 @@ sub list { $bad = 1; last; } } + if ( ! $rawprint and ! exists $data{format} ) { + $data{format} = 'l'; + } + if ( $reverse_sort and $data{orderby} =~ /^-/ ) { + $data{orderby} =~ s/^-/+/; + } elsif ($reverse_sort) { + $data{orderby} =~ s/^\+?(.*)/-$1/; + } + if (!defined $q) { $q = $config{query}; } + $q =~ s/^#//; # get rid of leading hash + if ($q =~ /^\d+$/) { + # only digits, must be an id, formulate a correct query + $q = "id=$q" if $q =~ /^\d+$/; + } else { + # a string only, take it as an owner or requestor (quoting done later) + $q = "(Owner=$q or Requestor like $q) and $config{query}" + if $q =~ /^[\w\-]+$/; + # always add a query for a specific queue or (comma separated) queues + $queue =~ s/,/ or Queue=/g if $queue; + $q .= " and (Queue=$queue)" if $queue and $q and $q !~ /Queue\s*=/i + and $q !~ /id\s*=/i; + } + # correctly quote strings in a query + $q =~ s/(=|like\s)\s*([^'\d\s]\S*)\b/$1\'$2\'/g; + $type ||= "ticket"; unless ($type && defined $q) { my $item = $type ? "query string" : "object type"; @@ -283,10 +348,17 @@ sub list { $bad = 1; } #return help("list", $type) if $bad; - return suggest_help("list", $type) if $bad; + return suggest_help("list", $type, $bad) if $bad; + print "Query:$q\n" if ! $rawprint; my $r = submit("$REST/search/$type", { query => $q, %data }); - print $r->content; + if ( $rawprint ) { + print $r->content; + } else { + my $forms = Form::parse($r->content); + prettylist ($forms); + } + return 0; } # Displays selected information about a single object. @@ -295,10 +367,12 @@ sub show { my ($type, @objects, %data); my $slurped = 0; my $bad = 0; + my $rawprint = 0; + my $histspec; while (@ARGV) { $_ = shift @ARGV; - + s/^#// if /^#\d+/; # get rid of leading hash if (/^-t$/) { $bad = 1, last unless defined($type = get_type_argument()); } @@ -307,6 +381,7 @@ sub show { } elsif (/^-([isl])$/) { $data{format} = $1; + $rawprint = 1; } elsif (/^-$/ && !$slurped) { chomp(my @lines = ); @@ -325,9 +400,21 @@ sub show { $bad = 1; last; } $data{fields} = shift @ARGV; + # option f requires short raw listing format + $data{format} = 's'; + $rawprint = 1; + } + elsif (/^\d+$/ and my $spc2 = is_object_spec("ticket/$_", $type)) { + push @objects, $spc2; + $histspec = is_object_spec("ticket/$_/history", $type); + } + elsif (/^\d+\// and my $spc3 = is_object_spec("ticket/$_", $type)) { + push @objects, $spc3; + $rawprint = 1 if $_ =~ /\/content$/; } elsif (my $spec = is_object_spec($_, $type)) { push @objects, $spec; + $rawprint = 1 if $_ =~ /\/content$/ or $_ !~ /^ticket/; } else { my $datum = /^-/ ? "option" : "argument"; @@ -335,13 +422,17 @@ sub show { $bad = 1; last; } } + if ( ! $rawprint ) { + push @objects, $histspec if $histspec; + $data{format} = 'l' if ! exists $data{format}; + } unless (@objects) { whine "No objects specified."; $bad = 1; } #return help("show", $type) if $bad; - return suggest_help("show", $type) if $bad; + return suggest_help("show", $type, $bad) if $bad; my $r = submit("$REST/show", { id => \@objects, %data }); my $c = $r->content; @@ -350,8 +441,17 @@ sub show { # show ticket/id/attachments/id/content > foo.tar.gz if ($r->content_type !~ /^text\//) { chomp($c); + $rawprint = 1; + } + if ( $rawprint ) { + print $c; + } else { + # I do not know how to get more than one form correctly returned + $c =~ s!^RT/[\d\.]+ 200 Ok$!--!mg; + my $forms = Form::parse($c); + prettyshow ($forms); } - print $c; + return 0; } # To create a new object, we ask the server for a form with the defaults @@ -373,6 +473,7 @@ sub edit { while (@ARGV) { $_ = shift @ARGV; + s/^#// if /^#\d+/; # get rid of leading hash if (/^-e$/) { $edit = 1 } elsif (/^-i$/) { $input = 1 } @@ -397,7 +498,7 @@ sub edit { elsif (/^set$/i) { my $vars = 0; - while (@ARGV && $ARGV[0] =~ /^($field)([+-]?=)(.*)$/) { + while (@ARGV && $ARGV[0] =~ /^($field)([+-]?=)(.*)$/s) { my ($key, $op, $val) = ($1, $2, $3); my $hash = ($op eq '=') ? \%set : ($op =~ /^\+/) ? \%add : \%del; @@ -415,7 +516,7 @@ sub edit { my $vars = 0; my $hash = ($_ eq "add") ? \%add : \%del; - while (@ARGV && $ARGV[0] =~ /^($field)=(.*)$/) { + while (@ARGV && $ARGV[0] =~ /^($field)=(.*)$/s) { my ($key, $val) = ($1, $2); vpush($hash, lc $key, $val); @@ -428,6 +529,9 @@ sub edit { } $cl = $vars; } + elsif (/^\d+$/ and my $spc2 = is_object_spec("ticket/$_", $type)) { + push @objects, $spc2; + } elsif (my $spec = is_object_spec($_, $type)) { push @objects, $spec; } @@ -453,10 +557,10 @@ sub edit { whine "What type of object do you want to create?"; $bad = 1; } - @objects = ("$type/new"); + @objects = ("$type/new") if defined($type); } #return help($action, $type) if $bad; - return suggest_help($action, $type) if $bad; + return suggest_help($action, $type, $bad) if $bad; # We need a form to make changes to. We usually ask the server for # one, but we can avoid that if we are fed one on STDIN, or if the @@ -540,7 +644,7 @@ sub edit { if ($output) { print $text; - return; + return 0; } my $synerr = 0; @@ -566,11 +670,60 @@ EDIT: } else { print $r->content; - return; + return 0; } } print $r->content; } + return 0; +} + +# handler for special edit commands. A valid edit command is constructed and +# further work is delegated to the edit handler + +sub setcommand { + my ($action) = @_; + my ($id, $bad, $what); + if ( @ARGV ) { + $_ = shift @ARGV; + $id = $1 if (m|^(?:ticket/)?($idlist)$|); + } + if ( ! $id ) { + $bad = 1; + whine "No ticket number specified."; + } + if ( @ARGV ) { + if ($action eq 'subject') { + my $subject = '"'.join (" ", @ARGV).'"'; + @ARGV = (); + $what = "subject=$subject"; + } elsif ($action eq 'give') { + my $owner = shift @ARGV; + $what = "owner=$owner"; + } + } else { + if ( $action eq 'delete' or $action eq 'del' ) { + $what = "status=deleted"; + } elsif ($action eq 'resolve' or $action eq 'res' ) { + $what = "status=resolved"; + } elsif ($action eq 'take' ) { + $what = "owner=$config{user}"; + } elsif ($action eq 'untake') { + $what = "owner=Nobody"; + } + } + if (@ARGV) { + $bad = 1; + whine "Extraneous arguments for action $action: @ARGV."; + } + if ( ! $what ) { + $bad = 1; + whine "unrecognized action $action."; + } + return help("edit", undef, $bad) if $bad; + @ARGV = ( $id, "set", $what ); + print "Executing: rt edit @ARGV\n"; + return edit("edit"); } # We roll "comment" and "correspond" into the same handler. @@ -595,7 +748,7 @@ sub comment { if (/-a/) { unless (-f $ARGV[0] && -r $ARGV[0]) { whine "Cannot read attachment: '$ARGV[0]'."; - return; + return 0; } push @files, shift @ARGV; } @@ -665,7 +818,7 @@ sub comment { goto NEXT; } elsif (!@$o) { - return; + return 0; } @files = @{ vsplit($k->{Attachment}) }; @@ -683,6 +836,7 @@ sub comment { my $r = submit("$REST/ticket/$id/comment", \%data); print $r->content; + return 0; } # Merge one ticket into another. @@ -693,6 +847,7 @@ sub merge { while (@ARGV) { $_ = shift @ARGV; + s/^#// if /^#\d+/; # get rid of leading hash if (/^\d+$/) { push @id, $_; @@ -709,16 +864,19 @@ sub merge { $bad = 1; } #return help("merge", "ticket") if $bad; - return suggest_help("merge", "ticket") if $bad; + return suggest_help("merge", "ticket", $bad) if $bad; my $r = submit("$REST/ticket/$id[0]/merge/$id[1]"); print $r->content; + return 0; } # Link one ticket to another. sub link { my ($bad, $del, %data) = (0, 0, ()); + my $type; + my %ltypes = map { lc $_ => $_ } qw(DependsOn DependedOnBy RefersTo ReferredToBy HasMember MemberOf); @@ -728,21 +886,26 @@ sub link { if (/^-d$/) { $del = 1; } + elsif (/^-t$/) { + $bad = 1, last unless defined($type = get_type_argument()); + } else { whine "Unrecognised option: '$_'."; $bad = 1; last; } } - + + $type = "ticket" unless $type; # default type to tickets + if (@ARGV == 3) { my ($from, $rel, $to) = @ARGV; if ($from !~ /^\d+$/ || $to !~ /^\d+$/) { my $bad = $from =~ /^\d+$/ ? $to : $from; - whine "Invalid ticket ID '$bad' specified."; + whine "Invalid $type ID '$bad' specified."; $bad = 1; } - unless (exists $ltypes{lc $rel}) { - whine "Invalid link '$rel' specified."; + if (($type eq "ticket") && ( ! exists $ltypes{lc $rel})) { + whine "Invalid link '$rel' for type $type specified."; $bad = 1; } %data = (id => $from, rel => $rel, to => $to, del => $del); @@ -752,11 +915,11 @@ sub link { whine "Too $bad arguments specified."; $bad = 1; } - #return help("link", "ticket") if $bad; - return suggest_help("link", "ticket") if $bad; - - my $r = submit("$REST/ticket/link", \%data); + return suggest_help("link", $type, $bad) if $bad; + + my $r = submit("$REST/$type/link", \%data); print $r->content; + return 0; } # Take/steal a ticket @@ -791,10 +954,11 @@ sub take { whine "Too $bad arguments specified."; $bad = 1; } - return suggest_help("take", "ticket") if $bad; + return suggest_help("take", "ticket", $bad) if $bad; my $r = submit("$REST/ticket/$id/take", \%data); print $r->content; + return 0; } # Grant/revoke a user's rights. @@ -807,6 +971,7 @@ sub grant { } $revoke = 1 if $cmd->{action} eq 'revoke'; + return 0; } # Client <-> Server communication. @@ -820,6 +985,7 @@ sub submit { my ($uri, $content) = @_; my ($req, $data); my $ua = new LWP::UserAgent(agent => "RT/3.0b", env_proxy => 1); + my $h = HTTP::Headers->new; # Did the caller specify any data to send with the request? $data = []; @@ -845,9 +1011,22 @@ sub submit { } # Should we send authentication information to start a new session? - if (!defined $session->cookie) { - push @$data, ( user => $config{user} ); - push @$data, ( pass => $config{passwd} || read_passwd() ); + my $how = $config{server} =~ /^https/ ? 'over SSL' : 'unencrypted'; + (my $server = $config{server}) =~ s/^.*\/\/([^\/]+)\/?/$1/; + if ($config{externalauth}) { + $h->authorization_basic($config{user}, $config{passwd} || read_passwd() ); + print " Password will be sent to $server $how\n", + " Press CTRL-C now if you do not want to continue\n" + if ! $config{passwd}; + } elsif ( $no_strong_auth ) { + if (!defined $session->cookie) { + print " Strong encryption not available, $no_strong_auth\n", + " Password will be sent to $server $how\n", + " Press CTRL-C now if you do not want to continue\n" + if ! $config{passwd}; + push @$data, ( user => $config{user} ); + push @$data, ( pass => $config{passwd} || read_passwd() ); + } } # Now, we construct the request. @@ -858,6 +1037,9 @@ sub submit { $req = GET($uri); } $session->add_cookie_header($req); + if ($config{externalauth}) { + $req->header(%$h); + } # Then we send the request and parse the response. DEBUG(3, $req->as_string); @@ -874,7 +1056,7 @@ sub submit { $text =~ s/\n*$/\n/ if ($text); # "RT/3.0.1 401 Credentials required" - if ($status !~ m#^RT/\d+(?:\S+) (\d+) ([\w\s]+)$#) { + if ($status !~ m#^RT/\d+(?:\S+) (\d+) ([\w\s]+)$#) { warn "rt: Malformed RT response from $config{server}.\n"; warn "(Rerun with RTDEBUG=3 for details.)\n" if $config{debug} < 3; exit -1; @@ -1043,7 +1225,7 @@ sub submit { sub Form::parse { my $state = 0; my @forms = (); - my @lines = split /\n/, $_[0]; + my @lines = split /\n/, $_[0] if $_[0]; my ($c, $o, $k, $e) = ("", [], {}, ""); LINE: @@ -1199,7 +1381,8 @@ sub Form::compose { sub config_from_env { my %env; - foreach my $k ("DEBUG", "USER", "PASSWD", "SERVER", "QUERY", "ORDERBY") { + foreach my $k (qw(EXTERNALAUTH DEBUG USER PASSWD SERVER QUERY ORDERBY)) { + if (exists $ENV{"RT$k"}) { $env{lc $k} = $ENV{"RT$k"}; } @@ -1251,7 +1434,7 @@ sub parse_config_file { chomp; next if (/^#/ || /^\s*$/); - if (/^(user|passwd|server|query|orderby)\s+(.*)\s?$/) { + if (/^(externalauth|user|passwd|server|query|orderby|queue)\s+(.*)\s?$/) { $cfg{$1} = $2; } else { @@ -1270,7 +1453,7 @@ sub whine { my $sub = (caller(1))[3]; $sub =~ s/^main:://; warn "rt: $sub: @_\n"; - return; + return 0; } sub read_passwd { @@ -1331,7 +1514,37 @@ sub vsplit { # XXX: This should become a real parser, à la Text::ParseWords. $line =~ s/^\s+//; $line =~ s/\s+$//; - push @words, split /\s*,\s*/, $line; + my ( $a, $b ) = split /,/, $line, 2; + + while ($a) { + no warnings 'uninitialized'; + if ( $a =~ /^'/ ) { + my $s = $a; + while ( $a !~ /'$/ || ( $a !~ /(\\\\)+'$/ + && $a =~ /(\\)+'$/ )) { + ( $a, $b ) = split /,/, $b, 2; + $s .= ',' . $a; + } + push @words, $s; + } + elsif ( $a =~ /^q{/ ) { + my $s = $a; + while ( $a !~ /}$/ ) { + ( $a, $b ) = + split /,/, $b, 2; + $s .= ',' . $a; + } + $s =~ s/^q{/'/; + $s =~ s/}/'/; + push @words, $s; + } + else { + push @words, $a; + } + ( $a, $b ) = split /,/, $b, 2; + } + + } return \@words; @@ -1406,14 +1619,130 @@ sub is_object_spec { $spec =~ s|^(?:$type/)?|$type/| if defined $type; return $spec if ($spec =~ m{^$name/(?:$idlist|$labels)(?:/.*)?$}o); - return; + return 0; } sub suggest_help { - my ($action, $type) = @_; + my ($action, $type, $rv) = @_; print STDERR "rt: For help, run 'rt help $action'.\n" if defined $action; print STDERR "rt: For help, run 'rt help $type'.\n" if defined $type; + return $rv; +} + +sub str2time { + # simplified procedure for parsing date, avoid loading Date::Parse + my %month = (Jan => 0, Feb => 1, Mar => 2, Apr => 3, May => 4, Jun => 5, + Jul => 6, Aug => 7, Sep => 8, Oct => 9, Nov => 10, Dec => 11); + $_ = shift; + my ($mon, $day, $hr, $min, $sec, $yr, $monstr); + if ( /(\w{3})\s+(\d\d?)\s+(\d\d):(\d\d):(\d\d)\s+(\d{4})/ ) { + ($monstr, $day, $hr, $min, $sec, $yr) = ($1, $2, $3, $4, $5, $6); + $mon = $month{$monstr} if exists $month{$monstr}; + } elsif ( /(\d{4})-(\d\d)-(\d\d)\s+(\d\d):(\d\d):(\d\d)/ ) { + ($yr, $mon, $day, $hr, $min, $sec) = ($1, $2-1, $3, $4, $5, $6); + } + if ( $yr and defined $mon and $day and defined $hr and defined $sec ) { + return timelocal($sec,$min,$hr,$day,$mon,$yr); + } else { + print "Unknown date format in parsedate: $_\n"; + return undef; + } +} + +sub date_diff { + my ($old, $new) = @_; + $new = time() if ! $new; + $old = str2time($old) if $old !~ /^\d+$/; + $new = str2time($new) if $new !~ /^\d+$/; + return "???" if ! $old or ! $new; + + my %seconds = (min => 60, + hr => 60*60, + day => 60*60*24, + wk => 60*60*24*7, + mth => 60*60*24*30, + yr => 60*60*24*365); + + my $diff = $new - $old; + my $what = 'sec'; + my $howmuch = $diff; + for ( sort {$seconds{$a} <=> $seconds{$b}} keys %seconds) { + last if $diff < $seconds{$_}; + $what = $_; + $howmuch = int($diff/$seconds{$_}); + } + return "$howmuch $what"; +} + +sub prettyshow { + my $forms = shift; + my ($form) = grep { exists $_->[2]->{Queue} } @$forms; + my $k = $form->[2]; + # dates are in local time zone + if ( $k ) { + print "Date: $k->{Created}\n"; + print "From: $k->{Requestors}\n"; + print "Cc: $k->{Cc}\n" if $k->{Cc}; + print "X-AdminCc: $k->{AdminCc}\n" if $k->{AdminCc}; + print "X-Queue: $k->{Queue}\n"; + print "Subject: [rt #$k->{id}] $k->{Subject}\n\n"; + } + # dates in these attributes are in GMT and will be converted + foreach my $form (@$forms) { + my ($c, $o, $k, $e) = @$form; + next if ! $k->{id} or exists $k->{Queue}; + if ( exists $k->{Created} ) { + my ($y,$m,$d,$hh,$mm,$ss) = ($k->{Created} =~ /(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)/); + $m--; + my $created = localtime(timegm($ss,$mm,$hh,$d,$m,$y)); + if ( exists $k->{Description} ) { + print "===> $k->{Description} on $created\n"; + } + } + print "$k->{Content}\n" if exists $k->{Content} and + $k->{Content} !~ /to have no content$/ and + $k->{Type} ne 'EmailRecord'; + print "$k->{Attachments}\n" if exists $k->{Attachments} and + $k->{Attachments}; + } +} + +sub prettylist { + my $forms = shift; + my $heading = "Ticket Owner Queue Age Told Status Requestor Subject\n"; + $heading .= '-' x 80 . "\n"; + my (@open, @me); + foreach my $form (@$forms) { + my ($c, $o, $k, $e) = @$form; + next if ! $k->{id}; + print $heading if $heading; + $heading = ''; + my $id = $k->{id}; + $id =~ s!^ticket/!!; + my $owner = $k->{Owner} eq 'Nobody' ? '' : $k->{Owner}; + $owner = substr($owner, 0, 5); + my $queue = substr($k->{Queue}, 0, 5); + my $subject = substr($k->{Subject}, 0, 30); + my $age = date_diff($k->{Created}); + my $told = $k->{Told} eq 'Not set' ? '' : date_diff($k->{Told}); + my $status = substr($k->{Status}, 0, 6); + my $requestor = substr($k->{Requestors}, 0, 9); + my $line = sprintf "%6s %5s %5s %6s %6s %-6s %-9s %-30s\n", + $id, $owner, $queue, $age, $told, $status, $requestor, $subject; + if ( $k->{Owner} eq 'Nobody' ) { + push @open, $line; + } elsif ($k->{Owner} eq $config{user} ) { + push @me, $line; + } else { + print $line; + } + } + print "No matches found\n" if $heading; + printf "========== my %2d open tickets ==========\n", scalar @me if @me; + print @me if @me; + printf "========== %2d unowned tickets ==========\n", scalar @open if @open; + print @open if @open; } __DATA__ @@ -1511,9 +1840,21 @@ Text: - passwd RT user's password. - query Default RT Query for list action - orderby Default RT order for list action + - queue Default RT Queue for list action + - externalauth <0|1> Use HTTP Basic authentication + explicitely setting externalauth to 0 inhibits also GSSAPI based + authentication, if LWP::Authen::Negotiate (and GSSAPI) is installed Blank and #-commented lines are ignored. + Sample configuration file contents: + + server https://rt.somewhere.com/ + # more than one queue can be given (by adding a query expression) + queue helpdesk or queue=support + query Status != resolved and Owner=myaccount + + Environment variables: The following environment variables override any corresponding @@ -1521,6 +1862,7 @@ Text: - RTUSER - RTPASSWD + - RTEXTERNALAUTH - RTSERVER - RTDEBUG Numeric debug level. (Set to 3 for full logs.) - RTCONFIG Specifies a name other than ".rtrc" for the @@ -1552,8 +1894,12 @@ Text: "user/root,1-3,5,7-10,ams" is a list of ten users; the same list can also be written as "user/ams,root,1,2,3,5,7,8-10". + If just a number is given as object specification it will be + interpreted as ticket/ + Examples: + 1 # the same as ticket/1 ticket/1 ticket/1/attachments ticket/1/attachments/3 @@ -1591,6 +1937,22 @@ Text: - rt help (action-specific details) - rt help types (a list of possible types) + The following actions on tickets are also possible: + + - comment Add comments to a ticket + - correspond Add comments to a ticket + - merge Merge one ticket into another + - link Link one ticket to another + - take Take a ticket (steal and untake are possible as well) + + For several edit set subcommands that are frequently used abbreviations + have been introduced. These abbreviations are: + + - delete or del delete a ticket (edit set status=deleted) + - resolve or res resolve a ticket (edit set status=resolved) + - subject change subject of ticket (edit set subject=string) + - give give a ticket to somebody (edit set owner=user) + -- Title: types @@ -1629,6 +1991,13 @@ Text: - merge - comment - correspond + - take + - steal + - untake + - give + - resolve + - delete + - subject Attributes: @@ -1687,6 +2056,83 @@ Text: -- +Title: subject +Text: + + Syntax: + + rt subject + + Change the subject of a ticket whose ticket id is given. + +-- + +Title: give +Text: + + Syntax: + + rt give + + Give a ticket whose ticket id is given to another user. + +-- + +Title: steal +Text: + + rt steal + + Steal a ticket whose ticket id is given, i.e. set the owner to myself. + +-- + +Title: take +Text: + + Syntax: + + rt take + + Take a ticket whose ticket id is given, i.e. set the owner to myself. + +-- + +Title: untake +Text: + + Syntax: + + rt untake + + Untake a ticket whose ticket id is given, i.e. set the owner to Nobody. + +-- + +Title: resolve +Title: res +Text: + + Syntax: + + rt resolve + + Resolves a ticket whose ticket id is given. + +-- + +Title: delete +Title: del +Text: + + Syntax: + + rt delete + + Deletes a ticket whose ticket id is given. + +-- + Title: logout Text: @@ -1725,24 +2171,30 @@ Text: The following options control how much information is displayed about each matching object: - -i Numeric IDs only. (Useful for |rt edit -; see examples.) - -s Short description. - -l Longer description. + -i Numeric IDs only. (Useful for |rt edit -; see examples.) + -s Short description. + -l Longer description. + -f Orders the returned list by the specified field. - -S var=val Submits the specified variable with the request. - -t type Specifies the type of object to look for. (The - default is "ticket".) + -o +/- Orders the returned list by the specified field. + -r reversed order (useful if a default was given) + -q queue[s] restricts the query to the queue[s] given + multiple queues are separated by comma + -S var=val Submits the specified variable with the request. + -t type Specifies the type of object to look for. (The + default is "ticket".) Examples: - rt ls "Priority > 5 and Status='new'" - rt ls -o +Subject "Priority > 5 and Status='new'" - rt ls -o -Created "Priority > 5 and Status='new'" + rt ls "Priority > 5 and Status=new" + rt ls -o +Subject "Priority > 5 and Status=new" + rt ls -o -Created "Priority > 5 and Status=new" rt ls -i "Priority > 5"|rt edit - set status=resolved rt ls -t ticket "Subject like '[PATCH]%'" + rt ls -q systems + rt ls -f owner,subject -- @@ -1760,16 +2212,28 @@ Text: that refers to the links for tickets 1-3). Consult "rt help " and "rt help objects" for further details. + If only a number is given it will be interpreted as the objects + ticket/number and ticket/number/history + This command writes a set of forms representing the requested object data to STDOUT. Options: + The following options control how much information is displayed + about each matching object: + + Without any formatting options prettyprinted output is generated. + Giving any of the two options below reverts to raw output. + -s Short description (history and attachments only). + -l Longer description (history and attachments only). + + In addition, - Read IDs from STDIN instead of the command-line. -t type Specifies object type. -f a,b,c Restrict the display to the specified fields. -S var=val Submits the specified variable with the request. - -v Verbose display + Examples: rt show -t ticket -f id,subject,status 1-3 @@ -1777,8 +2241,9 @@ Text: rt show ticket/3/attachments/29/content rt show ticket/1-3/links rt show ticket/3/history - rt show -v ticket/3/history + rt show -l ticket/3/history rt show -t user 2 + rt show 2 -- @@ -1795,6 +2260,8 @@ Text: Edits information corresponding to the specified objects. + A purely numeric object id nnn is translated into ticket/nnn + If, instead of "edit", an action of "new" or "create" is specified, then a new object is created. In this case, no numeric object IDs may be specified, but the syntax and behaviour remain otherwise @@ -1834,7 +2301,7 @@ Text: rt create -t ticket # Non-interactive. - rt edit ticket/1-3 add cc=foo@example.com set priority=3 + rt edit ticket/1-3 add cc=foo@example.com set priority=3 due=tomorrow rt ls -t tickets -i 'Priority > 5' | rt edit - set status=resolved rt edit ticket/4 set priority=3 owner=bar@example.com \ add cc=foo@example.com bcc=quux@example.net @@ -1930,6 +2397,35 @@ Text: (XXX: I'm going to have to write it, aren't I?) + Until it exists here a short description of important constructs: + + The two simple forms of query expressions are the constructs + Attribute like Value and + Attribute = Value or Attribute != Value + + Whether attributes can be matched using like or using = is built into RT. + The attributes id, Queue, Owner Priority and Status require the = or != + tests. + + If Value is a string it must be quoted and may contain the wildcard + character %. If the string does not contain white space, the quoting + may however be omitted, it will be added automatically when parsing + the input. + + Simple query expressions can be combined using and, or and parentheses + can be used to group expressions. + + As a special case a standalone string (which would not form a correct + query) is transformed into (Owner='string' or Requestor like 'string%') + and added to the default query, i.e. the query is narrowed down. + + If no Queue=name clause is contained in the query, a default clause + Queue=$config{queue} is added. + + Examples: + Status!='resolved' and Status!='rejected' + (Owner='myaccount' or Requestor like 'myaccount%') and Status!='resolved' + -- Title: form @@ -1984,10 +2480,43 @@ Title: example Title: examples Text: - This section will be filled in with useful examples, once it becomes - more clear what examples may be useful. - - For the moment, please consult examples provided with each action. + some useful examples + + All the following list requests will be restricted to the default queue. + That can be changed by adding the option -q queuename + + List all tickets that are not rejected/resolved + rt ls + List all tickets that are new and do not have an owner + rt ls "status=new and owner=nobody" + List all tickets which I have sent or of which I am the owner + rt ls myaccount + List all attributes for the ticket 6977 (ls -l instead of ls) + rt ls -l 6977 + Show the content of ticket 6977 + rt show 6977 + Show all attributes in the ticket and in the history of the ticket + rt show -l 6977 + Comment a ticket (mail is sent to all queue watchers, i.e. AdminCc's) + rt comment 6977 + This will open an editor and lets you add text (attribute Text:) + Other attributes may be changed as well, but usually don't do that. + Correspond a ticket (like comment, but mail is also sent to requestors) + rt correspond 6977 + Edit a ticket (generic change, interactive using the editor) + rt edit 6977 + Change the owner of a ticket non interactively + rt edit 6977 set owner=myaccount + or + rt give 6977 account + or + rt take 6977 + Change the status of a ticket + rt edit 6977 set status=resolved + or + rt resolve 6977 + Change the status of all tickets I own to resolved !!! + rt ls -i owner=myaccount | rt edit - set status=resolved -- diff --git a/rt/bin/standalone_httpd b/rt/bin/standalone_httpd index 1057ce0ea..241af8114 100755 --- a/rt/bin/standalone_httpd +++ b/rt/bin/standalone_httpd @@ -2,8 +2,8 @@ # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: -# -# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -49,19 +49,138 @@ use warnings; use strict; +# fix lib paths, some may be relative BEGIN { - use lib( "/opt/rt3/local/lib", "/opt/rt3/lib"); - use RT; - RT::LoadConfig(); - if ($RT::DevelMode) { require Module::Refresh; } + require File::Spec; + my @libs = ("lib", "local/lib"); + my $bin_path; + + for my $lib (@libs) { + unless ( File::Spec->file_name_is_absolute($lib) ) { + unless ($bin_path) { + if ( File::Spec->file_name_is_absolute(__FILE__) ) { + $bin_path = ( File::Spec->splitpath(__FILE__) )[1]; + } + else { + require FindBin; + no warnings "once"; + $bin_path = $FindBin::Bin; + } + } + $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib ); + } + unshift @INC, $lib; + } + } -RT::Init(); +use RT; +RT::LoadConfig(); +RT->InitLogging(); +if (RT->Config->Get('DevelMode')) { require Module::Refresh; } + +RT::CheckPerlRequirements(); +RT->InitPluginPaths(); + +my $explicit_port = shift @ARGV; +my $port = $explicit_port || RT->Config->Get('WebPort') || '8080'; + + +require RT::Handle; +my ($integrity, $state, $msg) = RT::Handle->CheckIntegrity; + +unless ( $integrity ) { + print STDERR <ConfigFile && !-w _) { + die 'Since your configuration exists (' + . RT::Installer->ConfigFile + . ") but is not writable, I'm refusing to do anything.\n"; + } -my $port = shift @ARGV || $RT::WebPort || '8080'; -use RT::Interface::Web::Standalone; + RT->Config->Set( 'LexiconLanguages' => '*' ); + RT::I18N->Init; + + RT->InstallMode(1); +} else { + RT->ConnectToDatabase(); + RT->InitSystemObjects(); + RT->InitClasses(); + RT->InitPlugins(); + RT->Config->PostLoadCheck(); + + my ($status, $msg) = RT::Handle->CheckCompatibility( + $RT::Handle->dbh, 'post' + ); + unless ( $status ) { + print STDERR $msg, "\n\n"; + exit -1; + } +} + +require RT::Interface::Web::Standalone; my $server = RT::Interface::Web::Standalone->new; -$server->port($port); -$server->run(); +run_server($port); +exit 0; +sub run_server { + my $port = shift; + $server->port($port); + eval { $server->run() }; + if ( my $err = $@ ) { + handle_startup_error($err); + } +} + +sub handle_startup_error { + my $err = shift; + if ( $err =~ /bind: Permission denied/ ) { + handle_bind_error(); + } else { + die + "Something went wrong while trying to run RT's standalone web server:\n\t" + . $err; + } +} + + +sub handle_bind_error { + + print STDERR < # # (Except where explicitly superseded by other copyright notices) @@ -49,19 +49,138 @@ use warnings; use strict; +# fix lib paths, some may be relative BEGIN { - use lib( "@LOCAL_LIB_PATH@", "@RT_LIB_PATH@"); - use RT; - RT::LoadConfig(); - if ($RT::DevelMode) { require Module::Refresh; } + require File::Spec; + my @libs = ("@RT_LIB_PATH@", "@LOCAL_LIB_PATH@"); + my $bin_path; + + for my $lib (@libs) { + unless ( File::Spec->file_name_is_absolute($lib) ) { + unless ($bin_path) { + if ( File::Spec->file_name_is_absolute(__FILE__) ) { + $bin_path = ( File::Spec->splitpath(__FILE__) )[1]; + } + else { + require FindBin; + no warnings "once"; + $bin_path = $FindBin::Bin; + } + } + $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib ); + } + unshift @INC, $lib; + } + } -RT::Init(); +use RT; +RT::LoadConfig(); +RT->InitLogging(); +if (RT->Config->Get('DevelMode')) { require Module::Refresh; } + +RT::CheckPerlRequirements(); +RT->InitPluginPaths(); + +my $explicit_port = shift @ARGV; +my $port = $explicit_port || RT->Config->Get('WebPort') || '8080'; + + +require RT::Handle; +my ($integrity, $state, $msg) = RT::Handle->CheckIntegrity; + +unless ( $integrity ) { + print STDERR <ConfigFile && !-w _) { + die 'Since your configuration exists (' + . RT::Installer->ConfigFile + . ") but is not writable, I'm refusing to do anything.\n"; + } -my $port = shift @ARGV || $RT::WebPort || '8080'; -use RT::Interface::Web::Standalone; + RT->Config->Set( 'LexiconLanguages' => '*' ); + RT::I18N->Init; + + RT->InstallMode(1); +} else { + RT->ConnectToDatabase(); + RT->InitSystemObjects(); + RT->InitClasses(); + RT->InitPlugins(); + RT->Config->PostLoadCheck(); + + my ($status, $msg) = RT::Handle->CheckCompatibility( + $RT::Handle->dbh, 'post' + ); + unless ( $status ) { + print STDERR $msg, "\n\n"; + exit -1; + } +} + +require RT::Interface::Web::Standalone; my $server = RT::Interface::Web::Standalone->new; -$server->port($port); -$server->run(); +run_server($port); +exit 0; +sub run_server { + my $port = shift; + $server->port($port); + eval { $server->run() }; + if ( my $err = $@ ) { + handle_startup_error($err); + } +} + +sub handle_startup_error { + my $err = shift; + if ( $err =~ /bind: Permission denied/ ) { + handle_bind_error(); + } else { + die + "Something went wrong while trying to run RT's standalone web server:\n\t" + . $err; + } +} + + +sub handle_bind_error { + + print STDERR < # # (Except where explicitly superseded by other copyright notices) @@ -64,7 +64,30 @@ BEGIN { } -use lib ( "/opt/rt3/local/lib", "/opt/rt3/lib" ); +# fix lib paths, some may be relative +BEGIN { + require File::Spec; + my @libs = ("lib", "local/lib"); + my $bin_path; + + for my $lib (@libs) { + unless ( File::Spec->file_name_is_absolute($lib) ) { + unless ($bin_path) { + if ( File::Spec->file_name_is_absolute(__FILE__) ) { + $bin_path = ( File::Spec->splitpath(__FILE__) )[1]; + } + else { + require FindBin; + no warnings "once"; + $bin_path = $FindBin::Bin; + } + } + $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib ); + } + unshift @INC, $lib; + } + +} use RT; package RT::Mason; @@ -74,21 +97,35 @@ use vars qw($Nobody $SystemUser $Handler $r); #This drags in RT's config.pm BEGIN { RT::LoadConfig(); - if ($RT::DevelMode) { require Module::Refresh; } + if (RT->Config->Get('DevelMode')) { require Module::Refresh; } + RT->InitPluginPaths(); } - { + require RT::Handle; + my $dsn = RT::Handle->DSN; + my $user = RT->Config->Get('DatabaseUser'); + my $pass = RT->Config->Get('DatabasePassword'); + + my $dbh = DBI->connect( + $dsn, $user, $pass, + { RaiseError => 0, PrintError => 0 }, + ); + if ( $dbh ) { + my ($status, $msg) = RT::Handle->CheckCompatibility( $dbh, 'post' ); + die $msg unless $status; + } +} +{ package HTML::Mason::Commands; use vars qw(%session); } use RT::Interface::Web; use RT::Interface::Web::Handler; -$Handler = RT::Interface::Web::Handler->new(@RT::MasonParameters); -if ($ENV{'MOD_PERL'} && !$RT::DevelMode) { +if ($ENV{'MOD_PERL'} && !RT->Config->Get('DevelMode')) { # Under static_source, we need to purge the component cache # each time we restart, so newer components may be reloaded. # @@ -116,10 +153,14 @@ sub handler { #$r->content_type !~ m!(^text/|\bxml\b)!i or return -1; # } - Module::Refresh->refresh if $RT::DevelMode; + Module::Refresh->refresh if RT->Config->Get('DevelMode'); RT::Init(); + $Handler ||= RT::Interface::Web::Handler->new( + RT->Config->Get('MasonParameters') + ); + my %session; my $status; eval { $status = $Handler->handle_request($r) }; diff --git a/rt/bin/webmux.pl.in b/rt/bin/webmux.pl.in index b21d02673..7e61b2775 100644 --- a/rt/bin/webmux.pl.in +++ b/rt/bin/webmux.pl.in @@ -2,8 +2,8 @@ # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: -# -# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC +# +# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -64,7 +64,30 @@ BEGIN { } -use lib ( "@LOCAL_LIB_PATH@", "@RT_LIB_PATH@" ); +# fix lib paths, some may be relative +BEGIN { + require File::Spec; + my @libs = ("@RT_LIB_PATH@", "@LOCAL_LIB_PATH@"); + my $bin_path; + + for my $lib (@libs) { + unless ( File::Spec->file_name_is_absolute($lib) ) { + unless ($bin_path) { + if ( File::Spec->file_name_is_absolute(__FILE__) ) { + $bin_path = ( File::Spec->splitpath(__FILE__) )[1]; + } + else { + require FindBin; + no warnings "once"; + $bin_path = $FindBin::Bin; + } + } + $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib ); + } + unshift @INC, $lib; + } + +} use RT; package RT::Mason; @@ -74,21 +97,35 @@ use vars qw($Nobody $SystemUser $Handler $r); #This drags in RT's config.pm BEGIN { RT::LoadConfig(); - if ($RT::DevelMode) { require Module::Refresh; } + if (RT->Config->Get('DevelMode')) { require Module::Refresh; } + RT->InitPluginPaths(); } - { + require RT::Handle; + my $dsn = RT::Handle->DSN; + my $user = RT->Config->Get('DatabaseUser'); + my $pass = RT->Config->Get('DatabasePassword'); + + my $dbh = DBI->connect( + $dsn, $user, $pass, + { RaiseError => 0, PrintError => 0 }, + ); + if ( $dbh ) { + my ($status, $msg) = RT::Handle->CheckCompatibility( $dbh, 'post' ); + die $msg unless $status; + } +} +{ package HTML::Mason::Commands; use vars qw(%session); } use RT::Interface::Web; use RT::Interface::Web::Handler; -$Handler = RT::Interface::Web::Handler->new(@RT::MasonParameters); -if ($ENV{'MOD_PERL'} && !$RT::DevelMode) { +if ($ENV{'MOD_PERL'} && !RT->Config->Get('DevelMode')) { # Under static_source, we need to purge the component cache # each time we restart, so newer components may be reloaded. # @@ -116,10 +153,14 @@ sub handler { #$r->content_type !~ m!(^text/|\bxml\b)!i or return -1; # } - Module::Refresh->refresh if $RT::DevelMode; + Module::Refresh->refresh if RT->Config->Get('DevelMode'); RT::Init(); + $Handler ||= RT::Interface::Web::Handler->new( + RT->Config->Get('MasonParameters') + ); + my %session; my $status; eval { $status = $Handler->handle_request($r) }; diff --git a/rt/config.layout b/rt/config.layout index 1550111d5..52fcef1ee 100644 --- a/rt/config.layout +++ b/rt/config.layout @@ -23,6 +23,7 @@ sbindir: ${exec_prefix}/sbin sysconfdir: ${prefix}/etc mandir: ${prefix}/man + plugindir: ${prefix}/plugins libdir: ${prefix}/lib datadir: ${prefix}/share htmldir: ${datadir}/html @@ -38,15 +39,16 @@ customlibdir: ${customdir}/lib - prefix: `pwd` + prefix: . exec_prefix: ${prefix} bindir: ${exec_prefix}/bin sbindir: ${exec_prefix}/sbin sysconfdir: ${prefix}/etc mandir: ${prefix}/man + plugindir: ${prefix}/plugins libdir: ${prefix}/lib datadir: ${prefix}/share - htmldir: ${prefix}/html + htmldir: ${datadir}/html manualdir: ${datadir}/doc localstatedir: ${prefix}/var logfiledir: ${localstatedir}/log @@ -70,6 +72,7 @@ libdir: ${prefix}/lib mandir: ${datadir}/man # FIXME: no such directory in FHS; shouldn't go to somewhere in "${datadir}/rt/"? + plugindir: ${datadir}/plugins htmldir: ${datadir}/html manualdir: ${datadir}/doc localstatedir: /var @@ -91,6 +94,7 @@ sbindir: ${exec_prefix}/sbin sysconfdir: ${prefix}/etc+ mandir: ${prefix}/man + plugindir: ${prefix}/plugins libdir: ${prefix}/lib+ datadir: ${prefix}/share+ htmldir: ${datadir}/html @@ -113,6 +117,7 @@ sbindir: ${exec_prefix}/sbin sysconfdir: ${prefix}/etc mandir: ${prefix}/man + plugindir: ${prefix}/plugins libdir: ${prefix}/lib datadir: ${prefix} htmldir: ${datadir}/html @@ -130,7 +135,7 @@ # RH path layout. - prefix: /usr/ + prefix: /usr exec_prefix: ${prefix} bindir: ${exec_prefix}/bin sbindir: ${exec_prefix}/sbin @@ -140,7 +145,8 @@ datadir: /var/rt htmldir: ${datadir}/html manualdir: ${datadir}/doc - localstatedir: /var/ + plugindir: ${datadir}/plugins + localstatedir: /var logfiledir: ${localstatedir}/log/rt masonstatedir: ${localstatedir}/rt/mason_data sessionstatedir: ${localstatedir}/rt/session_data @@ -150,3 +156,26 @@ customlexdir: ${customdir}/po customlibdir: ${customdir}/lib + + + prefix: /opt/rt3 + exec_prefix: ${prefix} + bindir: bin + sbindir: sbin + sysconfdir: etc + mandir: man + plugindir: plugins + libdir: lib + datadir: share + htmldir: ${datadir}/html + manualdir: ${datadir}/doc + localstatedir: var + logfiledir: ${localstatedir}/log + masonstatedir: ${localstatedir}/mason_data + sessionstatedir: ${localstatedir}/session_data + customdir: local + custometcdir: ${customdir}/etc + customhtmldir: ${customdir}/html + customlexdir: ${customdir}/po + customlibdir: ${customdir}/lib + diff --git a/rt/config.pld b/rt/config.pld index 3d0202762..c83c28fdb 100644 --- a/rt/config.pld +++ b/rt/config.pld @@ -1,18 +1,19 @@ (test "x$prefix" = "xNONE" || test "x$prefix" = "x") && prefix=/opt/rt3 (test "x$exec_prefix" = "xNONE" || test "x$exec_prefix" = "x") && exec_prefix=${prefix} -bindir=${exec_prefix}/bin -sbindir=${exec_prefix}/sbin -sysconfdir=${prefix}/etc -mandir=${prefix}/man -libdir=${prefix}/lib -datadir=${prefix}/share +bindir=bin +sbindir=sbin +sysconfdir=etc +mandir=man +(test "x$plugindir" = "xNONE" || test "x$plugindir" = "x") && plugindir=plugins +libdir=lib +datadir=share htmldir=${datadir}/html (test "x$manualdir" = "xNONE" || test "x$manualdir" = "x") && manualdir=${datadir}/doc -localstatedir=${prefix}/var +localstatedir=var (test "x$logfiledir" = "xNONE" || test "x$logfiledir" = "x") && logfiledir=${localstatedir}/log (test "x$masonstatedir" = "xNONE" || test "x$masonstatedir" = "x") && masonstatedir=${localstatedir}/mason_data (test "x$sessionstatedir" = "xNONE" || test "x$sessionstatedir" = "x") && sessionstatedir=${localstatedir}/session_data -(test "x$customdir" = "xNONE" || test "x$customdir" = "x") && customdir=${prefix}/local +(test "x$customdir" = "xNONE" || test "x$customdir" = "x") && customdir=local (test "x$custometcdir" = "xNONE" || test "x$custometcdir" = "x") && custometcdir=${customdir}/etc (test "x$customhtmldir" = "xNONE" || test "x$customhtmldir" = "x") && customhtmldir=${customdir}/html (test "x$customlexdir" = "xNONE" || test "x$customlexdir" = "x") && customlexdir=${customdir}/po diff --git a/rt/config.status b/rt/config.status index c5d48d96e..9c286b565 100755 --- a/rt/config.status +++ b/rt/config.status @@ -391,7 +391,7 @@ exec 6>&1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by RT $as_me 3.6.10, which was +This file was extended by RT $as_me 3.8.7, which was generated by GNU Autoconf 2.64. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -404,7 +404,7 @@ on `(hostname || uname -n) 2>/dev/null | sed 1q` " # Files that config.status was made for. -config_files=" sbin/rt-dump-database sbin/rt-setup-database sbin/rt-test-dependencies bin/mason_handler.fcgi bin/mason_handler.scgi bin/standalone_httpd bin/rt-crontool bin/rt-mailgate bin/rt Makefile etc/RT_Config.pm lib/RT.pm bin/mason_handler.svc bin/webmux.pl" +config_files=" etc/upgrade/3.8-branded-queues-extension etc/upgrade/3.8-ical-extension etc/upgrade/split-out-cf-categories sbin/rt-attributes-viewer sbin/rt-dump-database sbin/rt-setup-database sbin/rt-test-dependencies sbin/rt-email-digest sbin/rt-email-dashboards sbin/rt-clean-sessions sbin/rt-shredder sbin/rt-validator sbin/rt-email-group-admin sbin/rt-server bin/mason_handler.fcgi bin/mason_handler.scgi bin/standalone_httpd bin/rt-crontool bin/rt-mailgate bin/rt Makefile etc/RT_Config.pm lib/RT.pm bin/mason_handler.svc bin/webmux.pl t/data/configs/apache2.2+mod_perl.conf t/data/configs/apache2.2+fastcgi.conf" ac_cs_usage="\ \`$as_me' instantiates files and other configuration actions @@ -428,15 +428,15 @@ $config_files Report bugs to ." ac_cs_version="\ -RT config.status 3.6.10 +RT config.status 3.8.7 configured by ./configure, generated by GNU Autoconf 2.64, - with options \"'--with-db-type=SQLite' 'PERL=/usr/bin/perl'\" + with options \"'--with-db-type=SQLite' '--enable-layout=relative' '--with-web-handler=standalone' 'PERL=/usr/bin/perl'\" Copyright (C) 2009 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." -ac_pwd='/Users/falcone/work/rt/releases/rt-3.6.10' +ac_pwd='/Users/falcone/work/rt/releases/rt-3.8.7' srcdir='.' INSTALL='install-sh' AWK='gawk' @@ -498,7 +498,7 @@ if $ac_cs_silent; then fi if $ac_cs_recheck; then - set X '/bin/sh' './configure' '--with-db-type=SQLite' 'PERL=/usr/bin/perl' $ac_configure_extra_args --no-create --no-recursion + set X '/bin/sh' './configure' '--with-db-type=SQLite' '--enable-layout=relative' '--with-web-handler=standalone' 'PERL=/usr/bin/perl' $ac_configure_extra_args --no-create --no-recursion shift $as_echo "running CONFIG_SHELL=/bin/sh $*" >&6 CONFIG_SHELL='/bin/sh' @@ -520,9 +520,20 @@ _ASBOX for ac_config_target in $ac_config_targets do case $ac_config_target in + "etc/upgrade/3.8-branded-queues-extension") CONFIG_FILES="$CONFIG_FILES etc/upgrade/3.8-branded-queues-extension" ;; + "etc/upgrade/3.8-ical-extension") CONFIG_FILES="$CONFIG_FILES etc/upgrade/3.8-ical-extension" ;; + "etc/upgrade/split-out-cf-categories") CONFIG_FILES="$CONFIG_FILES etc/upgrade/split-out-cf-categories" ;; + "sbin/rt-attributes-viewer") CONFIG_FILES="$CONFIG_FILES sbin/rt-attributes-viewer" ;; "sbin/rt-dump-database") CONFIG_FILES="$CONFIG_FILES sbin/rt-dump-database" ;; "sbin/rt-setup-database") CONFIG_FILES="$CONFIG_FILES sbin/rt-setup-database" ;; "sbin/rt-test-dependencies") CONFIG_FILES="$CONFIG_FILES sbin/rt-test-dependencies" ;; + "sbin/rt-email-digest") CONFIG_FILES="$CONFIG_FILES sbin/rt-email-digest" ;; + "sbin/rt-email-dashboards") CONFIG_FILES="$CONFIG_FILES sbin/rt-email-dashboards" ;; + "sbin/rt-clean-sessions") CONFIG_FILES="$CONFIG_FILES sbin/rt-clean-sessions" ;; + "sbin/rt-shredder") CONFIG_FILES="$CONFIG_FILES sbin/rt-shredder" ;; + "sbin/rt-validator") CONFIG_FILES="$CONFIG_FILES sbin/rt-validator" ;; + "sbin/rt-email-group-admin") CONFIG_FILES="$CONFIG_FILES sbin/rt-email-group-admin" ;; + "sbin/rt-server") CONFIG_FILES="$CONFIG_FILES sbin/rt-server" ;; "bin/mason_handler.fcgi") CONFIG_FILES="$CONFIG_FILES bin/mason_handler.fcgi" ;; "bin/mason_handler.scgi") CONFIG_FILES="$CONFIG_FILES bin/mason_handler.scgi" ;; "bin/standalone_httpd") CONFIG_FILES="$CONFIG_FILES bin/standalone_httpd" ;; @@ -534,6 +545,8 @@ do "lib/RT.pm") CONFIG_FILES="$CONFIG_FILES lib/RT.pm" ;; "bin/mason_handler.svc") CONFIG_FILES="$CONFIG_FILES bin/mason_handler.svc" ;; "bin/webmux.pl") CONFIG_FILES="$CONFIG_FILES bin/webmux.pl" ;; + "t/data/configs/apache2.2+mod_perl.conf") CONFIG_FILES="$CONFIG_FILES t/data/configs/apache2.2+mod_perl.conf" ;; + "t/data/configs/apache2.2+fastcgi.conf") CONFIG_FILES="$CONFIG_FILES t/data/configs/apache2.2+fastcgi.conf" ;; *) as_fn_error "invalid argument: \`$ac_config_target'" "$LINENO" 5;; esac @@ -597,30 +610,58 @@ echo 'BEGIN {' >"$tmp/subs1.awk" && cat >>"$tmp/subs1.awk" <<\_ACAWK && S["LTLIBOBJS"]="" S["LIBOBJS"]="" -S["RT_LOG_PATH"]="/opt/rt3/var/log" -S["DESTDIR"]="/opt/rt3" -S["LOCAL_LIB_PATH"]="/opt/rt3/local/lib" -S["LOCAL_LEXICON_PATH"]="/opt/rt3/local/po" -S["MASON_LOCAL_HTML_PATH"]="/opt/rt3/local/html" -S["LOCAL_ETC_PATH"]="/opt/rt3/local/etc" -S["MASON_HTML_PATH"]="/opt/rt3/share/html" -S["MASON_SESSION_PATH"]="/opt/rt3/var/session_data" -S["MASON_DATA_PATH"]="/opt/rt3/var/mason_data" -S["RT_MAN_PATH"]="/opt/rt3/man" -S["RT_VAR_PATH"]="/opt/rt3/var" -S["RT_SBIN_PATH"]="/opt/rt3/sbin" -S["RT_BIN_PATH"]="/opt/rt3/bin" -S["CONFIG_FILE_PATH"]="/opt/rt3/etc" -S["RT_ETC_PATH"]="/opt/rt3/etc" -S["RT_LIB_PATH"]="/opt/rt3/lib" -S["RT_LOCAL_PATH"]="/opt/rt3/local" -S["RT_DOC_PATH"]="/opt/rt3/share/doc" +S["RT_LOG_PATH_R"]="/opt/rt3/var/log" +S["LOCAL_LIB_PATH_R"]="/opt/rt3/local/lib" +S["LOCAL_LEXICON_PATH_R"]="/opt/rt3/local/po" +S["MASON_LOCAL_HTML_PATH_R"]="/opt/rt3/local/html" +S["LOCAL_ETC_PATH_R"]="/opt/rt3/local/etc" +S["MASON_HTML_PATH_R"]="/opt/rt3/share/html" +S["MASON_SESSION_PATH_R"]="/opt/rt3/var/session_data" +S["MASON_DATA_PATH_R"]="/opt/rt3/var/mason_data" +S["RT_PLUGIN_PATH_R"]="/opt/rt3/plugins" +S["RT_MAN_PATH_R"]="/opt/rt3/man" +S["RT_VAR_PATH_R"]="/opt/rt3/var" +S["RT_SBIN_PATH_R"]="/opt/rt3/sbin" +S["RT_BIN_PATH_R"]="/opt/rt3/bin" +S["CONFIG_FILE_PATH_R"]="/opt/rt3/etc" +S["RT_ETC_PATH_R"]="/opt/rt3/etc" +S["RT_LIB_PATH_R"]="/opt/rt3/lib" +S["RT_LOCAL_PATH_R"]="/opt/rt3/local" +S["RT_DOC_PATH_R"]="/opt/rt3/share/doc" +S["RT_PATH_R"]="/opt/rt3" +S["RT_LOG_PATH"]="var/log" +S["LOCAL_LIB_PATH"]="local/lib" +S["LOCAL_LEXICON_PATH"]="local/po" +S["MASON_LOCAL_HTML_PATH"]="local/html" +S["LOCAL_ETC_PATH"]="local/etc" +S["MASON_HTML_PATH"]="share/html" +S["MASON_SESSION_PATH"]="var/session_data" +S["MASON_DATA_PATH"]="var/mason_data" +S["RT_PLUGIN_PATH"]="plugins" +S["RT_MAN_PATH"]="man" +S["RT_VAR_PATH"]="var" +S["RT_SBIN_PATH"]="sbin" +S["RT_BIN_PATH"]="bin" +S["CONFIG_FILE_PATH"]="etc" +S["RT_ETC_PATH"]="etc" +S["RT_LIB_PATH"]="lib" +S["RT_LOCAL_PATH"]="local" +S["RT_DOC_PATH"]="share/doc" S["RT_PATH"]="/opt/rt3" -S["RT_VERSION_PATCH"]="10" -S["RT_VERSION_MINOR"]="6" +S["RT_VERSION_PATCH"]="7" +S["RT_VERSION_MINOR"]="8" S["RT_VERSION_MAJOR"]="3" +S["RT_GPG"]="1" +S["RT_GD"]="1" +S["RT_GRAPHVIZ"]="0" +S["OBJEXT"]="o" +S["EXEEXT"]="" +S["ac_ct_CC"]="gcc" +S["CPPFLAGS"]="" +S["LDFLAGS"]="" +S["CFLAGS"]="-g -O2" +S["CC"]="gcc" S["RT_DEVEL_MODE"]="0" -S["RT_STANDALONE"]="0" S["APACHECTL"]="/usr/sbin/apachectl" S["RTGROUP"]="www" S["WEB_GROUP"]="www" @@ -637,43 +678,47 @@ S["DB_TYPE"]="SQLite" S["LIBS_GROUP"]="bin" S["LIBS_OWNER"]="root" S["BIN_OWNER"]="root" -S["rt_layout_name"]="RT3" -S["exp_customlibdir"]="/opt/rt3/local/lib" -S["customlibdir"]="/opt/rt3/local/lib" -S["exp_customlexdir"]="/opt/rt3/local/po" -S["customlexdir"]="/opt/rt3/local/po" -S["exp_customhtmldir"]="/opt/rt3/local/html" -S["customhtmldir"]="/opt/rt3/local/html" -S["exp_custometcdir"]="/opt/rt3/local/etc" -S["custometcdir"]="/opt/rt3/local/etc" -S["exp_customdir"]="/opt/rt3/local" -S["customdir"]="/opt/rt3/local" -S["exp_sessionstatedir"]="/opt/rt3/var/session_data" -S["sessionstatedir"]="/opt/rt3/var/session_data" -S["exp_masonstatedir"]="/opt/rt3/var/mason_data" -S["masonstatedir"]="/opt/rt3/var/mason_data" -S["exp_logfiledir"]="/opt/rt3/var/log" -S["logfiledir"]="/opt/rt3/var/log" -S["exp_localstatedir"]="/opt/rt3/var" -S["exp_manualdir"]="/opt/rt3/share/doc" -S["manualdir"]="/opt/rt3/share/doc" -S["exp_htmldir"]="/opt/rt3/share/html" -S["exp_datadir"]="/opt/rt3/share" -S["exp_libdir"]="/opt/rt3/lib" -S["exp_mandir"]="/opt/rt3/man" -S["exp_sysconfdir"]="/opt/rt3/etc" -S["exp_sbindir"]="/opt/rt3/sbin" -S["exp_bindir"]="/opt/rt3/bin" +S["COMMENT_INPLACE_LAYOUT"]="" +S["rt_layout_name"]="relative" +S["exp_customlibdir"]="local/lib" +S["customlibdir"]="local/lib" +S["exp_customlexdir"]="local/po" +S["customlexdir"]="local/po" +S["exp_customhtmldir"]="local/html" +S["customhtmldir"]="local/html" +S["exp_custometcdir"]="local/etc" +S["custometcdir"]="local/etc" +S["exp_customdir"]="local" +S["customdir"]="local" +S["exp_sessionstatedir"]="var/session_data" +S["sessionstatedir"]="var/session_data" +S["exp_masonstatedir"]="var/mason_data" +S["masonstatedir"]="var/mason_data" +S["exp_logfiledir"]="var/log" +S["logfiledir"]="var/log" +S["exp_localstatedir"]="var" +S["exp_plugindir"]="plugins" +S["plugindir"]="plugins" +S["exp_manualdir"]="share/doc" +S["manualdir"]="share/doc" +S["exp_htmldir"]="share/html" +S["exp_datadir"]="share" +S["exp_libdir"]="lib" +S["exp_mandir"]="man" +S["exp_sysconfdir"]="etc" +S["exp_sbindir"]="sbin" +S["exp_bindir"]="bin" S["exp_exec_prefix"]="/opt/rt3" S["exp_prefix"]="/opt/rt3" S["SPEEDY_BIN"]="/usr/local/bin/speedy" +S["WEB_HANDLER"]="standalone" S["PERL"]="/usr/bin/perl" S["AWK"]="gawk" S["INSTALL_DATA"]="${INSTALL} -m 644" S["INSTALL_SCRIPT"]="${INSTALL}" S["INSTALL_PROGRAM"]="${INSTALL}" -S["rt_version_patch"]="10" -S["rt_version_minor"]="6" +S["rt_version_patch"]="7" +S["rt_version_minor"]="8" S["rt_version_major"]="3" S["target_alias"]="" S["host_alias"]="" @@ -682,34 +727,34 @@ S["LIBS"]="" S["ECHO_T"]="" S["ECHO_N"]="" S["ECHO_C"]="\\c" -S["DEFS"]="-DPACKAGE_NAME=\\\"RT\\\" -DPACKAGE_TARNAME=\\\"rt\\\" -DPACKAGE_VERSION=\\\"3.6.10\\\" -DPACKAGE_STRING=\\\"RT\\ 3.6.10\\\" -DPACKAGE_BUGREPORT=\\\"rt-bugs@bestpracti"\ -"cal.com\\\" -DPACKAGE_URL=\\\"\\\"" -S["mandir"]="/opt/rt3/man" +S["DEFS"]="-DPACKAGE_NAME=\\\"RT\\\" -DPACKAGE_TARNAME=\\\"rt\\\" -DPACKAGE_VERSION=\\\"3.8.7\\\" -DPACKAGE_STRING=\\\"RT\\ 3.8.7\\\" -DPACKAGE_BUGREPORT=\\\"rt-bugs@bestpractica"\ +"l.com\\\" -DPACKAGE_URL=\\\"\\\"" +S["mandir"]="man" S["localedir"]="${datarootdir}/locale" -S["libdir"]="/opt/rt3/lib" +S["libdir"]="lib" S["psdir"]="${docdir}" S["pdfdir"]="${docdir}" S["dvidir"]="${docdir}" -S["htmldir"]="/opt/rt3/share/html" +S["htmldir"]="share/html" S["infodir"]="${datarootdir}/info" S["docdir"]="${datarootdir}/doc/${PACKAGE_TARNAME}" S["oldincludedir"]="/usr/include" S["includedir"]="${prefix}/include" -S["localstatedir"]="/opt/rt3/var" +S["localstatedir"]="var" S["sharedstatedir"]="${prefix}/com" -S["sysconfdir"]="/opt/rt3/etc" -S["datadir"]="/opt/rt3/share" +S["sysconfdir"]="etc" +S["datadir"]="share" S["datarootdir"]="${prefix}/share" S["libexecdir"]="${exec_prefix}/libexec" -S["sbindir"]="/opt/rt3/sbin" -S["bindir"]="/opt/rt3/bin" +S["sbindir"]="sbin" +S["bindir"]="bin" S["program_transform_name"]="s,x,x," S["prefix"]="/opt/rt3" S["exec_prefix"]="/opt/rt3" S["PACKAGE_URL"]="" S["PACKAGE_BUGREPORT"]="rt-bugs@bestpractical.com" -S["PACKAGE_STRING"]="RT 3.6.10" -S["PACKAGE_VERSION"]="3.6.10" +S["PACKAGE_STRING"]="RT 3.8.7" +S["PACKAGE_VERSION"]="3.8.7" S["PACKAGE_TARNAME"]="rt" S["PACKAGE_NAME"]="RT" S["PATH_SEPARATOR"]=":" @@ -907,11 +952,11 @@ case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 $as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} ac_datarootdir_hack=' - s&@datadir@&/opt/rt3/share&g + s&@datadir@&share&g s&@docdir@&${datarootdir}/doc/${PACKAGE_TARNAME}&g s&@infodir@&${datarootdir}/info&g s&@localedir@&${datarootdir}/locale&g - s&@mandir@&/opt/rt3/man&g + s&@mandir@&man&g s&\${datarootdir}&${prefix}/share&g' ;; esac ac_sed_extra="/^[ ]*VPATH[ ]*=/{ @@ -963,12 +1008,34 @@ which seems to be undefined. Please make sure it is defined." >&2;} case $ac_file$ac_mode in + "etc/upgrade/3.8-branded-queues-extension":F) chmod ug+x $ac_file + ;; + "etc/upgrade/3.8-ical-extension":F) chmod ug+x $ac_file + ;; + "etc/upgrade/split-out-cf-categories":F) chmod ug+x $ac_file + ;; + "sbin/rt-attributes-viewer":F) chmod ug+x $ac_file + ;; "sbin/rt-dump-database":F) chmod ug+x $ac_file ;; "sbin/rt-setup-database":F) chmod ug+x $ac_file ;; "sbin/rt-test-dependencies":F) chmod ug+x $ac_file ;; + "sbin/rt-email-digest":F) chmod ug+x $ac_file + ;; + "sbin/rt-email-dashboards":F) chmod ug+x $ac_file + ;; + "sbin/rt-clean-sessions":F) chmod ug+x $ac_file + ;; + "sbin/rt-shredder":F) chmod ug+x $ac_file + ;; + "sbin/rt-validator":F) chmod ug+x $ac_file + ;; + "sbin/rt-email-group-admin":F) chmod ug+x $ac_file + ;; + "sbin/rt-server":F) chmod ug+x $ac_file + ;; "bin/mason_handler.fcgi":F) chmod ug+x $ac_file ;; "bin/mason_handler.scgi":F) chmod ug+x $ac_file diff --git a/rt/configure b/rt/configure index 91b8655b4..27eb230b5 100755 --- a/rt/configure +++ b/rt/configure @@ -1,7 +1,7 @@ #! /bin/sh # From configure.ac Revision. # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.64 for RT 3.6.10. +# Generated by GNU Autoconf 2.64 for RT 3.8.7. # # Report bugs to . # @@ -549,8 +549,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='RT' PACKAGE_TARNAME='rt' -PACKAGE_VERSION='3.6.10' -PACKAGE_STRING='RT 3.6.10' +PACKAGE_VERSION='3.8.7' +PACKAGE_STRING='RT 3.8.7' PACKAGE_BUGREPORT='rt-bugs@bestpractical.com' PACKAGE_URL='' @@ -558,8 +558,26 @@ ac_unique_file="lib/RT.pm.in" ac_default_prefix=/opt/rt3 ac_subst_vars='LTLIBOBJS LIBOBJS +RT_LOG_PATH_R +LOCAL_LIB_PATH_R +LOCAL_LEXICON_PATH_R +MASON_LOCAL_HTML_PATH_R +LOCAL_ETC_PATH_R +MASON_HTML_PATH_R +MASON_SESSION_PATH_R +MASON_DATA_PATH_R +RT_PLUGIN_PATH_R +RT_MAN_PATH_R +RT_VAR_PATH_R +RT_SBIN_PATH_R +RT_BIN_PATH_R +CONFIG_FILE_PATH_R +RT_ETC_PATH_R +RT_LIB_PATH_R +RT_LOCAL_PATH_R +RT_DOC_PATH_R +RT_PATH_R RT_LOG_PATH -DESTDIR LOCAL_LIB_PATH LOCAL_LEXICON_PATH MASON_LOCAL_HTML_PATH @@ -567,6 +585,7 @@ LOCAL_ETC_PATH MASON_HTML_PATH MASON_SESSION_PATH MASON_DATA_PATH +RT_PLUGIN_PATH RT_MAN_PATH RT_VAR_PATH RT_SBIN_PATH @@ -580,8 +599,17 @@ RT_PATH RT_VERSION_PATCH RT_VERSION_MINOR RT_VERSION_MAJOR +RT_GPG +RT_GD +RT_GRAPHVIZ +OBJEXT +EXEEXT +ac_ct_CC +CPPFLAGS +LDFLAGS +CFLAGS +CC RT_DEVEL_MODE -RT_STANDALONE APACHECTL RTGROUP WEB_GROUP @@ -598,6 +626,7 @@ DB_TYPE LIBS_GROUP LIBS_OWNER BIN_OWNER +COMMENT_INPLACE_LAYOUT rt_layout_name exp_customlibdir customlibdir @@ -616,6 +645,8 @@ masonstatedir exp_logfiledir logfiledir exp_localstatedir +exp_plugindir +plugindir exp_manualdir manualdir exp_htmldir @@ -628,6 +659,7 @@ exp_bindir exp_exec_prefix exp_prefix SPEEDY_BIN +WEB_HANDLER PERL AWK INSTALL_DATA @@ -677,6 +709,7 @@ SHELL' ac_subst_files='' ac_user_opts=' enable_option_checking +with_web_handler with_speedycgi enable_layout with_bin_owner @@ -696,11 +729,22 @@ with_rt_group with_my_user_group with_apachectl with_devel_mode +enable_devel_mode +with_graphviz +enable_graphviz +with_gd +enable_gd +enable_gpg ' ac_precious_vars='build_alias host_alias target_alias -PERL' +PERL +CC +CFLAGS +LDFLAGS +LIBS +CPPFLAGS' # Initialize some variables set by options. @@ -1242,7 +1286,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures RT 3.6.10 to adapt to many kinds of systems. +\`configure' configures RT 3.8.7 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1303,7 +1347,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of RT 3.6.10:";; + short | recursive ) echo "Configuration of RT 3.8.7:";; esac cat <<\_ACEOF @@ -1311,18 +1355,27 @@ Optional Features: --disable-option-checking ignore unrecognized --enable/--with options --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) --enable-FEATURE[=ARG] include FEATURE [ARG=yes] - --enable-layout=LAYOUT Use a specific directory layout (Default: RT3) + --enable-layout=LAYOUT Use a specific directory layout (Default: relative) + --enable-devel-mode Turn on development aids that might hurt you in + production + --enable-graphviz Turns on support for RT's GraphViz dependency charts + --enable-gd Turns on support for RT's GD pie and bar charts + --enable-gpg Turns on GNU Privacy Guard (GPG) support Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) + --with-web-handler=LIST comma separated list of web-handlers RT will be able + to use. Default is fastcgi when modperl1, modperl2, + fastcgi, standalone and speedycgi are valid. To + succefuly run RT you need only one. --with-speedycgi=/path/to/speedy path to your speedycgi binary, if it exists --with-bin-owner=OWNER user that will own RT binaries (default root) --with-libs-owner=OWNER user that will own RT libraries (default root) --with-libs-group=GROUP group that will own RT binaries (default bin) --with-db-type=TYPE sort of database RT will use (default: mysql) - (mysql, Pg, Oracle, Informix and SQLite are valid) + (mysql, Pg, Oracle and SQLite are valid) --with-db-host=HOSTNAME FQDN of database server (default: localhost) --with-db-port=PORT port on which the database listens on --with-db-rt-host=HOSTNAME @@ -1341,12 +1394,17 @@ Optional Packages: --with-rt-group=GROUP group to own all files (default: rt) --with-my-user-group set all users and groups to current user/group --with-apachectl instruct RT where to find your apachectl - --with-standalone Install modules for pure perl standalone server - --with-devel-mode Turn on development aids that might hurt you in - production + Some influential environment variables: PERL Perl interpreter command + CC C compiler command + CFLAGS C compiler flags + LDFLAGS linker flags, e.g. -L if you have libraries in a + nonstandard directory + LIBS libraries to pass to the linker, e.g. -l + CPPFLAGS C/C++/Objective C preprocessor flags, e.g. -I if + you have headers in a nonstandard directory Use these variables to override the choices made by `configure' or to help it to find libraries and programs with nonstandard names/locations. @@ -1414,7 +1472,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -RT configure 3.6.10 +RT configure 3.8.7 generated by GNU Autoconf 2.64 Copyright (C) 2009 Free Software Foundation, Inc. @@ -1427,11 +1485,95 @@ fi ## ------------------------ ## ## Autoconf initialization. ## ## ------------------------ ## + +# ac_fn_c_try_compile LINENO +# -------------------------- +# Try to compile conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext + if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;} + return $ac_retval + +} # ac_fn_c_try_compile + +# ac_fn_c_try_link LINENO +# ----------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_link () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext conftest$ac_exeext + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + $as_test_x conftest$ac_exeext + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information + # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would + # interfere with the next link command; also delete a directory that is + # left behind by Apple's compiler. We do this before executing the actions. + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; test "x$as_lineno_stack" = x && { as_lineno=; unset as_lineno;} + return $ac_retval + +} # ac_fn_c_try_link cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by RT $as_me 3.6.10, which was +It was created by RT $as_me 3.8.7, which was generated by GNU Autoconf 2.64. Invocation command line was $ $0 $@ @@ -1782,9 +1924,9 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu rt_version_major=3 -rt_version_minor=6 +rt_version_minor=8 -rt_version_patch=10 +rt_version_patch=7 test "x$rt_version_major" = 'x' && rt_version_major=0 test "x$rt_version_minor" = 'x' && rt_version_minor=0 @@ -1996,6 +2138,20 @@ fi +# Check whether --with-web-handler was given. +if test "${with_web_handler+set}" = set; then : + withval=$with_web_handler; WEB_HANDLER=$withval +else + WEB_HANDLER=fastcgi +fi + +my_web_handler_test=$($PERL -e 'print "ok" unless grep $_ !~ /^(modperl12|fastcgi|speedycgi|standalone)$/i, grep defined && length, split /\s*,\s*/, "$WEB_HANDLER"') +if test "$my_web_handler_test" != "ok"; then + as_fn_error "Only modperl1, modperl2, fastcgi, speedycgi and standalone are valid web-handlers" "$LINENO" 5 +fi + + + # Check whether --with-speedycgi was given. if test "${with_speedycgi+set}" = set; then : withval=$with_speedycgi; SPEEDY_BIN=$withval @@ -2003,7 +2159,9 @@ else SPEEDY_BIN=/usr/local/bin/speedy fi - +if test "$WEB_HANDLER" = 'speedycgi' -a ! -x "$SPEEDY_BIN"; then + as_fn_error "cannot find speedycgi binary" "$LINENO" 5 +fi @@ -2015,7 +2173,7 @@ fi if test "x$LAYOUT" = "x"; then - LAYOUT="RT3" + LAYOUT="relative" fi if test ! -f $srcdir/config.layout; then @@ -2190,6 +2348,19 @@ $as_echo "$as_me: WARNING: Layout file $srcdir/config.layout not found" >&2;} ap_last='' + ap_cur='$plugindir' + while test "x${ap_cur}" != "x${ap_last}"; do + ap_last="${ap_cur}" + ap_cur=`eval "echo ${ap_cur}"` + done + exp_plugindir="${ap_cur}" + + + + + + + ap_last='' ap_cur='$localstatedir' while test "x${ap_cur}" != "x${ap_last}"; do ap_last="${ap_cur}" @@ -2321,6 +2492,13 @@ else { $as_echo "$as_me:${as_lineno-$LINENO}: result: $rt_layout_name" >&5 $as_echo "$rt_layout_name" >&6; } fi +if test "x$rt_layout_name" != "xinplace" ; then + COMMENT_INPLACE_LAYOUT="" + +else + COMMENT_INPLACE_LAYOUT=# + +fi @@ -2372,7 +2550,7 @@ else fi if test "$DB_TYPE" != 'mysql' -a "$DB_TYPE" != 'Pg' -a "$DB_TYPE" != 'SQLite' -a "$DB_TYPE" != 'Oracle' -a "$DB_TYPE" != 'Informix' -a "$DB_TYPE" != 'Sybase' ; then - as_fn_error "Only Oracle, Informix, Pg, mysql and SQLite are valid db types" "$LINENO" 5 + as_fn_error "Only Oracle, Pg, mysql and SQLite are valid db types" "$LINENO" 5 fi @@ -2549,7 +2727,7 @@ fi # Test for valid database names -if test "$DB_TYPE" == "mysql" ; then : +if test "$DB_TYPE" = "mysql" ; then : { $as_echo "$as_me:${as_lineno-$LINENO}: checking if database name is valid" >&5 $as_echo_n "checking if database name is valid... " >&6; } if echo $DB_DATABASE | $AWK '/-/ { exit 1 }' ; then : @@ -2577,74 +2755,1091 @@ fi # Check whether --with-devel-mode was given. if test "${with_devel_mode+set}" = set; then : - withval=$with_devel_mode; RT_STANDALONE="1" + withval=$with_devel_mode; RT_DEVEL_MODE=$withval else - RT_STANDALONE="0" + RT_DEVEL_MODE="0" fi - - - -# Check whether --with-devel-mode was given. -if test "${with_devel_mode+set}" = set; then : - withval=$with_devel_mode; RT_DEVEL_MODE="1" +# Check whether --enable-devel-mode was given. +if test "${enable_devel_mode+set}" = set; then : + enableval=$enable_devel_mode; RT_DEVEL_MODE=$enableval else - RT_DEVEL_MODE="0" + RT_DEVEL_MODE=$RT_DEVEL_MODE fi +if test "$RT_DEVEL_MODE" = yes; then + RT_DEVEL_MODE="1" +else + RT_DEVEL_MODE="0" +fi -RT_VERSION_MAJOR=${rt_version_major} - -RT_VERSION_MINOR=${rt_version_minor} - -RT_VERSION_PATCH=${rt_version_patch} - +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. +set dummy ${ac_tool_prefix}gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_CC+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_CC="${ac_tool_prefix}gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS -RT_PATH=${exp_prefix} +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi -RT_DOC_PATH=${exp_manualdir} -RT_LOCAL_PATH=${exp_customdir} +fi +if test -z "$ac_cv_prog_CC"; then + ac_ct_CC=$CC + # Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_ac_ct_CC+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_ac_ct_CC="gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS -RT_LIB_PATH=${exp_libdir} +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi -RT_ETC_PATH=${exp_sysconfdir} + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +else + CC="$ac_cv_prog_CC" +fi -CONFIG_FILE_PATH=${exp_sysconfdir} +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. +set dummy ${ac_tool_prefix}cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_CC+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_CC="${ac_tool_prefix}cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS -RT_BIN_PATH=${exp_bindir} +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi -RT_SBIN_PATH=${exp_sbindir} -RT_VAR_PATH=${exp_localstatedir} + fi +fi +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_CC+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + ac_prog_rejected=no +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS -RT_MAN_PATH=${exp_mandir} +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# != 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" + fi +fi +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi -MASON_DATA_PATH=${exp_masonstatedir} -MASON_SESSION_PATH=${exp_sessionstatedir} +fi +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + for ac_prog in cl.exe + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_CC+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_CC="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS -MASON_HTML_PATH=${exp_htmldir} +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi -LOCAL_ETC_PATH=${exp_custometcdir} -MASON_LOCAL_HTML_PATH=${exp_customhtmldir} + test -n "$CC" && break + done +fi +if test -z "$CC"; then + ac_ct_CC=$CC + for ac_prog in cl.exe +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_ac_ct_CC+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_ac_ct_CC="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS -LOCAL_LEXICON_PATH=${exp_customlexdir} +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi -LOCAL_LIB_PATH=${exp_customlibdir} -DESTDIR=${exp_prefix} + test -n "$ac_ct_CC" && break +done -RT_LOG_PATH=${exp_logfiledir} + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +fi +fi -ac_config_files="$ac_config_files sbin/rt-dump-database sbin/rt-setup-database sbin/rt-test-dependencies bin/mason_handler.fcgi bin/mason_handler.scgi bin/standalone_httpd bin/rt-crontool bin/rt-mailgate bin/rt" +test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error "no acceptable C compiler found in \$PATH +See \`config.log' for more details." "$LINENO" 5; } + +# Provide some information about the compiler. +$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +for ac_option in --version -v -V -qversion; do + { { ac_try="$ac_compiler $ac_option >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compiler $ac_option >&5") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + sed '10a\ +... rest of stderr output deleted ... + 10q' conftest.err >conftest.er1 + cat conftest.er1 >&5 + rm -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +done +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main () +{ +FILE *f = fopen ("conftest.out", "w"); + return ferror (f) || fclose (f) != 0; -ac_config_files="$ac_config_files Makefile etc/RT_Config.pm lib/RT.pm bin/mason_handler.svc bin/webmux.pl" + ; + return 0; +} +_ACEOF +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out conftest.out" +# Try to create an executable without -o first, disregard a.out. +# It will help us diagnose broken compilers, and finding out an intuition +# of exeext. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 +$as_echo_n "checking for C compiler default output file name... " >&6; } +ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'` + +# The possible output files: +ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" + +ac_rmfiles= +for ac_file in $ac_files +do + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + * ) ac_rmfiles="$ac_rmfiles $ac_file";; + esac +done +rm -f $ac_rmfiles + +if { { ac_try="$ac_link_default" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link_default") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. +# So ignore a value of `no', otherwise this would lead to `EXEEXT = no' +# in a Makefile. We should not override ac_cv_exeext if it was cached, +# so that the user can short-circuit this test for compilers unknown to +# Autoconf. +for ac_file in $ac_files '' +do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) + ;; + [ab].out ) + # We found the default executable, but exeext='' is most + # certainly right. + break;; + *.* ) + if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no; + then :; else + ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + fi + # We set ac_cv_exeext here because the later test for it is not + # safe: cross compilers may not add the suffix if given an `-o' + # argument, so we may need to know it at that point already. + # Even if this section looks crufty: it has the advantage of + # actually working. + break;; + * ) + break;; + esac +done +test "$ac_cv_exeext" = no && ac_cv_exeext= + +else + ac_file='' +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 +$as_echo "$ac_file" >&6; } +if test -z "$ac_file"; then : + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +{ as_fn_set_status 77 +as_fn_error "C compiler cannot create executables +See \`config.log' for more details." "$LINENO" 5; }; } +fi +ac_exeext=$ac_cv_exeext + +# Check that the compiler produces executables we can run. If not, either +# the compiler is broken, or we cross compile. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 +$as_echo_n "checking whether the C compiler works... " >&6; } +# If not cross compiling, check that we can run a simple program. +if test "$cross_compiling" != yes; then + if { ac_try='./$ac_file' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then + cross_compiling=no + else + if test "$cross_compiling" = maybe; then + cross_compiling=yes + else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error "cannot run C compiled programs. +If you meant to cross compile, use \`--host'. +See \`config.log' for more details." "$LINENO" 5; } + fi + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + +rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out conftest.out +ac_clean_files=$ac_clean_files_save +# Check that the compiler produces executables we can run. If not, either +# the compiler is broken, or we cross compile. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 +$as_echo_n "checking whether we are cross compiling... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 +$as_echo "$cross_compiling" >&6; } + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 +$as_echo_n "checking for suffix of executables... " >&6; } +if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # If both `conftest.exe' and `conftest' are `present' (well, observable) +# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will +# work properly (i.e., refer to `conftest.exe'), while it won't with +# `rm'. +for ac_file in conftest.exe conftest conftest.*; do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + break;; + * ) break;; + esac +done +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error "cannot compute suffix of executables: cannot compile and link +See \`config.log' for more details." "$LINENO" 5; } +fi +rm -f conftest$ac_cv_exeext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 +$as_echo "$ac_cv_exeext" >&6; } + +rm -f conftest.$ac_ext +EXEEXT=$ac_cv_exeext +ac_exeext=$EXEEXT +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 +$as_echo_n "checking for suffix of object files... " >&6; } +if test "${ac_cv_objext+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.o conftest.obj +if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + for ac_file in conftest.o conftest.obj conftest.*; do + test -f "$ac_file" || continue; + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; + *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` + break;; + esac +done +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error "cannot compute suffix of object files: cannot compile +See \`config.log' for more details." "$LINENO" 5; } +fi +rm -f conftest.$ac_cv_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 +$as_echo "$ac_cv_objext" >&6; } +OBJEXT=$ac_cv_objext +ac_objext=$OBJEXT +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 +$as_echo_n "checking whether we are using the GNU C compiler... " >&6; } +if test "${ac_cv_c_compiler_gnu+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_compiler_gnu=yes +else + ac_compiler_gnu=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_cv_c_compiler_gnu=$ac_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 +$as_echo "$ac_cv_c_compiler_gnu" >&6; } +if test $ac_compiler_gnu = yes; then + GCC=yes +else + GCC= +fi +ac_test_CFLAGS=${CFLAGS+set} +ac_save_CFLAGS=$CFLAGS +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 +$as_echo_n "checking whether $CC accepts -g... " >&6; } +if test "${ac_cv_prog_cc_g+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + ac_save_c_werror_flag=$ac_c_werror_flag + ac_c_werror_flag=yes + ac_cv_prog_cc_g=no + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +else + CFLAGS="" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + +else + ac_c_werror_flag=$ac_save_c_werror_flag + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_c_werror_flag=$ac_save_c_werror_flag +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 +$as_echo "$ac_cv_prog_cc_g" >&6; } +if test "$ac_test_CFLAGS" = set; then + CFLAGS=$ac_save_CFLAGS +elif test $ac_cv_prog_cc_g = yes; then + if test "$GCC" = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-g" + fi +else + if test "$GCC" = yes; then + CFLAGS="-O2" + else + CFLAGS= + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 +$as_echo_n "checking for $CC option to accept ISO C89... " >&6; } +if test "${ac_cv_prog_cc_c89+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_prog_cc_c89=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#include +#include +/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ +struct buf { int x; }; +FILE * (*rcsopen) (struct buf *, struct stat *, int); +static char *e (p, i) + char **p; + int i; +{ + return p[i]; +} +static char *f (char * (*g) (char **, int), char **p, ...) +{ + char *s; + va_list v; + va_start (v,p); + s = g (p, va_arg (v,int)); + va_end (v); + return s; +} + +/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has + function prototypes and stuff, but not '\xHH' hex character constants. + These don't provoke an error unfortunately, instead are silently treated + as 'x'. The following induces an error, until -std is added to get + proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an + array size at least. It's necessary to write '\x00'==0 to get something + that's true only with -std. */ +int osf4_cc_array ['\x00' == 0 ? 1 : -1]; + +/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters + inside strings and character constants. */ +#define FOO(x) 'x' +int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; + +int test (int i, double x); +struct s1 {int (*f) (int a);}; +struct s2 {int (*f) (double a);}; +int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); +int argc; +char **argv; +int +main () +{ +return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; + ; + return 0; +} +_ACEOF +for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ + -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_c89=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext + test "x$ac_cv_prog_cc_c89" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC + +fi +# AC_CACHE_VAL +case "x$ac_cv_prog_cc_c89" in + x) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +$as_echo "none needed" >&6; } ;; + xno) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +$as_echo "unsupported" >&6; } ;; + *) + CC="$CC $ac_cv_prog_cc_c89" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 +$as_echo "$ac_cv_prog_cc_c89" >&6; } ;; +esac +if test "x$ac_cv_prog_cc_c89" != xno; then : + +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for aginitlib in -lgraph" >&5 +$as_echo_n "checking for aginitlib in -lgraph... " >&6; } +if test "${ac_cv_lib_graph_aginitlib+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lgraph $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char aginitlib (); +int +main () +{ +return aginitlib (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_graph_aginitlib=yes +else + ac_cv_lib_graph_aginitlib=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_graph_aginitlib" >&5 +$as_echo "$ac_cv_lib_graph_aginitlib" >&6; } +if test "x$ac_cv_lib_graph_aginitlib" = x""yes; then : + RT_GRAPHVIZ="1" +fi + + +# Check whether --with-graphviz was given. +if test "${with_graphviz+set}" = set; then : + withval=$with_graphviz; RT_GRAPHVIZ=$withval +fi + +# Check whether --enable-graphviz was given. +if test "${enable_graphviz+set}" = set; then : + enableval=$enable_graphviz; RT_GRAPHVIZ=$enableval +fi + +if test "$RT_GRAPHVIZ" = yes; then + RT_GRAPHVIZ="1" +else + RT_GRAPHVIZ="0" +fi + + +# Extract the first word of "gdlib-config", so it can be a program name with args. +set dummy gdlib-config; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_RT_GD+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$RT_GD"; then + ac_cv_prog_RT_GD="$RT_GD" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_RT_GD=""yes"" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_prog_RT_GD" && ac_cv_prog_RT_GD=""no"" +fi +fi +RT_GD=$ac_cv_prog_RT_GD +if test -n "$RT_GD"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RT_GD" >&5 +$as_echo "$RT_GD" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + +# Check whether --with-gd was given. +if test "${with_gd+set}" = set; then : + withval=$with_gd; RT_GD=$withval +fi + +# Check whether --enable-gd was given. +if test "${enable_gd+set}" = set; then : + enableval=$enable_gd; RT_GD=$enableval +fi + +if test "$RT_GD" = yes; then + RT_GD="1" +else + RT_GD="0" +fi + + +# Extract the first word of "gpg", so it can be a program name with args. +set dummy gpg; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_prog_RT_GPG+set}" = set; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$RT_GPG"; then + ac_cv_prog_RT_GPG="$RT_GPG" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_prog_RT_GPG=""yes"" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_prog_RT_GPG" && ac_cv_prog_RT_GPG=""no"" +fi +fi +RT_GPG=$ac_cv_prog_RT_GPG +if test -n "$RT_GPG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RT_GPG" >&5 +$as_echo "$RT_GPG" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +# Check whether --enable-gpg was given. +if test "${enable_gpg+set}" = set; then : + enableval=$enable_gpg; RT_GPG=$enableval +fi + +if test "$RT_GPG" = yes; then + RT_GPG="1" +else + RT_GPG="0" +fi + + + + +RT_VERSION_MAJOR=${rt_version_major} + +RT_VERSION_MINOR=${rt_version_minor} + +RT_VERSION_PATCH=${rt_version_patch} + + +RT_PATH=${exp_prefix} + +RT_DOC_PATH=${exp_manualdir} + +RT_LOCAL_PATH=${exp_customdir} + +RT_LIB_PATH=${exp_libdir} + +RT_ETC_PATH=${exp_sysconfdir} + +CONFIG_FILE_PATH=${exp_sysconfdir} + +RT_BIN_PATH=${exp_bindir} + +RT_SBIN_PATH=${exp_sbindir} + +RT_VAR_PATH=${exp_localstatedir} + +RT_MAN_PATH=${exp_mandir} + +RT_PLUGIN_PATH=${exp_plugindir} + +MASON_DATA_PATH=${exp_masonstatedir} + +MASON_SESSION_PATH=${exp_sessionstatedir} + +MASON_HTML_PATH=${exp_htmldir} + +LOCAL_ETC_PATH=${exp_custometcdir} + +MASON_LOCAL_HTML_PATH=${exp_customhtmldir} + +LOCAL_LEXICON_PATH=${exp_customlexdir} + +LOCAL_LIB_PATH=${exp_customlibdir} + +RT_LOG_PATH=${exp_logfiledir} + + +if test ${exp_sysconfdir} = "etc"; then +RT_PATH_R=${exp_prefix} + +RT_DOC_PATH_R=${exp_prefix}/${exp_manualdir} + +RT_LOCAL_PATH_R=${exp_prefix}/${exp_customdir} + +RT_LIB_PATH_R=${exp_prefix}/${exp_libdir} + +RT_ETC_PATH_R=${exp_prefix}/${exp_sysconfdir} + +CONFIG_FILE_PATH_R=${exp_prefix}/${exp_sysconfdir} + +RT_BIN_PATH_R=${exp_prefix}/${exp_bindir} + +RT_SBIN_PATH_R=${exp_prefix}/${exp_sbindir} + +RT_VAR_PATH_R=${exp_prefix}/${exp_localstatedir} + +RT_MAN_PATH_R=${exp_prefix}/${exp_mandir} + +RT_PLUGIN_PATH_R=${exp_prefix}/${exp_plugindir} + +MASON_DATA_PATH_R=${exp_prefix}/${exp_masonstatedir} + +MASON_SESSION_PATH_R=${exp_prefix}/${exp_sessionstatedir} + +MASON_HTML_PATH_R=${exp_prefix}/${exp_htmldir} + +LOCAL_ETC_PATH_R=${exp_prefix}/${exp_custometcdir} + +MASON_LOCAL_HTML_PATH_R=${exp_prefix}/${exp_customhtmldir} + +LOCAL_LEXICON_PATH_R=${exp_prefix}/${exp_customlexdir} + +LOCAL_LIB_PATH_R=${exp_prefix}/${exp_customlibdir} + +RT_LOG_PATH_R=${exp_prefix}/${exp_logfiledir} + +else +RT_PATH_R=${exp_prefix} + +RT_DOC_PATH_R=${exp_manualdir} + +RT_LOCAL_PATH_R=${exp_customdir} + +RT_LIB_PATH_R=${exp_libdir} + +RT_ETC_PATH_R=${exp_sysconfdir} + +RT_PLUGIN_PATH_R=${exp_plugindir} + +CONFIG_FILE_PATH_R=${exp_sysconfdir} + +RT_BIN_PATH_R=${exp_bindir} + +RT_SBIN_PATH_R=${exp_sbindir} + +RT_VAR_PATH_R=${exp_localstatedir} + +RT_MAN_PATH_R=${exp_mandir} + +MASON_DATA_PATH_R=${exp_masonstatedir} + +MASON_SESSION_PATH_R=${exp_sessionstatedir} + +MASON_HTML_PATH_R=${exp_htmldir} + +LOCAL_ETC_PATH_R=${exp_custometcdir} + +MASON_LOCAL_HTML_PATH_R=${exp_customhtmldir} + +LOCAL_LEXICON_PATH_R=${exp_customlexdir} + +LOCAL_LIB_PATH_R=${exp_customlibdir} + +RT_LOG_PATH_R=${exp_logfiledir} + + +fi + + +ac_config_files="$ac_config_files etc/upgrade/3.8-branded-queues-extension etc/upgrade/3.8-ical-extension etc/upgrade/split-out-cf-categories sbin/rt-attributes-viewer sbin/rt-dump-database sbin/rt-setup-database sbin/rt-test-dependencies sbin/rt-email-digest sbin/rt-email-dashboards sbin/rt-clean-sessions sbin/rt-shredder sbin/rt-validator sbin/rt-email-group-admin sbin/rt-server bin/mason_handler.fcgi bin/mason_handler.scgi bin/standalone_httpd bin/rt-crontool bin/rt-mailgate bin/rt" + + +ac_config_files="$ac_config_files Makefile etc/RT_Config.pm lib/RT.pm bin/mason_handler.svc bin/webmux.pl t/data/configs/apache2.2+mod_perl.conf t/data/configs/apache2.2+fastcgi.conf" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure @@ -3187,7 +4382,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by RT $as_me 3.6.10, which was +This file was extended by RT $as_me 3.8.7, which was generated by GNU Autoconf 2.64. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -3238,7 +4433,7 @@ Report bugs to ." _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_version="\\ -RT config.status 3.6.10 +RT config.status 3.8.7 configured by $0, generated by GNU Autoconf 2.64, with options \\"`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`\\" @@ -3342,9 +4537,20 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 for ac_config_target in $ac_config_targets do case $ac_config_target in + "etc/upgrade/3.8-branded-queues-extension") CONFIG_FILES="$CONFIG_FILES etc/upgrade/3.8-branded-queues-extension" ;; + "etc/upgrade/3.8-ical-extension") CONFIG_FILES="$CONFIG_FILES etc/upgrade/3.8-ical-extension" ;; + "etc/upgrade/split-out-cf-categories") CONFIG_FILES="$CONFIG_FILES etc/upgrade/split-out-cf-categories" ;; + "sbin/rt-attributes-viewer") CONFIG_FILES="$CONFIG_FILES sbin/rt-attributes-viewer" ;; "sbin/rt-dump-database") CONFIG_FILES="$CONFIG_FILES sbin/rt-dump-database" ;; "sbin/rt-setup-database") CONFIG_FILES="$CONFIG_FILES sbin/rt-setup-database" ;; "sbin/rt-test-dependencies") CONFIG_FILES="$CONFIG_FILES sbin/rt-test-dependencies" ;; + "sbin/rt-email-digest") CONFIG_FILES="$CONFIG_FILES sbin/rt-email-digest" ;; + "sbin/rt-email-dashboards") CONFIG_FILES="$CONFIG_FILES sbin/rt-email-dashboards" ;; + "sbin/rt-clean-sessions") CONFIG_FILES="$CONFIG_FILES sbin/rt-clean-sessions" ;; + "sbin/rt-shredder") CONFIG_FILES="$CONFIG_FILES sbin/rt-shredder" ;; + "sbin/rt-validator") CONFIG_FILES="$CONFIG_FILES sbin/rt-validator" ;; + "sbin/rt-email-group-admin") CONFIG_FILES="$CONFIG_FILES sbin/rt-email-group-admin" ;; + "sbin/rt-server") CONFIG_FILES="$CONFIG_FILES sbin/rt-server" ;; "bin/mason_handler.fcgi") CONFIG_FILES="$CONFIG_FILES bin/mason_handler.fcgi" ;; "bin/mason_handler.scgi") CONFIG_FILES="$CONFIG_FILES bin/mason_handler.scgi" ;; "bin/standalone_httpd") CONFIG_FILES="$CONFIG_FILES bin/standalone_httpd" ;; @@ -3356,6 +4562,8 @@ do "lib/RT.pm") CONFIG_FILES="$CONFIG_FILES lib/RT.pm" ;; "bin/mason_handler.svc") CONFIG_FILES="$CONFIG_FILES bin/mason_handler.svc" ;; "bin/webmux.pl") CONFIG_FILES="$CONFIG_FILES bin/webmux.pl" ;; + "t/data/configs/apache2.2+mod_perl.conf") CONFIG_FILES="$CONFIG_FILES t/data/configs/apache2.2+mod_perl.conf" ;; + "t/data/configs/apache2.2+fastcgi.conf") CONFIG_FILES="$CONFIG_FILES t/data/configs/apache2.2+fastcgi.conf" ;; *) as_fn_error "invalid argument: \`$ac_config_target'" "$LINENO" 5;; esac @@ -3765,12 +4973,34 @@ which seems to be undefined. Please make sure it is defined." >&2;} case $ac_file$ac_mode in + "etc/upgrade/3.8-branded-queues-extension":F) chmod ug+x $ac_file + ;; + "etc/upgrade/3.8-ical-extension":F) chmod ug+x $ac_file + ;; + "etc/upgrade/split-out-cf-categories":F) chmod ug+x $ac_file + ;; + "sbin/rt-attributes-viewer":F) chmod ug+x $ac_file + ;; "sbin/rt-dump-database":F) chmod ug+x $ac_file ;; "sbin/rt-setup-database":F) chmod ug+x $ac_file ;; "sbin/rt-test-dependencies":F) chmod ug+x $ac_file ;; + "sbin/rt-email-digest":F) chmod ug+x $ac_file + ;; + "sbin/rt-email-dashboards":F) chmod ug+x $ac_file + ;; + "sbin/rt-clean-sessions":F) chmod ug+x $ac_file + ;; + "sbin/rt-shredder":F) chmod ug+x $ac_file + ;; + "sbin/rt-validator":F) chmod ug+x $ac_file + ;; + "sbin/rt-email-group-admin":F) chmod ug+x $ac_file + ;; + "sbin/rt-server":F) chmod ug+x $ac_file + ;; "bin/mason_handler.fcgi":F) chmod ug+x $ac_file ;; "bin/mason_handler.scgi":F) chmod ug+x $ac_file diff --git a/rt/configure.ac b/rt/configure.ac index 209744dc2..6f6b6f27b 100644 --- a/rt/configure.ac +++ b/rt/configure.ac @@ -3,11 +3,11 @@ dnl dnl Process this file with autoconf to produce a configure script dnl dnl Embed in generated ./configure script the following CVS info: -AC_REVISION($Revision: 1.1.1.10 $)dnl +AC_REVISION($Revision: 1.1.1.11 $)dnl dnl Setup autoconf -AC_PREREQ(2.53) -AC_INIT(RT, [3.6.10], [rt-bugs@bestpractical.com]) +AC_PREREQ([2.53]) +AC_INIT(RT, 3.8.7, [rt-bugs@bestpractical.com]) AC_CONFIG_SRCDIR([lib/RT.pm.in]) dnl Extract RT version number components @@ -31,15 +31,32 @@ if test "$PERL" = 'not found'; then fi +dnl WEB_HANDLER +AC_ARG_WITH(web-handler, + AC_HELP_STRING([--with-web-handler=LIST], + [comma separated list of web-handlers RT will be able to use. + Default is fastcgi when modperl1, modperl2, fastcgi, standalone + and speedycgi are valid. To succefuly run RT you need only one. + ]), + WEB_HANDLER=$withval, + WEB_HANDLER=fastcgi) +my_web_handler_test=$($PERL -e 'print "ok" unless grep $_ !~ /^(modperl[12]|fastcgi|speedycgi|standalone)$/i, grep defined && length, split /\s*,\s*/, "$WEB_HANDLER"') +if test "$my_web_handler_test" != "ok"; then + AC_MSG_ERROR([Only modperl1, modperl2, fastcgi, speedycgi and standalone are valid web-handlers]) +fi +AC_SUBST(WEB_HANDLER) + dnl SPEED_BIN AC_ARG_WITH(speedycgi, AC_HELP_STRING([--with-speedycgi=/path/to/speedy], [path to your speedycgi binary, if it exists]), SPEEDY_BIN=$withval, SPEEDY_BIN=/usr/local/bin/speedy) +if test "$WEB_HANDLER" = 'speedycgi' -a ! -x "$SPEEDY_BIN"; then + AC_MSG_ERROR([cannot find speedycgi binary]) +fi AC_SUBST(SPEEDY_BIN) - dnl Defaults paths for installation AC_PREFIX_DEFAULT([/opt/rt3]) RT_ENABLE_LAYOUT @@ -99,11 +116,11 @@ AC_SUBST(LIBS_GROUP) dnl DB_TYPE AC_ARG_WITH(db-type, AC_HELP_STRING([--with-db-type=TYPE], - [sort of database RT will use (default: mysql) (mysql, Pg, Oracle, Informix and SQLite are valid)]), + [sort of database RT will use (default: mysql) (mysql, Pg, Oracle and SQLite are valid)]), DB_TYPE=$withval, DB_TYPE=mysql) if test "$DB_TYPE" != 'mysql' -a "$DB_TYPE" != 'Pg' -a "$DB_TYPE" != 'SQLite' -a "$DB_TYPE" != 'Oracle' -a "$DB_TYPE" != 'Informix' -a "$DB_TYPE" != 'Sybase' ; then - AC_MSG_ERROR([Only Oracle, Informix, Pg, mysql and SQLite are valid db types]) + AC_MSG_ERROR([Only Oracle, Pg, mysql and SQLite are valid db types]) fi AC_SUBST(DB_TYPE) @@ -222,7 +239,7 @@ AC_ARG_WITH(my-user-group, WEB_GROUP=$my_group) # Test for valid database names -AS_IF([ test "$DB_TYPE" == "mysql" ], +AS_IF([ test "$DB_TYPE" = "mysql" ], [ AC_MSG_CHECKING([if database name is valid]) AS_IF([ echo $DB_DATABASE | $AWK '/-/ { exit 1 }' ], [ AC_MSG_RESULT([yes]) ], @@ -236,28 +253,66 @@ dnl Set the value of apachectl AC_ARG_WITH(apachectl, AC_HELP_STRING([--with-apachectl], [instruct RT where to find your apachectl]), - APACHECTL=$withval, APACHECTL=`which apachectl`) AC_SUBST(APACHECTL) -dnl RT's standalone pure perl server -AC_ARG_WITH(devel-mode, - AC_HELP_STRING([--with-standalone], - [Install modules for pure perl standalone server]), - - RT_STANDALONE="1", - RT_STANDALONE="0") -AC_SUBST(RT_STANDALONE) - dnl RT's "maintainer mode" -AC_ARG_WITH(devel-mode, - AC_HELP_STRING([--with-devel-mode], +AC_ARG_WITH(devel-mode,[],RT_DEVEL_MODE=$withval,RT_DEVEL_MODE="0") +AC_ARG_ENABLE(devel-mode, + AC_HELP_STRING([--enable-devel-mode], [Turn on development aids that might hurt you in production]), - - RT_DEVEL_MODE="1", - RT_DEVEL_MODE="0") + RT_DEVEL_MODE=$enableval, + RT_DEVEL_MODE=$RT_DEVEL_MODE) +if test "$RT_DEVEL_MODE" = yes; then + RT_DEVEL_MODE="1" +else + RT_DEVEL_MODE="0" +fi AC_SUBST(RT_DEVEL_MODE) + +dnl RT's GraphViz dependency charts +AC_CHECK_LIB([graph],[aginitlib],RT_GRAPHVIZ="1") +AC_ARG_WITH(graphviz,[],RT_GRAPHVIZ=$withval) +AC_ARG_ENABLE(graphviz, + AC_HELP_STRING([--enable-graphviz], + [Turns on support for RT's GraphViz dependency charts]), + RT_GRAPHVIZ=$enableval) +if test "$RT_GRAPHVIZ" = yes; then + RT_GRAPHVIZ="1" +else + RT_GRAPHVIZ="0" +fi +AC_SUBST(RT_GRAPHVIZ) + +dnl RT's GD pie and bar charts +AC_CHECK_PROG([RT_GD], [gdlib-config], "yes", "no") +AC_ARG_WITH(gd,[],RT_GD=$withval) +AC_ARG_ENABLE(gd, + AC_HELP_STRING([--enable-gd], + [Turns on support for RT's GD pie and bar charts]), + RT_GD=$enableval) +if test "$RT_GD" = yes; then + RT_GD="1" +else + RT_GD="0" +fi +AC_SUBST(RT_GD) + +dnl RT's GPG support +AC_CHECK_PROG([RT_GPG], [gpg], "yes", "no") +AC_ARG_ENABLE(gpg, + AC_HELP_STRING([--enable-gpg], + [Turns on GNU Privacy Guard (GPG) support]), + RT_GPG=$enableval) +if test "$RT_GPG" = yes; then + RT_GPG="1" +else + RT_GPG="0" +fi +AC_SUBST(RT_GPG) + + dnl This section maps the variable names this script 'natively' generates dnl to their existing names. They should be removed from here as the .in dnl files are changed to use the new names. @@ -278,6 +333,7 @@ AC_SUBST([RT_BIN_PATH], ${exp_bindir}) AC_SUBST([RT_SBIN_PATH], ${exp_sbindir}) AC_SUBST([RT_VAR_PATH], ${exp_localstatedir}) AC_SUBST([RT_MAN_PATH], ${exp_mandir}) +AC_SUBST([RT_PLUGIN_PATH], ${exp_plugindir}) AC_SUBST([MASON_DATA_PATH], ${exp_masonstatedir}) AC_SUBST([MASON_SESSION_PATH], ${exp_sessionstatedir}) AC_SUBST([MASON_HTML_PATH], ${exp_htmldir}) @@ -285,31 +341,86 @@ AC_SUBST([LOCAL_ETC_PATH], ${exp_custometcdir}) AC_SUBST([MASON_LOCAL_HTML_PATH], ${exp_customhtmldir}) AC_SUBST([LOCAL_LEXICON_PATH], ${exp_customlexdir}) AC_SUBST([LOCAL_LIB_PATH], ${exp_customlibdir}) -AC_SUBST([DESTDIR], ${exp_prefix}) AC_SUBST([RT_LOG_PATH], ${exp_logfiledir}) +if test ${exp_sysconfdir} = "etc"; then +AC_SUBST([RT_PATH_R], ${exp_prefix}) +AC_SUBST([RT_DOC_PATH_R], ${exp_prefix}/${exp_manualdir}) +AC_SUBST([RT_LOCAL_PATH_R], ${exp_prefix}/${exp_customdir}) +AC_SUBST([RT_LIB_PATH_R], ${exp_prefix}/${exp_libdir}) +AC_SUBST([RT_ETC_PATH_R], ${exp_prefix}/${exp_sysconfdir}) +AC_SUBST([CONFIG_FILE_PATH_R], ${exp_prefix}/${exp_sysconfdir}) +AC_SUBST([RT_BIN_PATH_R], ${exp_prefix}/${exp_bindir}) +AC_SUBST([RT_SBIN_PATH_R], ${exp_prefix}/${exp_sbindir}) +AC_SUBST([RT_VAR_PATH_R], ${exp_prefix}/${exp_localstatedir}) +AC_SUBST([RT_MAN_PATH_R], ${exp_prefix}/${exp_mandir}) +AC_SUBST([RT_PLUGIN_PATH_R], ${exp_prefix}/${exp_plugindir}) +AC_SUBST([MASON_DATA_PATH_R], ${exp_prefix}/${exp_masonstatedir}) +AC_SUBST([MASON_SESSION_PATH_R], ${exp_prefix}/${exp_sessionstatedir}) +AC_SUBST([MASON_HTML_PATH_R], ${exp_prefix}/${exp_htmldir}) +AC_SUBST([LOCAL_ETC_PATH_R], ${exp_prefix}/${exp_custometcdir}) +AC_SUBST([MASON_LOCAL_HTML_PATH_R], ${exp_prefix}/${exp_customhtmldir}) +AC_SUBST([LOCAL_LEXICON_PATH_R], ${exp_prefix}/${exp_customlexdir}) +AC_SUBST([LOCAL_LIB_PATH_R], ${exp_prefix}/${exp_customlibdir}) +AC_SUBST([RT_LOG_PATH_R], ${exp_prefix}/${exp_logfiledir}) +else +AC_SUBST([RT_PATH_R], ${exp_prefix}) +AC_SUBST([RT_DOC_PATH_R], ${exp_manualdir}) +AC_SUBST([RT_LOCAL_PATH_R], ${exp_customdir}) +AC_SUBST([RT_LIB_PATH_R], ${exp_libdir}) +AC_SUBST([RT_ETC_PATH_R], ${exp_sysconfdir}) +AC_SUBST([RT_PLUGIN_PATH_R], ${exp_plugindir}) +AC_SUBST([CONFIG_FILE_PATH_R], ${exp_sysconfdir}) +AC_SUBST([RT_BIN_PATH_R], ${exp_bindir}) +AC_SUBST([RT_SBIN_PATH_R], ${exp_sbindir}) +AC_SUBST([RT_VAR_PATH_R], ${exp_localstatedir}) +AC_SUBST([RT_MAN_PATH_R], ${exp_mandir}) +AC_SUBST([MASON_DATA_PATH_R], ${exp_masonstatedir}) +AC_SUBST([MASON_SESSION_PATH_R], ${exp_sessionstatedir}) +AC_SUBST([MASON_HTML_PATH_R], ${exp_htmldir}) +AC_SUBST([LOCAL_ETC_PATH_R], ${exp_custometcdir}) +AC_SUBST([MASON_LOCAL_HTML_PATH_R], ${exp_customhtmldir}) +AC_SUBST([LOCAL_LEXICON_PATH_R], ${exp_customlexdir}) +AC_SUBST([LOCAL_LIB_PATH_R], ${exp_customlibdir}) +AC_SUBST([RT_LOG_PATH_R], ${exp_logfiledir}) + +fi + dnl Configure the output files, and generate them. dnl Binaries that should be +x AC_CONFIG_FILES([ - sbin/rt-dump-database - sbin/rt-setup-database - sbin/rt-test-dependencies - bin/mason_handler.fcgi - bin/mason_handler.scgi - bin/standalone_httpd - bin/rt-crontool - bin/rt-mailgate - bin/rt], - [chmod ug+x $ac_file] + etc/upgrade/3.8-branded-queues-extension + etc/upgrade/3.8-ical-extension + etc/upgrade/split-out-cf-categories + sbin/rt-attributes-viewer + sbin/rt-dump-database + sbin/rt-setup-database + sbin/rt-test-dependencies + sbin/rt-email-digest + sbin/rt-email-dashboards + sbin/rt-clean-sessions + sbin/rt-shredder + sbin/rt-validator + sbin/rt-email-group-admin + sbin/rt-server + bin/mason_handler.fcgi + bin/mason_handler.scgi + bin/standalone_httpd + bin/rt-crontool + bin/rt-mailgate + bin/rt], + [chmod ug+x $ac_file] ) dnl All other generated files AC_CONFIG_FILES([ - Makefile - etc/RT_Config.pm - lib/RT.pm - bin/mason_handler.svc - bin/webmux.pl], + Makefile + etc/RT_Config.pm + lib/RT.pm + bin/mason_handler.svc + bin/webmux.pl + t/data/configs/apache2.2+mod_perl.conf + t/data/configs/apache2.2+fastcgi.conf], ) AC_OUTPUT diff --git a/rt/docs/creating_external_custom_fields.pod b/rt/docs/creating_external_custom_fields.pod new file mode 100644 index 000000000..f8eca44fb --- /dev/null +++ b/rt/docs/creating_external_custom_fields.pod @@ -0,0 +1,97 @@ +=head1 External custom fields + +=head2 Description + +C is extension to custom fields that allow +you to define CF with a dynamic list of values. Loading values into +this custom fields requires writing a little Perl code to fetch the +data from the external source; the code that we added to RT 3.7 +allows it to load data from arbitrary external sources. + +=head2 Introduction into writing source of values + +For each type of data source that you want, you'll need to put a file +in F (or equivilent if you +installed RT someplace other than F). To get a sense of the +code that you'll need to write, take a look at the code in +L for a simple example +which just uses RT's API to pull in a list of RT's groups. + +Running C will +show you the documentation for the API that needs to be fulfilled, by +copying and editing the C example is probably a fine place to +start. + +Later in this doc we'll describe the example a little bit more. + +=head2 Configuration + +After the custom code is written, you need to tell RT about its +existence by adding something like following to your RT_SiteConfig.pm: +Set(@CustomFieldValuesSources, "RT::CustomFieldValues::MySource"); + +The value in quotes should be the name of the class that you created. + +Stop and start your web server to enable any config changes. Open the web interface with +as an administrative user (such as root) create new custom field. In order to use +a custom source of values, you should select a custom field type +that lets users pick values from a list, for example it could be C box using: + <& /Widgets/Form/Integer:InputOnly, + Name => 'NameOfInputElement', + &> + +In such a simple case you even can avoid processing. Yeah, most probably +you want to check if value is really integer, but these widgets don't +do validation for you, but they are more about fetching values from +hash of arguments, showing these values to user and preserving state +of value between form reloads (see below). + +=head2 Processing + +Processing is required when you use L, +such as Default, Multiple or Alternative. + +To process arguments of a request you have to do the following: + $ARGS{'NameOfInputElement'} = $m->comp( + '/Widgets/Form/Integer:Process', + Arguments => \%ARGS, + Name => 'NameOfInputElement', + ); + +The method returns processed value in canonical form. For different widgets +a canonical form is different and depends on activated features, so you must +always activate the same features during showing a widget and processing +results. + +=head2 Extendent features + +=head3 Default value + +If C argument is true then widgets expect that there is some +default value for argument if user fills nothing. 'Nothing' in each +widget is different, for example in select box it's special option +which is always the first one, in integer box string '' means empty +value, but boolean box uses radio buttons in this case with three +options: Yes, No and Default. + +Each widget that supports C feature as well has C and +C arguments. + +=head4 Processing and showing with activated Default feature + +When this option is activated then C method returns undef +value if user selected default value. So for integer box it's empty +string and so on. + +As well when you show a widget you should pass undef as C +to inform widget that the current value is default one. + +As all methods of a widget are consistent in this behaviour so you +shouldn't care much about that, but this allows you to implement +custom actions if processing returned undef, for example delete user's +preference record instead of updating it (default value may change later to). + +=head4 C when C is not active + +DefaultValue argument is still actual in the Process method even if +C is not true. This argument defines intial value. If value +of a key in Arguments is not defined then it's treated as intial state +and the method returns default value. + +=head3 Multiple and Alternative + +These options are only supported by the select widget. + +TODO: Add more info + +=head2 Implementation details + +=head3 Boolean widget + +This widget a little bit tricky. When you use Default option then +things are simple and you see three radio buttons, but in other +case we use a checkbox. But as you know browsers don't pass unchecked +boxes to server, so arguments of a request has no entry for them. + +In the latter case it's hard to figure out case when user unselected +value. Imagine form with a checkbox, you want show it checked by +default and as well form is reloadable (like Reply forms that have +"Add Another File" buttons). User uncheck the box and then upload +file, in this case you want to show user's choice instead of default, +but browser doesn't send any value and you can not figure out if +it's initial state or page reload. To solve this problem we use magic +hidden input field with the same name as the box and value equal to +zero (0). Mason folds arguments with the same name into array refs, so +we get 0 if box is unchecked and [0, 1] if box is checked. An array +reference is true value and 0 is defined value so we know that it's +not initial state and avoid switching back to default. As well this +trick works good in a case when you want show a link to a page and +define default choice for some boolean argument, you don't need +to set argument twice, you just set it to true value (for ex. 1) and +things just work. + diff --git a/rt/etc/RT_Config.pm b/rt/etc/RT_Config.pm index 34a424342..823189b45 100644 --- a/rt/etc/RT_Config.pm +++ b/rt/etc/RT_Config.pm @@ -1,7 +1,3 @@ -# -# WARNING: NEVER EDIT RT_Config.pm. Instead, copy any sections you want to change to RT_SiteConfig.pm -# and edit them there. -# package RT; @@ -15,597 +11,1613 @@ use RT::Config; =cut -# {{{ Base Configuration +=head1 WARNING -# $rtname is the string that RT will look for in mail messages to -# figure out what ticket a new piece of mail belongs to +NEVER EDIT RT_Config.pm. -# Your domain name is recommended, so as not to pollute the namespace. -# once you start using a given tag, you should probably never change it. -# (otherwise, mail for existing tickets won't get put in the right place +Instead, copy any sections you want to change to F and edit them there. + +=cut + +=head1 Base Configuration + +=over 4 + +=item C<$rtname> + +C<$rtname> is the string that RT will look for in mail messages to +figure out what ticket a new piece of mail belongs to. + +Your domain name is recommended, so as not to pollute the namespace. +once you start using a given tag, you should probably never change it. +(otherwise, mail for existing tickets won't get put in the right place) + +=cut Set($rtname , "example.com"); -# This regexp controls what subject tags RT recognizes as its own. -# If you're not dealing with historical $rtname values, you'll likely -# never have to enable this feature. -# -# Be VERY CAREFUL with it. Note that it overrides $rtname for subject -# token matching and that you should use only "non-capturing" parenthesis -# grouping. For example: -# -# Set($EmailSubjectTagRegex, qr/(?:example.com|example.org)/i ); -# -# and NOT -# -# Set($EmailSubjectTagRegex, qr/(example.com|example.org)/i ); -# -# This setting would make RT behave exactly as it does without the -# setting enabled. -# -# Set($EmailSubjectTagRegex, qr/\Q$rtname\E/i ); +=item C<$EmailSubjectTagRegex> + +This regexp controls what subject tags RT recognizes as its own. +If you're not dealing with historical C<$rtname> values, you'll likely +never have to enable this feature. + +Be VERY CAREFUL with it. Note that it overrides C<$rtname> for subject +token matching and that you should use only "non-capturing" parenthesis +grouping. For example: + +C + +and NOT + +C + +This setting would make RT behave exactly as it does without the +setting enabled. + +=cut + +#Set($EmailSubjectTagRegex, qr/\Q$rtname\E/i ); + + +=item C<$Organization> +You should set this to your organization's DNS domain. For example, +I or I. It's used by the linking interface to +guarantee that ticket URIs are unique and easy to construct. -# You should set this to your organization's DNS domain. For example, -# fsck.com or asylum.arkham.ma.us. It's used by the linking interface to -# guarantee that ticket URIs are unique and easy to construct. +=cut Set($Organization , "example.com"); -# $user_passwd_min defines the minimum length for user passwords. Setting -# it to 0 disables this check +=item C<$MinimumPasswordLength> + +C<$MinimumPasswordLength> defines the minimum length for user +passwords. Setting it to 0 disables this check. + +=cut + Set($MinimumPasswordLength , "5"); -# $Timezone is used to convert times entered by users into GMT and back again -# It should be set to a timezone recognized by your local unix box. +=item C<$Timezone> + +C<$Timezone> is used to convert times entered by users into GMT and back again +It should be set to a timezone recognized by your local unix box. + +=cut + Set($Timezone , 'US/Eastern'); -# }}} +=back + +=head1 Database Configuration + +=over 4 + +=item C<$DatabaseType> -# {{{ Database Configuration +Database driver being used; case matters. -# Database driver beeing used. Case matters -# Valid types are "mysql", "Oracle" and "Pg" +Valid types are "mysql", "Oracle" and "Pg" + +=cut Set($DatabaseType , 'SQLite'); -# The domain name of your database server -# If you're running mysql and it's on localhost, -# leave it blank for enhanced performance +=item C<$DatabaseHost>, C<$DatabaseRTHost> + +The domain name of your database server. + +If you're running mysql and it's on localhost, +leave it blank for enhanced performance + +=cut + Set($DatabaseHost , 'localhost'); Set($DatabaseRTHost , 'localhost'); -# The port that your database server is running on. Ignored unless it's -# a positive integer. It's usually safe to leave this blank +=item C<$DatabasePort> + +The port that your database server is running on. Ignored unless it's +a positive integer. It's usually safe to leave this blank + +=cut + Set($DatabasePort , ''); -#The name of the database user (inside the database) +=item C<$DatabaseUser> + +The name of the database user (inside the database) + +=cut + Set($DatabaseUser , 'rt_user'); -# Password the DatabaseUser should use to access the database +=item C<$DatabasePassword> + +Password the C<$DatabaseUser> should use to access the database + +=cut + Set($DatabasePassword , 'rt_pass'); -# The name of the RT's database on your database server +=item C<$DatabaseName> + +The name of the RT's database on your database server. For Oracle +it's SID, DB objects are created in L<$DatabaseUser>'s schema. + +=cut + Set($DatabaseName , 'rt3'); -# If you're using Postgres and have compiled in SSL support, -# set DatabaseRequireSSL to 1 to turn on SSL communication +=item C<$DatabaseRequireSSL> + +If you're using Postgres and have compiled in SSL support, +set C<$DatabaseRequireSSL> to 1 to turn on SSL communication + +=cut + Set($DatabaseRequireSSL , undef); -# }}} +=item C<$UseSQLForACLChecks> + +In RT for ages ACL are checked after search what in some situtations +result in empty search pages and wrong count of tickets. + +Set C<$UseSQLForACLChecks> to 1 to use SQL and get rid of these problems. + +However, this option is beta. In some cases it result in performance +improvements, but some setups can not handle it. + +=cut + +Set($UseSQLForACLChecks, undef); + +=back + +=head1 Incoming Mail Gateway Configuration + +=over 4 -# {{{ Incoming mail gateway configuration +=item C<$OwnerEmail> -# OwnerEmail is the address of a human who manages RT. RT will send -# errors generated by the mail gateway to this address. This address -# should _not_ be an address that's managed by your RT instance. +C<$OwnerEmail> is the address of a human who manages RT. RT will send +errors generated by the mail gateway to this address. This address +should _not_ be an address that's managed by your RT instance. + +=cut Set($OwnerEmail , 'root'); -# If $LoopsToRTOwner is defined, RT will send mail that it believes -# might be a loop to $RT::OwnerEmail +=item C<$LoopsToRTOwner> + +If C<$LoopsToRTOwner> is defined, RT will send mail that it believes +might be a loop to C<$OwnerEmail> + +=cut Set($LoopsToRTOwner , 1); -# If $StoreLoops is defined, RT will record messages that it believes -# to be part of mail loops. -# As it does this, it will try to be careful not to send mail to the -# sender of these messages +=item C<$StoreLoops> + +If C<$StoreLoops> is defined, RT will record messages that it believes +to be part of mail loops. + +As it does this, it will try to be careful not to send mail to the +sender of these messages + +=cut Set($StoreLoops , undef); -# $MaxAttachmentSize sets the maximum size (in bytes) of attachments stored -# in the database. +=item C<$MaxAttachmentSize> + +C<$MaxAttachmentSize> sets the maximum size (in bytes) of attachments stored +in the database. + +For mysql and oracle, we set this size at 10 megabytes. +If you're running a postgres version earlier than 7.1, you will need +to drop this to 8192. (8k) + +=cut -# For mysql and oracle, we set this size at 10 megabytes. -# If you're running a postgres version earlier than 7.1, you will need -# to drop this to 8192. (8k) Set($MaxAttachmentSize , 10000000); -# $TruncateLongAttachments: if this is set to a non-undef value, -# RT will truncate attachments longer than MaxAttachmentSize. +=item C<$TruncateLongAttachments> + +C<$TruncateLongAttachments>: if this is set to a non-undef value, +RT will truncate attachments longer than C<$MaxAttachmentSize>. + +=cut Set($TruncateLongAttachments , undef); -# $DropLongAttachments: if this is set to a non-undef value, -# RT will silently drop attachments longer than MaxAttachmentSize. +=item C<$DropLongAttachments> + +C<$DropLongAttachments>: if this is set to a non-undef value, +RT will silently drop attachments longer than C. + +=cut Set($DropLongAttachments , undef); -# If $ParseNewMessageForTicketCcs is true, RT will attempt to divine -# Ticket 'Cc' watchers from the To and Cc lines of incoming messages -# Be forewarned that if you have _any_ addresses which forward mail to -# RT automatically and you enable this option without modifying -# "RTAddressRegexp" below, you will get yourself into a heap of trouble. +=item C<$ParseNewMessageForTicketCcs> + +If C<$ParseNewMessageForTicketCcs> is true, RT will attempt to divine +Ticket 'Cc' watchers from the To and Cc lines of incoming messages +Be forewarned that if you have _any_ addresses which forward mail to +RT automatically and you enable this option without modifying +C<$RTAddressRegexp> below, you will get yourself into a heap of trouble. + +=cut Set($ParseNewMessageForTicketCcs , undef); -# RTAddressRegexp is used to make sure RT doesn't add itself as a ticket CC if -# the setting above is enabled. +=item C<$RTAddressRegexp> + +C<$RTAddressRegexp> is used to make sure RT doesn't add itself as a ticket CC if +the setting above is enabled. It is important that you set this to a +regular expression that matches all addresses used by your RT. This lets RT +avoid sending mail to itself. It will also hide RT addresses from the list of +"One-time Cc" and Bcc lists on ticket reply. + +=cut Set($RTAddressRegexp , '^rt\@example.com$'); -# RT provides functionality which allows the system to rewrite -# incoming email addresses. In its simplest form, -# you can substitute the value in CanonicalizeEmailAddressReplace -# for the value in CanonicalizeEmailAddressMatch -# (These values are passed to the CanonicalizeEmailAddress subroutine in RT/User.pm) -# By default, that routine performs a s/$Match/$Replace/gi on any address passed to it +=item C<$CanonicalizeEmailAddressMatch>, C<$CanonicalizeEmailAddressReplace> + +RT provides functionality which allows the system to rewrite +incoming email addresses. In its simplest form, +you can substitute the value in $ +for the value in $ +(These values are passed to the $ subroutine in + F) + +By default, that routine performs a C on any address +passed to it. + +=cut #Set($CanonicalizeEmailAddressMatch , '@subdomain\.example\.com$'); #Set($CanonicalizeEmailAddressReplace , '@example.com'); -# set this to true and the create new user page will use the values that you -# enter in the form but use the function CanonicalizeUserInfo in User_Local.pm -Set($CanonicalizeOnCreate , 0); - -# If $SenderMustExistInExternalDatabase is true, RT will refuse to -# create non-privileged accounts for unknown users if you are using -# the "LookupSenderInExternalDatabase" option. -# Instead, an error message will be mailed and RT will forward the -# message to $RTOwner. -# -# If you are not using $LookupSenderInExternalDatabase, this option -# has no effect. -# -# If you define an AutoRejectRequest template, RT will use this -# template for the rejection message. +=item C<$CanonicalizeEmailAddressMatch> + +Set this to true and the create new user page will use the values that you +enter in the form but use the function CanonicalizeUserInfo in +F + +=cut + +Set($CanonicalizeOnCreate, 0); + +=item C<$SenderMustExistInExternalDatabase> + +If C<$SenderMustExistInExternalDatabase> is true, RT will refuse to +create non-privileged accounts for unknown users if you are using +the C<$LookupSenderInExternalDatabase> option. +Instead, an error message will be mailed and RT will forward the +message to C<$RTOwner>. + +If you are not using C<$LookupSenderInExternalDatabase>, this option +has no effect. + +If you define an AutoRejectRequest template, RT will use this +template for the rejection message. + +=cut Set($SenderMustExistInExternalDatabase , undef); -# }}} +=item C<$ValidateUserEmailAddresses> + +If C<$ValidateUserEmailAddresses> is true, RT will refuse to create users with +an invalid email address (as specified in RFC 2822) or with an email address +made of multiple email adresses. + +=cut + +Set($ValidateUserEmailAddresses, undef); + +=item C<@MailPlugins> -# {{{ Outgoing mail configuration +C<@MailPlugins> is a list of auth plugins for L +to use; see L -# RT is designed such that any mail which already has a ticket-id associated -# with it will get to the right place automatically. +=cut + +=item C<$UnsafeEmailCommands> + +C<$UnsafeEmailCommands>, if set to true, enables 'take' and 'resolve' +as possible actions via the mail gateway. As its name implies, this +is very unsafe, as it allows email with a forged sender to possibly +resolve arbitrary tickets! + +=cut + +=item C<$ExtractSubjectTagMatch>, C<$ExtractSubjectTagNoMatch> + +The default "extract remote tracking tags" scrip settings; these +detect when your RT is talking to another RT, and adjusts the +subject accordingly. + +=cut + +Set($ExtractSubjectTagMatch, qr/\[.+? #\d+\]/); +Set($ExtractSubjectTagNoMatch, ( ${RT::EmailSubjectTagRegex} + ? qr/\[(?:${RT::EmailSubjectTagRegex}) #\d+\]/ + : qr/\[\Q$RT::rtname\E #\d+\]/)); + +=back + +=head1 Outgoing Mail Configuration -# $CorrespondAddress and $CommentAddress are the default addresses -# that will be listed in From: and Reply-To: headers of correspondence -# and comment mail tracked by RT, unless overridden by a queue-specific -# address. +=over 4 -Set($CorrespondAddress , 'RT_CorrespondAddressNotSet'); +=item C<$MailCommand> -Set($CommentAddress , 'RT_CommentAddressNotSet'); +C<$MailCommand> defines which method RT will use to try to send mail. +We know that 'sendmailpipe' works fairly well. If 'sendmailpipe' +doesn't work well for you, try 'sendmail'. Other options are 'smtp' +or 'qmail'. -#Sendmail Configuration +Note that you should remove the '-t' from C<$SendmailArguments> +if you use 'sendmail' rather than 'sendmailpipe' -# $MailCommand defines which method RT will use to try to send mail -# We know that 'sendmailpipe' works fairly well. -# If 'sendmailpipe' doesn't work well for you, try 'sendmail' -# -# Note that you should remove the '-t' from $SendmailArguments -# if you use 'sendmail' rather than 'sendmailpipe' +=cut Set($MailCommand , 'sendmailpipe'); -# $SendmailArguments defines what flags to pass to $Sendmail -# assuming you picked 'sendmail' or 'sendmailpipe' as the $MailCommand above. -# If you picked 'sendmailpipe', you MUST add a -t flag to $SendmailArguments +=item C<$SetOutgoingMailFrom> + +C<$SetOutgoingMailFrom> tells RT to set the sender envelope with the correspond +mail address of the ticket's queue. + +Warning: If you use this setting, bounced mails will appear to be incoming +mail to the system, thus creating new tickets. + +=cut + +Set($SetOutgoingMailFrom, 0); + +=item C<$OverrideOutgoingMailFrom> + +C<$OverrideOutgoingMailFrom> is used for overwriting the Correspond +address of the queue. The option is a hash reference of queue name to +email address. + +If there is no ticket involved, then the value of the C key will be +used. + +=cut + +Set($OverrideOutgoingMailFrom, { +# 'Default' => 'admin@rt.example.com', +# 'General' => 'general@rt.example.com', +}); + +=back + +=head1 Sendmail Configuration + +These options only take effect if C<$MailCommand> is 'sendmail' or +'sendmailpipe' + +=over 4 + +=item C<$SendmailArguments> + +C<$SendmailArguments> defines what flags to pass to C<$SendmailPath> +If you picked 'sendmailpipe', you MUST add a -t flag to C<$SendmailArguments> +These options are good for most sendmail wrappers and workalikes + +These arguments are good for sendmail brand sendmail 8 and newer +C + +=cut -# These options are good for most sendmail wrappers and workalikes Set($SendmailArguments , "-oi -t"); -# $SendmailBounceArguments defines what flags to pass to $Sendmail -# assuming RT needs to send an error (ie. bounce). + +=item C<$SendmailBounceArguments> + +C<$SendmailBounceArguments> defines what flags to pass to C<$Sendmail> +assuming RT needs to send an error (ie. bounce). + +=cut Set($SendmailBounceArguments , '-f "<>"'); -# These arguments are good for sendmail brand sendmail 8 and newer -#Set($SendmailArguments,"-oi -t -ODeliveryMode=b -OErrorMode=m"); +=item C<$SendmailPath> + +If you selected 'sendmailpipe' above, you MUST specify the path to +your sendmail binary in C<$SendmailPath>. + +=cut -# If you selected 'sendmailpipe' above, you MUST specify the path -# to your sendmail binary in $SendmailPath. -# !! If you did not # select 'sendmailpipe' above, this has no effect!! Set($SendmailPath , "/usr/sbin/sendmail"); -# By default, RT sets the outgoing mail's "From:" header to -# "SenderName via RT". Setting this option to 0 disables it. -Set($UseFriendlyFromLine , 1); +=back -# sprintf() format of the friendly 'From:' header; its arguments -# are SenderName and SenderEmailAddress. -Set($FriendlyFromLineFormat , "\"%s via RT\" <%s>"); +=head1 SMTP Configuration -# RT can optionally set a "Friendly" 'To:' header when sending messages to -# Ccs or AdminCcs (rather than having a blank 'To:' header. +These options only take effect if C<$MailCommand> is 'smtp' -# This feature DOES NOT WORK WITH SENDMAIL[tm] BRAND SENDMAIL -# If you are using sendmail, rather than postfix, qmail, exim or some other MTA, -# you _must_ disable this option. +=over 4 -Set($UseFriendlyToLine , 0); +=item C<$SMTPServer> -# sprintf() format of the friendly 'From:' header; its arguments -# are WatcherType and TicketId. -Set($FriendlyToLineFormat, "\"%s of $RT::rtname Ticket #%s\":;"); +C<$SMTPServer> should be set to the hostname of the SMTP server to use -# By default, RT doesn't notify the person who performs an update, as they -# already know what they've done. If you'd like to change this behaviour, -# Set $NotifyActor to 1 +=cut -Set($NotifyActor, 0); +Set($SMTPServer, undef); -# By default, RT records each message it sends out to its own internal database.# To change this behaviour, set $RecordOutgoingEmail to 0 +=item C<$SMTPFrom> -Set($RecordOutgoingEmail, 1); +C<$SMTPFrom> should be set to the 'From' address to use, if not the +email's 'From' -# VERP support (http://cr.yp.to/proto/verp.txt) -# uncomment the following two directives to generate envelope senders -# of the form ${VERPPrefix}${originaladdress}@${VERPDomain} -# (i.e. rt-jesse=fsck.com@rt.example.com ) This currently only works -# with sendmail and sendmailppie. -# Set($VERPPrefix, 'rt-'); -# Set($VERPDomain, $RT::Organization); +=cut -# }}} +Set($SMTPFrom, undef); -# {{{ Logging +=item C<$SMTPDebug> -# Logging. The default is to log anything except debugging -# information to syslog. Check the Log::Dispatch POD for -# information about how to get things by syslog, mail or anything -# else, get debugging info in the log, etc. +C<$SMTPDebug> should be set to true to debug SMTP mail sending -# It might generally make -# sense to send error and higher by email to some administrator. -# If you do this, be careful that this email isn't sent to this RT instance. +=cut -# the minimum level error that will be logged to the specific device. -# levels from lowest to highest: -# debug info notice warning error critical alert emergency +Set($SMTPDebug, 0); -# Mail loops will generate a critical log message. -Set($LogToSyslog , 'debug'); -Set($LogToScreen , 'error'); -Set($LogToFile , undef); -Set($LogDir, '/opt/rt3/var/log'); -Set($LogToFileNamed , "rt.log"); #log to rt.log +=back -# If true generates stack traces to file log or screen -# never generates traces to syslog +=head1 Other Mailer Configuration -Set($LogStackTraces , 0); +=over 4 -# On Solaris or UnixWare, set to ( socket => 'inet' ). Options here -# override any other options RT passes to Log::Dispatch::Syslog. -# Other interesting flags include facility and logopt. (See the -# Log::Dispatch::Syslog documentation for more information.) (Maybe -# ident too, if you have multiple RT installations.) +=item C<@MailParams> -@LogToSyslogConf = () unless (@LogToSyslogConf); +C<@MailParams> defines a list of options passed to $MailCommand if it +is not 'sendmailpipe', 'sendmail', or 'smtp' -# RT has rudimentary SQL statement logging support if you have -# DBIx-SearchBuilder 1.31_1 or higher; simply set $StatementLog to be -# the level that you wish SQL statements to be logged at. -Set($StatementLog, undef); +=cut -# }}} +Set(@MailParams, ()); -# {{{ Web interface configuration +=item C<$CorrespondAddress>, C<$CommentAddress> -# This determines the default stylesheet the RT web interface will use. -# RT ships with two valid values by default: -# -# 3.5-default The totally new, default layout for RT 3.5 -# 3.4-compat A 3.4 compatibility stylesheet to make RT 3.5 look -# (mostly) like 3.4 -# -# This value actually specifies a directory in share/html/NoAuth/css/ -# from which RT will try to load the file main.css (which should -# @import any other files the stylesheet needs). This allows you to -# easily and cleanly create your own stylesheets to apply to RT. +RT is designed such that any mail which already has a ticket-id associated +with it will get to the right place automatically. -Set($WebDefaultStylesheet, '3.5-default'); +C<$CorrespondAddress> and C<$CommentAddress> are the default addresses +that will be listed in From: and Reply-To: headers of correspondence +and comment mail tracked by RT, unless overridden by a queue-specific +address. -# Define the directory name to be used for images in rt web -# documents. +=cut -# If you're putting the web ui somewhere other than at the root of -# your server, you should set $WebPath to the path you'll be -# serving RT at. -# $WebPath requires a leading / but no trailing /. -# -# In most cases, you should leave $WebPath set to '' (an empty value). +Set($CorrespondAddress , ''); -Set($WebPath , ""); +Set($CommentAddress , ''); -# If we're running as a superuser, run on port 80 -# Otherwise, pick a high port for this user. +=item C<$DashboardAddress> -Set($WebPort , 80);# + ($< * 7274) % 32766 + ($< && 1024)); +The email address from which RT will send dashboards. If none is set, then +C<$OwnerEmail> will be used. -# This is the Scheme, server and port for constructing urls to webrt -# $WebBaseURL doesn't need a trailing / +=cut -Set($WebBaseURL , "http://localhost:$WebPort"); +Set($DashboardAddress, ''); -Set($WebURL , $WebBaseURL . $WebPath . "/"); +=item C<$UseFriendlyFromLine> -# $WebImagesURL points to the base URL where RT can find its images. +By default, RT sets the outgoing mail's "From:" header to +"SenderName via RT". Setting C<$UseFriendlyFromLine> to 0 disables it. -Set($WebImagesURL , $WebPath . "/NoAuth/images/"); +=cut -# $LogoURL points to the URL of the RT logo displayed in the web UI +Set($UseFriendlyFromLine, 1); -Set($LogoURL , $WebImagesURL . "bplogo.gif"); +=item C<$FriendlyFromLineFormat> -# WebNoAuthRegex - What portion of RT's URLspace should not require -# authentication. -Set($WebNoAuthRegex, qr!^(?:/+NoAuth/| - /+REST/\d+\.\d+/NoAuth/)!x ); +C format of the friendly 'From:' header; its arguments +are SenderName and SenderEmailAddress. -# SelfServiceRegex - What portion of RT's URLspace should -# be accessible to Unprivileged users -# This does not override the redirect from /Ticket/Display.html -# to /SelfService/Display.html when Unprivileged -# users attempt to access ticked displays -Set($SelfServiceRegex, qr!^(?:/+SelfService/)!x ); +=cut -# For message boxes, set the entry box width and what type of wrapping -# to use. -# -# Default width: 72 -Set($MessageBoxWidth , 72); +Set($FriendlyFromLineFormat, "\"%s via RT\" <%s>"); -# Default wrapping: "HARD" (choices "SOFT", "HARD") -Set($MessageBoxWrap, "HARD"); +=item C<$UseFriendlyToLine> -# Support implicit links in WikiText custom fields? A true value -# causes InterCapped or ALLCAPS words in WikiText fields to -# automatically become links to searches for those words. If used on -# RTFM articles, it links to the RTFM article with that name. -Set($WikiImplicitLinks, 0); +RT can optionally set a "Friendly" 'To:' header when sending messages to +Ccs or AdminCcs (rather than having a blank 'To:' header. -# if TrustHTMLAttachments is not defined, we will display them -# as text. This prevents malicious HTML and javascript from being -# sent in a request (although there is probably more to it than that) -Set($TrustHTMLAttachments , undef); - -# Should RT redistribute correspondence that it identifies as -# machine generated? A true value will do so; setting this to '0' -# will cause no such messages to be redistributed. -# You can also use 'privileged' (the default), which will redistribute -# only to privileged users. This helps to protect against malformed -# bounces and loops caused by autocreated requestors with bogus addresses. -Set($RedistributeAutoGeneratedMessages, 'privileged'); +This feature DOES NOT WORK WITH SENDMAIL[tm] BRAND SENDMAIL +If you are using sendmail, rather than postfix, qmail, exim or some other MTA, +you _must_ disable this option. -# If PreferRichText is set to a true value, RT will show HTML/Rich text -# messages in preference to their plaintext alternatives. RT "scrubs" the -# html to show only a minimal subset of HTML to avoid possible contamination -# by cross-site-scripting attacks. -Set($PreferRichText, undef); +=cut -# If $WebExternalAuth is defined, RT will defer to the environment's -# REMOTE_USER variable. +Set($UseFriendlyToLine, 0); -Set($WebExternalAuth , undef); +=item C<$FriendlyToLineFormat> -# If $WebFallbackToInternalAuth is undefined, the user is allowed a chance -# of fallback to the login screen, even if REMOTE_USER failed. +C format of the friendly 'From:' header; its arguments +are WatcherType and TicketId. -Set($WebFallbackToInternalAuth , undef); +=cut -# $WebExternalGecos means to match 'gecos' field as the user identity); -# useful with mod_auth_pwcheck and IIS Integrated Windows logon. +Set($FriendlyToLineFormat, "\"%s of ". RT->Config->Get('rtname') ." Ticket #%s\":;"); -Set($WebExternalGecos , undef); +=item C<$NotifyActor> -# $WebExternalAuto will create users under the same name as REMOTE_USER -# upon login, if it's missing in the Users table. +By default, RT doesn't notify the person who performs an update, as they +already know what they've done. If you'd like to change this behaviour, +Set C<$NotifyActor> to 1 -Set($WebExternalAuto , undef); +=cut -# If $WebExternalAuto is true, this will be passed to User's -# Create method. Use it to set defaults, such as creating -# Unprivileged users with { Privileged => 0 } -# Must be a hashref of arguments +Set($NotifyActor, 0); -Set($AutoCreate, undef); +=item C<$RecordOutgoingEmail> -# $WebSessionClass is the class you wish to use for managing Sessions. -# It defaults to use your SQL database, but if you are using MySQL 3.x and -# plans to use non-ascii Queue names, uncomment and add this line to -# RT_SiteConfig.pm will prevent session corruption. +By default, RT records each message it sends out to its own internal database. +To change this behavior, set C<$RecordOutgoingEmail> to 0 -# Set($WebSessionClass , 'Apache::Session::File'); +=cut +Set($RecordOutgoingEmail, 1); -# By default, RT's session cookie isn't marked as "secure" Some web browsers -# will treat secure cookies more carefully than non-secure ones, being careful -# not to write them to disk, only send them over an SSL secured connection -# and so on. To enable this behaviour, set # $WebSecureCookies to a true value. -# NOTE: You probably don't want to turn this on _unless_ users are only connecting -# via SSL encrypted HTTP connections. +=item C<$VERPPrefix>, C<$VERPPrefix> -Set($WebSecureCookies, 0); +VERP support (http://cr.yp.to/proto/verp.txt) +uncomment the following two directives to generate envelope senders +of the form C<${VERPPrefix}${originaladdress}@${VERPDomain}> +(i.e. rt-jesse=fsck.com@rt.example.com ). -# By default, RT clears its database cache after every page view. -# This ensures that you've always got the most current information -# when working in a multi-process (mod_perl or FastCGI) Environment -# Setting $WebFlushDbCacheEveryRequest to '0' will turn this off, -# which will speed RT up a bit, at the expense of a tiny bit of data -# accuracy. +This currently only works with sendmail and sendmailppie. -Set($WebFlushDbCacheEveryRequest, '1'); +=cut +# Set($VERPPrefix, 'rt-'); +# Set($VERPDomain, $RT::Organization); -# $MaxInlineBody is the maximum attachment size that we want to see -# inline when viewing a transaction. 13456 is a random sane-sounding -# default. -Set($MaxInlineBody, 13456); +=item C<$ForwardFromUser> -# $DefaultSummaryRows is default number of rows displayed in for search -# results on the frontpage. +By default, RT forwards a message using queue's address and adds RT's tag into +subject of the outgoing message, so recipients' replies go into RT as correspondents. -Set($DefaultSummaryRows, 10); +To change this behavior, set C<$ForwardFromUser> to true value and RT will use +address of the current user and leave subject without RT's tag. -# By default, RT shows newest transactions at the bottom of the ticket -# history page, if you want see them at the top set this to '0'. +=cut -Set($OldestTransactionsFirst, '1'); +Set($ForwardFromUser, 0); -# By default, RT shows images attached to incoming (and outgoing) ticket updates -# inline. Set this variable to 0 if you'd like to disable that behaviour +=item C<$ShowBccHeader> -Set($ShowTransactionImages, 1); +By default RT hides from the web UI information about blind copies user sent on +reply or comment. +To change this set the following option to true value. -# $HomepageComponents is an arrayref of allowed components on a user's -# customized homepage ("RT at a glance"). +=cut -Set($HomepageComponents, [qw(QuickCreate Quicksearch MyAdminQueues MySupportQueues MyReminders RefreshHomepage)]); +Set($ShowBccHeader, 0); -# @MasonParameters is the list of parameters for the constructor of -# HTML::Mason's Apache or CGI Handler. This is normally only useful -# for debugging, eg. profiling individual components with: -# use MasonX::Profiler; # available on CPAN -# @MasonParameters = (preamble => 'my $p = MasonX::Profiler->new($m, $r);'); +=item C<$DashboardSubject> -@MasonParameters = () unless (@MasonParameters); +Lets you set the subject of dashboards. Arguments are the frequency (Daily, +Weekly, Monthly) of the dashboard and the dashboard's name. [_1] for the name +of the dashboard. -# $DefaultSearchResultFormat is the default format for RT search results -Set ($DefaultSearchResultFormat, qq{ - '__id__/TITLE:#', - '__Subject__/TITLE:Subject', - Status, - QueueName, - OwnerName, - Priority, - '__NEWLINE__', - '', - '__Requestors__', - '__CreatedRelative__', - '__ToldRelative__', - '__LastUpdatedRelative__', - '__TimeLeft__'}); +=cut -# If $SuppressInlineTextFiles is set to a true value, then uploaded -# text files (text-type attachments with file names) are prevented -# from being displayed in-line when viewing a ticket's history. +Set($DashboardSubject, '%s Dashboard: %s'); -Set($SuppressInlineTextFiles, undef); +=back -# If $DontSearchFileAttachments is set to a true value, then uploaded -# files (attachments with file names) are not searched during full-content -# ticket searches. +=head1 GnuPG Configuration -Set($DontSearchFileAttachments, undef); +A full description of the (somewhat extensive) GnuPG integration can be found +by running the command `perldoc L` (or `perldoc + lib/RT/Crypt/GnuPG.pm` from your RT install directory). -# The GD module (which RT uses for graphs) uses a builtin font that doesn't -# have full Unicode support. You can use a particular TrueType font by setting -# $ChartFont to the absolute path of that font. Your GD library must have -# support for TrueType fonts to use this option. +=over 4 -Set($ChartFont, undef); +=item C<%GnuPG> +Set C to 'inline' to use inline encryption and +signatures instead of 'RFC' (GPG/MIME: RFC3156 and RFC1847) format. -# }}} +If you want to allow people to encrypt attachments inside the DB then +set C to true -# {{{ RT UTF-8 Settings +Set C to false if you don't want to reject +emails encrypted for key RT doesn't have and can not decrypt. -# An array that contains languages supported by RT's internationalization -# interface. Defaults to all *.po lexicons; setting it to qw(en ja) will make -# RT bilingual instead of multilingual, but will save some memory. +Set C to false if you don't want to reject letters +with incorrect GnuPG data. -@LexiconLanguages = qw(*) unless (@LexiconLanguages); +=cut -# An array that contains default encodings used to guess which charset -# an attachment uses if not specified. Must be recognized by -# Encode::Guess. +Set( %GnuPG, + Enable => 1, + OutgoingMessagesFormat => 'RFC', # Inline + AllowEncryptDataInDB => 0, -@EmailInputEncodings = qw(utf-8 iso-8859-1 us-ascii) unless (@EmailInputEncodings); + RejectOnMissingPrivateKey => 1, + RejectOnBadData => 1, +); -# The charset for localized email. Must be recognized by Encode. +=item C<%GnuPGOptions> -Set($EmailOutputEncoding , 'utf-8'); +Options of GnuPG program. -# }}} +If you override this in your RT_SiteConfig, you should be sure +to include a homedir setting. -# {{{ RT Date Handling Options (for Time::ParseDate) +NOTE that options with '-' character MUST be quoted. -# Set this to 1 if your local date convention looks like "dd/mm/yy" -# instead of "mm/dd/yy". +=cut -Set($DateDayBeforeMonth , 1); +Set(%GnuPGOptions, + homedir => 'var/data/gpg', -# Should an unspecified day or year in a date refer to a future or a -# past value? For example, should a date of "Tuesday" default to mean -# the date for next Tuesday or last Tuesday? Should the date "March 1" -# default to the date for next March or last March? -# Set to 0 for the next date or 1 for the last date. +# URL of a keyserver +# keyserver => 'hkp://subkeys.pgp.net', -Set($AmbiguousDayInPast , 1); +# enables the automatic retrieving of keys when encrypting +# 'auto-key-locate' => 'keyserver', -# }}} +# enables the automatic retrieving of keys when verifying signatures +# 'auto-key-retrieve' => undef, +); -# {{{ Miscellaneous RT Settings -# You can define new statuses and even reorder existing statuses here. -# WARNING. DO NOT DELETE ANY OF THE DEFAULT STATUSES. If you do, RT -# will break horribly. The statuses you add must be no longer than -# 10 characters. +=back -@ActiveStatus = qw(new open stalled) unless @ActiveStatus; -@InactiveStatus = qw(resolved rejected deleted) unless @InactiveStatus; +=head1 Logging Configuration -# Backward compatability setting. Add/Delete Link used to record one -# transaction and run one scrip. Set this value to 1 if you want -# only one of the link transactions to have scrips run. -Set($LinkTransactionsRun1Scrip , 0); +The default is to log anything except debugging +information to syslog. Check the L POD for +information about how to get things by syslog, mail or anything +else, get debugging info in the log, etc. -# When this feature is enabled an user need ModifyTicket right on both -# tickets to link them together, otherwise he can have right on any of -# two. -Set($StrictLinkACL, 1); +It might generally make sense to send error and higher by email to +some administrator. If you do this, be careful that this email +isn't sent to this RT instance. Mail loops will generate a critical +log message. -# }}} +=over 4 +=item C<$LogToSyslog>, C<$LogToScreen> -# {{{ Development Mode -# -# RT comes with a "Development mode" setting. -# This setting, as a convenience for developers, turns on -# all sorts of development options that you most likely don't want in -# production: -# -# * Turns off Mason's 'static_source' directive. By default, you can't -# edit RT's web ui components on the fly and have RT magically pick up -# your changes. (It's a big performance hit) -# -# * More to come -# +The minimum level error that will be logged to the specific device. +From lowest to highest priority, the levels are: + debug info notice warning error critical alert emergency -Set($DevelMode, '0'); +=cut + +Set($LogToSyslog , 'info'); +Set($LogToScreen , 'info'); + +=item C<$LogToFile>, C<$LogDir>, C<$LogToFileNamed> + +Logging to a standalone file is also possible, but note that the +file should needs to both exist and be writable by all direct users +of the RT API. This generally include the web server, whoever +rt-crontool runs as. Note that as rt-mailgate and the RT CLI go +through the webserver, so their users do not need to have write +permissions to this file. If you expect to have multiple users of +the direct API, Best Practical recommends using syslog instead of +direct file logging. + +=cut + +Set($LogToFile , undef); +Set($LogDir, 'var/log'); +Set($LogToFileNamed , "rt.log"); #log to rt.log + +=item C<$LogStackTraces> + +If set to a log level then logging will include stack traces for +messages with level equal to or greater than specified. + +NOTICE: Stack traces include parameters supplied to functions or +methods. It is possible for stack trace logging to reveal sensitive +information such as passwords or ticket content in your logs. + +=cut + +Set($LogStackTraces, ''); + +=item C<@LogToSyslogConf> + +On Solaris or UnixWare, set to ( socket => 'inet' ). Options here +override any other options RT passes to L. +Other interesting flags include facility and logopt. (See the +L documentation for more information.) (Maybe +ident too, if you have multiple RT installations.) + +=cut + +Set(@LogToSyslogConf, ()); + +=item C<$StatementLog>, + +RT has rudimentary SQL statement logging support if you have +DBIx-SearchBuilder 1.31_1 or higher; simply set C<$StatementLog> to be +the level that you wish SQL statements to be logged at. + +=cut + +Set($StatementLog, undef); + +=back + +=head1 Web Interface Configuration + +=over 4 + +=item C<$WebDefaultStylesheet> + +This determines the default stylesheet the RT web interface will use. +RT ships with several themes by default: + + web2 The totally new, default layout for RT 3.8 + 3.5-default RT 3.5 and 3.6 original layout + 3.4-compat A 3.4 compatibility stylesheet to make RT look + (mostly) like 3.4 + +This value actually specifies a directory in F +from which RT will try to load the file main.css (which should +@import any other files the stylesheet needs). This allows you to +easily and cleanly create your own stylesheets to apply to RT. This +option can be overridden by users in their preferences. + +=cut + +Set($WebDefaultStylesheet, 'web2'); + +=item C<$UsernameFormat> + +This determines how user info is displayed. 'concise' will show one of +either NickName, RealName, Name or EmailAddress, depending on what exists +and whether the user is privileged or not. 'verbose' will show RealName and +EmailAddress. + +=cut + +Set($UsernameFormat, 'concise'); + +=item C<$WebDomain> -# }}} +Domain name of the RT server, eg 'www.example.com'. It should not contain +anything else, but server name. +=cut + +Set( $WebDomain, 'localhost' ); + +=item C<$WebPort> + +If we're running as a superuser, run on port 80 +Otherwise, pick a high port for this user. + +443 is default port for https protocol. + +=cut + +Set($WebPort, 80);# + ($< * 7274) % 32766 + ($< && 1024)); + +=item C<$WebPath> + +If you're putting the web ui somewhere other than at the root of +your server, you should set C<$WebPath> to the path you'll be +serving RT at. + +C<$WebPath> requires a leading / but no trailing /, or it can be blank. + +In most cases, you should leave C<$WebPath> set to '' (an empty value). + +=cut + +Set($WebPath, ""); + +=item C<$WebBaseURL>, C<$WebURL> + +Usually you don't want to set these options. The only obviouse reason is +RT accessible via https protocol on non standard port, eg +'https://rt.example.com:9999'. In all other cases these options are computed +using C<$WebDomain>, C<$WebPort> and C<$WebPath>. + +C<$WebBaseURL> is the scheme, server and port (eg 'http://rt.example.com') +for constructing urls to the web UI. C<$WebBaseURL> doesn't need a trailing /. + +C<$WebURL> is the C<$WebBaseURL>, C<$WebPath> and trailing /, for example: +'http://www.example.com/rt/'. + +=cut + +my $port = RT->Config->Get('WebPort'); +Set($WebBaseURL, + ($port == 443? 'https': 'http') .'://' + . RT->Config->Get('WebDomain') + . ($port != 80 && $port != 443? ":$port" : '') +); + +Set($WebURL, RT->Config->Get('WebBaseURL') . RT->Config->Get('WebPath') . "/"); + +=item C<$WebImagesURL> + +C<$WebImagesURL> points to the base URL where RT can find its images. +Define the directory name to be used for images in rt web +documents. + +=cut + +Set($WebImagesURL, RT->Config->Get('WebPath') . "/NoAuth/images/"); + +=item C<$LogoURL> + +C<$LogoURL> points to the URL of the RT logo displayed in the web UI + +=cut + +Set($LogoURL, RT->Config->Get('WebImagesURL') . "bplogo.gif"); + +=item C<$WebNoAuthRegex> + +What portion of RT's URL space should not require authentication. + +This is mostly for extension and doesn't mean RT will work without +login if you change it. + +=cut + +Set($WebNoAuthRegex, qr{^ (?:/+NoAuth/ | /+REST/\d+\.\d+/NoAuth/) }x ); + +=item C<$SelfServiceRegex> + +What portion of RT's URLspace should be accessible to Unprivileged users +This does not override the redirect from F to +F when Unprivileged users attempt to access +ticked displays + +=cut + +Set($SelfServiceRegex, qr!^(?:/+SelfService/)!x ); + +=item C<$MessageBoxWidth>, C<$MessageBoxHeight> + +For message boxes, set the entry box width, height and what type of +wrapping to use. These options can be overridden by users in their +preferences. + +Default width: 72, height: 15 + +These settings only apply to the non-RichText message box. +See below for Rich Text settings. + +=cut + +Set($MessageBoxWidth, 72); +Set($MessageBoxHeight, 15); + +=item C<$MessageBoxWrap> + +Default wrapping: "HARD" (choices "SOFT", "HARD") + +Wrapping is disabled when using MessageBoxRichText because +of a bad interaction between IE and wrapping with the Rich +Text Editor. + +=cut + +Set($MessageBoxWrap, "HARD"); + +=item C<$MessageBoxRichText> + +Should "rich text" editing be enabled? This option lets your users send html email messages from the web interface. + +=cut + +Set($MessageBoxRichText, 1); + +=item C<$MessageBoxRichTextHeight> + +Height of RichText javascript enabled editing boxes (in pixels) + +=cut + +Set($MessageBoxRichTextHeight, 200); + +=item C<$MessageBoxIncludeSignature> + +Should your user's signatures (from their Preferences page) be included in Comments and Replies + +=cut + +Set($MessageBoxIncludeSignature, 1); + +=item C<$WikiImplicitLinks> + +Support implicit links in WikiText custom fields? A true value +causes InterCapped or ALLCAPS words in WikiText fields to +automatically become links to searches for those words. If used on +RTFM articles, it links to the RTFM article with that name. + +=cut + +Set($WikiImplicitLinks, 0); + +=item C<$TrustHTMLAttachments> + +if C is not defined, we will display them +as text. This prevents malicious HTML and javascript from being +sent in a request (although there is probably more to it than that) + +=cut + +Set($TrustHTMLAttachments, undef); + +=item C<$RedistributeAutoGeneratedMessages> + +Should RT redistribute correspondence that it identifies as +machine generated? A true value will do so; setting this to '0' +will cause no such messages to be redistributed. +You can also use 'privileged' (the default), which will redistribute +only to privileged users. This helps to protect against malformed +bounces and loops caused by autocreated requestors with bogus addresses. + +=cut + +Set($RedistributeAutoGeneratedMessages, 'privileged'); + +=item C<$PreferRichText> + +If C<$PreferRichText> is set to a true value, RT will show HTML/Rich text +messages in preference to their plaintext alternatives. RT "scrubs" the +html to show only a minimal subset of HTML to avoid possible contamination +by cross-site-scripting attacks. + +=cut + +Set($PreferRichText, undef); + +=item C<$WebExternalAuth> + +If C<$WebExternalAuth> is defined, RT will defer to the environment's +REMOTE_USER variable. + +=cut + +Set($WebExternalAuth, undef); + +=item C<$WebExternalAuthContinuous> + +If C<$WebExternalAuthContinuous> is defined, RT will check for the +REMOTE_USER on each access. If you would prefer this to only happen +once (at initial login) set this to a false value. The default setting +will help ensure that if your external auth system deauthenticates a +user, RT notices as soon as possible. + +=cut + +Set($WebExternalAuthContinuous, 1); + +=item C<$WebFallbackToInternalAuth> + +If C<$WebFallbackToInternalAuth> is defined, the user is allowed a chance +of fallback to the login screen, even if REMOTE_USER failed. + +=cut + +Set($WebFallbackToInternalAuth , undef); + +=item C<$WebExternalGecos> + +C<$WebExternalGecos> means to match 'gecos' field as the user identity); +useful with mod_auth_pwcheck and IIS Integrated Windows logon. + +=cut + +Set($WebExternalGecos , undef); + +=item C<$WebExternalAuto> + +C<$WebExternalAuto> will create users under the same name as REMOTE_USER +upon login, if it's missing in the Users table. + +=cut + +Set($WebExternalAuto , undef); + +=item C<$AutoCreate> + +If C<$WebExternalAuto> is true, C<$AutoCreate> will be passed to User's +Create method. Use it to set defaults, such as creating +Unprivileged users with C<{ Privileged => 0 }> +( Must be a hashref of arguments ) + +=cut + +Set($AutoCreate, undef); + +=item C<$WebSessionClass> + +C<$WebSessionClass> is the class you wish to use for managing Sessions. +It defaults to use your SQL database, but if you are using MySQL 3.x and +plans to use non-ascii Queue names, uncomment and add this line to +F will prevent session corruption. + +=cut + +# Set($WebSessionClass , 'Apache::Session::File'); + +=item C<$AutoLogoff> + +By default, RT's user sessions persist until a user closes his or her +browser. With the C<$AutoLogoff> option you can setup session lifetime in +minutes. A user will be logged out if he or she doesn't send any requests +to RT for the defined time. + +=cut + +Set($AutoLogoff, 0); + +=item C<$WebSecureCookies> + +By default, RT's session cookie isn't marked as "secure" Some web browsers +will treat secure cookies more carefully than non-secure ones, being careful +not to write them to disk, only send them over an SSL secured connection +and so on. To enable this behaviour, set C<$WebSecureCookies> to a true value. +NOTE: You probably don't want to turn this on _unless_ users are only connecting +via SSL encrypted HTTP connections. + +=cut + +Set($WebSecureCookies, 0); + +=item C<$WebFlushDbCacheEveryRequest> + +By default, RT clears its database cache after every page view. +This ensures that you've always got the most current information +when working in a multi-process (mod_perl or FastCGI) Environment +Setting C<$WebFlushDbCacheEveryRequest> to '0' will turn this off, +which will speed RT up a bit, at the expense of a tiny bit of data +accuracy. + +=cut + +Set($WebFlushDbCacheEveryRequest, '1'); + + +=item C<$MaxInlineBody> + +C<$MaxInlineBody> is the maximum attachment size that we want to see +inline when viewing a transaction. RT will inline any text if value +is undefined or 0. This option can be overridden by users in their +preferences. + +=cut + +Set($MaxInlineBody, 12000); + +=item C<$DefaultSummaryRows> + +C<$DefaultSummaryRows> is default number of rows displayed in for search +results on the frontpage. + +=cut + +Set($DefaultSummaryRows, 10); + +=item C<$HomePageRefreshInterval> + +C<$HomePageRefreshInterval> is default number of seconds to refresh the RT +home page. Choose from [0, 120, 300, 600, 1200, 3600, 7200]. + +=cut + +Set($HomePageRefreshInterval, 0); + +=item C<$SearchResultsRefreshInterval> + +C<$SearchResultsRefreshInterval> is default number of seconds to refresh +search results in RT. Choose from [0, 120, 300, 600, 1200, 3600, 7200]. + +=cut + +Set($SearchResultsRefreshInterval, 0); + +=item C<$OldestTransactionsFirst> + +By default, RT shows newest transactions at the bottom of the ticket +history page, if you want see them at the top set this to '0'. This +option can be overridden by users in their preferences. + +=cut + +Set($OldestTransactionsFirst, '1'); + +=item C<$ShowTransactionImages> + +By default, RT shows images attached to incoming (and outgoing) ticket updates +inline. Set this variable to 0 if you'd like to disable that behaviour + +=cut + +Set($ShowTransactionImages, 1); + +=item C<$PlainTextPre> + +Normally plaintext attachments are displayed as HTML with line +breaks preserved. This causes space- and tab-based formatting not +to be displayed correctly. By setting $PlainTextPre they'll be +displayed using
 instead so such formatting works, but they'll
+use a monospaced font, no matter what the value of C<$PlainTextMono> is.
+
+=cut
+
+Set($PlainTextPre, 0);
+
+
+=item C<$PlainTextMono> 
+To display plaintext attachments,
+Set C<$PlainTextMono> to 1 to use monospaced font and preserve
+formatting, but unlike PlainTextPre, the text will wrap to fit into the
+UI.
+
+=cut
+
+Set($PlainTextMono, 0);
+
+=item C<$ShowUnreadMessageNotifications>
+
+By default, RT will prompt users when there are new, unread messages on
+tickets they are viewing.
+
+Set C<$ShowUnreadMessageNotifications> to a false value to disable this feature.
+
+=cut
+
+Set($ShowUnreadMessageNotifications, 1);
+
+
+=item C<$HomepageComponents>
+
+C<$HomepageComponents> is an arrayref of allowed components on a user's
+customized homepage ("RT at a glance").
+
+=cut
+
+Set($HomepageComponents, [qw(QuickCreate Quicksearch MyAdminQueues MySupportQueues MyReminders RefreshHomepage Dashboards)]);
+
+=item C<@MasonParameters>
+
+C<@MasonParameters> is the list of parameters for the constructor of
+HTML::Mason's Apache or CGI Handler.  This is normally only useful
+for debugging, eg. profiling individual components with:
+
+    use MasonX::Profiler; # available on CPAN
+    Set(@MasonParameters, (preamble => 'my $p = MasonX::Profiler->new($m, $r);'));
+
+=cut
+
+Set(@MasonParameters, ());
+
+=item C<$DefaultSearchResultFormat>
+
+C<$DefaultSearchResultFormat> is the default format for RT search results
+
+=cut
+
+Set ($DefaultSearchResultFormat, qq{
+   '__id__/TITLE:#',
+   '__Subject__/TITLE:Subject',
+   Status,
+   QueueName, 
+   OwnerName, 
+   Priority, 
+   '__NEWLINE__',
+   '', 
+   '__Requestors__',
+   '__CreatedRelative__',
+   '__ToldRelative__',
+   '__LastUpdatedRelative__',
+   '__TimeLeft__'});
+
+=item C<$DefaultSelfServiceSearchResultFormat>
+
+C<$DefaultSelfServiceSearchResultFormat> is the default format of searches displayed in the 
+SelfService interface.
+
+=cut
+
+Set($DefaultSelfServiceSearchResultFormat, qq{
+   '__id__/TITLE:#',
+   '__Subject__/TITLE:Subject',
+   Status,
+   Requestors,
+   OwnerName});
+
+=item C<$SuppressInlineTextFiles>
+
+If C<$SuppressInlineTextFiles> is set to a true value, then uploaded
+text files (text-type attachments with file names) are prevented
+from being displayed in-line when viewing a ticket's history.
+
+=cut
+
+Set($SuppressInlineTextFiles, undef);
+
+=item C<$DontSearchFileAttachments>
+
+If C<$DontSearchFileAttachments> is set to a true value, then uploaded
+files (attachments with file names) are not searched during full-content
+ticket searches.
+
+=cut
+
+Set($DontSearchFileAttachments, undef);
+
+=item C<$ChartFont>
+
+The L module (which RT uses for graphs) uses a builtin font that doesn't
+have full Unicode support. You can use a particular TrueType font by setting
+$ChartFont to the absolute path of that font. Your GD library must have
+support for TrueType fonts to use this option.
+
+=cut
+
+Set($ChartFont, undef);
+
+
+=item C<@Active_MakeClicky>
+
+MakeClicky detects various formats of data in headers and email
+messages, and extends them with supporting links.  By default, RT
+provides two formats:
+
+* 'httpurl': detects http:// and https:// URLs and adds '[Open URL]'
+  link after the URL.
+
+* 'httpurl_overwrite': also detects URLs as 'httpurl' format, but
+  replace URL with link and *adds spaces* into text if it's longer
+  then 30 chars. This allow browser to wrap long URLs and avoid
+  horizontal scrolling.
+
+See F for documentation on how to add your own.
+
+=cut
+
+Set(@Active_MakeClicky, qw());
+
+=item C<$DefaultQueue>
+
+Use this to select the default queue name that will be used for creating new
+tickets. You may use either the queue's name or its ID. This only affects the
+queue selection boxes on the web interface.
+
+=cut
+
+#Set($DefaultQueue, 'General');
+
+=item C<$DefaultTimeUnitsToHours>
+
+Use this to set the default units for time entry to hours instead of minutes.
+
+=cut
+
+Set($DefaultTimeUnitsToHours, 0);
+
+=back
+
+=head1 L (rt-server) Configuration
+
+=over 4
+
+=item C<$StandaloneMinServers>, C<$StandaloneMaxServers>
+
+The absolute minimum and maximum number of servers that will be created to
+handle requests. Having multiple servers means that serving a slow page will
+affect other users less.
+
+=cut
+
+Set($StandaloneMinServers, 1);
+Set($StandaloneMaxServers, 1);
+
+=item C<$StandaloneMinSpareServers>, C<$StandaloneMaxSpareServers>
+
+These next two options can be used to scale up and down the number of servers
+to adjust to load. These two options will respect the C<$StandaloneMinServers
+> and C<$StandaloneMaxServers options>.
+
+=cut
+
+Set($StandaloneMinSpareServers, 0);
+Set($StandaloneMaxSpareServers, 0);
+
+=item C<$StandaloneMaxRequests>
+
+This sets the absolute maximum number of requests a single server will serve.
+Setting this would be useful if, for example, memory usage slowly crawls up
+every hit.
+
+=cut
+
+#Set($StandaloneMaxRequests, 50);
+
+=item C<%NetServerOptions>
+
+C<%NetServerOptions> is a hash of additional options to use for
+L. For example, you could set
+reverse_lookups to get the hostnames for all users with:
+
+C 1));>
+
+=cut
+
+Set(%NetServerOptions, ());
+
+=back
+
+
+=head1 UTF-8 Configuration
+
+=over 4
+
+=item C<@LexiconLanguages>
+
+An array that contains languages supported by RT's internationalization
+interface.  Defaults to all *.po lexicons; setting it to C will make
+RT bilingual instead of multilingual, but will save some memory.
+
+=cut
+
+Set(@LexiconLanguages, qw(*));
+
+=item C<@EmailInputEncodings>
+
+An array that contains default encodings used to guess which charset
+an attachment uses if not specified.  Must be recognized by
+L.
+
+=cut
+
+Set(@EmailInputEncodings, qw(utf-8 iso-8859-1 us-ascii));
+
+=item C<$EmailOutputEncoding>
+
+The charset for localized email.  Must be recognized by Encode.
+
+=cut
+
+Set($EmailOutputEncoding, 'utf-8');
+
+
+=back
+
+=head1 Date Handling Configuration
+
+=over 4
+
+=item C<$DateTimeFormat>
+
+You can choose date and time format.  See "Output formatters"
+section in perldoc F for more options.  This option can
+be overridden by users in their preferences.
+Some examples:
+
+C
+C 'ISO', Seconds => 0 });>
+C
+C 'RFC2822', Seconds => 0, DayOfWeek => 0 });>
+
+=cut
+
+Set($DateTimeFormat, 'DefaultFormat');
+
+# Next two options are for Time::ParseDate
+
+=item C<$DateDayBeforeMonth>
+
+Set this to 1 if your local date convention looks like "dd/mm/yy" instead of
+"mm/dd/yy". Used only for parsing, not for displaying dates.
+
+=cut
+
+Set($DateDayBeforeMonth , 1);
+
+=item C<$AmbiguousDayInPast>, C<$AmbiguousDayInFuture>
+
+Should an unspecified day or year in a date refer to a future or a
+past value? For example, should a date of "Tuesday" default to mean
+the date for next Tuesday or last Tuesday? Should the date "March 1"
+default to the date for next March or last March?
+
+Set $ for the last date, or $<$AmbiguousDayInFuture> for the
+next date.
+
+The default is usually good.
+
+=cut
+
+Set($AmbiguousDayInPast, 0);
+Set($AmbiguousDayInFuture, 0);
+
+=back
+
+=head1 Approval Configuration
+
+Configration for the approvl system
+
+=over 4
+
+=item C<$ApprovalRejectionNotes>
+
+Should rejection notes be sent to the requestors?  The default is true.
+
+=cut
+
+Set($ApprovalRejectionNotes, 1);
+
+
+=back
+
+=head1 Miscellaneous Configuration
+
+=over 4
+
+=item C<@ActiveStatus>, C<@InactiveStatus>
+
+You can define new statuses and even reorder existing statuses here.
+WARNING. DO NOT DELETE ANY OF THE DEFAULT STATUSES. If you do, RT
+will break horribly. The statuses you add must be no longer than
+10 characters.
+
+=cut
+
+Set(@ActiveStatus, qw(new open stalled));
+Set(@InactiveStatus, qw(resolved rejected deleted));
+
+=item C<$LinkTransactionsRun1Scrip>
+
+RT-3.4 backward compatibility setting. Add/Delete Link used to record one
+transaction and run one scrip. Set this value to 1 if you want
+only one of the link transactions to have scrips run.
+
+=cut
+
+Set($LinkTransactionsRun1Scrip, 0);
+
+=item C<$StrictLinkACL>
+
+When this feature is enabled a user needs I rights on both
+tickets to link them together, otherwise he can have rights on either of
+them.
+
+=cut
+
+Set($StrictLinkACL, 1);
+
+=item C<$PreviewScripMessages>
+
+Set C<$PreviewScripMessages> to 1 if the scrips preview on the ticket
+reply page should include the content of the messages to be sent.
+
+=cut
+
+Set($PreviewScripMessages, 0);
+
+=item C<$UseTransactionBatch>
+
+Set C<$UseTransactionBatch> to 1 to execute transactions in batches,
+such that a resolve and comment (for example) would happen
+simultaneously, instead of as two transactions, unaware of each
+others' existence.
+
+=cut
+
+Set($UseTransactionBatch, 1);
+
+=item C<@CustomFieldValuesSources>
+
+Set C<@CustomFieldValuesSources> to a list of class names which extend
+L.  This can be used to pull lists of
+custom field values from external sources at runtime.
+
+=cut
+
+Set(@CustomFieldValuesSources, ());
+
+=item C<$CanonicalizeRedirectURLs>
+
+Set C<$CanonicalizeRedirectURLs> to 1 to use $C when redirecting rather
+than the one we get from C<%ENV>.
+
+If you use RT behind a reverse proxy, you almost certainly want to
+enable this option.
+
+=cut
+
+Set($CanonicalizeRedirectURLs, 0);
+=item C<$EnableReminders>
+
+Hide links/portlets related to Reminders by setting this to 0
+
+=cut
+
+Set($EnableReminders,1);
+
+
+=item C<@Plugins>
+
+Set C<@Plugins> to a list of external RT plugins that should be enabled (those
+plugins have to be previously downloaded and installed).
+Example:
+
+C
+
+=cut
+
+Set(@Plugins, ());
+
+=back
+
+=head1 Development Configuration
+
+=over 4
+
+=item C<$DevelMode>
+
+RT comes with a "Development mode" setting. 
+This setting, as a convenience for developers, turns on 
+all sorts of development options that you most likely don't want in 
+production:
+
+* Turns off Mason's 'static_source' directive. By default, you can't 
+  edit RT's web ui components on the fly and have RT magically pick up
+  your changes. (It's a big performance hit)
+
+ * More to come
+
+=cut
+
+Set($DevelMode, '0');
+
+
+=back
+
+=head1 Deprecated Options
+
+=over 4
+
+=item C<$AlwaysUseBase64>
+
+Encode blobs as base64 in DB (?)
+
+=item C<$TicketBaseURI>
+
+Base URI to tickets in this system; used when loading (?)
+
+=item C<$UseCodeTickets>
+
+This option is exists for backwards compatibility.  Don't use it.
+
+=back
+
+=cut
 
 1;
diff --git a/rt/etc/RT_Config.pm.in b/rt/etc/RT_Config.pm.in
index 18d691965..5d28e719c 100644
--- a/rt/etc/RT_Config.pm.in
+++ b/rt/etc/RT_Config.pm.in
@@ -1,7 +1,3 @@
-#
-# WARNING: NEVER EDIT RT_Config.pm. Instead, copy any sections you want to change to RT_SiteConfig.pm
-# and edit them there.
-#
 
 package RT;
 
@@ -15,597 +11,1613 @@ use RT::Config;
 
 =cut
 
-# {{{ Base Configuration
+=head1 WARNING
 
-# $rtname is the string that RT will look for in mail messages to
-# figure out what ticket a new piece of mail belongs to
+NEVER EDIT RT_Config.pm.
 
-# Your domain name is recommended, so as not to pollute the namespace.
-# once you start using a given tag, you should probably never change it.
-# (otherwise, mail for existing tickets won't get put in the right place
+Instead, copy any sections you want to change to F and edit them there.
+
+=cut
+
+=head1 Base Configuration
+
+=over 4
+
+=item C<$rtname>
+
+C<$rtname> is the string that RT will look for in mail messages to
+figure out what ticket a new piece of mail belongs to.
+
+Your domain name is recommended, so as not to pollute the namespace.
+once you start using a given tag, you should probably never change it.
+(otherwise, mail for existing tickets won't get put in the right place)
+
+=cut
 
 Set($rtname , "example.com");
 
 
-# This regexp controls what subject tags RT recognizes as its own.
-# If you're not dealing with historical $rtname values, you'll likely
-# never have to enable this feature.
-#
-# Be VERY CAREFUL with it. Note that it overrides $rtname for subject
-# token matching and that you should use only "non-capturing" parenthesis
-# grouping. For example:
-#
-# 	Set($EmailSubjectTagRegex, qr/(?:example.com|example.org)/i );
-#
-# and NOT
-# 
-# 	Set($EmailSubjectTagRegex, qr/(example.com|example.org)/i );
-#
-# This setting would make RT behave exactly as it does without the 
-# setting enabled.
-#
-# Set($EmailSubjectTagRegex, qr/\Q$rtname\E/i );
+=item C<$EmailSubjectTagRegex>
+
+This regexp controls what subject tags RT recognizes as its own.
+If you're not dealing with historical C<$rtname> values, you'll likely
+never have to enable this feature.
+
+Be VERY CAREFUL with it. Note that it overrides C<$rtname> for subject
+token matching and that you should use only "non-capturing" parenthesis
+grouping. For example:
+
+C
+
+and NOT
+
+C
+
+This setting would make RT behave exactly as it does without the 
+setting enabled.
+
+=cut
+
+#Set($EmailSubjectTagRegex, qr/\Q$rtname\E/i );
+
+
 
+=item C<$Organization>
 
+You should set this to your organization's DNS domain. For example,
+I or I. It's used by the linking interface to
+guarantee that ticket URIs are unique and easy to construct.
 
-# You should set this to your organization's DNS domain. For example,
-# fsck.com or asylum.arkham.ma.us. It's used by the linking interface to
-# guarantee that ticket URIs are unique and easy to construct.
+=cut
 
 Set($Organization , "example.com");
 
-# $user_passwd_min defines the minimum length for user passwords. Setting
-# it to 0 disables this check
+=item C<$MinimumPasswordLength>
+
+C<$MinimumPasswordLength> defines the minimum length for user
+passwords. Setting it to 0 disables this check.
+
+=cut
+
 Set($MinimumPasswordLength , "5");
 
-# $Timezone is used to convert times entered by users into GMT and back again
-# It should be set to a timezone recognized by your local unix box.
+=item C<$Timezone>
+
+C<$Timezone> is used to convert times entered by users into GMT and back again
+It should be set to a timezone recognized by your local unix box.
+
+=cut
+
 Set($Timezone , 'US/Eastern');
 
-# }}}
+=back
+
+=head1 Database Configuration
+
+=over 4
+
+=item C<$DatabaseType>
 
-# {{{ Database Configuration
+Database driver being used; case matters.
 
-# Database driver beeing used. Case matters
-# Valid types are "mysql", "Oracle" and "Pg"
+Valid types are "mysql", "Oracle" and "Pg"
+
+=cut
 
 Set($DatabaseType , '@DB_TYPE@');
 
-# The domain name of your database server
-# If you're running mysql and it's on localhost,
-# leave it blank for enhanced performance
+=item C<$DatabaseHost>, C<$DatabaseRTHost>
+
+The domain name of your database server.
+
+If you're running mysql and it's on localhost,
+leave it blank for enhanced performance
+
+=cut
+
 Set($DatabaseHost   , '@DB_HOST@');
 Set($DatabaseRTHost , '@DB_RT_HOST@');
 
-# The port that your database server is running on.  Ignored unless it's
-# a positive integer. It's usually safe to leave this blank
+=item C<$DatabasePort>
+
+The port that your database server is running on.  Ignored unless it's
+a positive integer. It's usually safe to leave this blank
+
+=cut
+
 Set($DatabasePort , '@DB_PORT@');
 
-#The name of the database user (inside the database)
+=item C<$DatabaseUser>
+
+The name of the database user (inside the database)
+
+=cut
+
 Set($DatabaseUser , '@DB_RT_USER@');
 
-# Password the DatabaseUser should use to access the database
+=item C<$DatabasePassword>
+
+Password the C<$DatabaseUser> should use to access the database
+
+=cut
+
 Set($DatabasePassword , '@DB_RT_PASS@');
 
-# The name of the RT's database on your database server
+=item C<$DatabaseName>
+
+The name of the RT's database on your database server. For Oracle
+it's SID, DB objects are created in L<$DatabaseUser>'s schema.
+
+=cut
+
 Set($DatabaseName , '@DB_DATABASE@');
 
-# If you're using Postgres and have compiled in SSL support,
-# set DatabaseRequireSSL to 1 to turn on SSL communication
+=item C<$DatabaseRequireSSL>
+
+If you're using Postgres and have compiled in SSL support,
+set C<$DatabaseRequireSSL> to 1 to turn on SSL communication
+
+=cut
+
 Set($DatabaseRequireSSL , undef);
 
-# }}}
+=item C<$UseSQLForACLChecks>
+
+In RT for ages ACL are checked after search what in some situtations
+result in empty search pages and wrong count of tickets.
+
+Set C<$UseSQLForACLChecks> to 1 to use SQL and get rid of these problems.
+
+However, this option is beta. In some cases it result in performance
+improvements, but some setups can not handle it.
+
+=cut
+
+Set($UseSQLForACLChecks, undef);
+
+=back
+
+=head1 Incoming Mail Gateway Configuration
+
+=over 4
 
-# {{{ Incoming mail gateway configuration
+=item C<$OwnerEmail>
 
-# OwnerEmail is the address of a human who manages RT. RT will send
-# errors generated by the mail gateway to this address.  This address
-# should _not_ be an address that's managed by your RT instance.
+C<$OwnerEmail> is the address of a human who manages RT. RT will send
+errors generated by the mail gateway to this address.  This address
+should _not_ be an address that's managed by your RT instance.
+
+=cut
 
 Set($OwnerEmail , 'root');
 
-# If $LoopsToRTOwner is defined, RT will send mail that it believes
-# might be a loop to $RT::OwnerEmail
+=item C<$LoopsToRTOwner>
+
+If C<$LoopsToRTOwner> is defined, RT will send mail that it believes
+might be a loop to C<$OwnerEmail>
+
+=cut
 
 Set($LoopsToRTOwner , 1);
 
-# If $StoreLoops is defined, RT will record messages that it believes
-# to be part of mail loops.
-# As it does this, it will try to be careful not to send mail to the
-# sender of these messages
+=item C<$StoreLoops>
+
+If C<$StoreLoops> is defined, RT will record messages that it believes
+to be part of mail loops.
+
+As it does this, it will try to be careful not to send mail to the
+sender of these messages
+
+=cut
 
 Set($StoreLoops , undef);
 
-# $MaxAttachmentSize sets the maximum size (in bytes) of attachments stored
-# in the database.
+=item C<$MaxAttachmentSize>
+
+C<$MaxAttachmentSize> sets the maximum size (in bytes) of attachments stored
+in the database.
+
+For mysql and oracle, we set this size at 10 megabytes.
+If you're running a postgres version earlier than 7.1, you will need
+to drop this to 8192. (8k)
+
+=cut
 
-# For mysql and oracle, we set this size at 10 megabytes.
-# If you're running a postgres version earlier than 7.1, you will need
-# to drop this to 8192. (8k)
 
 Set($MaxAttachmentSize , 10000000);
 
-# $TruncateLongAttachments: if this is set to a non-undef value,
-# RT will truncate attachments longer than MaxAttachmentSize.
+=item C<$TruncateLongAttachments>
+
+C<$TruncateLongAttachments>: if this is set to a non-undef value,
+RT will truncate attachments longer than C<$MaxAttachmentSize>.
+
+=cut
 
 Set($TruncateLongAttachments , undef);
 
-# $DropLongAttachments: if this is set to a non-undef value,
-# RT will silently drop attachments longer than MaxAttachmentSize.
+=item C<$DropLongAttachments>
+
+C<$DropLongAttachments>: if this is set to a non-undef value,
+RT will silently drop attachments longer than C.
+
+=cut
 
 Set($DropLongAttachments , undef);
 
-# If $ParseNewMessageForTicketCcs is true, RT will attempt to divine
-# Ticket 'Cc' watchers from the To and Cc lines of incoming messages
-# Be forewarned that if you have _any_ addresses which forward mail to
-# RT automatically and you enable this option without modifying
-# "RTAddressRegexp" below, you will get yourself into a heap of trouble.
+=item C<$ParseNewMessageForTicketCcs>
+
+If C<$ParseNewMessageForTicketCcs> is true, RT will attempt to divine
+Ticket 'Cc' watchers from the To and Cc lines of incoming messages
+Be forewarned that if you have _any_ addresses which forward mail to
+RT automatically and you enable this option without modifying
+C<$RTAddressRegexp> below, you will get yourself into a heap of trouble.
+
+=cut
 
 Set($ParseNewMessageForTicketCcs , undef);
 
-# RTAddressRegexp is used to make sure RT doesn't add itself as a ticket CC if
-# the setting above is enabled.
+=item C<$RTAddressRegexp> 
+
+C<$RTAddressRegexp> is used to make sure RT doesn't add itself as a ticket CC if
+the setting above is enabled.  It is important that you set this to a 
+regular expression that matches all addresses used by your RT.  This lets RT
+avoid sending mail to itself.  It will also hide RT addresses from the list of 
+"One-time Cc" and Bcc lists on ticket reply.
+
+=cut
 
 Set($RTAddressRegexp , '^rt\@example.com$');
 
-# RT provides functionality which allows the system to rewrite
-# incoming email addresses.  In its simplest form,
-# you can substitute the value in CanonicalizeEmailAddressReplace
-# for the value in CanonicalizeEmailAddressMatch
-# (These values are passed to the CanonicalizeEmailAddress subroutine in RT/User.pm)
-# By default, that routine performs a s/$Match/$Replace/gi on any address passed to it
+=item C<$CanonicalizeEmailAddressMatch>, C<$CanonicalizeEmailAddressReplace>
+
+RT provides functionality which allows the system to rewrite
+incoming email addresses.  In its simplest form,
+you can substitute the value in $
+for the value in $
+(These values are passed to the $ subroutine in
+ F)
+
+By default, that routine performs a C on any address
+passed to it.
+
+=cut
 
 #Set($CanonicalizeEmailAddressMatch , '@subdomain\.example\.com$');
 #Set($CanonicalizeEmailAddressReplace , '@example.com');
 
-# set this to true and the create new user page will use the values that you
-# enter in the form but use the function CanonicalizeUserInfo in User_Local.pm
-Set($CanonicalizeOnCreate , 0);
-
-# If $SenderMustExistInExternalDatabase is true, RT will refuse to
-# create non-privileged accounts for unknown users if you are using
-# the "LookupSenderInExternalDatabase" option.
-# Instead, an error message will be mailed and RT will forward the
-# message to $RTOwner.
-#
-# If you are not using $LookupSenderInExternalDatabase, this option
-# has no effect.
-#
-# If you define an AutoRejectRequest template, RT will use this
-# template for the rejection message.
+=item C<$CanonicalizeEmailAddressMatch>
+
+Set this to true and the create new user page will use the values that you
+enter in the form but use the function CanonicalizeUserInfo in
+F
+
+=cut
+
+Set($CanonicalizeOnCreate, 0);
+
+=item C<$SenderMustExistInExternalDatabase>
+
+If C<$SenderMustExistInExternalDatabase> is true, RT will refuse to
+create non-privileged accounts for unknown users if you are using
+the C<$LookupSenderInExternalDatabase> option.
+Instead, an error message will be mailed and RT will forward the
+message to C<$RTOwner>.
+
+If you are not using C<$LookupSenderInExternalDatabase>, this option
+has no effect.
+
+If you define an AutoRejectRequest template, RT will use this
+template for the rejection message.
+
+=cut
 
 Set($SenderMustExistInExternalDatabase , undef);
 
-# }}}
+=item C<$ValidateUserEmailAddresses>
+
+If C<$ValidateUserEmailAddresses> is true, RT will refuse to create users with
+an invalid email address (as specified in RFC 2822) or with an email address
+made of multiple email adresses.
+
+=cut
+
+Set($ValidateUserEmailAddresses, undef);
+
+=item C<@MailPlugins>
 
-# {{{ Outgoing mail configuration
+C<@MailPlugins> is a list of auth plugins for L
+to use; see L
 
-# RT is designed such that any mail which already has a ticket-id associated
-# with it will get to the right place automatically.
+=cut
+
+=item C<$UnsafeEmailCommands>
+
+C<$UnsafeEmailCommands>, if set to true, enables 'take' and 'resolve'
+as possible actions via the mail gateway.  As its name implies, this
+is very unsafe, as it allows email with a forged sender to possibly
+resolve arbitrary tickets!
+
+=cut
+
+=item C<$ExtractSubjectTagMatch>, C<$ExtractSubjectTagNoMatch>
+
+The default "extract remote tracking tags" scrip settings; these
+detect when your RT is talking to another RT, and adjusts the
+subject accordingly.
+
+=cut
+
+Set($ExtractSubjectTagMatch, qr/\[.+? #\d+\]/);
+Set($ExtractSubjectTagNoMatch, ( ${RT::EmailSubjectTagRegex}
+       ? qr/\[(?:${RT::EmailSubjectTagRegex}) #\d+\]/
+       : qr/\[\Q$RT::rtname\E #\d+\]/));
+
+=back
+
+=head1 Outgoing Mail Configuration
 
-# $CorrespondAddress and $CommentAddress are the default addresses
-# that will be listed in From: and Reply-To: headers of correspondence
-# and comment mail tracked by RT, unless overridden by a queue-specific
-# address.
+=over 4
 
-Set($CorrespondAddress , 'RT_CorrespondAddressNotSet');
+=item C<$MailCommand>
 
-Set($CommentAddress , 'RT_CommentAddressNotSet');
+C<$MailCommand> defines which method RT will use to try to send mail.
+We know that 'sendmailpipe' works fairly well.  If 'sendmailpipe'
+doesn't work well for you, try 'sendmail'.  Other options are 'smtp'
+or 'qmail'.
 
-#Sendmail Configuration
+Note that you should remove the '-t' from C<$SendmailArguments>
+if you use 'sendmail' rather than 'sendmailpipe'
 
-# $MailCommand defines which method RT will use to try to send mail
-# We know that 'sendmailpipe' works fairly well.
-# If 'sendmailpipe' doesn't work well for you, try 'sendmail'
-#
-# Note that you should remove the '-t' from $SendmailArguments
-# if you use 'sendmail' rather than 'sendmailpipe'
+=cut
 
 Set($MailCommand , 'sendmailpipe');
 
-# $SendmailArguments defines what flags to pass to $Sendmail
-# assuming you picked 'sendmail' or 'sendmailpipe' as the $MailCommand above.
-# If you picked 'sendmailpipe', you MUST add a -t flag to $SendmailArguments
+=item C<$SetOutgoingMailFrom>
+
+C<$SetOutgoingMailFrom> tells RT to set the sender envelope with the correspond
+mail address of the ticket's queue.
+
+Warning: If you use this setting, bounced mails will appear to be incoming
+mail to the system, thus creating new tickets.
+
+=cut
+
+Set($SetOutgoingMailFrom, 0);
+
+=item C<$OverrideOutgoingMailFrom>
+
+C<$OverrideOutgoingMailFrom> is used for overwriting the Correspond
+address of the queue. The option is a hash reference of queue name to
+email address.
+
+If there is no ticket involved, then the value of the C key will be
+used.
+
+=cut
+
+Set($OverrideOutgoingMailFrom, {
+#    'Default' => 'admin@rt.example.com',
+#    'General' => 'general@rt.example.com',
+});
+
+=back
+
+=head1 Sendmail Configuration
+
+These options only take effect if C<$MailCommand> is 'sendmail' or
+'sendmailpipe'
+
+=over 4
+
+=item C<$SendmailArguments> 
+
+C<$SendmailArguments> defines what flags to pass to C<$SendmailPath>
+If you picked 'sendmailpipe', you MUST add a -t flag to C<$SendmailArguments>
+These options are good for most sendmail wrappers and workalikes
+
+These arguments are good for sendmail brand sendmail 8 and newer
+C
+
+=cut
 
-# These options are good for most sendmail wrappers and workalikes
 Set($SendmailArguments , "-oi -t");
 
-# $SendmailBounceArguments defines what flags to pass to $Sendmail
-# assuming RT needs to send an error (ie. bounce).
+
+=item C<$SendmailBounceArguments>
+
+C<$SendmailBounceArguments> defines what flags to pass to C<$Sendmail>
+assuming RT needs to send an error (ie. bounce).
+
+=cut
 
 Set($SendmailBounceArguments , '-f "<>"');
 
-# These arguments are good for sendmail brand sendmail 8 and newer
-#Set($SendmailArguments,"-oi -t -ODeliveryMode=b -OErrorMode=m");
+=item C<$SendmailPath>
+
+If you selected 'sendmailpipe' above, you MUST specify the path to
+your sendmail binary in C<$SendmailPath>.
+
+=cut
 
-# If you selected 'sendmailpipe' above, you MUST specify the path
-# to your sendmail binary in $SendmailPath.
-# !! If you did not # select 'sendmailpipe' above, this has no effect!!
 Set($SendmailPath , "/usr/sbin/sendmail");
 
-# By default, RT sets the outgoing mail's "From:" header to
-# "SenderName via RT".  Setting this option to 0 disables it.
 
-Set($UseFriendlyFromLine , 1);
+=back
 
-# sprintf() format of the friendly 'From:' header; its arguments
-# are SenderName and SenderEmailAddress.
-Set($FriendlyFromLineFormat , "\"%s via RT\" <%s>");
+=head1 SMTP Configuration
 
-# RT can optionally set a "Friendly" 'To:' header when sending messages to
-# Ccs or AdminCcs (rather than having a blank 'To:' header.
+These options only take effect if C<$MailCommand> is 'smtp'
 
-# This feature DOES NOT WORK WITH SENDMAIL[tm] BRAND SENDMAIL
-# If you are using sendmail, rather than postfix, qmail, exim or some other MTA,
-# you _must_ disable this option.
+=over 4
 
-Set($UseFriendlyToLine , 0);
+=item C<$SMTPServer>
 
-# sprintf() format of the friendly 'From:' header; its arguments
-# are WatcherType and TicketId.
-Set($FriendlyToLineFormat, "\"%s of $RT::rtname Ticket #%s\":;");
+C<$SMTPServer> should be set to the hostname of the SMTP server to use
 
-# By default, RT doesn't notify the person who performs an update, as they
-# already know what they've done. If you'd like to change this behaviour,
-# Set $NotifyActor to 1
+=cut
 
-Set($NotifyActor, 0);
+Set($SMTPServer, undef);
 
-# By default, RT records each message it sends out to its own internal database.# To change this behaviour, set $RecordOutgoingEmail to 0 
+=item C<$SMTPFrom>
 
-Set($RecordOutgoingEmail, 1);
+C<$SMTPFrom> should be set to the 'From' address to use, if not the
+email's 'From'
 
-# VERP support (http://cr.yp.to/proto/verp.txt)
-# uncomment the following two directives to generate envelope senders
-# of the form ${VERPPrefix}${originaladdress}@${VERPDomain}
-# (i.e. rt-jesse=fsck.com@rt.example.com ) This currently only works
-# with sendmail and sendmailppie.
-# Set($VERPPrefix, 'rt-');
-# Set($VERPDomain, $RT::Organization);
+=cut
 
-# }}}
+Set($SMTPFrom, undef);
 
-# {{{ Logging
+=item C<$SMTPDebug> 
 
-# Logging.  The default is to log anything except debugging
-# information to syslog.  Check the Log::Dispatch POD for
-# information about how to get things by syslog, mail or anything
-# else, get debugging info in the log, etc.
+C<$SMTPDebug> should be set to true to debug SMTP mail sending
 
-#  It might generally make
-# sense to send error and higher by email to some administrator.
-# If you do this, be careful that this email isn't sent to this RT instance.
+=cut
 
-# the minimum level error that will be logged to the specific device.
-# levels from lowest to highest:
-#  debug info notice warning error critical alert emergency
+Set($SMTPDebug, 0);
 
-#  Mail loops will generate a critical log message.
-Set($LogToSyslog    , 'debug');
-Set($LogToScreen    , 'error');
-Set($LogToFile      , undef);
-Set($LogDir, '@RT_LOG_PATH@');
-Set($LogToFileNamed , "rt.log");    #log to rt.log
+=back
 
-# If true generates stack traces to file log or screen
-# never generates traces to syslog
+=head1 Other Mailer Configuration
 
-Set($LogStackTraces , 0);
+=over 4
 
-# On Solaris or UnixWare, set to ( socket => 'inet' ).  Options here
-# override any other options RT passes to Log::Dispatch::Syslog.
-# Other interesting flags include facility and logopt.  (See the
-# Log::Dispatch::Syslog documentation for more information.)  (Maybe
-# ident too, if you have multiple RT installations.)
+=item C<@MailParams>
 
-@LogToSyslogConf = () unless (@LogToSyslogConf);
+C<@MailParams> defines a list of options passed to $MailCommand if it
+is not 'sendmailpipe', 'sendmail', or 'smtp'
 
-# RT has rudimentary SQL statement logging support if you have
-# DBIx-SearchBuilder 1.31_1 or higher; simply set $StatementLog to be
-# the level that you wish SQL statements to be logged at.
-Set($StatementLog, undef);
+=cut
 
-# }}}
+Set(@MailParams, ());
 
-# {{{ Web interface configuration
+=item C<$CorrespondAddress>, C<$CommentAddress>
 
-# This determines the default stylesheet the RT web interface will use.
-# RT ships with two valid values by default:
-#
-#   3.5-default     The totally new, default layout for RT 3.5
-#   3.4-compat      A 3.4 compatibility stylesheet to make RT 3.5 look
-#                   (mostly) like 3.4
-#
-# This value actually specifies a directory in share/html/NoAuth/css/
-# from which RT will try to load the file main.css (which should
-# @import any other files the stylesheet needs).  This allows you to
-# easily and cleanly create your own stylesheets to apply to RT.
+RT is designed such that any mail which already has a ticket-id associated
+with it will get to the right place automatically.
 
-Set($WebDefaultStylesheet, '3.5-default');
+C<$CorrespondAddress> and C<$CommentAddress> are the default addresses
+that will be listed in From: and Reply-To: headers of correspondence
+and comment mail tracked by RT, unless overridden by a queue-specific
+address.
 
-# Define the directory name to be used for images in rt web
-# documents.
+=cut
 
-# If you're putting the web ui somewhere other than at the root of
-# your server, you should set $WebPath to the path you'll be 
-# serving RT at.
-# $WebPath requires a leading / but no trailing /.
-#
-# In most cases, you should leave $WebPath set to '' (an empty value).
+Set($CorrespondAddress , '');
 
-Set($WebPath , "");
+Set($CommentAddress , '');
 
-# If we're running as a superuser, run on port 80
-# Otherwise, pick a high port for this user.
+=item C<$DashboardAddress>
 
-Set($WebPort , 80);# + ($< * 7274) % 32766 + ($< && 1024));
+The email address from which RT will send dashboards. If none is set, then
+C<$OwnerEmail> will be used.
 
-# This is the Scheme, server and port for constructing urls to webrt
-# $WebBaseURL doesn't need a trailing /
+=cut
 
-Set($WebBaseURL , "http://localhost:$WebPort");
+Set($DashboardAddress, '');
 
-Set($WebURL , $WebBaseURL . $WebPath . "/");
+=item C<$UseFriendlyFromLine>
 
-# $WebImagesURL points to the base URL where RT can find its images.
+By default, RT sets the outgoing mail's "From:" header to
+"SenderName via RT".  Setting C<$UseFriendlyFromLine> to 0 disables it.
 
-Set($WebImagesURL , $WebPath . "/NoAuth/images/");
+=cut
 
-# $LogoURL points to the URL of the RT logo displayed in the web UI
+Set($UseFriendlyFromLine, 1);
 
-Set($LogoURL , $WebImagesURL . "bplogo.gif");
+=item C<$FriendlyFromLineFormat>
 
-# WebNoAuthRegex - What portion of RT's URLspace should not require
-# authentication.
-Set($WebNoAuthRegex, qr!^(?:/+NoAuth/|
-                            /+REST/\d+\.\d+/NoAuth/)!x );
+C format of the friendly 'From:' header; its arguments
+are SenderName and SenderEmailAddress.
 
-# SelfServiceRegex - What portion of RT's URLspace should
-# be accessible to Unprivileged users
-# This does not override the redirect from /Ticket/Display.html
-# to /SelfService/Display.html when Unprivileged
-# users attempt to access ticked displays
-Set($SelfServiceRegex, qr!^(?:/+SelfService/)!x );
+=cut
 
-# For message boxes, set the entry box width and what type of wrapping
-# to use.
-#
-# Default width: 72
-Set($MessageBoxWidth , 72);
+Set($FriendlyFromLineFormat, "\"%s via RT\" <%s>");
 
-# Default wrapping: "HARD"  (choices "SOFT", "HARD")
-Set($MessageBoxWrap, "HARD");
+=item C<$UseFriendlyToLine>
 
-# Support implicit links in WikiText custom fields?  A true value
-# causes InterCapped or ALLCAPS words in WikiText fields to
-# automatically become links to searches for those words.  If used on
-# RTFM articles, it links to the RTFM article with that name.
-Set($WikiImplicitLinks, 0);
+RT can optionally set a "Friendly" 'To:' header when sending messages to
+Ccs or AdminCcs (rather than having a blank 'To:' header.
 
-# if TrustHTMLAttachments is not defined, we will display them
-# as text. This prevents malicious HTML and javascript from being
-# sent in a request (although there is probably more to it than that)
-Set($TrustHTMLAttachments , undef);
-
-# Should RT redistribute correspondence that it identifies as
-# machine generated? A true value will do so; setting this to '0'
-# will cause no such messages to be redistributed.
-# You can also use 'privileged' (the default), which will redistribute
-# only to privileged users. This helps to protect against malformed
-# bounces and loops caused by autocreated requestors with bogus addresses.
-Set($RedistributeAutoGeneratedMessages, 'privileged');
+This feature DOES NOT WORK WITH SENDMAIL[tm] BRAND SENDMAIL
+If you are using sendmail, rather than postfix, qmail, exim or some other MTA,
+you _must_ disable this option.
 
-# If PreferRichText is set to a true value, RT will show HTML/Rich text
-# messages in preference to their plaintext alternatives. RT "scrubs" the 
-# html to show only a minimal subset of HTML to avoid possible contamination
-# by cross-site-scripting attacks.
-Set($PreferRichText, undef);
+=cut
 
-# If $WebExternalAuth is defined, RT will defer to the environment's
-# REMOTE_USER variable.
+Set($UseFriendlyToLine, 0);
 
-Set($WebExternalAuth , undef);
+=item C<$FriendlyToLineFormat>
 
-# If $WebFallbackToInternalAuth is undefined, the user is allowed a chance
-# of fallback to the login screen, even if REMOTE_USER failed.
+C format of the friendly 'From:' header; its arguments
+are WatcherType and TicketId.
 
-Set($WebFallbackToInternalAuth , undef);
+=cut
 
-# $WebExternalGecos means to match 'gecos' field as the user identity);
-# useful with mod_auth_pwcheck and IIS Integrated Windows logon.
+Set($FriendlyToLineFormat, "\"%s of ". RT->Config->Get('rtname') ." Ticket #%s\":;");
 
-Set($WebExternalGecos , undef);
+=item C<$NotifyActor>
 
-# $WebExternalAuto will create users under the same name as REMOTE_USER
-# upon login, if it's missing in the Users table.
+By default, RT doesn't notify the person who performs an update, as they
+already know what they've done. If you'd like to change this behaviour,
+Set C<$NotifyActor> to 1
 
-Set($WebExternalAuto , undef);
+=cut
 
-# If $WebExternalAuto is true, this will be passed to User's
-# Create method.  Use it to set defaults, such as creating 
-# Unprivileged users with { Privileged => 0 }
-# Must be a hashref of arguments
+Set($NotifyActor, 0);
 
-Set($AutoCreate, undef);
+=item C<$RecordOutgoingEmail>
 
-# $WebSessionClass is the class you wish to use for managing Sessions.
-# It defaults to use your SQL database, but if you are using MySQL 3.x and
-# plans to use non-ascii Queue names, uncomment and add this line to
-# RT_SiteConfig.pm will prevent session corruption.
+By default, RT records each message it sends out to its own internal database.
+To change this behavior, set C<$RecordOutgoingEmail> to 0 
 
-# Set($WebSessionClass , 'Apache::Session::File');
+=cut
 
+Set($RecordOutgoingEmail, 1);
 
-# By default, RT's session cookie isn't marked as "secure" Some web browsers 
-# will treat secure cookies more carefully than non-secure ones, being careful
-# not to write them to disk, only send them over an SSL secured connection 
-# and so on. To enable this behaviour, set # $WebSecureCookies to a true value. 
-# NOTE: You probably don't want to turn this on _unless_ users are only connecting
-# via SSL encrypted HTTP connections.
+=item C<$VERPPrefix>, C<$VERPPrefix>
 
-Set($WebSecureCookies, 0);
+VERP support (http://cr.yp.to/proto/verp.txt)
 
+uncomment the following two directives to generate envelope senders
+of the form C<${VERPPrefix}${originaladdress}@${VERPDomain}>
+(i.e. rt-jesse=fsck.com@rt.example.com ).
 
-# By default, RT clears its database cache after every page view.
-# This ensures that you've always got the most current information 
-# when working in a multi-process (mod_perl or FastCGI) Environment
-# Setting $WebFlushDbCacheEveryRequest to '0' will turn this off,
-# which will speed RT up a bit, at the expense of a tiny bit of data 
-# accuracy.
+This currently only works with sendmail and sendmailppie.
 
-Set($WebFlushDbCacheEveryRequest, '1');
+=cut
 
+# Set($VERPPrefix, 'rt-');
+# Set($VERPDomain, $RT::Organization);
 
-# $MaxInlineBody is the maximum attachment size that we want to see
-# inline when viewing a transaction. 13456 is a random sane-sounding
-# default.
 
-Set($MaxInlineBody, 13456);
+=item C<$ForwardFromUser>
 
-# $DefaultSummaryRows is default number of rows displayed in for search
-# results on the frontpage.
+By default, RT forwards a message using queue's address and adds RT's tag into
+subject of the outgoing message, so recipients' replies go into RT as correspondents.
 
-Set($DefaultSummaryRows, 10);
+To change this behavior, set C<$ForwardFromUser> to true value and RT will use
+address of the current user and leave subject without RT's tag.
 
-# By default, RT shows newest transactions at the bottom of the ticket
-# history page, if you want see them at the top set this to '0'.
+=cut
 
-Set($OldestTransactionsFirst, '1');
+Set($ForwardFromUser, 0);
 
-# By default, RT shows images attached to incoming (and outgoing) ticket updates
-# inline. Set this variable to 0 if you'd like to disable that behaviour
+=item C<$ShowBccHeader>
 
-Set($ShowTransactionImages, 1);
+By default RT hides from the web UI information about blind copies user sent on
+reply or comment.
 
+To change this set the following option to true value.
 
-# $HomepageComponents is an arrayref of allowed components on a user's
-# customized homepage ("RT at a glance").
+=cut
 
-Set($HomepageComponents, [qw(QuickCreate Quicksearch MyAdminQueues MySupportQueues MyReminders  RefreshHomepage)]);
+Set($ShowBccHeader, 0);
 
-# @MasonParameters is the list of parameters for the constructor of
-# HTML::Mason's Apache or CGI Handler.  This is normally only useful
-# for debugging, eg. profiling individual components with:
-#     use MasonX::Profiler; # available on CPAN
-#     @MasonParameters = (preamble => 'my $p = MasonX::Profiler->new($m, $r);');
+=item C<$DashboardSubject>
 
-@MasonParameters = () unless (@MasonParameters);
+Lets you set the subject of dashboards. Arguments are the frequency (Daily,
+Weekly, Monthly) of the dashboard and the dashboard's name. [_1] for the name
+of the dashboard.
 
-# $DefaultSearchResultFormat is the default format for RT search results
-Set ($DefaultSearchResultFormat, qq{
-   '__id__/TITLE:#',
-   '__Subject__/TITLE:Subject',
-   Status,
-   QueueName, 
-   OwnerName, 
-   Priority, 
-   '__NEWLINE__',
-   '', 
-   '__Requestors__',
-   '__CreatedRelative__',
-   '__ToldRelative__',
-   '__LastUpdatedRelative__',
-   '__TimeLeft__'});
+=cut
 
-# If $SuppressInlineTextFiles is set to a true value, then uploaded
-# text files (text-type attachments with file names) are prevented
-# from being displayed in-line when viewing a ticket's history.
+Set($DashboardSubject, '%s Dashboard: %s');
 
-Set($SuppressInlineTextFiles, undef);
+=back
 
-# If $DontSearchFileAttachments is set to a true value, then uploaded
-# files (attachments with file names) are not searched during full-content
-# ticket searches.
+=head1 GnuPG Configuration
 
-Set($DontSearchFileAttachments, undef);
+A full description of the (somewhat extensive) GnuPG integration can be found 
+by running the command `perldoc L`  (or `perldoc
+        lib/RT/Crypt/GnuPG.pm` from your RT install directory).
 
-# The GD module (which RT uses for graphs) uses a builtin font that doesn't
-# have full Unicode support. You can use a particular TrueType font by setting
-# $ChartFont to the absolute path of that font. Your GD library must have
-# support for TrueType fonts to use this option.
+=over 4
 
-Set($ChartFont, undef);
+=item C<%GnuPG>
 
+Set C to 'inline' to use inline encryption and
+signatures instead of 'RFC' (GPG/MIME: RFC3156 and RFC1847) format.
 
-# }}}
+If you want to allow people to encrypt attachments inside the DB then
+set C to true
 
-# {{{ RT UTF-8 Settings
+Set C to false if you don't want to reject
+emails encrypted for key RT doesn't have and can not decrypt.
 
-# An array that contains languages supported by RT's internationalization
-# interface.  Defaults to all *.po lexicons; setting it to qw(en ja) will make
-# RT bilingual instead of multilingual, but will save some memory.
+Set C to false if you don't want to reject letters
+with incorrect GnuPG data.
 
-@LexiconLanguages = qw(*) unless (@LexiconLanguages);
+=cut
 
-# An array that contains default encodings used to guess which charset
-# an attachment uses if not specified.  Must be recognized by
-# Encode::Guess.
+Set( %GnuPG,
+    Enable => @RT_GPG@,
+    OutgoingMessagesFormat => 'RFC', # Inline
+    AllowEncryptDataInDB   => 0,
 
-@EmailInputEncodings = qw(utf-8 iso-8859-1 us-ascii) unless (@EmailInputEncodings);
+    RejectOnMissingPrivateKey => 1,
+    RejectOnBadData           => 1,
+);
 
-# The charset for localized email.  Must be recognized by Encode.
+=item C<%GnuPGOptions>
 
-Set($EmailOutputEncoding , 'utf-8');
+Options of GnuPG program.
 
-# }}}
+If you override this in your RT_SiteConfig, you should be sure
+to include a homedir setting.
 
-# {{{ RT Date Handling Options (for Time::ParseDate)
+NOTE that options with '-' character MUST be quoted.
 
-# Set this to 1 if your local date convention looks like "dd/mm/yy"
-# instead of "mm/dd/yy".
+=cut
 
-Set($DateDayBeforeMonth , 1);
+Set(%GnuPGOptions,
+    homedir => '@RT_VAR_PATH@/data/gpg',
 
-# Should an unspecified day or year in a date refer to a future or a
-# past value? For example, should a date of "Tuesday" default to mean
-# the date for next Tuesday or last Tuesday? Should the date "March 1"
-# default to the date for next March or last March?
-# Set to 0 for the next date or 1 for the last date.
+# URL of a keyserver
+#    keyserver => 'hkp://subkeys.pgp.net',
 
-Set($AmbiguousDayInPast , 1);
+# enables the automatic retrieving of keys when encrypting
+#    'auto-key-locate' => 'keyserver',
 
-# }}}
+# enables the automatic retrieving of keys when verifying signatures
+#    'auto-key-retrieve' => undef,
+);
 
-# {{{ Miscellaneous RT Settings
 
-# You can define new statuses and even reorder existing statuses here.
-# WARNING. DO NOT DELETE ANY OF THE DEFAULT STATUSES. If you do, RT
-# will break horribly. The statuses you add must be no longer than
-# 10 characters.
+=back
 
-@ActiveStatus = qw(new open stalled) unless @ActiveStatus;
-@InactiveStatus = qw(resolved rejected deleted) unless @InactiveStatus;
+=head1 Logging Configuration
 
-# Backward compatability setting. Add/Delete Link used to record one
-# transaction and run one scrip. Set this value to 1 if you want
-# only one of the link transactions to have scrips run.
-Set($LinkTransactionsRun1Scrip , 0);
+The default is to log anything except debugging
+information to syslog.  Check the L POD for
+information about how to get things by syslog, mail or anything
+else, get debugging info in the log, etc.
 
-# When this feature is enabled an user need ModifyTicket right on both
-# tickets to link them together, otherwise he can have right on any of
-# two.
-Set($StrictLinkACL, 1);
+It might generally make sense to send error and higher by email to
+some administrator.  If you do this, be careful that this email
+isn't sent to this RT instance.  Mail loops will generate a critical
+log message.
 
-# }}}
+=over 4
 
+=item C<$LogToSyslog>, C<$LogToScreen>
 
-# {{{ Development Mode
-#
-# RT comes with a "Development mode" setting. 
-# This setting, as a convenience for developers, turns on 
-# all sorts of development options that you most likely don't want in 
-# production:
-#
-# * Turns off Mason's 'static_source' directive. By default, you can't 
-#   edit RT's web ui components on the fly and have RT magically pick up
-#   your changes. (It's a big performance hit)
-#
-#  * More to come
-#
+The minimum level error that will be logged to the specific device.
+From lowest to highest priority, the levels are:
+ debug info notice warning error critical alert emergency
 
-Set($DevelMode, '@RT_DEVEL_MODE@');
+=cut
+
+Set($LogToSyslog    , 'info');
+Set($LogToScreen    , 'info');
+
+=item C<$LogToFile>, C<$LogDir>, C<$LogToFileNamed>
+
+Logging to a standalone file is also possible, but note that the
+file should needs to both exist and be writable by all direct users
+of the RT API.  This generally include the web server, whoever
+rt-crontool runs as.  Note that as rt-mailgate and the RT CLI go
+through the webserver, so their users do not need to have write
+permissions to this file. If you expect to have multiple users of
+the direct API, Best Practical recommends using syslog instead of
+direct file logging.
+
+=cut
+
+Set($LogToFile      , undef);
+Set($LogDir, '@RT_LOG_PATH@');
+Set($LogToFileNamed , "rt.log");    #log to rt.log
+
+=item C<$LogStackTraces>
+
+If set to a log level then logging will include stack traces for
+messages with level equal to or greater than specified.
+
+NOTICE: Stack traces include parameters supplied to functions or
+methods. It is possible for stack trace logging to reveal sensitive
+information such as passwords or ticket content in your logs.
+
+=cut
+
+Set($LogStackTraces, '');
+
+=item C<@LogToSyslogConf>
+
+On Solaris or UnixWare, set to ( socket => 'inet' ).  Options here
+override any other options RT passes to L.
+Other interesting flags include facility and logopt.  (See the
+L documentation for more information.)  (Maybe
+ident too, if you have multiple RT installations.)
+
+=cut
+
+Set(@LogToSyslogConf, ());
+
+=item C<$StatementLog>,
+
+RT has rudimentary SQL statement logging support if you have
+DBIx-SearchBuilder 1.31_1 or higher; simply set C<$StatementLog> to be
+the level that you wish SQL statements to be logged at.
+
+=cut
+
+Set($StatementLog, undef);
+
+=back
+
+=head1 Web Interface Configuration
+
+=over 4
+
+=item C<$WebDefaultStylesheet>
+
+This determines the default stylesheet the RT web interface will use.
+RT ships with several themes by default:
+
+  web2            The totally new, default layout for RT 3.8
+  3.5-default     RT 3.5 and 3.6 original layout
+  3.4-compat      A 3.4 compatibility stylesheet to make RT look
+                  (mostly) like 3.4
+
+This value actually specifies a directory in F
+from which RT will try to load the file main.css (which should
+@import any other files the stylesheet needs).  This allows you to
+easily and cleanly create your own stylesheets to apply to RT.  This
+option can be overridden by users in their preferences.
+
+=cut
+
+Set($WebDefaultStylesheet, 'web2');
+
+=item C<$UsernameFormat>
+
+This determines how user info is displayed. 'concise' will show one of 
+either NickName, RealName, Name or EmailAddress, depending on what exists 
+and whether the user is privileged or not. 'verbose' will show RealName and
+EmailAddress.
+
+=cut
 
-# }}}
+Set($UsernameFormat, 'concise');
 
+=item C<$WebDomain>
+
+Domain name of the RT server, eg 'www.example.com'. It should not contain
+anything else, but server name.
+
+=cut
+
+Set( $WebDomain, 'localhost' );
+
+=item C<$WebPort>
+
+If we're running as a superuser, run on port 80
+Otherwise, pick a high port for this user.
+
+443 is default port for https protocol.
+
+=cut
+
+Set($WebPort, 80);# + ($< * 7274) % 32766 + ($< && 1024));
+
+=item C<$WebPath>
+
+If you're putting the web ui somewhere other than at the root of
+your server, you should set C<$WebPath> to the path you'll be 
+serving RT at.
+
+C<$WebPath> requires a leading / but no trailing /, or it can be blank.
+
+In most cases, you should leave C<$WebPath> set to '' (an empty value).
+
+=cut
+
+Set($WebPath, "");
+
+=item C<$WebBaseURL>, C<$WebURL>
+
+Usually you don't want to set these options. The only obviouse reason is
+RT accessible via https protocol on non standard port, eg
+'https://rt.example.com:9999'. In all other cases these options are computed
+using C<$WebDomain>, C<$WebPort> and C<$WebPath>.
+
+C<$WebBaseURL> is the scheme, server and port (eg 'http://rt.example.com')
+for constructing urls to the web UI. C<$WebBaseURL> doesn't need a trailing /.
+
+C<$WebURL> is the C<$WebBaseURL>, C<$WebPath> and trailing /, for example:
+'http://www.example.com/rt/'.
+
+=cut
+
+my $port = RT->Config->Get('WebPort');
+Set($WebBaseURL,
+    ($port == 443? 'https': 'http') .'://'
+    . RT->Config->Get('WebDomain')
+    . ($port != 80 && $port != 443? ":$port" : '')
+);
+
+Set($WebURL, RT->Config->Get('WebBaseURL') . RT->Config->Get('WebPath') . "/");
+
+=item C<$WebImagesURL>
+
+C<$WebImagesURL> points to the base URL where RT can find its images.
+Define the directory name to be used for images in rt web
+documents.
+
+=cut
+
+Set($WebImagesURL, RT->Config->Get('WebPath') . "/NoAuth/images/");
+
+=item C<$LogoURL>
+
+C<$LogoURL> points to the URL of the RT logo displayed in the web UI
+
+=cut
+
+Set($LogoURL, RT->Config->Get('WebImagesURL') . "bplogo.gif");
+
+=item C<$WebNoAuthRegex>
+
+What portion of RT's URL space should not require authentication.
+
+This is mostly for extension and doesn't mean RT will work without
+login if you change it.
+
+=cut
+
+Set($WebNoAuthRegex, qr{^ (?:/+NoAuth/ | /+REST/\d+\.\d+/NoAuth/) }x );
+
+=item C<$SelfServiceRegex>
+
+What portion of RT's URLspace should be accessible to Unprivileged users
+This does not override the redirect from F to
+F when Unprivileged users attempt to access
+ticked displays
+
+=cut
+
+Set($SelfServiceRegex, qr!^(?:/+SelfService/)!x );
+
+=item C<$MessageBoxWidth>, C<$MessageBoxHeight>
+
+For message boxes, set the entry box width, height and what type of
+wrapping to use.  These options can be overridden by users in their
+preferences.
+
+Default width: 72, height: 15
+
+These settings only apply to the non-RichText message box.
+See below for Rich Text settings.
+
+=cut
+
+Set($MessageBoxWidth, 72);
+Set($MessageBoxHeight, 15);
+
+=item C<$MessageBoxWrap>
+
+Default wrapping: "HARD"  (choices "SOFT", "HARD")
+
+Wrapping is disabled when using MessageBoxRichText because
+of a bad interaction between IE and wrapping with the Rich
+Text Editor.
+
+=cut
+
+Set($MessageBoxWrap, "HARD");
+
+=item C<$MessageBoxRichText>
+
+Should "rich text" editing be enabled? This option lets your users send html email messages from the web interface.
+
+=cut
+
+Set($MessageBoxRichText, 1);
+
+=item C<$MessageBoxRichTextHeight>
+
+Height of RichText javascript enabled editing boxes (in pixels)
+
+=cut
+
+Set($MessageBoxRichTextHeight, 200);
+
+=item C<$MessageBoxIncludeSignature>
+
+Should your user's signatures (from their Preferences page) be included in Comments and Replies
+
+=cut
+
+Set($MessageBoxIncludeSignature, 1);
+
+=item C<$WikiImplicitLinks>
+
+Support implicit links in WikiText custom fields?  A true value
+causes InterCapped or ALLCAPS words in WikiText fields to
+automatically become links to searches for those words.  If used on
+RTFM articles, it links to the RTFM article with that name.
+
+=cut
+
+Set($WikiImplicitLinks, 0);
+
+=item C<$TrustHTMLAttachments>
+
+if C is not defined, we will display them
+as text. This prevents malicious HTML and javascript from being
+sent in a request (although there is probably more to it than that)
+
+=cut
+
+Set($TrustHTMLAttachments, undef);
+
+=item C<$RedistributeAutoGeneratedMessages>
+
+Should RT redistribute correspondence that it identifies as
+machine generated? A true value will do so; setting this to '0'
+will cause no such messages to be redistributed.
+You can also use 'privileged' (the default), which will redistribute
+only to privileged users. This helps to protect against malformed
+bounces and loops caused by autocreated requestors with bogus addresses.
+
+=cut
+
+Set($RedistributeAutoGeneratedMessages, 'privileged');
+
+=item C<$PreferRichText>
+
+If C<$PreferRichText> is set to a true value, RT will show HTML/Rich text
+messages in preference to their plaintext alternatives. RT "scrubs" the 
+html to show only a minimal subset of HTML to avoid possible contamination
+by cross-site-scripting attacks.
+
+=cut
+
+Set($PreferRichText, undef);
+
+=item C<$WebExternalAuth>
+
+If C<$WebExternalAuth> is defined, RT will defer to the environment's
+REMOTE_USER variable.
+
+=cut
+
+Set($WebExternalAuth, undef);
+
+=item C<$WebExternalAuthContinuous>
+
+If C<$WebExternalAuthContinuous> is defined, RT will check for the
+REMOTE_USER on each access.  If you would prefer this to only happen
+once (at initial login) set this to a false value.  The default setting
+will help ensure that if your external auth system deauthenticates a
+user, RT notices as soon as possible.
+
+=cut
+
+Set($WebExternalAuthContinuous, 1);
+
+=item C<$WebFallbackToInternalAuth>
+
+If C<$WebFallbackToInternalAuth> is defined, the user is allowed a chance
+of fallback to the login screen, even if REMOTE_USER failed.
+
+=cut
+
+Set($WebFallbackToInternalAuth , undef);
+
+=item C<$WebExternalGecos>
+
+C<$WebExternalGecos> means to match 'gecos' field as the user identity);
+useful with mod_auth_pwcheck and IIS Integrated Windows logon.
+
+=cut
+
+Set($WebExternalGecos , undef);
+
+=item C<$WebExternalAuto>
+
+C<$WebExternalAuto> will create users under the same name as REMOTE_USER
+upon login, if it's missing in the Users table.
+
+=cut
+
+Set($WebExternalAuto , undef);
+
+=item C<$AutoCreate>
+
+If C<$WebExternalAuto> is true, C<$AutoCreate> will be passed to User's
+Create method.  Use it to set defaults, such as creating 
+Unprivileged users with C<{ Privileged => 0 }>
+( Must be a hashref of arguments )
+
+=cut
+
+Set($AutoCreate, undef);
+
+=item C<$WebSessionClass>
+
+C<$WebSessionClass> is the class you wish to use for managing Sessions.
+It defaults to use your SQL database, but if you are using MySQL 3.x and
+plans to use non-ascii Queue names, uncomment and add this line to
+F will prevent session corruption.
+
+=cut
+
+# Set($WebSessionClass , 'Apache::Session::File');
+
+=item C<$AutoLogoff>
+
+By default, RT's user sessions persist until a user closes his or her 
+browser. With the C<$AutoLogoff> option you can setup session lifetime in 
+minutes. A user will be logged out if he or she doesn't send any requests 
+to RT for the defined time.
+
+=cut
+
+Set($AutoLogoff, 0);
+
+=item C<$WebSecureCookies>
+
+By default, RT's session cookie isn't marked as "secure" Some web browsers 
+will treat secure cookies more carefully than non-secure ones, being careful
+not to write them to disk, only send them over an SSL secured connection 
+and so on. To enable this behaviour, set C<$WebSecureCookies> to a true value. 
+NOTE: You probably don't want to turn this on _unless_ users are only connecting
+via SSL encrypted HTTP connections.
+
+=cut
+
+Set($WebSecureCookies, 0);
+
+=item C<$WebFlushDbCacheEveryRequest>
+
+By default, RT clears its database cache after every page view.
+This ensures that you've always got the most current information 
+when working in a multi-process (mod_perl or FastCGI) Environment
+Setting C<$WebFlushDbCacheEveryRequest> to '0' will turn this off,
+which will speed RT up a bit, at the expense of a tiny bit of data 
+accuracy.
+
+=cut
+
+Set($WebFlushDbCacheEveryRequest, '1');
+
+
+=item C<$MaxInlineBody>
+
+C<$MaxInlineBody> is the maximum attachment size that we want to see
+inline when viewing a transaction.  RT will inline any text if value
+is undefined or 0.  This option can be overridden by users in their
+preferences.
+
+=cut
+
+Set($MaxInlineBody, 12000);
+
+=item C<$DefaultSummaryRows>
+
+C<$DefaultSummaryRows> is default number of rows displayed in for search
+results on the frontpage.
+
+=cut
+
+Set($DefaultSummaryRows, 10);
+
+=item C<$HomePageRefreshInterval>
+
+C<$HomePageRefreshInterval> is default number of seconds to refresh the RT
+home page. Choose from [0, 120, 300, 600, 1200, 3600, 7200].
+
+=cut
+
+Set($HomePageRefreshInterval, 0);
+
+=item C<$SearchResultsRefreshInterval>
+
+C<$SearchResultsRefreshInterval> is default number of seconds to refresh
+search results in RT. Choose from [0, 120, 300, 600, 1200, 3600, 7200].
+
+=cut
+
+Set($SearchResultsRefreshInterval, 0);
+
+=item C<$OldestTransactionsFirst>
+
+By default, RT shows newest transactions at the bottom of the ticket
+history page, if you want see them at the top set this to '0'.  This
+option can be overridden by users in their preferences.
+
+=cut
+
+Set($OldestTransactionsFirst, '1');
+
+=item C<$ShowTransactionImages>
+
+By default, RT shows images attached to incoming (and outgoing) ticket updates
+inline. Set this variable to 0 if you'd like to disable that behaviour
+
+=cut
+
+Set($ShowTransactionImages, 1);
+
+=item C<$PlainTextPre>
+
+Normally plaintext attachments are displayed as HTML with line
+breaks preserved.  This causes space- and tab-based formatting not
+to be displayed correctly.  By setting $PlainTextPre they'll be
+displayed using 
 instead so such formatting works, but they'll
+use a monospaced font, no matter what the value of C<$PlainTextMono> is.
+
+=cut
+
+Set($PlainTextPre, 0);
+
+
+=item C<$PlainTextMono> 
+To display plaintext attachments,
+Set C<$PlainTextMono> to 1 to use monospaced font and preserve
+formatting, but unlike PlainTextPre, the text will wrap to fit into the
+UI.
+
+=cut
+
+Set($PlainTextMono, 0);
+
+=item C<$ShowUnreadMessageNotifications>
+
+By default, RT will prompt users when there are new, unread messages on
+tickets they are viewing.
+
+Set C<$ShowUnreadMessageNotifications> to a false value to disable this feature.
+
+=cut
+
+Set($ShowUnreadMessageNotifications, 1);
+
+
+=item C<$HomepageComponents>
+
+C<$HomepageComponents> is an arrayref of allowed components on a user's
+customized homepage ("RT at a glance").
+
+=cut
+
+Set($HomepageComponents, [qw(QuickCreate Quicksearch MyAdminQueues MySupportQueues MyReminders RefreshHomepage Dashboards)]);
+
+=item C<@MasonParameters>
+
+C<@MasonParameters> is the list of parameters for the constructor of
+HTML::Mason's Apache or CGI Handler.  This is normally only useful
+for debugging, eg. profiling individual components with:
+
+    use MasonX::Profiler; # available on CPAN
+    Set(@MasonParameters, (preamble => 'my $p = MasonX::Profiler->new($m, $r);'));
+
+=cut
+
+Set(@MasonParameters, ());
+
+=item C<$DefaultSearchResultFormat>
+
+C<$DefaultSearchResultFormat> is the default format for RT search results
+
+=cut
+
+Set ($DefaultSearchResultFormat, qq{
+   '__id__/TITLE:#',
+   '__Subject__/TITLE:Subject',
+   Status,
+   QueueName, 
+   OwnerName, 
+   Priority, 
+   '__NEWLINE__',
+   '', 
+   '__Requestors__',
+   '__CreatedRelative__',
+   '__ToldRelative__',
+   '__LastUpdatedRelative__',
+   '__TimeLeft__'});
+
+=item C<$DefaultSelfServiceSearchResultFormat>
+
+C<$DefaultSelfServiceSearchResultFormat> is the default format of searches displayed in the 
+SelfService interface.
+
+=cut
+
+Set($DefaultSelfServiceSearchResultFormat, qq{
+   '__id__/TITLE:#',
+   '__Subject__/TITLE:Subject',
+   Status,
+   Requestors,
+   OwnerName});
+
+=item C<$SuppressInlineTextFiles>
+
+If C<$SuppressInlineTextFiles> is set to a true value, then uploaded
+text files (text-type attachments with file names) are prevented
+from being displayed in-line when viewing a ticket's history.
+
+=cut
+
+Set($SuppressInlineTextFiles, undef);
+
+=item C<$DontSearchFileAttachments>
+
+If C<$DontSearchFileAttachments> is set to a true value, then uploaded
+files (attachments with file names) are not searched during full-content
+ticket searches.
+
+=cut
+
+Set($DontSearchFileAttachments, undef);
+
+=item C<$ChartFont>
+
+The L module (which RT uses for graphs) uses a builtin font that doesn't
+have full Unicode support. You can use a particular TrueType font by setting
+$ChartFont to the absolute path of that font. Your GD library must have
+support for TrueType fonts to use this option.
+
+=cut
+
+Set($ChartFont, undef);
+
+
+=item C<@Active_MakeClicky>
+
+MakeClicky detects various formats of data in headers and email
+messages, and extends them with supporting links.  By default, RT
+provides two formats:
+
+* 'httpurl': detects http:// and https:// URLs and adds '[Open URL]'
+  link after the URL.
+
+* 'httpurl_overwrite': also detects URLs as 'httpurl' format, but
+  replace URL with link and *adds spaces* into text if it's longer
+  then 30 chars. This allow browser to wrap long URLs and avoid
+  horizontal scrolling.
+
+See F for documentation on how to add your own.
+
+=cut
+
+Set(@Active_MakeClicky, qw());
+
+=item C<$DefaultQueue>
+
+Use this to select the default queue name that will be used for creating new
+tickets. You may use either the queue's name or its ID. This only affects the
+queue selection boxes on the web interface.
+
+=cut
+
+#Set($DefaultQueue, 'General');
+
+=item C<$DefaultTimeUnitsToHours>
+
+Use this to set the default units for time entry to hours instead of minutes.
+
+=cut
+
+Set($DefaultTimeUnitsToHours, 0);
+
+=back
+
+=head1 L (rt-server) Configuration
+
+=over 4
+
+=item C<$StandaloneMinServers>, C<$StandaloneMaxServers>
+
+The absolute minimum and maximum number of servers that will be created to
+handle requests. Having multiple servers means that serving a slow page will
+affect other users less.
+
+=cut
+
+Set($StandaloneMinServers, 1);
+Set($StandaloneMaxServers, 1);
+
+=item C<$StandaloneMinSpareServers>, C<$StandaloneMaxSpareServers>
+
+These next two options can be used to scale up and down the number of servers
+to adjust to load. These two options will respect the C<$StandaloneMinServers
+> and C<$StandaloneMaxServers options>.
+
+=cut
+
+Set($StandaloneMinSpareServers, 0);
+Set($StandaloneMaxSpareServers, 0);
+
+=item C<$StandaloneMaxRequests>
+
+This sets the absolute maximum number of requests a single server will serve.
+Setting this would be useful if, for example, memory usage slowly crawls up
+every hit.
+
+=cut
+
+#Set($StandaloneMaxRequests, 50);
+
+=item C<%NetServerOptions>
+
+C<%NetServerOptions> is a hash of additional options to use for
+L. For example, you could set
+reverse_lookups to get the hostnames for all users with:
+
+C 1));>
+
+=cut
+
+Set(%NetServerOptions, ());
+
+=back
+
+
+=head1 UTF-8 Configuration
+
+=over 4
+
+=item C<@LexiconLanguages>
+
+An array that contains languages supported by RT's internationalization
+interface.  Defaults to all *.po lexicons; setting it to C will make
+RT bilingual instead of multilingual, but will save some memory.
+
+=cut
+
+Set(@LexiconLanguages, qw(*));
+
+=item C<@EmailInputEncodings>
+
+An array that contains default encodings used to guess which charset
+an attachment uses if not specified.  Must be recognized by
+L.
+
+=cut
+
+Set(@EmailInputEncodings, qw(utf-8 iso-8859-1 us-ascii));
+
+=item C<$EmailOutputEncoding>
+
+The charset for localized email.  Must be recognized by Encode.
+
+=cut
+
+Set($EmailOutputEncoding, 'utf-8');
+
+
+=back
+
+=head1 Date Handling Configuration
+
+=over 4
+
+=item C<$DateTimeFormat>
+
+You can choose date and time format.  See "Output formatters"
+section in perldoc F for more options.  This option can
+be overridden by users in their preferences.
+Some examples:
+
+C
+C 'ISO', Seconds => 0 });>
+C
+C 'RFC2822', Seconds => 0, DayOfWeek => 0 });>
+
+=cut
+
+Set($DateTimeFormat, 'DefaultFormat');
+
+# Next two options are for Time::ParseDate
+
+=item C<$DateDayBeforeMonth>
+
+Set this to 1 if your local date convention looks like "dd/mm/yy" instead of
+"mm/dd/yy". Used only for parsing, not for displaying dates.
+
+=cut
+
+Set($DateDayBeforeMonth , 1);
+
+=item C<$AmbiguousDayInPast>, C<$AmbiguousDayInFuture>
+
+Should an unspecified day or year in a date refer to a future or a
+past value? For example, should a date of "Tuesday" default to mean
+the date for next Tuesday or last Tuesday? Should the date "March 1"
+default to the date for next March or last March?
+
+Set $ for the last date, or $<$AmbiguousDayInFuture> for the
+next date.
+
+The default is usually good.
+
+=cut
+
+Set($AmbiguousDayInPast, 0);
+Set($AmbiguousDayInFuture, 0);
+
+=back
+
+=head1 Approval Configuration
+
+Configration for the approvl system
+
+=over 4
+
+=item C<$ApprovalRejectionNotes>
+
+Should rejection notes be sent to the requestors?  The default is true.
+
+=cut
+
+Set($ApprovalRejectionNotes, 1);
+
+
+=back
+
+=head1 Miscellaneous Configuration
+
+=over 4
+
+=item C<@ActiveStatus>, C<@InactiveStatus>
+
+You can define new statuses and even reorder existing statuses here.
+WARNING. DO NOT DELETE ANY OF THE DEFAULT STATUSES. If you do, RT
+will break horribly. The statuses you add must be no longer than
+10 characters.
+
+=cut
+
+Set(@ActiveStatus, qw(new open stalled));
+Set(@InactiveStatus, qw(resolved rejected deleted));
+
+=item C<$LinkTransactionsRun1Scrip>
+
+RT-3.4 backward compatibility setting. Add/Delete Link used to record one
+transaction and run one scrip. Set this value to 1 if you want
+only one of the link transactions to have scrips run.
+
+=cut
+
+Set($LinkTransactionsRun1Scrip, 0);
+
+=item C<$StrictLinkACL>
+
+When this feature is enabled a user needs I rights on both
+tickets to link them together, otherwise he can have rights on either of
+them.
+
+=cut
+
+Set($StrictLinkACL, 1);
+
+=item C<$PreviewScripMessages>
+
+Set C<$PreviewScripMessages> to 1 if the scrips preview on the ticket
+reply page should include the content of the messages to be sent.
+
+=cut
+
+Set($PreviewScripMessages, 0);
+
+=item C<$UseTransactionBatch>
+
+Set C<$UseTransactionBatch> to 1 to execute transactions in batches,
+such that a resolve and comment (for example) would happen
+simultaneously, instead of as two transactions, unaware of each
+others' existence.
+
+=cut
+
+Set($UseTransactionBatch, 1);
+
+=item C<@CustomFieldValuesSources>
+
+Set C<@CustomFieldValuesSources> to a list of class names which extend
+L.  This can be used to pull lists of
+custom field values from external sources at runtime.
+
+=cut
+
+Set(@CustomFieldValuesSources, ());
+
+=item C<$CanonicalizeRedirectURLs>
+
+Set C<$CanonicalizeRedirectURLs> to 1 to use $C when redirecting rather
+than the one we get from C<%ENV>.
+
+If you use RT behind a reverse proxy, you almost certainly want to
+enable this option.
+
+=cut
+
+Set($CanonicalizeRedirectURLs, 0);
+=item C<$EnableReminders>
+
+Hide links/portlets related to Reminders by setting this to 0
+
+=cut
+
+Set($EnableReminders,1);
+
+
+=item C<@Plugins>
+
+Set C<@Plugins> to a list of external RT plugins that should be enabled (those
+plugins have to be previously downloaded and installed).
+Example:
+
+C
+
+=cut
+
+Set(@Plugins, ());
+
+=back
+
+=head1 Development Configuration
+
+=over 4
+
+=item C<$DevelMode>
+
+RT comes with a "Development mode" setting. 
+This setting, as a convenience for developers, turns on 
+all sorts of development options that you most likely don't want in 
+production:
+
+* Turns off Mason's 'static_source' directive. By default, you can't 
+  edit RT's web ui components on the fly and have RT magically pick up
+  your changes. (It's a big performance hit)
+
+ * More to come
+
+=cut
+
+Set($DevelMode, '@RT_DEVEL_MODE@');
+
+
+=back
+
+=head1 Deprecated Options
+
+=over 4
+
+=item C<$AlwaysUseBase64>
+
+Encode blobs as base64 in DB (?)
+
+=item C<$TicketBaseURI>
+
+Base URI to tickets in this system; used when loading (?)
+
+=item C<$UseCodeTickets>
+
+This option is exists for backwards compatibility.  Don't use it.
+
+=back
+
+=cut
 
 1;
diff --git a/rt/etc/RT_SiteConfig.pm b/rt/etc/RT_SiteConfig.pm
index f5cc2985b..1661e4d6e 100644
--- a/rt/etc/RT_SiteConfig.pm
+++ b/rt/etc/RT_SiteConfig.pm
@@ -15,4 +15,5 @@
 #   perl -c /path/to/your/etc/RT_SiteConfig.pm
 
 Set( $rtname, 'example.com');
+#Set(@Plugins,(qw(Extension::QuickDelete RT::FM)));
 1;
diff --git a/rt/etc/acl.Informix b/rt/etc/acl.Informix
index bca0408dd..eff232f4d 100644
--- a/rt/etc/acl.Informix
+++ b/rt/etc/acl.Informix
@@ -1,5 +1,6 @@
+
 sub acl {
-return  (
-"GRANT RESOURCE TO ${RT::DatabaseUser};");
+    return ("GRANT RESOURCE TO ". RT->Config->Get('DatabaseUser') .";");
 }
+
 1;
diff --git a/rt/etc/acl.Oracle b/rt/etc/acl.Oracle
index ac29215c2..9ca4122a0 100644
--- a/rt/etc/acl.Oracle
+++ b/rt/etc/acl.Oracle
@@ -1,10 +1,4 @@
-sub acl {
-return (
-"CREATE USER ${RT::DatabaseUser} identified by ${RT::DatabasePassword} ".
-"default tablespace USERS " .
-"temporary tablespace TEMP " .
-"quota unlimited on USERS" ,
-"grant connect, resource to ${RT::DatabaseUser}"
-);
-}
+
+sub acl { return () }
+
 1;
diff --git a/rt/etc/acl.Pg b/rt/etc/acl.Pg
index fb625592d..8a0d4f28c 100755
--- a/rt/etc/acl.Pg
+++ b/rt/etc/acl.Pg
@@ -1,67 +1,76 @@
+
 sub acl {
     my $dbh = shift;
 
     my @acls;
 
     my @tables = qw (
-
-      attachments_id_seq
-      Attachments
-      Attributes
-      attributes_id_seq
-      queues_id_seq
- Queues 
-      links_id_seq
- Links 
-      principals_id_seq
- Principals 
-      groups_id_seq
- Groups 
-      scripconditions_id_seq
- ScripConditions 
-      transactions_id_seq
- Transactions 
-      scrips_id_seq
- Scrips 
-      acl_id_seq
- ACL 
-      groupmembers_id_seq
- GroupMembers 
-      cachedgroupmembers_id_seq
- CachedGroupMembers 
-      users_id_seq
- Users 
-      tickets_id_seq
- Tickets 
-      scripactions_id_seq
- ScripActions 
-      templates_id_seq
- Templates 
- objectcustomfieldvalues_id_s
- ObjectCustomFieldValues 
-      customfields_id_seq
- CustomFields 
- objectcustomfields_id_s
- ObjectCustomFields 
-      customfieldvalues_id_seq
-      CustomFieldValues
-      sessions
+        attachments_id_seq
+        Attachments
+        Attributes
+        attributes_id_seq
+        queues_id_seq
+        Queues 
+        links_id_seq
+        Links 
+        principals_id_seq
+        Principals 
+        groups_id_seq
+        Groups 
+        scripconditions_id_seq
+        ScripConditions 
+        transactions_id_seq
+        Transactions 
+        scrips_id_seq
+        Scrips 
+        acl_id_seq
+        ACL 
+        groupmembers_id_seq
+        GroupMembers 
+        cachedgroupmembers_id_seq
+        CachedGroupMembers 
+        users_id_seq
+        Users 
+        tickets_id_seq
+        Tickets 
+        scripactions_id_seq
+        ScripActions 
+        templates_id_seq
+        Templates 
+        objectcustomfieldvalues_id_s
+        ObjectCustomFieldValues 
+        customfields_id_seq
+        CustomFields 
+        objectcustomfields_id_s
+        ObjectCustomFields 
+        customfieldvalues_id_seq
+        CustomFieldValues
+        sessions
     );
 
-    # if there's already an rt_user, drop it.
-    my @row =
-      $dbh->selectrow_array( "select usename from pg_user where usename = '" . $RT::DatabaseUser."'" );
-    if ( $row[0] ) {
-        push @acls, "drop user ${RT::DatabaseUser};",;
+    my $db_user = RT->Config->Get('DatabaseUser');
+    my $db_pass = RT->Config->Get('DatabasePassword');
+
+    # if there's already an rt_user, use it.
+    my @row = $dbh->selectrow_array( "SELECT usename FROM pg_user WHERE usename = '$db_user'" );
+    unless ( $row[0] ) {
+	 push @acls, "CREATE USER \"$db_user\" WITH PASSWORD '$db_pass' NOCREATEDB NOCREATEUSER;";
     }
 
-    push @acls, "create user ${RT::DatabaseUser} with password '${RT::DatabasePassword}' NOCREATEDB NOCREATEUSER;";
+    my $sequence_right
+        = ( $dbh->{pg_server_version} >= 80200 )
+        ? "USAGE, SELECT, UPDATE"
+        : "SELECT, UPDATE";
     foreach my $table (@tables) {
-        push @acls,
-          "GRANT SELECT, INSERT, UPDATE, DELETE ON $table to "
-          . $RT::DatabaseUser . ";";
-
+        if ( $table =~ /^[a-z]/ && $table ne 'sessions' ) {
+# table like objectcustomfields_id_s
+            push @acls, "GRANT $sequence_right ON $table TO \"$db_user\";"
+        }
+        else {
+            push @acls, "GRANT SELECT, INSERT, UPDATE, DELETE ON $table TO \"$db_user\";"
+        }
     }
     return (@acls);
 }
+
 1;
diff --git a/rt/etc/acl.Sybase b/rt/etc/acl.Sybase
index 6192b4ebe..7583c02e3 100644
--- a/rt/etc/acl.Sybase
+++ b/rt/etc/acl.Sybase
@@ -1,6 +1,10 @@
+
 sub acl {
-return (
-"SP_ADDLOGIN ${RT::DatabaseUser}, ${RT::DatabasePassword}, ${RT::DatabaseName} ",
-);
+    my $db_name = RT->Config->Get('DatabaseName');
+    my $db_user = RT->Config->Get('DatabaseUser');
+    my $db_pass = RT->Config->Get('DatabasePassword');
+
+    return ("SP_ADDLOGIN $db_user, $db_pass, $db_name");
 }
+
 1;
diff --git a/rt/etc/acl.mysql b/rt/etc/acl.mysql
index 724c7f12a..0982ca228 100755
--- a/rt/etc/acl.mysql
+++ b/rt/etc/acl.mysql
@@ -1,9 +1,27 @@
+
 sub acl {
-return () if !$RT::DatabaseUser or $RT::DatabaseUser eq 'root';
-return  (
-"USE mysql;",
-"DELETE FROM user WHERE user = '${RT::DatabaseUser}';",
-"DELETE FROM db where db = '${RT::DatabaseName}';",
-"GRANT SELECT,INSERT,CREATE,INDEX,UPDATE,DELETE ON ${RT::DatabaseName}.* TO '${RT::DatabaseUser}'\@'${RT::DatabaseRTHost}' IDENTIFIED BY '${RT::DatabasePassword}';");
+    my $db_name = RT->Config->Get('DatabaseName');
+    my $db_rthost = RT->Config->Get('DatabaseRTHost');
+    my $db_user = RT->Config->Get('DatabaseUser');
+    my $db_pass = RT->Config->Get('DatabasePassword');
+    unless ( $db_user ) {
+        print STDERR "DatabaseUser option is not defined or empty. Skipping...\n";
+        return;
+    }
+    if ( $db_user eq 'root' ) {
+        print STDERR "DatabaseUser is root. Skipping...\n";
+        return;
+    }
+    print "Granting access to $db_user\@'$db_rthost' on $db_name.\n";
+    return (
+        "USE mysql;",
+        "DELETE FROM user WHERE user = '$db_user';",
+        "DELETE FROM db where db = '$db_name';",
+        "GRANT SELECT,INSERT,CREATE,INDEX,UPDATE,DELETE
+               ON $db_name.*
+               TO '$db_user'\@'$db_rthost'
+               IDENTIFIED BY '$db_pass';",
+    );
 }
+
 1;
diff --git a/rt/etc/initialdata b/rt/etc/initialdata
index 54fa9d195..89db2cc64 100644
--- a/rt/etc/initialdata
+++ b/rt/etc/initialdata
@@ -1,63 +1,18 @@
 # Initial data for a fresh RT3 Installation.
 
 @Users = (
-    {  Name     => 'Nobody',
-       RealName => 'Nobody in particular',
-       Comments => 'Do not delete or modify this user. It is integral '
-         . 'to RT\'s internal data structures',
-       Privileged => '0', },
-
     {  Name         => 'root',
        Gecos        => 'root',
        RealName     => 'Enoch Root',
        Password     => 'password',
        EmailAddress => "root\@localhost",
        Comments     => 'SuperUser',
-       Privileged   => '1', } );
+       Privileged   => '1',
+    },
+);
 
 @Groups = (
-    { Name        => '',
-      Type        => 'Everyone',                        # loc
-      Domain      => 'SystemInternal',
-      Instance    => '',
-      Description => 'Pseudogroup for internal use',    # loc
-    },
-    { Type        => 'Privileged',                      # loc
-      Domain      => 'SystemInternal',
-      Instance    => '',
-      Name        => '',
-      Description => 'Pseudogroup for internal use',    # loc
-    },
-    { Name        => '',
-      Type        => 'Unprivileged',                    # loc
-      Domain      => 'SystemInternal',
-      Instance    => '',
-      Description => 'Pseudogroup for internal use',    # loc
-    },
-    { Name        => '',
-      Type        => 'Owner',                               # loc
-      Domain      => 'RT::System-Role',
-      Instance    => '',
-      Description => 'SystemRolegroup for internal use',    # loc
-    },
-    { Name        => '',
-      Type        => 'Requestor',                           # loc
-      Domain      => 'RT::System-Role',
-      Instance    => '',
-      Description => 'SystemRolegroup for internal use',    # loc
-    },
-    { Name        => '',
-      Type        => 'Cc',                                  # loc
-      Domain      => 'RT::System-Role',
-      Instance    => '',
-      Description => 'SystemRolegroup for internal use',    # loc
-    },
-    { Name        => '',
-      Type        => 'AdminCc',                             # loc
-      Domain      => 'RT::System-Role',
-      Instance    => '',
-      Description => 'Pseudogroup for internal use',        # loc
-    }, );
+);
 
 @Queues = ({ Name              => 'General',
              Description       => 'The default queue',
@@ -113,12 +68,12 @@
       ExecModule  => 'Notify',
       Argument    => 'Requestor,Cc' },
 
-    { Name        => 'Notify Requestors, Ccs and AdminCcs as Comment',    # loc
-      Description => 'Send mail to all watchers as a "comment"',          # loc
+    { Name        => 'Notify Owner, Requestors, Ccs and AdminCcs as Comment',    # loc
+      Description => 'Send mail to owner and all watchers as a "comment"',          # loc
       ExecModule  => 'NotifyAsComment',
       Argument    => 'All' },
-    { Name        => 'Notify Requestors, Ccs and AdminCcs',               # loc
-      Description => 'Send mail to all watchers',                         # loc
+    { Name        => 'Notify Owner, Requestors, Ccs and AdminCcs',               # loc
+      Description => 'Send mail to owner and all watchers',                         # loc
       ExecModule  => 'Notify',
       Argument    => 'All' },
     { Name        => 'Notify Other Recipients as Comment',                # loc
@@ -139,6 +94,9 @@
     { Name        => 'Open Tickets',                                      # loc
       Description => 'Open tickets on correspondence',                    # loc
       ExecModule  => 'AutoOpen' },
+    { Name        => 'Extract Subject Tag',                               # loc
+      Description => 'Extract tags from a Transaction\'s subject and add them to the Ticket\'s subject.', # loc
+      ExecModule  => 'ExtractSubjectTag' },
 );
 
 @ScripConditions = (
@@ -202,7 +160,13 @@
        Argument             => 'resolved'
 
     },
+    {  Name                 => 'On Reject',                                # loc
+       Description          => 'Whenever a ticket is rejected',            # loc
+       ApplicableTransTypes => 'Status',
+       ExecModule           => 'StatusChange',
+       Argument             => 'rejected'
 
+    },
     {  Name                 => 'User Defined',                             # loc
        Description          => 'Whenever a user-defined condition occurs', # loc
        ApplicableTransTypes => 'Any',
@@ -210,6 +174,17 @@
 
     },
 
+    {  Name                 => 'On Close',                                 # loc
+       Description          => 'Whenever a ticket is closed', # loc
+       ApplicableTransTypes => 'Status,Set',
+       ExecModule           => 'CloseTicket',
+    },
+    {  Name                 => 'On Reopen',                                # loc
+       Description          => 'Whenever a ticket is reopened', # loc
+       ApplicableTransTypes => 'Status,Set',
+       ExecModule           => 'ReopenTicket',
+    },
+
 );
 
 @Templates = (
@@ -231,11 +206,11 @@ creation of a trouble ticket regarding:
 a summary of which appears below.
 
 There is no need to reply to this message right now.  Your ticket has been
-assigned an ID of [{$rtname} #{$Ticket->id()}].
+assigned an ID of [{$Ticket->QueueObj->SubjectTag || $rtname} #{$Ticket->id()}].
 
 Please include the string:
 
-         [{$rtname} #{$Ticket->id}]
+         [{$Ticket->QueueObj->SubjectTag || $rtname} #{$Ticket->id}]
 
 in the subject line of all future correspondence about this issue. To do so, 
 you may reply to this message.
@@ -255,13 +230,13 @@ you may reply to this message.
 
 
 {$Transaction->CreatedAsString}: Request {$Ticket->id} was acted upon.
-Transaction: {$Transaction->Description}
+ Transaction: {$Transaction->Description}
        Queue: {$Ticket->QueueObj->Name}
      Subject: {$Transaction->Subject || $Ticket->Subject || "(No subject given)"}
        Owner: {$Ticket->OwnerObj->Name}
   Requestors: {$Ticket->RequestorAddresses}
       Status: {$Ticket->Status}
- Ticket id} >
+ Ticket Config->Get(\'WebURL\')}Ticket/Display.html?id={$Ticket->id} >
 
 
 {$Transaction->Content()}
@@ -276,7 +251,7 @@ Transaction: {$Transaction->Description}
       Content     => 'RT-Attach-Message: yes
 
 
-id} >
+Config->Get(\'WebURL\')}Ticket/Display.html?id={$Ticket->id} >
 
 {$Transaction->Content()}
 '
@@ -295,10 +270,11 @@ Transaction: {$Transaction->Description}
        Name        => 'Admin Comment',                           # loc
        Description => 'Default admin comment template',          # loc
        Content     =>
-'Subject: [Comment] {my $s=($Transaction->Subject||$Ticket->Subject); $s =~ s/\\[Comment\\]//g; $comment =~ s/^Re//i; $s;}
+'Subject: [Comment] {my $s=($Transaction->Subject||$Ticket->Subject); $s =~ s/\\[Comment\\]//g; $s =~ s/^Re//i; $s;}
+RT-Attach-Message: yes
 
 
-{$RT::WebURL}Ticket/Display.html?id={$Ticket->id}
+{RT->Config->Get(\'WebURL\')}Ticket/Display.html?id={$Ticket->id}
 This is a comment.  It is not sent to the Requestor(s):
 
 {$Transaction->Content()}
@@ -311,7 +287,7 @@ This is a comment.  It is not sent to the Requestor(s):
        Content     => 'Subject: Status Changed to: {$Transaction->NewValue}
 
 
-{$RT::WebURL}Ticket/Display.html?id={$Ticket->id}
+{RT->Config->Get(\'WebURL\')}Ticket/Display.html?id={$Ticket->id}
 
 {$Transaction->Content()}
 '
@@ -339,8 +315,8 @@ Greetings,
 There is a new item pending your approval: "{$Ticket->Subject()}", 
 a summary of which appears below.
 
-Please visit {$RT::WebURL}Approvals/Display.html?id={$Ticket->id}
-to approve or reject this ticket, or {$RT::WebURL}Approvals/ to
+Please visit {RT->Config->Get(\'WebURL\')}Approvals/Display.html?id={$Ticket->id}
+to approve or reject this ticket, or {RT->Config->Get(\'WebURL\')}Approvals/ to
 batch-process all your pending approvals.
 
 -------------------------------------------------------------------------
@@ -350,24 +326,29 @@ batch-process all your pending approvals.
     {  Queue       => '___Approvals',
        Name        => "Approval Passed",    # loc
        Description =>
-         "Notify Owner of their ticket has been approved by some approver", # loc
+         "Notify Requestor of their ticket has been approved by some approver", # loc
        Content => 'Subject: Ticket Approved: {$Ticket->Subject}
 
 Greetings,
 
 Your ticket has been approved by { eval { $Approval->OwnerObj->Name } }.
 Other approvals may be pending.
+
+Approver\'s notes: { $Notes }
 '
     },
     {  Queue       => '___Approvals',
        Name        => "All Approvals Passed",    # loc
        Description =>
-         "Notify Owner of their ticket has been approved by all approvers", # loc
+         "Notify Requestor of their ticket has been approved by all approvers", # loc
        Content => 'Subject: Ticket Approved: {$Ticket->Subject}
 
 Greetings,
 
-Your ticket has been approved.  Its Owner may now start to act on it.
+Your ticket has been approved by { eval { $Approval->OwnerObj->Name } }.
+Its Owner may now start to act on it.
+
+Approver\'s notes: { $Notes }
 '
     },
     {  Queue       => '___Approvals',
@@ -379,211 +360,190 @@ Your ticket has been approved.  Its Owner may now start to act on it.
 Greetings,
 
 Your ticket has been rejected by { eval { $Approval->OwnerObj->Name } }.
+
+Approver\'s notes: { $Notes }
+'
+    },
+    {  Queue       => '___Approvals',
+       Name        => "Approval Ready for Owner",    # loc
+       Description =>
+         "Notify Owner of their ticket has been approved and is ready to be acted on", # loc
+       Content => 'Subject: Ticket Approved: {$Ticket->Subject}
+
+Greetings,
+
+The ticket has been approved, you may now start to act on it.
+
 '
     },
+    {  Queue       => 0,
+       Name        => "Forward",    # loc
+       Description => "Heading of a forwarded message", # loc
+       Content => q{
+This is a forward of transaction #{$Transaction->id} of ticket #{ $Ticket->id }
+}
+    },
+    {  Queue       => 0,
+       Name        => "Forward Ticket",    # loc
+       Description => "Heading of a forwarded Ticket", # loc
+       Content => q{
+
+This is a forward of ticket #{ $Ticket->id }
+}
+    },
+    {  Queue       => 0,
+       Name        => "Error: public key",    # loc
+       Description =>
+         "Inform user that he has problems with public key and couldn't recieve encrypted content", # loc
+       Content => q{Subject: We have no your public key or it's wrong
+
+You received this message as we have no your public PGP key or we have a problem with your key. Inform the administrator about the problem.
+}
+    },
+    {  Queue       => 0,
+       Name        => "Error to RT owner: public key",    # loc
+       Description =>
+         "Inform RT owner that user(s) have problems with public keys", # loc
+       Content => q{Subject: Some users have problems with public keys
+
+You received this message as RT has problems with public keys of the following user:
+{
+    foreach my $e ( @BadRecipients ) {
+        $OUT .= "* ". $e->{'Message'} ."\n";
+    }
+}}
+    },
+    {  Queue       => 0,
+       Name        => "Error: no private key",    # loc
+       Description =>
+         "Inform user that we received an encrypted email and we have no private keys to decrypt", # loc
+       Content => q{Subject: we received message we cannot decrypt
+
+You sent an encrypted message with subject '{ $Message->head->get('Subject') }',
+but we have no private key it's encrypted to.
+
+Please, check that you encrypt messages with correct keys
+or contact the system administrator.}
+    },
+    {  Queue       => 0,
+       Name        => "Error: bad GnuPG data",    # loc
+       Description =>
+         "Inform user that a message he sent has invalid GnuPG data", # loc
+       Content => q{Subject: We received a message we cannot handle
+
+You sent us a message that we cannot handle due to corrupted GnuPG signature or encrypted block. we get the following error(s):
+{ foreach my $msg ( @Messages ) {
+    $OUT .= "* $msg\n";
+  }
+}}
+    },
+    {  Queue       => 0,
+       Name        => "PasswordChange",    # loc
+       Description =>
+         "Inform user that his password has been reset", # loc
+       Content => q{Subject: [{RT->Config->Get('rtname')}] Password reset
+
+Greetings,
+
+Someone at {$ENV{'REMOTE_ADDR'}} requested a password reset for you on {RT->Config->Get('WebURL')}
+
+Your new password is:
+  {$NewPassword}
+}
+    },
+
+	       {   Queue       => '0',
+		   Name        => 'Email Digest',    # loc
+		   Description => 'Email template for periodic notification digests',  # loc
+		   Content => q[Subject: RT Email Digest
+
+{ $Argument }
+],
+               },
+
+{
+    Queue       => 0,
+    Name        => "Error: Missing dashboard",    # loc
+    Description =>
+      "Inform user that a dashboard he subscribed to is missing", # loc
+    Content => q{Subject: [{RT->Config->Get('rtname')}] Missing dashboard!
+
+Greetings,
+
+You are subscribed to a dashboard that is currently missing. Most likely, the dashboard was deleted.
+
+RT will remove this subscription as it is no longer useful. Here's the information RT had about your subscription:
+
+DashboardID:  { $SubscriptionObj->SubValue('DashboardId') }
+Frequency:    { $SubscriptionObj->SubValue('Frequency') }
+Hour:         { $SubscriptionObj->SubValue('Hour') }
+{
+    $SubscriptionObj->SubValue('Frequency') eq 'weekly'
+    ? "Day of week:  " . $SubscriptionObj->SubValue('Dow')
+    : $SubscriptionObj->SubValue('Frequency') eq 'monthly'
+      ? "Day of month: " . $SubscriptionObj->SubValue('Dom')
+      : ''
+}
+}
+},
 );
 # }}}
 
 @Scrips = (
-    {  ScripCondition => 'On Correspond',
+    {  Description    => 'On Correspond Open Tickets',
+       ScripCondition => 'On Correspond',
        ScripAction    => 'Open Tickets',
        Template       => 'Blank' },
-    {  ScripCondition => 'On Owner Change',
+    {  Description    => 'On Owner Change Notify Owner',
+       ScripCondition => 'On Owner Change',
        ScripAction    => 'Notify Owner',
        Template       => 'Transaction' },
-    {  ScripCondition => 'On Create',
+    {  Description    => 'On Create Autoreply To Requestors',
+       ScripCondition => 'On Create',
        ScripAction    => 'AutoReply To Requestors',
        Template       => 'AutoReply' },
-    {  ScripCondition => 'On Create',
+    {  Description    => 'On Create Notify AdminCcs',
+       ScripCondition => 'On Create',
        ScripAction    => 'Notify AdminCcs',
        Template       => 'Transaction' },
-    {  ScripCondition => 'On Correspond',
+    {  Description    => 'On Correspond Notify AdminCcs',
+       ScripCondition => 'On Correspond',
        ScripAction    => 'Notify AdminCcs',
        Template       => 'Admin Correspondence' },
-    {  ScripCondition => 'On Correspond',
+    {  Description    => 'On Correspond Notify Requestors and Ccs',
+       ScripCondition => 'On Correspond',
        ScripAction    => 'Notify Requestors And Ccs',
        Template       => 'Correspondence' },
-    {  ScripCondition => 'On Correspond',
+    {  Description    => 'On Correspond Notify Other Recipients',
+       ScripCondition => 'On Correspond',
        ScripAction    => 'Notify Other Recipients',
        Template       => 'Correspondence' },
-    {  ScripCondition => 'On Comment',
+    {  Description    => 'On Comment Notify AdminCcs as Comment',
+       ScripCondition => 'On Comment',
        ScripAction    => 'Notify AdminCcs As Comment',
        Template       => 'Admin Comment' },
-    {  ScripCondition => 'On Comment',
+    {  Description    => 'On Comment Notify Other Recipients as Comment',
+       ScripCondition => 'On Comment',
        ScripAction    => 'Notify Other Recipients As Comment',
        Template       => 'Correspondence' },
-    {  ScripCondition => 'On Resolve',
+    {  Description    => 'On Resolve Notify Requestors',
+       ScripCondition => 'On Resolve',
        ScripAction    => 'Notify Requestors',
        Template       => 'Resolved' },
-    {  Description => "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval",    # loc
-       Queue          => '___Approvals',
-       ScripCondition => 'User Defined',
-       CustomIsApplicableCode => q[
-	    $self->TicketObj->Type eq 'approval'	and
-	    $self->TransactionObj->Field eq 'Status'	and
-	    $self->TransactionObj->NewValue eq 'open'   and
-	    eval { $T::Approving = ($self->TicketObj->AllDependedOnBy( Type => 'ticket' ))[0] }
-       ],
-       ScripAction    => 'Notify Owner',
-       Template       => 'New Pending Approval' },
-    {  Description => "If an approval is rejected, reject the original and delete pending approvals",    # loc
-       Queue            => '___Approvals',
-       ScripCondition   => 'On Status Change',
-       ScripAction      => 'User Defined',
-       CustomPrepareCode => q[
-# ------------------------------------------------------------------- #
-return(0) unless ( lc($self->TransactionObj->NewValue) eq "rejected" or
-	           lc($self->TransactionObj->NewValue) eq "deleted" );
-
-my $rejected = 0;
-my $links = $self->TicketObj->DependedOnBy;
-foreach my $link (@{ $links->ItemsArrayRef }) {
-    my $obj = $link->BaseObj;
-    if ($obj->QueueObj->IsActiveStatus($obj->Status)) {
-	if ($obj->Type eq 'ticket') {
-	    $obj->Comment(
-		Content	=> $self->loc("Your request was rejected."),
-	    );
-	    $obj->SetStatus(
-		Status	=> 'rejected',
-		Force	=> 1,
-	    );
-
-	    $T::Approval = $self->TicketObj; # so we can access it inside templates
-	    $self->{TicketObj} = $obj;  # we want the original id in the token line
-	    $rejected = 1;
-	}
-	else {
-	    $obj->SetStatus(
-		Status	=> 'deleted',
-		Force	=> 1,
-	    );
-	}
-    }
-}
-
-$links = $self->TicketObj->DependsOn;
-foreach my $link (@{ $links->ItemsArrayRef }) {
-    my $obj = $link->TargetObj;
-    if ($obj->QueueObj->IsActiveStatus($obj->Status)) {
-	$obj->SetStatus(
-	    Status	=> 'deleted',
-	    Force	=> 1,
-	);
-    }
-}
-
-# Now magically turn myself into a Requestor Notify object...
-require RT::Action::Notify; bless($self, 'RT::Action::Notify');
-$self->{Argument} = 'Requestor'; $self->Prepare;
-
-return $rejected;
-# ------------------------------------------------------------------- #
-	],
-       CustomCommitCode => '"never needed"',
-       Template          => 'Approval Rejected', },
-    {  Description => "When a ticket has been approved by any approver, add correspondence to the original ticket", # loc
-       Queue             => '___Approvals',
-       ScripCondition    => 'On Resolve',
-       ScripAction       => 'User Defined',
-       CustomPrepareCode => q[
-# ------------------------------------------------------------------- #
-return(0) unless ($self->TicketObj->Type eq 'approval');
-
-my $note;
-my $t = $self->TicketObj->Transactions;
-while (my $o = $t->Next) {
-    $note .= $o->Content . "\n" if $o->ContentObj
-	    and $o->Content !~ /Default Approval/;
-}
-
-foreach my $obj ($self->TicketObj->AllDependedOnBy( Type => 'ticket' )) {
-    $obj->Comment(
-	Content => $self->loc( "Your request has been approved by [_1]. Other approvals may still be pending.", # loc
-	    $self->TransactionObj->CreatorObj->Name,
-	) . "\n" . $self->loc( "Approver's notes: [_1]", # loc
-	    $note
-	),
-    );
-    $T::Approval = $self->TicketObj; # so we can access it inside templates
-    $self->{TicketObj} = $obj;  # we want the original id in the token line
-}
-
-# Now magically turn myself into a Requestor Notify object...
-require RT::Action::Notify; bless($self, 'RT::Action::Notify');
-$self->{Argument} = 'Requestor'; $self->Prepare;
-
-return 1;
-# ------------------------------------------------------------------- #
-	],
-       CustomCommitCode => '"never needed"',
-       Template => 'Approval Passed' },
-    {  Description => "When a ticket has been approved by all approvers, add correspondence to the original ticket", # loc
-       Queue             => '___Approvals',
-       ScripCondition    => 'On Resolve',
-       ScripAction       => 'User Defined',
-       CustomPrepareCode  => q[
-# ------------------------------------------------------------------- #
-# Find all the tickets that depend on this (that this is approving)
-
-my $Ticket = $self->TicketObj;
-my @TOP    = $Ticket->AllDependedOnBy( Type => 'ticket' );
-my $links  = $Ticket->DependedOnBy;
-my $passed = 0;
-
-while (my $link = $links->Next) {
-    my $obj = $link->BaseObj;
-    next if ($obj->HasUnresolvedDependencies( Type => 'approval' ));
-
-    if ($obj->Type eq 'ticket') {
-	$obj->Comment(
-	    Content	=> $self->loc("Your request has been approved."),
-	);
-	$T::Approval  = $Ticket;    # so we can access it inside templates
-	$self->{TicketObj} = $obj;  # we want the original id in the token line
-	$passed = 1;
-    }
-    elsif ($obj->Type eq 'approval') {
-	$obj->SetStatus( Status => 'open', Force => 1 );
-    }
-    elsif ($RT::UseCodeTickets and $obj->Type eq 'code') {
-	my $code = $obj->Transactions->First->Content;
-	my $rv;
-
-	foreach my $TOP (@TOP) {
-	    local $@;
-	    $rv++ if eval $code;
-	    $RT::Logger->error("Cannot eval code: $@") if $@;
-	}
-
-	if ($rv or !@TOP) {
-	    $obj->SetStatus( Status	=> 'resolved', Force	=> 1,);
-	}
-	else {
-	    $obj->SetStatus( Status	=> 'rejected', Force	=> 1,);
-	}
-    }
-}
-
-# Now magically turn myself into a Requestor Notify object...
-require RT::Action::Notify; bless($self, 'RT::Action::Notify');
-$self->{Argument} = 'Requestor'; $self->Prepare;
-
-return 0; # ignore $passed;
-# ------------------------------------------------------------------- #
-	],
-       CustomCommitCode => '"never needed"',
-       Template => 'All Approvals Passed', },
-
+    {  Description    => "On transaction, add any tags in the transaction's subject to the ticket's subject",
+       ScripCondition => 'On Transaction',
+       ScripAction    => 'Extract Subject Tag',
+       Template       => 'Blank' },
 );
 
 @ACL = (
-    { UserId => 'Nobody',      # - principalId
-      Right  => 'OwnTicket', },
-
     { UserId => 'root',        # - principalid
       Right  => 'SuperUser', },
 
+    { GroupDomain => 'SystemInternal',
+      GroupType => 'privileged',
+      Right  => 'ShowApprovalsTab', },
+
 );
 
 # Predefined searches
@@ -592,34 +552,53 @@ return 0; # ignore $passed;
     { Name => 'Search - My Tickets',
       Description => '[_1] highest priority tickets I own', # loc
       Content     =>
-      { Format => "'__id__/TITLE:#', '__Subject__/TITLE:Subject', Priority, QueueName, ExtendedStatus",
+      { Format =>  q{'__id__/TITLE:#',}
+                 . q{'__Subject__/TITLE:Subject',}
+                 . q{Priority, QueueName, ExtendedStatus},
         Query   => " Owner = '__CurrentUser__' AND ( Status = 'new' OR Status = 'open')",
         OrderBy => 'Priority',
-        Order   => 'DESC' },
+        Order   => 'DESC'
+      },
     },
     { Name => 'Search - Unowned Tickets',
       Description => '[_1] newest unowned tickets', # loc
       Content     =>
 # 'Take' #loc
-      { Format => "'__id__/TITLE:#', '__Subject__/TITLE:Subject', QueueName, ExtendedStatus, CreatedRelative, '__loc(Take)__/TITLE: ' ",
+      { Format =>  q{'__id__/TITLE:#',}
+                 . q{'__Subject__/TITLE:Subject',}
+                 . q{QueueName, ExtendedStatus, CreatedRelative, }
+                 . q{'__loc(Take)__/TITLE:NBSP'},
         Query   => " Owner = 'Nobody' AND ( Status = 'new' OR Status = 'open')",
         OrderBy => 'Created',
+        Order   => 'DESC'
+      },
+    },
+    { Name => 'Search - Bookmarked Tickets',
+      Description => 'Bookmarked Tickets', #loc
+      Content     =>
+      { Format => q{'__id__/TITLE:#',}
+                . q{'__Subject__/TITLE:Subject',}
+                . q{Priority, QueueName, ExtendedStatus, Bookmark},
+        Query   => "id = '__Bookmarked__'",
+        OrderBy => 'LastUpdated',
         Order   => 'DESC' },
     },
     { Name => 'HomepageSettings',
       Description => 'HomepageSettings',
       Content =>
       { 'body' => # loc
-	[ { type => 'system', name => 'My Tickets' },
-	  { type => 'system', name => 'Unowned Tickets' },
-	  { type => 'component',  name => 'QuickCreate'},
-	],
+        [ { type => 'system', name => 'My Tickets' },
+          { type => 'system', name => 'Unowned Tickets' },
+          { type => 'system', name => 'Bookmarked Tickets' },
+          { type => 'component', name => 'QuickCreate' },
+        ],
         'summary' => # loc
-	[ 
-	  { type => 'component', name => 'MyReminders' },
+        [
+          { type => 'component', name => 'MyReminders' },
           { type => 'component', name => 'Quicksearch' },
-	  { type => 'component', name => 'RefreshHomepage' },
-	]
+          { type => 'component', name => 'Dashboards' },
+          { type => 'component', name => 'RefreshHomepage' },
+        ],
+      },
     },
-}
 );
diff --git a/rt/etc/schema.Oracle b/rt/etc/schema.Oracle
index 77efefadb..693e75ae5 100644
--- a/rt/etc/schema.Oracle
+++ b/rt/etc/schema.Oracle
@@ -182,6 +182,7 @@ CREATE TABLE CachedGroupMembers (
 );
 CREATE INDEX DisGrouMem ON CachedGroupMembers (GroupId, MemberId, Disabled);
 CREATE INDEX GrouMem ON CachedGroupMembers (GroupId, MemberId);
+CREATE INDEX CachedGroupMembers3 on CachedGroupMembers (MemberId, ImmediateParentId);
 
 
 CREATE SEQUENCE USERS_seq;
@@ -341,7 +342,7 @@ CREATE TABLE CustomFields (
 	Name		VARCHAR2(200),
 	Type		VARCHAR2(200),
 	MaxValues	NUMBER(11,0) DEFAULT 0 NOT NULL,
-	Pattern		VARCHAR2(255),
+	Pattern		CLOB,
         Repeated        NUMBER(11,0) DEFAULT 0 NOT NULL,
 	Description	VARCHAR2(255),
 	SortOrder	NUMBER(11,0) DEFAULT 0 NOT NULL,
diff --git a/rt/etc/schema.Pg b/rt/etc/schema.Pg
index 2d45a946a..48525c8d7 100755
--- a/rt/etc/schema.Pg
+++ b/rt/etc/schema.Pg
@@ -283,6 +283,8 @@ CREATE TABLE GroupMembers (
 
 );
 
+CREATE UNIQUE INDEX GroupMembers1 ON GroupMembers(GroupId, MemberId);
+
 -- }}}
 
 -- {{{ GroupMembersCache
@@ -364,7 +366,6 @@ CREATE TABLE Users (
 
 
 CREATE UNIQUE INDEX Users1 ON Users (Name) ;
-CREATE INDEX Users2 ON Users (Name);
 CREATE INDEX Users3 ON Users (id, EmailAddress);
 CREATE INDEX Users4 ON Users (EmailAddress);
 
@@ -528,7 +529,7 @@ CREATE TABLE CustomFields (
   Type varchar(200) NULL  ,
   MaxValues integer NOT NULL DEFAULT 0  ,
   Repeated integer NOT NULL DEFAULT 0 , 
-  Pattern varchar(255) NULL  ,
+  Pattern varchar(65536) NULL  ,
   LookupType varchar(255) NOT NULL  ,
   Description varchar(255) NULL  ,
   SortOrder integer NOT NULL DEFAULT 0  ,
diff --git a/rt/etc/schema.SQLite b/rt/etc/schema.SQLite
index 8791bb47e..ce75ccc13 100644
--- a/rt/etc/schema.SQLite
+++ b/rt/etc/schema.SQLite
@@ -367,7 +367,7 @@ CREATE TABLE CustomFields (
   Name varchar(200) NULL  ,
   Type varchar(200) NULL  ,	# Changed -- 'Single' and 'Multiple' is moved out
   MaxValues integer,		# New -- was 'Single'(1) and 'Multiple'(0)
-  Pattern varchar(255) NULL  ,	# New -- Must validate against this
+  Pattern varchar(65536) NULL  ,	# New -- Must validate against this
   Repeated int2 NOT NULL DEFAULT 0 , # New -- repeated table entry
   Description varchar(255) NULL  ,
   SortOrder integer NOT NULL DEFAULT 0  ,
diff --git a/rt/etc/schema.mysql-4.0 b/rt/etc/schema.mysql-4.0
new file mode 100755
index 000000000..c4299d472
--- /dev/null
+++ b/rt/etc/schema.mysql-4.0
@@ -0,0 +1,464 @@
+# {{{ Attachments
+
+CREATE TABLE Attachments (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  TransactionId integer NOT NULL  ,
+  Parent integer NOT NULL DEFAULT 0  ,
+  MessageId varchar(160) NULL  ,
+  Subject varchar(255) NULL  ,
+  Filename varchar(255) NULL  ,
+  ContentType varchar(80) NULL  ,
+  ContentEncoding varchar(80) NULL  ,
+  Content LONGTEXT NULL  ,
+  Headers LONGTEXT NULL  ,
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+CREATE INDEX Attachments2 ON Attachments (TransactionId) ;
+CREATE INDEX Attachments3 ON Attachments (Parent, TransactionId) ;
+# }}}
+
+# {{{ Queues
+CREATE TABLE Queues (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  Name varchar(200) NOT NULL  ,
+  Description varchar(255) NULL  ,
+  CorrespondAddress varchar(120) NULL  ,
+  CommentAddress varchar(120) NULL  ,
+  InitialPriority integer NOT NULL DEFAULT 0  ,
+  FinalPriority integer NOT NULL DEFAULT 0  ,
+  DefaultDueIn integer NOT NULL DEFAULT 0  ,
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  Disabled int2 NOT NULL DEFAULT 0 ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB;
+CREATE UNIQUE INDEX Queues1 ON Queues (Name) ;
+CREATE INDEX Queues2 ON Queues (Disabled) ;
+
+# }}}
+
+# {{{ Links
+
+CREATE TABLE Links (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  Base varchar(240) NULL  ,
+  Target varchar(240) NULL  ,
+  Type varchar(20) NOT NULL  ,
+  LocalTarget integer NOT NULL DEFAULT 0  ,
+  LocalBase integer NOT NULL DEFAULT 0  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+CREATE INDEX Links2 ON Links (Base,  Type) ;
+CREATE INDEX Links3 ON Links (Target,  Type) ;
+CREATE INDEX Links4 ON Links (Type,LocalBase);
+
+# }}}
+
+# {{{ Principals
+
+CREATE TABLE Principals (
+        id INTEGER  AUTO_INCREMENT not null,
+        PrincipalType VARCHAR(16) not null,
+        ObjectId integer, # foreign key to Users or Groups, depending
+        Disabled int2 NOT NULL DEFAULT 0 ,
+        PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+CREATE INDEX Principals2 ON Principals (ObjectId);
+
+# }}}
+
+# {{{ Groups
+
+CREATE TABLE Groups (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  Name varchar(200) NULL  ,
+  Description varchar(255) NULL  ,
+  Domain varchar(64),
+  Type varchar(64),
+  Instance integer,
+  PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+CREATE INDEX Groups1 ON Groups (Domain,Instance,Type,id);
+CREATE INDEX Groups2 On Groups (Type, Instance);   
+
+# }}}
+
+# {{{ ScripConditions
+
+CREATE TABLE ScripConditions (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  Name varchar(200) NULL  ,
+  Description varchar(255) NULL  ,
+  ExecModule varchar(60) NULL  ,
+  Argument varchar(255) NULL  ,
+  ApplicableTransTypes varchar(60) NULL  ,
+
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+# }}}
+
+# {{{ Transactions
+CREATE TABLE Transactions (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  ObjectType varchar(64) NOT NULL,
+  ObjectId integer NOT NULL DEFAULT 0  ,
+  TimeTaken integer NOT NULL DEFAULT 0  ,
+  Type varchar(20) NULL  ,
+  Field varchar(40) NULL  ,
+  OldValue varchar(255) NULL  ,
+  NewValue varchar(255) NULL  ,
+  ReferenceType varchar(255) NULL,
+  OldReference integer NULL  ,
+  NewReference integer NULL  ,
+  Data varchar(255) NULL  ,
+
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB;
+CREATE INDEX Transactions1 ON Transactions (ObjectType, ObjectId);
+
+# }}}
+
+# {{{ Scrips 
+
+CREATE TABLE Scrips (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  Description varchar(255),
+  ScripCondition integer NOT NULL DEFAULT 0  ,
+  ScripAction integer NOT NULL DEFAULT 0  ,
+  ConditionRules text NULL  ,
+  ActionRules text NULL  ,
+  CustomIsApplicableCode text NULL  ,
+  CustomPrepareCode text NULL  ,
+  CustomCommitCode text NULL  ,
+  Stage varchar(32) NULL  ,
+  Queue integer NOT NULL DEFAULT 0  ,
+  Template integer NOT NULL DEFAULT 0  ,
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+# }}}
+
+# {{{ ACL
+CREATE TABLE ACL (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  PrincipalType varchar(25) NOT NULL, #"User" "Group", "Owner", "Cc" "AdminCc", "Requestor", "Requestor" 
+
+  PrincipalId integer NOT NULL  , #Foreign key to principals
+  RightName varchar(25) NOT NULL  ,
+  ObjectType varchar(25) NOT NULL  ,
+  ObjectId integer NOT NULL default 0,
+  DelegatedBy integer NOT NULL default 0, #foreign key to principals with a userid
+  DelegatedFrom integer NOT NULL default 0, #foreign key to ACL
+  PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+CREATE INDEX  ACL1 on ACL(RightName, ObjectType, ObjectId,PrincipalType,PrincipalId);
+
+# }}}
+
+# {{{ GroupMembers 
+
+CREATE TABLE GroupMembers (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  GroupId integer NOT NULL DEFAULT 0,
+  MemberId integer NOT NULL DEFAULT 0,  #Foreign key to Principals
+  PRIMARY KEY (id)
+) TYPE=InnoDB;
+CREATE UNIQUE INDEX GroupMembers1 on GroupMembers (GroupId, MemberId);
+
+
+# }}}
+
+# {{{ GroupMembersCache
+
+create table CachedGroupMembers (
+        id int auto_increment,
+        GroupId int, # foreign key to Principals
+        MemberId int, # foreign key to Principals
+        Via int, #foreign key to CachedGroupMembers. (may point to $self->id)
+        ImmediateParentId int, #foreign key to prinicpals.         
+                               # this points to the group that the member is
+                               # a member of, for ease of deletes.
+        Disabled int2 NOT NULL DEFAULT 0 , # if this cached group member is a member of this group by way of a disabled
+                                           # group or this group is disabled, this will be set to 1
+                                           # this allows us to not find members of disabled subgroups when listing off
+                                           # group members recursively.
+                                           # Also, this allows us to have the ACL system elide members of disabled groups
+        PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+CREATE INDEX DisGrouMem  on CachedGroupMembers (GroupId,MemberId,Disabled);
+CREATE INDEX CachedGroupMembers3 on CachedGroupMembers (MemberId, ImmediateParentId);
+
+# }}}
+
+# {{{ Users
+
+CREATE TABLE Users (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  Name varchar(200) NOT NULL  ,
+  Password varchar(40) NULL  ,
+  Comments blob NULL  ,
+  Signature blob NULL  ,
+  EmailAddress varchar(120) NULL  ,
+  FreeformContactInfo blob NULL  ,
+  Organization varchar(200) NULL  ,
+  RealName varchar(120) NULL  ,
+  NickName varchar(16) NULL  ,
+  Lang varchar(16) NULL  ,
+  EmailEncoding varchar(16) NULL  ,
+  WebEncoding varchar(16) NULL  ,
+  ExternalContactInfoId varchar(100) NULL  ,
+  ContactInfoSystem varchar(30) NULL  ,
+  ExternalAuthId varchar(100) NULL  ,
+  AuthSystem varchar(30) NULL  ,
+  Gecos varchar(16) NULL  ,
+  HomePhone varchar(30) NULL  ,
+  WorkPhone varchar(30) NULL  ,
+  MobilePhone varchar(30) NULL  ,
+  PagerPhone varchar(30) NULL  ,
+  Address1 varchar(200) NULL  ,
+  Address2 varchar(200) NULL  ,
+  City varchar(100) NULL  ,
+  State varchar(100) NULL  ,
+  Zip varchar(16) NULL  ,
+  Country varchar(50) NULL  ,
+  Timezone varchar(50) NULL  ,
+  PGPKey text NULL,
+
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+
+CREATE UNIQUE INDEX Users1 ON Users (Name) ;
+CREATE INDEX Users4 ON Users (EmailAddress);
+
+
+# }}}
+
+# {{{ Tickets
+
+CREATE TABLE Tickets (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  EffectiveId integer NOT NULL DEFAULT 0  ,
+  Queue integer NOT NULL DEFAULT 0  ,
+  Type varchar(16) NULL  ,
+  IssueStatement integer NOT NULL DEFAULT 0  ,
+  Resolution integer NOT NULL DEFAULT 0  ,
+  Owner integer NOT NULL DEFAULT 0  ,
+  Subject varchar(200) NULL DEFAULT '[no subject]' ,
+  InitialPriority integer NOT NULL DEFAULT 0  ,
+  FinalPriority integer NOT NULL DEFAULT 0  ,
+  Priority integer NOT NULL DEFAULT 0  ,
+  TimeEstimated integer NOT NULL DEFAULT 0  ,
+  TimeWorked integer NOT NULL DEFAULT 0  ,
+  Status varchar(10) NULL  ,
+  TimeLeft integer NOT NULL DEFAULT 0  ,
+  Told DATETIME NULL  ,
+  Starts DATETIME NULL  ,
+  Started DATETIME NULL  ,
+  Due DATETIME NULL  ,
+  Resolved DATETIME NULL  ,
+
+
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  Disabled int2 NOT NULL DEFAULT 0 ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+CREATE INDEX Tickets1 ON Tickets (Queue, Status) ;
+CREATE INDEX Tickets2 ON Tickets (Owner) ;
+CREATE INDEX Tickets6 ON Tickets (EffectiveId, Type) ;
+
+# }}}
+
+# {{{ ScripActions
+
+CREATE TABLE ScripActions (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  Name varchar(200) NULL  ,
+  Description varchar(255) NULL  ,
+  ExecModule varchar(60) NULL  ,
+  Argument varchar(255) NULL  ,
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+# }}}
+
+# {{{ Templates
+
+CREATE TABLE Templates (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  Queue integer NOT NULL DEFAULT 0 ,
+  Name varchar(200) NOT NULL  ,
+  Description varchar(255) NULL  ,
+  Type varchar(16) NULL  ,
+  Language varchar(16) NULL  ,
+  TranslationOf integer NOT NULL DEFAULT 0  ,
+  Content blob NULL  ,
+  LastUpdated DATETIME NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+# }}}
+
+# {{{ ObjectCustomFieldValues 
+
+CREATE TABLE ObjectCustomFieldValues (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  CustomField int NOT NULL  ,
+  ObjectType varchar(255) NOT NULL,	    # Final target of the Object
+  ObjectId int NOT NULL  ,		    # New -- Replaces Ticket
+  SortOrder integer NOT NULL DEFAULT 0  ,   # New -- ordering for multiple values
+
+  Content varchar(255) NULL  ,
+  LargeContent LONGTEXT NULL,		    # New -- to hold 255+ strings
+  ContentType varchar(80) NULL,		    # New -- only text/* gets searched
+  ContentEncoding varchar(80) NULL  ,	    # New -- for binary Content
+
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  Disabled int2 NOT NULL DEFAULT 0 ,        # New -- whether the value was current
+  PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+CREATE INDEX ObjectCustomFieldValues1 ON ObjectCustomFieldValues (Content); 
+CREATE INDEX ObjectCustomFieldValues2 ON ObjectCustomFieldValues (CustomField,ObjectType,ObjectId); 
+
+# }}}
+
+# {{{ CustomFields
+
+CREATE TABLE CustomFields (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  Name varchar(200) NULL  ,
+  Type varchar(200) NULL  ,	# Changed -- 'Single' and 'Multiple' is moved out
+  MaxValues integer,		# New -- was 'Single'(1) and 'Multiple'(0)
+  Pattern TEXT NULL  ,	# New -- Must validate against this
+  Repeated int2 NOT NULL DEFAULT 0 , # New -- repeated table entry
+  Description varchar(255) NULL  ,
+  SortOrder integer NOT NULL DEFAULT 0  ,
+  LookupType varchar(255) NOT NULL,
+
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  Disabled int2 NOT NULL DEFAULT 0 ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+# }}}
+
+# {{{ ObjectCustomFields 
+
+CREATE TABLE ObjectCustomFields (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  CustomField int NOT NULL  ,
+  ObjectId integer NOT NULL,
+  SortOrder integer NOT NULL DEFAULT 0  ,
+
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+# }}}
+
+# {{{ CustomFieldValues 
+
+CREATE TABLE CustomFieldValues (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  CustomField int NOT NULL  ,
+  Name varchar(200) NULL  ,
+  Description varchar(255) NULL  ,
+  SortOrder integer NOT NULL DEFAULT 0  ,
+
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+CREATE INDEX CustomFieldValues1 ON CustomFieldValues (CustomField);
+ 
+# }}}
+
+
+# {{{ Attributes
+
+CREATE TABLE Attributes (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  Name varchar(255) NULL  ,
+  Description varchar(255) NULL  ,
+  Content text,
+  ContentType varchar(16),
+  ObjectType varchar(64),
+  ObjectId integer, # foreign key to anything
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB;
+
+CREATE INDEX Attributes1 on Attributes(Name);
+CREATE INDEX Attributes2 on Attributes(ObjectType, ObjectId);
+
+# }}}
+
+# {{{ Sessions
+
+# sessions is used by Apache::Session to keep sessions in the database.
+# We should have a reaper script somewhere.
+
+CREATE TABLE sessions (
+    id char(32) NOT NULL,
+    a_session LONGTEXT,
+    LastUpdated TIMESTAMP,
+    PRIMARY KEY (id)
+);
+
+# }}}
diff --git a/rt/etc/schema.mysql-4.1 b/rt/etc/schema.mysql-4.1
new file mode 100755
index 000000000..172e477c1
--- /dev/null
+++ b/rt/etc/schema.mysql-4.1
@@ -0,0 +1,466 @@
+# {{{ Attachments
+
+CREATE TABLE Attachments (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  TransactionId integer NOT NULL  ,
+  Parent integer NOT NULL DEFAULT 0  ,
+  MessageId varchar(160) CHARACTER SET ascii NULL  ,
+  Subject varchar(255) NULL  ,
+  Filename varchar(255) NULL  ,
+  ContentType varchar(80) CHARACTER SET ascii NULL  ,
+  ContentEncoding varchar(80) CHARACTER SET ascii NULL  ,
+  Content LONGBLOB NULL  ,
+  Headers LONGTEXT NULL  ,
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB  CHARACTER SET utf8;
+
+CREATE INDEX Attachments2 ON Attachments (TransactionId) ;
+CREATE INDEX Attachments3 ON Attachments (Parent, TransactionId) ;
+# }}}
+
+# {{{ Queues
+CREATE TABLE Queues (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  Name varchar(200) NOT NULL  ,
+  Description varchar(255) NULL  ,
+  CorrespondAddress varchar(120) CHARACTER SET ascii NULL,
+  CommentAddress varchar(120) CHARACTER SET ascii NULL,
+  InitialPriority integer NOT NULL DEFAULT 0  ,
+  FinalPriority integer NOT NULL DEFAULT 0  ,
+  DefaultDueIn integer NOT NULL DEFAULT 0  ,
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  Disabled int2 NOT NULL DEFAULT 0 ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB CHARACTER SET utf8;
+
+CREATE UNIQUE INDEX Queues1 ON Queues (Name) ;
+CREATE INDEX Queues2 ON Queues (Disabled) ;
+
+# }}}
+
+# {{{ Links
+
+CREATE TABLE Links (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  Base varchar(240) NULL,
+  Target varchar(240) NULL,
+  Type varchar(20) NOT NULL,
+  LocalTarget integer NOT NULL DEFAULT 0  ,
+  LocalBase integer NOT NULL DEFAULT 0  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB CHARACTER SET ascii;
+
+CREATE INDEX Links2 ON Links (Base,  Type) ;
+CREATE INDEX Links3 ON Links (Target,  Type) ;
+CREATE INDEX Links4 ON Links (Type,LocalBase);
+
+# }}}
+
+# {{{ Principals
+
+CREATE TABLE Principals (
+        id INTEGER  AUTO_INCREMENT not null,
+        PrincipalType VARCHAR(16) not null,
+        ObjectId integer, # foreign key to Users or Groups, depending
+        Disabled int2 NOT NULL DEFAULT 0 ,
+        PRIMARY KEY (id)
+) TYPE=InnoDB CHARACTER SET ascii;
+
+CREATE INDEX Principals2 ON Principals (ObjectId);
+
+# }}}
+
+# {{{ Groups
+
+CREATE TABLE Groups (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  Name varchar(200) NULL  ,
+  Description varchar(255) NULL  ,
+  Domain varchar(64) CHARACTER SET ascii NULL,
+  Type varchar(64) CHARACTER SET ascii NULL,
+  Instance integer,
+  PRIMARY KEY (id)
+) TYPE=InnoDB CHARACTER SET utf8;
+
+CREATE INDEX Groups1 ON Groups (Domain,Instance,Type,id);
+CREATE INDEX Groups2 On Groups (Type, Instance);   
+
+# }}}
+
+# {{{ ScripConditions
+
+CREATE TABLE ScripConditions (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  Name varchar(200) NULL  ,
+  Description varchar(255) NULL  ,
+  ExecModule varchar(60) CHARACTER SET ascii NULL,
+  Argument VARBINARY(255) NULL  ,
+  ApplicableTransTypes varchar(60) CHARACTER SET ascii NULL  ,
+
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB CHARACTER SET utf8;
+
+# }}}
+
+# {{{ Transactions
+CREATE TABLE Transactions (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  ObjectType varchar(64) CHARACTER SET ascii NOT NULL,
+  ObjectId integer NOT NULL DEFAULT 0  ,
+  TimeTaken integer NOT NULL DEFAULT 0  ,
+  Type varchar(20) CHARACTER SET ascii NULL,
+  Field varchar(40) CHARACTER SET ascii NULL,
+  OldValue varchar(255) NULL  ,
+  NewValue varchar(255) NULL  ,
+  ReferenceType varchar(255) CHARACTER SET ascii NULL,
+  OldReference integer NULL  ,
+  NewReference integer NULL  ,
+  Data varchar(255) NULL  ,
+
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB CHARACTER SET utf8;
+
+CREATE INDEX Transactions1 ON Transactions (ObjectType, ObjectId);
+
+# }}}
+
+# {{{ Scrips 
+
+CREATE TABLE Scrips (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  Description varchar(255),
+  ScripCondition integer NOT NULL DEFAULT 0  ,
+  ScripAction integer NOT NULL DEFAULT 0  ,
+  ConditionRules text NULL  ,
+  ActionRules text NULL  ,
+  CustomIsApplicableCode text NULL  ,
+  CustomPrepareCode text NULL  ,
+  CustomCommitCode text NULL  ,
+  Stage varchar(32) CHARACTER SET ascii NULL  ,
+  Queue integer NOT NULL DEFAULT 0  ,
+  Template integer NOT NULL DEFAULT 0  ,
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB CHARACTER SET utf8;
+
+# }}}
+
+# {{{ ACL
+CREATE TABLE ACL (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  PrincipalType varchar(25) CHARACTER SET ascii NOT NULL, #"User" "Group", "Owner", "Cc" "AdminCc", "Requestor"
+
+  PrincipalId integer NOT NULL  , #Foreign key to principals
+  RightName varchar(25) CHARACTER SET ascii NOT NULL,
+  ObjectType varchar(25) CHARACTER SET ascii NOT NULL,
+  ObjectId integer NOT NULL default 0,
+  DelegatedBy integer NOT NULL default 0, #foreign key to principals with a userid
+  DelegatedFrom integer NOT NULL default 0, #foreign key to ACL
+  PRIMARY KEY (id)
+) TYPE=InnoDB CHARACTER SET utf8;
+
+CREATE INDEX  ACL1 on ACL(RightName, ObjectType, ObjectId,PrincipalType,PrincipalId);
+
+# }}}
+
+# {{{ GroupMembers 
+
+CREATE TABLE GroupMembers (
+  id INTEGER NOT NULL AUTO_INCREMENT,
+  GroupId integer NOT NULL DEFAULT 0,
+  MemberId integer NOT NULL DEFAULT 0,  #Foreign key to Principals
+  PRIMARY KEY (id)
+) TYPE=InnoDB CHARACTER SET utf8;
+CREATE UNIQUE INDEX GroupMembers1 on GroupMembers (GroupId, MemberId);
+
+
+# }}}
+
+# {{{ GroupMembersCache
+
+create table CachedGroupMembers (
+        id int auto_increment,
+        GroupId int, # foreign key to Principals
+        MemberId int, # foreign key to Principals
+        Via int, #foreign key to CachedGroupMembers. (may point to $self->id)
+        ImmediateParentId int, #foreign key to prinicpals.         
+                               # this points to the group that the member is
+                               # a member of, for ease of deletes.
+        Disabled int2 NOT NULL DEFAULT 0 , # if this cached group member is a member of this group by way of a disabled
+                                           # group or this group is disabled, this will be set to 1
+                                           # this allows us to not find members of disabled subgroups when listing off
+                                           # group members recursively.
+                                           # Also, this allows us to have the ACL system elide members of disabled groups
+        PRIMARY KEY (id)
+) TYPE=InnoDB CHARACTER SET utf8;
+
+CREATE INDEX DisGrouMem  on CachedGroupMembers (GroupId,MemberId,Disabled);
+CREATE INDEX CachedGroupMembers3 on CachedGroupMembers (MemberId, ImmediateParentId);
+
+# }}}
+
+# {{{ Users
+
+CREATE TABLE Users (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  Name varchar(200) NOT NULL  ,
+  Password VARBINARY(40) NULL  ,
+  Comments TEXT NULL  ,
+  Signature TEXT NULL  ,
+  EmailAddress varchar(120) NULL  ,
+  FreeformContactInfo TEXT NULL  ,
+  Organization varchar(200) NULL  ,
+  RealName varchar(120) NULL  ,
+  NickName varchar(16) NULL  ,
+  Lang varchar(16) NULL  ,
+  EmailEncoding varchar(16) NULL  ,
+  WebEncoding varchar(16) NULL  ,
+  ExternalContactInfoId varchar(100) NULL  ,
+  ContactInfoSystem varchar(30) NULL  ,
+  ExternalAuthId varchar(100) NULL  ,
+  AuthSystem varchar(30) NULL  ,
+  Gecos varchar(16) NULL  ,
+  HomePhone varchar(30) NULL  ,
+  WorkPhone varchar(30) NULL  ,
+  MobilePhone varchar(30) NULL  ,
+  PagerPhone varchar(30) NULL  ,
+  Address1 varchar(200) NULL  ,
+  Address2 varchar(200) NULL  ,
+  City varchar(100) NULL  ,
+  State varchar(100) NULL  ,
+  Zip varchar(16) NULL  ,
+  Country varchar(50) NULL  ,
+  Timezone varchar(50) NULL  ,
+  PGPKey text NULL,
+
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB CHARACTER SET utf8;
+
+
+CREATE UNIQUE INDEX Users1 ON Users (Name) ;
+CREATE INDEX Users4 ON Users (EmailAddress);
+
+
+# }}}
+
+# {{{ Tickets
+
+CREATE TABLE Tickets (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  EffectiveId integer NOT NULL DEFAULT 0  ,
+  Queue integer NOT NULL DEFAULT 0  ,
+  Type varchar(16) CHARACTER SET ascii NULL  ,
+  IssueStatement integer NOT NULL DEFAULT 0  ,
+  Resolution integer NOT NULL DEFAULT 0  ,
+  Owner integer NOT NULL DEFAULT 0  ,
+  Subject varchar(200) NULL DEFAULT '[no subject]' ,
+  InitialPriority integer NOT NULL DEFAULT 0  ,
+  FinalPriority integer NOT NULL DEFAULT 0  ,
+  Priority integer NOT NULL DEFAULT 0  ,
+  TimeEstimated integer NOT NULL DEFAULT 0  ,
+  TimeWorked integer NOT NULL DEFAULT 0  ,
+  Status varchar(10) NULL  ,
+  TimeLeft integer NOT NULL DEFAULT 0  ,
+  Told DATETIME NULL  ,
+  Starts DATETIME NULL  ,
+  Started DATETIME NULL  ,
+  Due DATETIME NULL  ,
+  Resolved DATETIME NULL  ,
+
+
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  Disabled int2 NOT NULL DEFAULT 0 ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB CHARACTER SET utf8;
+
+CREATE INDEX Tickets1 ON Tickets (Queue, Status) ;
+CREATE INDEX Tickets2 ON Tickets (Owner) ;
+CREATE INDEX Tickets6 ON Tickets (EffectiveId, Type) ;
+
+# }}}
+
+# {{{ ScripActions
+
+CREATE TABLE ScripActions (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  Name varchar(200) NULL  ,
+  Description varchar(255) NULL  ,
+  ExecModule varchar(60) CHARACTER SET ascii NULL,
+  Argument VARBINARY(255) NULL  ,
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB CHARACTER SET utf8;
+
+# }}}
+
+# {{{ Templates
+
+CREATE TABLE Templates (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  Queue integer NOT NULL DEFAULT 0 ,
+  Name varchar(200) NOT NULL  ,
+  Description varchar(255) NULL  ,
+  Type varchar(16) CHARACTER SET ascii NULL  ,
+  Language varchar(16) CHARACTER SET ascii NULL  ,
+  TranslationOf integer NOT NULL DEFAULT 0  ,
+  Content TEXT NULL  ,
+  LastUpdated DATETIME NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB CHARACTER SET utf8;
+
+# }}}
+
+# {{{ ObjectCustomFieldValues 
+
+CREATE TABLE ObjectCustomFieldValues (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  CustomField int NOT NULL  ,
+  ObjectType varchar(255) CHARACTER SET ascii NOT NULL,	    # Final target of the Object
+  ObjectId int NOT NULL  ,		    # New -- Replaces Ticket
+  SortOrder integer NOT NULL DEFAULT 0  ,   # New -- ordering for multiple values
+
+  Content varchar(255) NULL  ,
+  LargeContent LONGBLOB NULL,		    # New -- to hold 255+ strings
+  ContentType varchar(80) CHARACTER SET ascii NULL,		    # New -- only text/* gets searched
+  ContentEncoding varchar(80) CHARACTER SET ascii NULL  ,	    # New -- for binary Content
+
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  Disabled int2 NOT NULL DEFAULT 0 ,        # New -- whether the value was current
+  PRIMARY KEY (id)
+) TYPE=InnoDB CHARACTER SET utf8;
+
+CREATE INDEX ObjectCustomFieldValues1 ON ObjectCustomFieldValues (Content); 
+CREATE INDEX ObjectCustomFieldValues2 ON ObjectCustomFieldValues (CustomField,ObjectType,ObjectId); 
+
+# }}}
+
+# {{{ CustomFields
+
+CREATE TABLE CustomFields (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  Name varchar(200) NULL  ,
+  Type varchar(200) CHARACTER SET ascii NULL  ,	# Changed -- 'Single' and 'Multiple' is moved out
+  MaxValues integer,		# New -- was 'Single'(1) and 'Multiple'(0)
+  Pattern TEXT NULL  ,	# New -- Must validate against this
+  Repeated int2 NOT NULL DEFAULT 0 , # New -- repeated table entry
+  Description varchar(255) NULL  ,
+  SortOrder integer NOT NULL DEFAULT 0  ,
+  LookupType varchar(255) CHARACTER SET ascii NOT NULL,
+
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  Disabled int2 NOT NULL DEFAULT 0 ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB CHARACTER SET utf8;
+
+# }}}
+
+# {{{ ObjectCustomFields 
+
+CREATE TABLE ObjectCustomFields (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  CustomField integer NOT NULL  ,
+  ObjectId integer NOT NULL,
+  SortOrder integer NOT NULL DEFAULT 0  ,
+
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB CHARACTER SET utf8;
+
+# }}}
+
+# {{{ CustomFieldValues 
+
+CREATE TABLE CustomFieldValues (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  CustomField int NOT NULL  ,
+  Name varchar(200) NULL  ,
+  Description varchar(255) NULL  ,
+  SortOrder integer NOT NULL DEFAULT 0  ,
+
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB CHARACTER SET utf8;
+
+CREATE INDEX CustomFieldValues1 ON CustomFieldValues (CustomField);
+ 
+# }}}
+
+
+# {{{ Attributes
+
+CREATE TABLE Attributes (
+  id INTEGER NOT NULL  AUTO_INCREMENT,
+  Name varchar(255) NULL  ,
+  Description varchar(255) NULL  ,
+  Content BLOB,
+  ContentType varchar(16) CHARACTER SET ascii,
+  ObjectType varchar(64) CHARACTER SET ascii,
+  ObjectId integer, # foreign key to anything
+  Creator integer NOT NULL DEFAULT 0  ,
+  Created DATETIME NULL  ,
+  LastUpdatedBy integer NOT NULL DEFAULT 0  ,
+  LastUpdated DATETIME NULL  ,
+  PRIMARY KEY (id)
+) TYPE=InnoDB CHARACTER SET utf8;
+
+CREATE INDEX Attributes1 on Attributes(Name);
+CREATE INDEX Attributes2 on Attributes(ObjectType, ObjectId);
+
+# }}}
+
+# {{{ Sessions
+
+# sessions is used by Apache::Session to keep sessions in the database.
+# We should have a reaper script somewhere.
+
+CREATE TABLE sessions (
+    id char(32) NOT NULL,
+    a_session LONGBLOB,
+    LastUpdated TIMESTAMP,
+    PRIMARY KEY (id)
+);
+
+# }}}
diff --git a/rt/etc/upgrade/3.1.0/acl.Pg b/rt/etc/upgrade/3.1.0/acl.Pg
index 809e99ab3..9c8878260 100755
--- a/rt/etc/upgrade/3.1.0/acl.Pg
+++ b/rt/etc/upgrade/3.1.0/acl.Pg
@@ -11,7 +11,7 @@ sub acl {
     foreach my $table (@tables) {
         push @acls,
           "GRANT SELECT, INSERT, UPDATE, DELETE ON $table to "
-          . $RT::DatabaseUser . ";";
+          . RT->Config->Get('DatabaseUser') . ";";
 
     }
     return (@acls);
diff --git a/rt/etc/upgrade/3.3.0/acl.Pg b/rt/etc/upgrade/3.3.0/acl.Pg
index 2069a198e..bd2e36c48 100644
--- a/rt/etc/upgrade/3.3.0/acl.Pg
+++ b/rt/etc/upgrade/3.3.0/acl.Pg
@@ -12,7 +12,7 @@ sub acl {
     foreach my $table (@tables) {
         push @acls,
           "GRANT SELECT, INSERT, UPDATE, DELETE ON $table to "
-          . $RT::DatabaseUser . ";";
+          . RT->Config->Get('DatabaseUser') . ";";
 
     }
     return (@acls);
diff --git a/rt/etc/upgrade/3.5.1/content b/rt/etc/upgrade/3.5.1/content
index e3898a7cc..02d6a0cac 100644
--- a/rt/etc/upgrade/3.5.1/content
+++ b/rt/etc/upgrade/3.5.1/content
@@ -2,7 +2,7 @@
     { Name => 'Search - My Tickets',
       Description => '[_1] highest priority tickets I own',
       Content     =>
-      { Format => "'__id__/TITLE:#', '__Subject__/TITLE:Subject', Priority, QueueName, ExtendedStatus",
+      { Format => q{'__id__/TITLE:#', '__Subject__/TITLE:Subject', Priority, QueueName, ExtendedStatus},
         Query   => " Owner = '__CurrentUser__' AND ( Status = 'new' OR Status = 'open')",
         OrderBy => 'Priority',
         Order   => 'DESC' },
diff --git a/rt/etc/upgrade/3.7.1/content b/rt/etc/upgrade/3.7.1/content
new file mode 100644
index 000000000..fdd506144
--- /dev/null
+++ b/rt/etc/upgrade/3.7.1/content
@@ -0,0 +1,14 @@
+@ScripConditions = (
+    {  Name                 => 'On Close',                                 # loc
+       Description          => 'Whenever a ticket is closed', # loc
+       ApplicableTransTypes => 'Status,Set',
+       ExecModule           => 'CloseTicket',
+    },
+    {  Name                 => 'On Reopen',                                # loc
+       Description          => 'Whenever a ticket is reopened', # loc
+       ApplicableTransTypes => 'Status,Set',
+       ExecModule           => 'ReopenTicket',
+    },
+);
+
+
diff --git a/rt/etc/upgrade/3.7.10/content b/rt/etc/upgrade/3.7.10/content
new file mode 100644
index 000000000..d19f9e6fa
--- /dev/null
+++ b/rt/etc/upgrade/3.7.10/content
@@ -0,0 +1,49 @@
+
+@Templates = (
+    {  Queue       => 0,
+       Name        => "Error: public key",    # loc
+       Description =>
+         "Inform user that he has problems with public key and couldn't recieve encrypted content", # loc
+       Content => q{Subject: We have no your public key or it's wrong
+
+You received this message as we have no your public PGP key or we have a problem with your key. Inform the administrator about the problem.
+}
+    },
+    {  Queue       => 0,
+       Name        => "Error to RT owner: public key",    # loc
+       Description =>
+         "Inform RT owner that user(s) have problems with public keys", # loc
+       Content => q{Subject: Some users have problems with public keys
+
+You received this message as RT has problems with public keys of the following user:
+{
+    foreach my $e ( @BadRecipients ) {
+        $OUT .= "* ". $e->{'Message'} ."\n";
+    }
+}}
+    },
+    {  Queue       => 0,
+       Name        => "Error: no private key",    # loc
+       Description =>
+         "Inform user that we received an encrypted email and we have no private keys to decrypt", # loc
+       Content => q{Subject: we received message we cannot decrypt
+
+You sent an encrypted message with subject '{ $Message->head->get('Subject') }',
+but we have no private key it's encrypted to.
+
+Please, check that you encrypt messages with correct keys
+or contact the system administrator.}
+    },
+    {  Queue       => 0,
+       Name        => "Error: bad GnuPG data",    # loc
+       Description =>
+         "Inform user that a message he sent has invalid GnuPG data", # loc
+       Content => q{Subject: We received a message we cannot handle
+
+You sent us a message that we cannot handle due to corrupted GnuPG signature or encrypted block. we get the following error(s):
+{ foreach my $msg ( @Messages ) {
+    $OUT .= "* $msg\n";
+  }
+}}
+    },
+);
diff --git a/rt/etc/upgrade/3.7.15/content b/rt/etc/upgrade/3.7.15/content
new file mode 100644
index 000000000..9d97c356c
--- /dev/null
+++ b/rt/etc/upgrade/3.7.15/content
@@ -0,0 +1,12 @@
+
+@Templates = (
+    {  Queue       => 0,
+       Name        => "Forward",    # loc
+       Description => "Heading of a forwarded message", # loc
+       Content => q{
+
+This is forward of transaction #{ $Transaction->id } of a ticket #{ $Ticket->id }
+}
+    },
+);
+
diff --git a/rt/etc/upgrade/3.7.19/content b/rt/etc/upgrade/3.7.19/content
new file mode 100644
index 000000000..ac34ebeb5
--- /dev/null
+++ b/rt/etc/upgrade/3.7.19/content
@@ -0,0 +1,37 @@
+
+{ use strict;
+add_description_to_all_scrips();
+
+sub add_description_to_all_scrips {
+    require RT::Scrips;
+    my $scrips = RT::Scrips->new( $RT::SystemUser );
+    $scrips->Limit( FIELD => 'Description', OPERATOR => 'IS', VALUE => 'NULL' );
+    $scrips->Limit( FIELD => 'Description', VALUE => '' );
+    while ( my $scrip = $scrips->Next ) {
+        my $desc = $scrip->Description;
+        next if defined $desc && length $desc;
+
+        $desc = gen_scrip_description( $scrip );
+
+        my ($status, $msg) = $scrip->SetDescription( $desc );
+        unless ( $status ) {
+            print STDERR "Couldn't set description of a scrip: $msg";
+        } else {
+            print "Added description to scrip #". $scrip->id ."\n";
+        }
+    }
+}
+
+sub gen_scrip_description {
+    my $scrip = shift;
+    my $condition = $scrip->ConditionObj->Name
+        || $scrip->ConditionObj->Description
+        || ('On Condition #'. $scrip->Condition);
+    my $action = $scrip->ActionObj->Name
+        || $scrip->ActionObj->Description
+        || ('Run Action #'. $scrip->Action);
+    return join ' ', $condition, $action;
+}
+}
+
+1;
diff --git a/rt/etc/upgrade/3.7.3/schema.Oracle b/rt/etc/upgrade/3.7.3/schema.Oracle
new file mode 100644
index 000000000..6136efae7
--- /dev/null
+++ b/rt/etc/upgrade/3.7.3/schema.Oracle
@@ -0,0 +1,5 @@
+alter table CustomFields add Pattern_TMP clob;
+update CustomFields set Pattern_TMP = Pattern;
+commit;
+alter table CustomFields drop column Pattern;
+alter table CustomFields rename column Pattern_TMP to Pattern;
diff --git a/rt/etc/upgrade/3.7.3/schema.Pg b/rt/etc/upgrade/3.7.3/schema.Pg
new file mode 100644
index 000000000..5d0312e92
--- /dev/null
+++ b/rt/etc/upgrade/3.7.3/schema.Pg
@@ -0,0 +1 @@
+ALTER TABLE customfields ALTER COLUMN pattern TYPE VARCHAR(65536);
diff --git a/rt/etc/upgrade/3.7.3/schema.mysql b/rt/etc/upgrade/3.7.3/schema.mysql
new file mode 100644
index 000000000..51c376d81
--- /dev/null
+++ b/rt/etc/upgrade/3.7.3/schema.mysql
@@ -0,0 +1 @@
+ALTER TABLE CustomFields CHANGE Pattern Pattern TEXT NULL;
diff --git a/rt/etc/upgrade/3.7.81/schema.Oracle b/rt/etc/upgrade/3.7.81/schema.Oracle
new file mode 100644
index 000000000..02da4ece9
--- /dev/null
+++ b/rt/etc/upgrade/3.7.81/schema.Oracle
@@ -0,0 +1,2 @@
+CREATE INDEX CachedGroupMembers3 on CachedGroupMembers (MemberId, ImmediateParentId);
+
diff --git a/rt/etc/upgrade/3.7.81/schema.mysql b/rt/etc/upgrade/3.7.81/schema.mysql
new file mode 100644
index 000000000..02da4ece9
--- /dev/null
+++ b/rt/etc/upgrade/3.7.81/schema.mysql
@@ -0,0 +1,2 @@
+CREATE INDEX CachedGroupMembers3 on CachedGroupMembers (MemberId, ImmediateParentId);
+
diff --git a/rt/etc/upgrade/3.7.82/content b/rt/etc/upgrade/3.7.82/content
new file mode 100644
index 000000000..a1c555f78
--- /dev/null
+++ b/rt/etc/upgrade/3.7.82/content
@@ -0,0 +1,13 @@
+@Attributes = (
+    { Name => 'Search - Bookmarked Tickets',
+      Description => 'Bookmarked Tickets', #loc
+      Content     =>
+      { Format => q{'__id__/TITLE:#',}
+                . q{'__Subject__/TITLE:Subject',}
+                . q{Priority, QueueName, ExtendedStatus, Bookmark},
+        Query   => "__Bookmarks__",
+        OrderBy => 'LastUpdated',
+        Order   => 'DESC' },
+    },
+);
+
diff --git a/rt/etc/upgrade/3.7.85/content b/rt/etc/upgrade/3.7.85/content
new file mode 100644
index 000000000..49ab2860f
--- /dev/null
+++ b/rt/etc/upgrade/3.7.85/content
@@ -0,0 +1,11 @@
+@Templates = ( 
+	       
+	       {   Queue       => '0',
+		   Name        => 'Email Digest',    # loc
+		   Description => 'Email template for periodic notification digests',  # loc
+		   Content => q[Subject: RT Email Digest
+
+{ $Argument }
+],
+               },
+);
diff --git a/rt/etc/upgrade/3.7.86/content b/rt/etc/upgrade/3.7.86/content
new file mode 100644
index 000000000..da087ed75
--- /dev/null
+++ b/rt/etc/upgrade/3.7.86/content
@@ -0,0 +1,23 @@
+@Final = (
+    sub {
+        $RT::Logger->debug("Adding search for bookmarked tickets to defaults");
+        my $sys = RT::System->new($RT::SystemUser);
+
+        my $attrs = RT::Attributes->new( $RT::SystemUser );
+        $attrs->LimitToObject( $sys );
+        my ($attr) = $attrs->Named( 'HomepageSettings' );
+        unless ($attr) {
+            $RT::Logger->error("You have no global home page settings");
+            return;
+        }
+        my $content = $attr->Content;
+        unshift @{ $content->{'body'} ||= [] },
+            { type => 'system', name => 'Bookmarked Tickets' };
+
+        my ($status, $msg) = $attr->SetContent( $content );
+        $RT::Logger->error($msg) unless $status;
+
+        $RT::Logger->debug("done.");
+        return 1;
+    },
+);
diff --git a/rt/etc/upgrade/3.7.87/content b/rt/etc/upgrade/3.7.87/content
new file mode 100644
index 000000000..0c677c4a1
--- /dev/null
+++ b/rt/etc/upgrade/3.7.87/content
@@ -0,0 +1,28 @@
+@Templates = (
+{
+    Queue       => 0,
+    Name        => "Error: Missing dashboard",    # loc
+    Description =>
+      "Inform user that a dashboard he subscribed to is missing", # loc
+    Content => q{Subject: [{RT->Config->Get('rtname')}] Missing dashboard!
+
+Greetings,
+
+You are subscribed to a dashboard that is currently missing. Most likely, the dashboard was deleted.
+
+RT will remove this subscription as it is no longer useful. Here's the information RT had about your subscription:
+
+DashboardID:  { $SubscriptionObj->SubValue('DashboardId') }
+Frequency:    { $SubscriptionObj->SubValue('Frequency') }
+Hour:         { $SubscriptionObj->SubValue('Hour') }
+{
+    $SubscriptionObj->SubValue('Frequency') eq 'weekly'
+    ? "Day of week:  " . $SubscriptionObj->SubValue('Dow')
+    : $SubscriptionObj->SubValue('Frequency') eq 'monthly'
+      ? "Day of month: " . $SubscriptionObj->SubValue('Dom')
+      : ''
+}
+}
+},
+);
+
diff --git a/rt/etc/upgrade/3.8-branded-queues-extension b/rt/etc/upgrade/3.8-branded-queues-extension
new file mode 100755
index 000000000..6a0ea48b6
--- /dev/null
+++ b/rt/etc/upgrade/3.8-branded-queues-extension
@@ -0,0 +1,95 @@
+#!/usr/bin/perl
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+#  
+# This software is Copyright (c) 1996-2008 Best Practical Solutions, LLC 
+#                                          
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+use strict;
+use warnings;
+
+use lib "local/lib";
+use lib "lib";
+
+
+use RT;
+RT::LoadConfig();
+RT::Init();
+
+use RT::Queues;
+
+my $queues = RT::Queues->new( $RT::SystemUser );
+$queues->UnLimit();
+while ( my $queue = $queues->Next ) {
+    print "Processing queue ". ($queue->Name || $queue->id) ."...\n";
+    my $old_attr = $queue->FirstAttribute('BrandedSubjectTag');
+    unless ( $old_attr ) {
+        print "\thas no old-style subject tag. skipping\n";
+        next;
+    }
+    my $old_value = $old_attr->Content;
+    unless ( $old_value ) {
+        print "\thas empty old-style subject tag\n";
+    } else {
+        my ($status, $msg) = $queue->SetSubjectTag( $old_value );
+        unless ( $status ) {
+            print STDERR "\tERROR. Couldn't set tag: $msg\n";
+            next;
+        } else {
+            print "\thave set new-style subject tag to '$old_value'\n";
+        }
+    }
+
+    my ($status, $msg) = $queue->DeleteAttribute('BrandedSubjectTag');
+    unless ( $status ) {
+        print STDERR "\tERROR. Couldn't delete old-style tag: $msg\n";
+        next;
+    } else {
+        print "\tdeleted old-style tag entry\n";
+    }
+    print "\tDONE\n";
+}
+
+exit 0;
+
diff --git a/rt/etc/upgrade/3.8-branded-queues-extension.in b/rt/etc/upgrade/3.8-branded-queues-extension.in
new file mode 100755
index 000000000..2f07d6e60
--- /dev/null
+++ b/rt/etc/upgrade/3.8-branded-queues-extension.in
@@ -0,0 +1,95 @@
+#!@PERL@
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+#  
+# This software is Copyright (c) 1996-2008 Best Practical Solutions, LLC 
+#                                          
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+use strict;
+use warnings;
+
+use lib "@LOCAL_LIB_PATH@";
+use lib "@RT_LIB_PATH@";
+
+
+use RT;
+RT::LoadConfig();
+RT::Init();
+
+use RT::Queues;
+
+my $queues = RT::Queues->new( $RT::SystemUser );
+$queues->UnLimit();
+while ( my $queue = $queues->Next ) {
+    print "Processing queue ". ($queue->Name || $queue->id) ."...\n";
+    my $old_attr = $queue->FirstAttribute('BrandedSubjectTag');
+    unless ( $old_attr ) {
+        print "\thas no old-style subject tag. skipping\n";
+        next;
+    }
+    my $old_value = $old_attr->Content;
+    unless ( $old_value ) {
+        print "\thas empty old-style subject tag\n";
+    } else {
+        my ($status, $msg) = $queue->SetSubjectTag( $old_value );
+        unless ( $status ) {
+            print STDERR "\tERROR. Couldn't set tag: $msg\n";
+            next;
+        } else {
+            print "\thave set new-style subject tag to '$old_value'\n";
+        }
+    }
+
+    my ($status, $msg) = $queue->DeleteAttribute('BrandedSubjectTag');
+    unless ( $status ) {
+        print STDERR "\tERROR. Couldn't delete old-style tag: $msg\n";
+        next;
+    } else {
+        print "\tdeleted old-style tag entry\n";
+    }
+    print "\tDONE\n";
+}
+
+exit 0;
+
diff --git a/rt/etc/upgrade/3.8-ical-extension b/rt/etc/upgrade/3.8-ical-extension
new file mode 100755
index 000000000..9561b9735
--- /dev/null
+++ b/rt/etc/upgrade/3.8-ical-extension
@@ -0,0 +1,96 @@
+#!/usr/bin/perl
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+#  
+# This software is Copyright (c) 1996-2008 Best Practical Solutions, LLC 
+#                                          
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+use strict;
+use warnings;
+
+use lib "local/lib";
+use lib "lib";
+
+
+use RT;
+RT::LoadConfig();
+RT::Init();
+
+use RT::Attributes;
+my $attrs = RT::Attributes->new( $RT::SystemUser );
+$attrs->Limit(FIELD => 'ObjectType', OPERATOR=> '=', VALUE => 'RT::User');
+$attrs->Limit(FIELD => 'Name', OPERATOR=> '=', VALUE => 'ical-auth-token');
+while ( my $attr = $attrs->Next ) {
+    my $uid = $attr->ObjectId;
+    print "Processing auth token of user #". $uid ."...\n";
+
+    my $user = RT::User->new( $RT::SystemUser );
+    $user->Load( $uid );
+    unless ( $user->id ) {
+        print STDERR "\tERROR. Couldn't load user record\n";
+        next;
+    }
+
+    my ($status, $msg);
+
+    ($status, $msg) = $user->DeleteAttribute('AuthToken')
+        if $user->FirstAttribute('AuthToken');
+    unless ( $status ) {
+        print STDERR "\tERROR. Couldn't delete duplicated attribute: $msg\n";
+        next;
+    } else {
+        print "\tdeleted duplicate attribute\n";
+    }
+
+    ($status, $msg) = $attr->SetName('AuthToken');
+    unless ( $status ) {
+        print STDERR "\tERROR. Couldn't rename attribute: $msg\n";
+        next;
+    } else {
+        print "\trenamed attribute\n";
+    }
+    print "\tDONE\n";
+}
+
+exit 0;
diff --git a/rt/etc/upgrade/3.8-ical-extension.in b/rt/etc/upgrade/3.8-ical-extension.in
new file mode 100644
index 000000000..510419c5a
--- /dev/null
+++ b/rt/etc/upgrade/3.8-ical-extension.in
@@ -0,0 +1,96 @@
+#!@PERL@
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+#  
+# This software is Copyright (c) 1996-2008 Best Practical Solutions, LLC 
+#                                          
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+use strict;
+use warnings;
+
+use lib "@LOCAL_LIB_PATH@";
+use lib "@RT_LIB_PATH@";
+
+
+use RT;
+RT::LoadConfig();
+RT::Init();
+
+use RT::Attributes;
+my $attrs = RT::Attributes->new( $RT::SystemUser );
+$attrs->Limit(FIELD => 'ObjectType', OPERATOR=> '=', VALUE => 'RT::User');
+$attrs->Limit(FIELD => 'Name', OPERATOR=> '=', VALUE => 'ical-auth-token');
+while ( my $attr = $attrs->Next ) {
+    my $uid = $attr->ObjectId;
+    print "Processing auth token of user #". $uid ."...\n";
+
+    my $user = RT::User->new( $RT::SystemUser );
+    $user->Load( $uid );
+    unless ( $user->id ) {
+        print STDERR "\tERROR. Couldn't load user record\n";
+        next;
+    }
+
+    my ($status, $msg);
+
+    ($status, $msg) = $user->DeleteAttribute('AuthToken')
+        if $user->FirstAttribute('AuthToken');
+    unless ( $status ) {
+        print STDERR "\tERROR. Couldn't delete duplicated attribute: $msg\n";
+        next;
+    } else {
+        print "\tdeleted duplicate attribute\n";
+    }
+
+    ($status, $msg) = $attr->SetName('AuthToken');
+    unless ( $status ) {
+        print STDERR "\tERROR. Couldn't rename attribute: $msg\n";
+        next;
+    } else {
+        print "\trenamed attribute\n";
+    }
+    print "\tDONE\n";
+}
+
+exit 0;
diff --git a/rt/etc/upgrade/3.8.0/content b/rt/etc/upgrade/3.8.0/content
new file mode 100644
index 000000000..f4e389b90
--- /dev/null
+++ b/rt/etc/upgrade/3.8.0/content
@@ -0,0 +1,22 @@
+@Final = (
+    # by incident we've changed 'My Bookmarks' to 'Bookmarked Tickets' when
+    # 3.7.82 upgrade script still was creating 'My Bookmarks', try to fix it
+    sub {
+        $RT::Logger->debug("Going to rename 'My Bookmarks' to 'Bookmarked Tickets'");
+        my $sys = RT::System->new($RT::SystemUser);
+
+        my $attrs = RT::Attributes->new( $RT::SystemUser );
+        $attrs->LimitToObject( $sys );
+        my ($attr) = $attrs->Named( 'Search - My Bookmarks' );
+        unless ($attr) {
+            $RT::Logger->debug("You have no global search 'My Bookmarks'. Skipped.");
+            return 1;
+        }
+        my ($status, $msg) = $attr->SetName( 'Search - Bookmarked Tickets' );
+        $RT::Logger->error($msg) and return undef unless $status;
+
+        $RT::Logger->debug("Renamed.");
+        return 1;
+    },
+);
+
diff --git a/rt/etc/upgrade/3.8.1/content b/rt/etc/upgrade/3.8.1/content
new file mode 100644
index 000000000..89db596ec
--- /dev/null
+++ b/rt/etc/upgrade/3.8.1/content
@@ -0,0 +1,24 @@
+@Final = (
+    sub {
+        $RT::Logger->debug("Going to adjust 'Bookmarked Tickets'");
+        my $sys = RT::System->new($RT::SystemUser);
+
+        my $attrs = RT::Attributes->new( $RT::SystemUser );
+        $attrs->LimitToObject( $sys );
+        my ($attr) = $attrs->Named( 'Search - Bookmarked Tickets' );
+        unless ($attr) {
+            $RT::Logger->debug("You have no global search 'Bookmarked Tickets'. Skipped.");
+            return 1;
+        }
+        my $props = $attr->Content;
+        $props->{'Query'} =~ s/__Bookmarks__/id = '__Bookmarked__'/g;
+
+        my ($status, $msg) = $attr->SetContent( $props );
+        $RT::Logger->error($msg) and return undef unless $status;
+
+        $RT::Logger->debug("Fixed.");
+        return 1;
+    },
+);
+
+
diff --git a/rt/etc/upgrade/3.8.2/content b/rt/etc/upgrade/3.8.2/content
new file mode 100644
index 000000000..9bd92be21
--- /dev/null
+++ b/rt/etc/upgrade/3.8.2/content
@@ -0,0 +1,186 @@
+@Initial = (
+    sub {
+        $RT::Logger->warning(
+            "Going to add [OLD] prefix to all temlates in approvals queue."
+            ." If you never used approvals then you can delete all these"
+            ." templates with [OLD] prefix. Leave new there may be you will"
+            ." want to use approvals some time."
+        );
+
+        my $approvals_q = RT::Queue->new( $RT::SystemUser );
+        $approvals_q->Load('___Approvals');
+        unless ( $approvals_q->id ) {
+            $RT::Logger->error("You have no approvals queue.");
+            return 1;
+        }
+
+        my $templates = RT::Templates->new( $RT::SystemUser );
+        $templates->LimitToQueue( $approvals_q->id );
+        while ( my $tmpl = $templates->Next ) {
+            my ($status, $msg) = $tmpl->SetName( "[OLD] ". $tmpl->Name );
+            unless ( $status ) {
+                $RT::Logger->error("Couldn't rename template #". $tmpl->id .": $msg");
+            }
+        }
+        return 1;
+    },
+);
+@ACL = (
+    { GroupDomain => 'SystemInternal',
+      GroupType => 'privileged',
+      Right  => 'ShowApprovalsTab', },
+);
+
+@Templates = (
+    {  Queue       => '___Approvals',
+       Name        => "New Pending Approval",    # loc
+       Description =>
+         "Notify Owners and AdminCcs of new items pending their approval", # loc
+       Content => 'Subject: New Pending Approval: {$Ticket->Subject}
+
+Greetings,
+
+There is a new item pending your approval: "{$Ticket->Subject()}", 
+a summary of which appears below.
+
+Please visit {RT->Config->Get(\'WebURL\')}Approvals/Display.html?id={$Ticket->id}
+to approve or reject this ticket, or {RT->Config->Get(\'WebURL\')}Approvals/ to
+batch-process all your pending approvals.
+
+-------------------------------------------------------------------------
+{$Transaction->Content()}
+'
+    },
+    {  Queue       => '___Approvals',
+       Name        => "Approval Passed",    # loc
+       Description =>
+         "Notify Requestor of their ticket has been approved by some approver", # loc
+       Content => 'Subject: Ticket Approved: {$Ticket->Subject}
+
+Greetings,
+
+Your ticket has been approved by { eval { $Approval->OwnerObj->Name } }.
+Other approvals may be pending.
+
+Approver\'s notes: { $Notes }
+'
+    },
+    {  Queue       => '___Approvals',
+       Name        => "All Approvals Passed",    # loc
+       Description =>
+         "Notify Requestor of their ticket has been approved by all approvers", # loc
+       Content => 'Subject: Ticket Approved: {$Ticket->Subject}
+
+Greetings,
+
+Your ticket has been approved by { eval { $Approval->OwnerObj->Name } }.
+Its Owner may now start to act on it.
+
+Approver\'s notes: { $Notes }
+'
+    },
+    {  Queue       => '___Approvals',
+       Name        => "Approval Rejected",    # loc
+       Description =>
+         "Notify Owner of their rejected ticket", # loc
+       Content => 'Subject: Ticket Rejected: {$Ticket->Subject}
+
+Greetings,
+
+Your ticket has been rejected by { eval { $Approval->OwnerObj->Name } }.
+
+Approver\'s notes: { $Notes }
+'
+    },
+    {  Queue       => '___Approvals',
+       Name        => "Approval Ready for Owner",    # loc
+       Description =>
+         "Notify Owner of their ticket has been approved and is ready to be acted on", # loc
+       Content => 'Subject: Ticket Approved: {$Ticket->Subject}
+
+Greetings,
+
+The ticket has been approved, you may now start to act on it.
+
+'
+    },
+);
+
+@Final = (
+    sub {
+        $RT::Logger->debug("Going to adjust dashboards");
+        my $sys = RT::System->new($RT::SystemUser);
+
+        my $attrs = RT::Attributes->new( $RT::SystemUser );
+        $attrs->UnLimit;
+        my @dashboards = $attrs->Named('Dashboard');
+
+        if (@dashboards == 0) {
+            $RT::Logger->debug("You have no dashboards. Skipped.");
+            return 1;
+        }
+
+        for my $attr (@dashboards) {
+            my $props = $attr->Content;
+            if (exists $props->{Searches}) {
+                $props->{Panes} = {
+                    body => [
+                        map {
+                            my ($privacy, $id, $desc) = @$_;
+
+                            {
+                                portlet_type => 'search',
+                                privacy      => $privacy,
+                                id           => $id,
+                                description  => $desc,
+                                pane         => 'body',
+                            }
+                        } @{ delete $props->{Searches} }
+                    ],
+                };
+            }
+            my ($status, $msg) = $attr->SetContent( $props );
+            $RT::Logger->error($msg) unless $status;
+        }
+
+        $RT::Logger->debug("Fixed.");
+        return 1;
+    },
+    sub {
+        my $approvals_q = RT::Queue->new( $RT::SystemUser );
+        $approvals_q->Load('___Approvals');
+        unless ( $approvals_q->id ) {
+            $RT::Logger->error("You have no approvals queue.");
+            return 1;
+        }
+
+        require File::Temp;
+        my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( 'rt-approvals-scrips-XXXX', CLEANUP => 0 );
+        unless ( $tmp_fh ) {
+            $RT::Logger->error("Couldn't create temporary file.");
+            return 0;
+        }
+
+        $RT::Logger->warning(
+            "IMPORTANT: We're going to delete all scrips in Approvals queue"
+            ." and save them in '$tmp_fn' file."
+        );
+
+        require Data::Dumper;
+
+        my $scrips = RT::Scrips->new( $RT::SystemUser );
+        $scrips->LimitToQueue( $approvals_q->id );
+        while ( my $scrip = $scrips->Next ) {
+            my %tmp =
+                map { $tmp->{ $_ } = $scrip->_Value( $_ ) }
+                $scrip->ReadableAttributes;
+
+            print $tmp_fh Data::Dumper::Dumper( \%tmp );
+
+            my ($status, $msg) = $scrip->Delete;
+            unless ( $status ) {
+                $RT::Logger->error( "Couldn't delete scrip: $msg");
+            }
+        }
+    },
+);
diff --git a/rt/etc/upgrade/3.8.3/content b/rt/etc/upgrade/3.8.3/content
new file mode 100644
index 000000000..8538e4287
--- /dev/null
+++ b/rt/etc/upgrade/3.8.3/content
@@ -0,0 +1,91 @@
+@ScripConditions = (
+    {  Name                 => 'On Reject',                                # loc
+       Description          => 'Whenever a ticket is rejected',            # loc
+       ApplicableTransTypes => 'Status',
+       ExecModule           => 'StatusChange',
+       Argument             => 'rejected'
+
+    },
+);
+
+@Final = (
+    sub {
+        $RT::Logger->debug("Going to correct descriptions of notify actions in the DB");
+
+        my $actions = RT::ScripActions->new( $RT::SystemUser );
+        $actions->Limit(
+            FIELD => 'ExecModule',
+            VALUE => 'Notify',
+        );
+        $actions->Limit(
+            FIELD => 'Argument',
+            VALUE => 'All',
+        );
+        while ( my $action = $actions->Next ) {
+            my ($status, $msg) = $action->__Set( Field => 'Name', Value => 'Notify Owner, Requestors, Ccs and AdminCcs' );
+            $RT::Logger->warning( "Couldn't change action name: $msg" )
+                unless $status;
+
+            ($status, $msg) = $action->__Set( Field => 'Description', Value => 'Send mail to owner and all watchers' );
+            $RT::Logger->warning( "Couldn't change action description: $msg" )
+                unless $status;
+        }
+
+        $actions = RT::ScripActions->new( $RT::SystemUser );
+        $actions->Limit(
+            FIELD => 'ExecModule',
+            VALUE => 'NotifyAsComment',
+        );
+        $actions->Limit(
+            FIELD => 'Argument',
+            VALUE => 'All',
+        );
+        while ( my $action = $actions->Next ) {
+            my ($status, $msg) = $action->__Set( Field => 'Name', Value => 'Notify Owner, Requestors, Ccs and AdminCcs as Comment' );
+            $RT::Logger->warning( "Couldn't change action name: $msg" )
+                unless $status;
+
+            ($status, $msg) = $action->__Set( Field => 'Description', Value => 'Send mail to owner and all watchers as a "comment"' );
+            $RT::Logger->warning( "Couldn't change action description: $msg" )
+                unless $status;
+        }
+
+        $RT::Logger->debug("Corrected descriptions of notify actions in the DB.");
+        return 1;
+    },
+);
+
+
+{
+$RT::Logger->debug("Going to add in Extract Subject Tag actions if they were missed during a previous upgrade");
+
+$actions = RT::ScripActions->new( $RT::SystemUser );
+$actions->Limit(
+    FIELD => 'ExecModule',
+    VALUE => 'ExtractSubjectTag',
+);
+my $extract_action = $actions->First;
+
+if ( $extract_action && $extract_action->Id ) {
+    $RT::Logger->debug("You appear to already have an Extract Subject Tag action, skipping");
+    return 1;
+} else {
+    $RT::Logger->debug("Didn't find an existing Extract Subject Tag action, adding it");
+    push @ScripActions, (
+            { Name        => 'Extract Subject Tag',                               # loc
+              Description => 'Extract tags from a Transaction\'s subject and add them to the Ticket\'s subject.', # loc
+              ExecModule  => 'ExtractSubjectTag' 
+            },
+    );
+
+    $RT::Logger->debug("Adding Extract Subject Tag Scrip");
+    push @Scrips, (
+        {  Description    => "On transaction, add any tags in the transaction's subject to the ticket's subject",
+           ScripCondition => 'On Transaction',
+           ScripAction    => 'Extract Subject Tag',
+           Template       => 'Blank' 
+        },
+    );
+}
+}
+
diff --git a/rt/etc/upgrade/3.8.3/schema.Pg b/rt/etc/upgrade/3.8.3/schema.Pg
new file mode 100644
index 000000000..bbe55365d
--- /dev/null
+++ b/rt/etc/upgrade/3.8.3/schema.Pg
@@ -0,0 +1,3 @@
+
+CREATE UNIQUE INDEX GroupMembers1 ON GroupMembers(GroupId, MemberId);
+
diff --git a/rt/etc/upgrade/3.8.4/content b/rt/etc/upgrade/3.8.4/content
new file mode 100644
index 000000000..be5a6bf39
--- /dev/null
+++ b/rt/etc/upgrade/3.8.4/content
@@ -0,0 +1,59 @@
+
+@Final = (
+    sub {
+        $RT::Logger->debug("Going to correct arguments of NotifyGroup actions if you have any");
+        use strict;
+
+        my $actions = RT::ScripActions->new( $RT::SystemUser );
+        $actions->Limit(
+            FIELD => 'ExecModule',
+            VALUE => 'NotifyGroup',
+        );
+        $actions->Limit(
+            FIELD => 'ExecModule',
+            VALUE => 'NotifyGroupAsComment',
+        );
+
+        my $converter = sub {
+            my $arg = shift;
+            my @res;
+            foreach my $r ( @{ $arg } ) {
+                my $obj;
+                next unless $r->{'Type'};
+                if( lc $r->{'Type'} eq 'user' ) {
+                    $obj = RT::User->new( $RT::SystemUser );
+                } elsif ( lc $r->{'Type'} eq 'group' ) {
+                    $obj = RT::Group->new( $RT::SystemUser );
+                } else {
+                    next;
+                }
+                $obj->Load( $r->{'Instance'} );
+                my $id = $obj->id;
+                next unless( $id );
+
+                push @res, $id;
+            }
+
+            return join ',', @res;
+        };
+
+        require Storable;
+        while ( my $action = $actions->Next ) {
+            my $argument = $action->Argument;
+            my $new = '';
+            local $@;
+            if ( my $struct = eval { Storable::thaw( $argument ) } ) {
+                $new = $converter->( $struct );
+            } else {
+                $new = join /, /, grep length, split /[^0-9]+/, $argument;
+            }
+            next if $new eq $argument;
+
+            my ($status, $msg) = $action->__Set( Field => 'Argument', Value => $new );
+            $RT::Logger->warning( "Couldn't change argument value of the action: $msg" )
+                unless $status;
+        }
+    },
+);
+
+
diff --git a/rt/etc/upgrade/3.8.6/content b/rt/etc/upgrade/3.8.6/content
new file mode 100644
index 000000000..a9793c6e1
--- /dev/null
+++ b/rt/etc/upgrade/3.8.6/content
@@ -0,0 +1,10 @@
+@Templates = (
+    {  Queue       => 0,
+       Name        => "Forward Ticket",    # loc
+       Description => "Heading of a forwarded Ticket", # loc
+       Content => q{
+
+This is a forward of ticket #{ $Ticket->id }
+}
+    },
+);
diff --git a/rt/etc/upgrade/shrink_cgm_table.pl b/rt/etc/upgrade/shrink_cgm_table.pl
new file mode 100644
index 000000000..3d153c709
--- /dev/null
+++ b/rt/etc/upgrade/shrink_cgm_table.pl
@@ -0,0 +1,72 @@
+#!/usr/bin/perl
+
+use 5.8.3;
+use strict;
+use warnings;
+
+use RT;
+RT::LoadConfig();
+RT->Config->Set('LogToScreen' => 'debug');
+RT::Init();
+
+use RT::CachedGroupMembers;
+my $cgms = RT::CachedGroupMembers->new( $RT::SystemUser );
+$cgms->Limit(
+    FIELD => 'id',
+    OPERATOR => '!=',
+    VALUE => 'main.Via',
+    QUOTEVALUE => 0,
+    ENTRYAGGREGATOR => 'AND',
+);
+$cgms->FindAllRows;
+
+my $alias = $cgms->Join(
+    TYPE   => 'LEFT',
+    FIELD1 => 'Via',
+    TABLE2 => 'CachedGroupMembers',
+    FIELD2 => 'id',
+);
+$cgms->Limit(
+    ALIAS => $alias,
+    FIELD => 'MemberId',
+    OPERATOR => '=',
+    VALUE => $alias .'.GroupId',
+    QUOTEVALUE => 0,
+    ENTRYAGGREGATOR => 'AND',
+);
+$cgms->Limit(
+    ALIAS => $alias,
+    FIELD => 'id',
+    OPERATOR => '=',
+    VALUE => $alias .'.Via',
+    QUOTEVALUE => 0,
+    ENTRYAGGREGATOR => 'AND',
+);
+
+FetchNext( $cgms, 'init' );
+while ( my $rec = FetchNext( $cgms ) ) {
+    $RT::Handle->BeginTransaction;
+    my ($status) = $rec->Delete;
+    unless ($status) {
+        print STDERR "Couldn't delete CGM #". $rec->id;
+        exit 1;
+    }
+    $RT::Handle->Commit;
+}
+
+use constant PAGE_SIZE => 1000;
+sub FetchNext {
+    my ($objs, $init) = @_;
+    if ( $init ) {
+        $objs->RowsPerPage( PAGE_SIZE );
+        $objs->FirstPage;
+        return;
+    }
+
+    my $obj = $objs->Next;
+    return $obj if $obj;
+    $objs->RedoSearch;
+    $objs->FirstPage;
+    return $objs->Next;
+}
+
diff --git a/rt/etc/upgrade/split-out-cf-categories b/rt/etc/upgrade/split-out-cf-categories
new file mode 100755
index 000000000..d4077ce54
--- /dev/null
+++ b/rt/etc/upgrade/split-out-cf-categories
@@ -0,0 +1,171 @@
+#!/usr/bin/perl
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+#  
+# This software is Copyright (c) 1996-2008 Best Practical Solutions, LLC 
+#                                          
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+use strict;
+use warnings;
+
+use lib "local/lib";
+use lib "lib";
+
+use RT;
+RT::LoadConfig();
+RT->Config->Set('LogToScreen' => 'debug');
+RT::Init();
+
+$| = 1;
+
+$RT::Handle->BeginTransaction();
+
+use RT::CustomFields;
+my $CFs = RT::CustomFields->new( $RT::SystemUser );
+$CFs->UnLimit;
+$CFs->Limit( FIELD => 'Type', VALUE => 'Select' );
+
+my $seen;
+while (my $cf  = $CFs->Next ) {
+    next if $cf->BasedOnObj->Id;
+    my @categories;
+    my %mapping;
+    my $values = $cf->Values;
+    while (my $value = $values->Next) {
+        next unless defined $value->Category and length $value->Category;
+        push @categories, $value->Category unless grep {$_ eq $value->Category} @categories;
+        $mapping{$value->Name} = $value->Category;
+    }
+    next unless @categories;
+
+    $seen++;
+    print "Found CF '@{[$cf->Name]}' with categories:\n";
+    print "  $_\n" for @categories;
+
+    print "Split this CF's categories into a hierarchical custom field (Y/n)? ";
+    my $dothis = <>;
+    next if $dothis =~ /n/i;
+
+    print "Enter name of CF to create as category ('@{[$cf->Name]} category'): ";
+    my $newname = <>;
+    chomp $newname;
+    $newname = $cf->Name . " category" unless length $newname;
+
+    # bump the CF's sort oder up by one
+    $cf->SetSortOrder( ($cf->SortOrder || 0) + 1 );
+
+    # ..and add a new CF before it
+    my $new = RT::CustomField->new( $RT::SystemUser );
+    my ($id, $msg) = $new->Create(
+        Name => $newname,
+        Type => 'Select',
+        MaxValues => 1,
+        LookupType => $cf->LookupType,
+        SortOrder => $cf->SortOrder - 1,
+    );
+    die "Can't create custom field '$newname': $msg" unless $id;
+
+    # Set the CF to be based on what we just made
+    $cf->SetBasedOn( $new->Id );
+
+    # Apply it to all of the same things
+    {
+        my $ocfs = RT::ObjectCustomFields->new( $RT::SystemUser );
+        $ocfs->LimitToCustomField( $cf->Id );
+        while (my $ocf = $ocfs->Next) {
+            my $newocf = RT::ObjectCustomField->new( $RT::SystemUser );
+            ($id, $msg) = $newocf->Create(
+                SortOrder => $ocf->SortOrder,
+                CustomField => $new->Id,
+                ObjectId => $ocf->ObjectId,
+            );
+            die "Can't create ObjectCustomField: $msg" unless $id;
+        }
+    }
+
+    # Copy over all of the rights
+    {
+        my $acl = RT::ACL->new( $RT::SystemUser );
+        $acl->LimitToObject( $cf );
+        while (my $ace = $acl->Next) {
+            my $newace = RT::ACE->new( $RT::SystemUser );
+            ($id, $msg) = $newace->Create(
+                PrincipalId => $ace->PrincipalId,
+                PrincipalType => $ace->PrincipalType,
+                RightName => $ace->RightName,
+                Object => $new,
+            );
+            die "Can't assign rights: $msg" unless $id;
+        }
+    }
+
+    # Add values for all of the categories
+    for my $i (0..$#categories) {
+        ($id, $msg) = $new->AddValue(
+            Name => $categories[$i],
+            SortOrder => $i + 1,
+        );
+        die "Can't create custom field value: $msg" unless $id;
+    }
+
+    # Grovel through all ObjectCustomFieldValues, and add the
+    # appropriate category
+    {
+        my $ocfvs = RT::ObjectCustomFieldValues->new( $RT::SystemUser );
+        $ocfvs->LimitToCustomField( $cf->Id );
+        while (my $ocfv = $ocfvs->Next) {
+            next unless exists $mapping{$ocfv->Content};
+            my $newocfv = RT::ObjectCustomFieldValue->new( $RT::SystemUser );
+            ($id, $msg) = $newocfv->Create(
+                CustomField => $new->Id,
+                ObjectType => $ocfv->ObjectType,
+                ObjectId   => $ocfv->ObjectId,
+                Content    => $mapping{$ocfv->Content},
+            );
+        }
+    }
+}
+
+$RT::Handle->Commit;
+print "No custom fields with categories found\n" unless $seen;
diff --git a/rt/etc/upgrade/split-out-cf-categories.in b/rt/etc/upgrade/split-out-cf-categories.in
new file mode 100644
index 000000000..f34a1b616
--- /dev/null
+++ b/rt/etc/upgrade/split-out-cf-categories.in
@@ -0,0 +1,171 @@
+#!@PERL@
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+#  
+# This software is Copyright (c) 1996-2008 Best Practical Solutions, LLC 
+#                                          
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+use strict;
+use warnings;
+
+use lib "@LOCAL_LIB_PATH@";
+use lib "@RT_LIB_PATH@";
+
+use RT;
+RT::LoadConfig();
+RT->Config->Set('LogToScreen' => 'debug');
+RT::Init();
+
+$| = 1;
+
+$RT::Handle->BeginTransaction();
+
+use RT::CustomFields;
+my $CFs = RT::CustomFields->new( $RT::SystemUser );
+$CFs->UnLimit;
+$CFs->Limit( FIELD => 'Type', VALUE => 'Select' );
+
+my $seen;
+while (my $cf  = $CFs->Next ) {
+    next if $cf->BasedOnObj->Id;
+    my @categories;
+    my %mapping;
+    my $values = $cf->Values;
+    while (my $value = $values->Next) {
+        next unless defined $value->Category and length $value->Category;
+        push @categories, $value->Category unless grep {$_ eq $value->Category} @categories;
+        $mapping{$value->Name} = $value->Category;
+    }
+    next unless @categories;
+
+    $seen++;
+    print "Found CF '@{[$cf->Name]}' with categories:\n";
+    print "  $_\n" for @categories;
+
+    print "Split this CF's categories into a hierarchical custom field (Y/n)? ";
+    my $dothis = <>;
+    next if $dothis =~ /n/i;
+
+    print "Enter name of CF to create as category ('@{[$cf->Name]} category'): ";
+    my $newname = <>;
+    chomp $newname;
+    $newname = $cf->Name . " category" unless length $newname;
+
+    # bump the CF's sort oder up by one
+    $cf->SetSortOrder( ($cf->SortOrder || 0) + 1 );
+
+    # ..and add a new CF before it
+    my $new = RT::CustomField->new( $RT::SystemUser );
+    my ($id, $msg) = $new->Create(
+        Name => $newname,
+        Type => 'Select',
+        MaxValues => 1,
+        LookupType => $cf->LookupType,
+        SortOrder => $cf->SortOrder - 1,
+    );
+    die "Can't create custom field '$newname': $msg" unless $id;
+
+    # Set the CF to be based on what we just made
+    $cf->SetBasedOn( $new->Id );
+
+    # Apply it to all of the same things
+    {
+        my $ocfs = RT::ObjectCustomFields->new( $RT::SystemUser );
+        $ocfs->LimitToCustomField( $cf->Id );
+        while (my $ocf = $ocfs->Next) {
+            my $newocf = RT::ObjectCustomField->new( $RT::SystemUser );
+            ($id, $msg) = $newocf->Create(
+                SortOrder => $ocf->SortOrder,
+                CustomField => $new->Id,
+                ObjectId => $ocf->ObjectId,
+            );
+            die "Can't create ObjectCustomField: $msg" unless $id;
+        }
+    }
+
+    # Copy over all of the rights
+    {
+        my $acl = RT::ACL->new( $RT::SystemUser );
+        $acl->LimitToObject( $cf );
+        while (my $ace = $acl->Next) {
+            my $newace = RT::ACE->new( $RT::SystemUser );
+            ($id, $msg) = $newace->Create(
+                PrincipalId => $ace->PrincipalId,
+                PrincipalType => $ace->PrincipalType,
+                RightName => $ace->RightName,
+                Object => $new,
+            );
+            die "Can't assign rights: $msg" unless $id;
+        }
+    }
+
+    # Add values for all of the categories
+    for my $i (0..$#categories) {
+        ($id, $msg) = $new->AddValue(
+            Name => $categories[$i],
+            SortOrder => $i + 1,
+        );
+        die "Can't create custom field value: $msg" unless $id;
+    }
+
+    # Grovel through all ObjectCustomFieldValues, and add the
+    # appropriate category
+    {
+        my $ocfvs = RT::ObjectCustomFieldValues->new( $RT::SystemUser );
+        $ocfvs->LimitToCustomField( $cf->Id );
+        while (my $ocfv = $ocfvs->Next) {
+            next unless exists $mapping{$ocfv->Content};
+            my $newocfv = RT::ObjectCustomFieldValue->new( $RT::SystemUser );
+            ($id, $msg) = $newocfv->Create(
+                CustomField => $new->Id,
+                ObjectType => $ocfv->ObjectType,
+                ObjectId   => $ocfv->ObjectId,
+                Content    => $mapping{$ocfv->Content},
+            );
+        }
+    }
+}
+
+$RT::Handle->Commit;
+print "No custom fields with categories found\n" unless $seen;
\ No newline at end of file
diff --git a/rt/etc/upgrade/upgrade-mysql-schema.pl b/rt/etc/upgrade/upgrade-mysql-schema.pl
new file mode 100755
index 000000000..bc59c97a1
--- /dev/null
+++ b/rt/etc/upgrade/upgrade-mysql-schema.pl
@@ -0,0 +1,390 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use DBI;
+use DBD::mysql 4.002;
+
+unless (@ARGV) {
+    print STDERR "usage: $0 db_name[:server_name] db_user db_password\n";
+    exit 1;
+}
+
+# pretty correct support of charsets has been introduced in mysql 4.1
+# as RT doesn't use it may result in issues:
+# 1) data corruptions when default charset of mysql server has data restrictions like utf8
+# 2) wrong ordering (collations)
+
+# we have to define correct types for all columns. RT uses UTF-8, ascii and binary.
+# * ascii is subset of many mysql's charsets except may be one or two rare where some ascii
+#   characters replaced with local
+# * for many charsets mysql allows us to store any octets sequences even when those are
+#   invalid for this particula set, for example we can store UTF-8 data in latin1
+#   column and fetch it as UTF-8, however sorting will be wrong
+
+# here is tricky algorithm to change column to desired charset:
+# * text to binary convertion is pretty straight forward except that text types
+#   have length definitions in terms of characters and in some cases we must
+#   use longer binary types to satisfy space requirements
+# * binary to text is much easier as we know that there is ascii or UTF-8 then
+#   we just make convertion, also 32 chars are long enough to store 32 bytes, so
+#   length changes is not required
+# * text to text convertion is trickier. no matter what is the current character set
+#   of the column we know that there is either ascii or UTF-8, so we can not use
+#   direct convertion, instead we do text to binary plus binary to text convertion
+#   instead
+# * as well we add charset definition for all tables and for the DB as well,
+#   so all new columns by default will be in UTF-8 charset
+
+my @tables = qw(
+    ACL
+    Attachments
+    Attributes
+    CustomFields
+    CustomFieldValues
+    GroupMembers
+    Groups
+    Links
+    ObjectCustomFields
+    ObjectCustomFieldValues
+    Principals
+    Queues
+    ScripActions
+    ScripConditions
+    Scrips
+    sessions
+    Templates
+    Tickets
+    Transactions
+    Users
+);
+
+my %charset = (
+    ACL                      => {
+        RightName     => 'ascii',
+        ObjectType    => 'ascii',
+        PrincipalType => 'ascii',
+    },
+    Attachments              => {
+        MessageId  => 'ascii',
+        Subject  => 'utf8',
+        Filename  => 'utf8',
+        ContentType  => 'ascii',
+        ContentEncoding  => 'ascii',
+        Content  => 'binary',
+        Headers  => 'utf8',
+    },
+    Attributes               => {
+        Name  => 'utf8',
+        Description  => 'utf8',
+        Content  => 'binary',
+        ContentType  => 'ascii',
+        ObjectType  => 'ascii',
+    },
+    CustomFields             => {
+        Name  => 'utf8',
+        Type  => 'ascii',
+        Pattern  => 'utf8',
+        Description  => 'utf8',
+        LookupType => 'ascii',
+    },
+    CustomFieldValues        => {
+        Name  => 'utf8',
+        Description  => 'utf8',
+    },
+    Groups                   => {
+        Name  => 'utf8',
+        Description  => 'utf8',
+        Domain  => 'ascii',
+        Type  => 'ascii',
+    },
+    Links                    => {
+        Base  => 'ascii',
+        Target  => 'ascii',
+        Type  => 'ascii',
+    },
+    ObjectCustomFieldValues  => {
+        ObjectType  => 'ascii',
+        Content  => 'utf8',
+        LargeContent  => 'binary',
+        ContentType  => 'ascii',
+        ContentEncoding  => 'ascii',
+    },
+    Principals               => {
+        PrincipalType  => 'ascii',
+    },
+    Queues                   => {
+        Name  => 'utf8',
+        Description  => 'utf8',
+        CorrespondAddress  => 'ascii',
+        CommentAddress  => 'ascii',
+    },
+    ScripActions             => {
+        Name  => 'utf8',
+        Description  => 'utf8',
+        ExecModule  => 'ascii',
+        Argument  => 'binary',
+    },
+    ScripConditions          => {
+        Name  => 'utf8',
+        Description  => 'utf8',
+        ExecModule  => 'ascii',
+        Argument  => 'binary',
+        ApplicableTransTypes  => 'ascii',
+    },
+    Scrips                   => {
+        Description  => 'utf8',
+        ConditionRules  => 'utf8',
+        ActionRules  => 'utf8',
+        CustomIsApplicableCode  => 'utf8',
+        CustomPrepareCode  => 'utf8',
+        CustomCommitCode  => 'utf8',
+        Stage  => 'ascii',
+    },
+    sessions                 => {
+        id         => 'binary', # ascii?
+        a_session  => 'binary',
+    },
+    Templates                => {
+        Name  => 'utf8',
+        Description  => 'utf8',
+        Type  => 'ascii',
+        Language  => 'ascii',
+        Content  => 'utf8',
+    },
+    Tickets                  => {
+        Type  => 'ascii',
+        Subject  => 'utf8',
+        Status  => 'ascii',
+    },
+    Transactions             => {
+        ObjectType  => 'ascii',
+        Type  => 'ascii',
+        Field  => 'ascii',
+        OldValue  => 'utf8',
+        NewValue  => 'utf8',
+        ReferenceType  => 'ascii',
+        Data  => 'utf8',
+    },
+    Users                    => {
+        Name  => 'utf8',
+        Password  => 'binary',
+        Comments  => 'utf8',
+        Signature  => 'utf8',
+        EmailAddress  => 'ascii',
+        FreeformContactInfo  => 'utf8',
+        Organization  => 'utf8',
+        RealName  => 'utf8',
+        NickName  => 'utf8',
+        Lang  => 'ascii',
+        EmailEncoding  => 'ascii',
+        WebEncoding  => 'ascii',
+        ExternalContactInfoId  => 'utf8',
+        ContactInfoSystem  => 'utf8',
+        ExternalAuthId  => 'utf8',
+        AuthSystem  => 'utf8',
+        Gecos  => 'utf8',
+        HomePhone  => 'utf8',
+        WorkPhone  => 'utf8',
+        MobilePhone  => 'utf8',
+        PagerPhone  => 'utf8',
+        Address1  => 'utf8',
+        Address2  => 'utf8',
+        City  => 'utf8',
+        State  => 'utf8',
+        Zip  => 'utf8',
+        Country  => 'utf8',
+        Timezone  => 'ascii',
+        PGPKey  => 'binary',
+    },
+);
+
+my %max_type_length = (
+    char       => int 1<<8,
+    varchar    => int 1<<8,
+    tinytext   => int 1<<8,
+    mediumtext => int 1<<16,
+    text       => int 1<<24,
+    longtext   => int 1<<32,
+);
+
+my @sql_commands;
+
+my ($db_datasource, $db_user, $db_pass) = (shift, shift, shift);
+my $dbh = DBI->connect("dbi:mysql:$db_datasource", $db_user, $db_pass, { RaiseError => 1 });
+my $db_name = $db_datasource;
+$db_name =~ s/:.*$//;
+
+my $version = ($dbh->selectrow_array("show variables like 'version'"))[1];
+($version) = $version =~ /^(\d+\.\d+)/;
+
+push @sql_commands, qq{ALTER DATABASE $db_name DEFAULT CHARACTER SET utf8};
+convert_table($_) foreach @tables;
+
+print join "\n", map(/;$/? $_ : "$_;", @sql_commands), "";
+my $use_p = $db_pass ? " -p" : '';
+print STDERR <column_info( undef, $db_name, $table, undef );
+    $sth->execute;
+    while ( my $info = $sth->fetchrow_hashref ) {
+        convert_column(%$info);
+    }
+    for my $conversiontype (qw(char_to_binary binary_to_char)) {
+        next unless @{$alter_aggregator{$conversiontype}};
+        push @sql_commands, qq{ALTER TABLE $table\n   }.
+            join(",\n   ",@{$alter_aggregator{$conversiontype}});
+    }
+}
+
+sub convert_column {
+    my %info = @_;
+    my $table = $info{'TABLE_NAME'};
+    my $column = $info{'COLUMN_NAME'};
+    my $type = $info{'TYPE_NAME'};
+    return unless $type =~ /(CHAR|TEXT|BLOB|BINARY)$/i;
+
+    my $required_charset = $charset{$table}{$column};
+    unless ( $required_charset ) {
+        print STDERR join(".", @info{'TABLE_SCHEMA', 'TABLE_NAME', 'COLUMN_NAME'})
+            ." has type $type however mapping is missing.\n";
+        return;
+    }
+
+    my $collation = column_info($table, $column)->{'collation'};
+    # mysql 4.1 returns literal NULL instead of undef
+    my $current_charset = $collation && $collation ne 'NULL'? (split /_/, $collation)[0]: 'binary';
+    return if $required_charset eq $current_charset;
+
+    if ( $required_charset eq 'binary' ) {
+        char_to_binary(%info);
+    }
+    elsif ( $current_charset eq 'binary' ) {
+        binary_to_char( $required_charset, %info);
+    } else {
+        char_to_char( $required_charset, %info);
+    }
+}
+
+sub char_to_binary {
+    my %info = @_;
+
+    my $table = $info{'TABLE_NAME'};
+    my $column = $info{'COLUMN_NAME'};
+    my $new_type = calc_suitable_binary_type(%info);
+    push @{$alter_aggregator{char_to_binary}},
+        "MODIFY $column $new_type ".build_column_definition(%info);
+
+}
+
+sub binary_to_char {
+    my ($charset, %info) = @_;
+
+    my $table = $info{'TABLE_NAME'};
+    my $column = $info{'COLUMN_NAME'};
+    my $new_type = lc $info{'TYPE_NAME'};
+    if ( $new_type =~ /binary/ ) {
+        $new_type =~ s/binary/char/;
+        $new_type .= '('. $info{'COLUMN_SIZE'} .')';
+    } else {
+        $new_type =~ s/blob/text/;
+    }
+
+    push @{$alter_aggregator{binary_to_char}},
+        "MODIFY $column ". uc($new_type) ." CHARACTER SET ". $charset
+        ." ". build_column_definition(%info);
+}
+
+sub char_to_char {
+    my ($charset, %info) = @_;
+
+    my $table = $info{'TABLE_NAME'};
+    my $column = $info{'COLUMN_NAME'};
+    my $new_type = $info{'mysql_type_name'};
+
+    char_to_binary(%info);
+    push @{$alter_aggregator{binary_to_char}},
+        "MODIFY $column ". uc($new_type)." CHARACTER SET ". $charset
+        ." ". build_column_definition(%info);
+}
+
+sub calc_suitable_binary_type {
+    my %info = @_;
+    my $type = lc $info{'TYPE_NAME'};
+    return 'LONGBLOB' if $type eq 'longtext';
+
+    my $current_max_byte_length = column_byte_length(@info{qw(TABLE_NAME COLUMN_NAME)}) || 0;
+    if ( $max_type_length{ $type } > $current_max_byte_length ) {
+        if ( $type eq 'varchar' || $type eq 'char' ) {
+            my $new_type = $type;
+            $new_type =~ s/char/binary/;
+            $new_type .= $info{'COLUMN_SIZE'} >= $current_max_byte_length
+                ? '('. $info{'COLUMN_SIZE'} .')'
+                : '('. $current_max_byte_length .')';
+            return uc $new_type;
+        } else {
+            my $new_type = $type;
+            $new_type =~ s/text/blob/;
+            return uc $new_type;
+        }
+    } else {
+        my $new_type;
+        foreach ( sort { $max_type_length{$a} <=> $max_type_length{$b} } keys %max_type_length ) {
+            next if $max_type_length{ $_ } <= $current_max_byte_length;
+            
+            $new_type = $_; last;
+        }
+        $new_type =~ s/text/blob/;
+        return uc $new_type;
+    }
+}
+
+sub build_column_definition {
+    my %info = @_;
+
+    my $res = '';
+    $res .= 'NOT ' unless $info{'NULLABLE'};
+    $res .= 'NULL';
+    my $default = column_info(@info{qw(TABLE_NAME COLUMN_NAME)})->{default};
+    if ( defined $default ) {
+        $res .= ' DEFAULT '. $dbh->quote($default);
+    } elsif ( $info{'NULLABLE'} ) {
+        $res .= ' DEFAULT NULL';
+    }
+    $res .= ' AUTO_INCREMENT' if $info{'mysql_is_auto_increment'};
+    return $res;
+}
+
+sub column_byte_length {
+    my ($table, $column) = @_;
+    if ( $version >= 5.0 ) {
+        my ($char, $octet) = @{ $dbh->selectrow_arrayref(
+            "SELECT CHARACTER_MAXIMUM_LENGTH, CHARACTER_OCTET_LENGTH FROM information_schema.COLUMNS WHERE"
+            ."     TABLE_SCHEMA = ". $dbh->quote($db_name)
+            ." AND TABLE_NAME   = ". $dbh->quote($table)
+            ." AND COLUMN_NAME  = ". $dbh->quote($column)
+        ) };
+        return $octet if $octet == $char;
+    }
+    return $dbh->selectrow_arrayref("SELECT MAX(LENGTH(". $dbh->quote_identifier($column) .")) FROM $table")->[0];
+}
+
+sub column_info {
+    my ($table, $column) = @_;
+    # XXX: DBD::mysql doesn't provide this info, may be will do in 4.0007 if I'll write a patch
+    local $dbh->{FetchHashKeyName} = 'NAME_lc';
+    return $dbh->selectrow_hashref("SHOW FULL COLUMNS FROM $table LIKE " . $dbh->quote($column));
+}
+
diff --git a/rt/install-sh b/rt/install-sh
index 11870f1b0..a5897de6e 100644
--- a/rt/install-sh
+++ b/rt/install-sh
@@ -1,251 +1,519 @@
 #!/bin/sh
-#
 # install - install a program, script, or datafile
-# This comes from X11R5 (mit/util/scripts/install.sh).
+
+scriptversion=2006-12-25.00
+
+# This originates from X11R5 (mit/util/scripts/install.sh), which was
+# later released in X11R6 (xc/config/util/install.sh) with the
+# following copyright and license.
+#
+# Copyright (C) 1994 X Consortium
+#
+# 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
+# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
+# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 #
-# Copyright 1991 by the Massachusetts Institute of Technology
+# Except as contained in this notice, the name of the X Consortium shall not
+# be used in advertising or otherwise to promote the sale, use or other deal-
+# ings in this Software without prior written authorization from the X Consor-
+# tium.
 #
-# Permission to use, copy, modify, distribute, and sell this software and its
-# documentation for any purpose is hereby granted without fee, provided that
-# the above copyright notice appear in all copies and that both that
-# copyright notice and this permission notice appear in supporting
-# documentation, and that the name of M.I.T. not be used in advertising or
-# publicity pertaining to distribution of the software without specific,
-# written prior permission.  M.I.T. makes no representations about the
-# suitability of this software for any purpose.  It is provided "as is"
-# without express or implied warranty.
+#
+# FSF changes to this file are in the public domain.
 #
 # Calling this script install-sh is preferred over install.sh, to prevent
 # `make' implicit rules from creating a file called install from it
 # when there is no Makefile.
 #
 # This script is compatible with the BSD install script, but was written
-# from scratch.  It can only install one file at a time, a restriction
-# shared with many OS's install programs.
+# from scratch.
 
+nl='
+'
+IFS=" ""	$nl"
 
 # set DOITPROG to echo to test this script
 
 # Don't use :- since 4.3BSD and earlier shells don't like it.
-doit="${DOITPROG-}"
-
-
-# put in absolute paths if you don't have them in your path; or use env. vars.
-
-mvprog="${MVPROG-mv}"
-cpprog="${CPPROG-cp}"
-chmodprog="${CHMODPROG-chmod}"
-chownprog="${CHOWNPROG-chown}"
-chgrpprog="${CHGRPPROG-chgrp}"
-stripprog="${STRIPPROG-strip}"
-rmprog="${RMPROG-rm}"
-mkdirprog="${MKDIRPROG-mkdir}"
-
-transformbasename=""
-transform_arg=""
-instcmd="$mvprog"
-chmodcmd="$chmodprog 0755"
-chowncmd=""
-chgrpcmd=""
-stripcmd=""
-rmcmd="$rmprog -f"
-mvcmd="$mvprog"
-src=""
-dst=""
-dir_arg=""
-
-while [ x"$1" != x ]; do
-    case $1 in
-	-c) instcmd="$cpprog"
-	    shift
-	    continue;;
-
-	-d) dir_arg=true
-	    shift
-	    continue;;
-
-	-m) chmodcmd="$chmodprog $2"
-	    shift
-	    shift
-	    continue;;
-
-	-o) chowncmd="$chownprog $2"
-	    shift
-	    shift
-	    continue;;
-
-	-g) chgrpcmd="$chgrpprog $2"
-	    shift
-	    shift
-	    continue;;
-
-	-s) stripcmd="$stripprog"
-	    shift
-	    continue;;
-
-	-t=*) transformarg=`echo $1 | sed 's/-t=//'`
-	    shift
-	    continue;;
-
-	-b=*) transformbasename=`echo $1 | sed 's/-b=//'`
-	    shift
-	    continue;;
-
-	*)  if [ x"$src" = x ]
-	    then
-		src=$1
-	    else
-		# this colon is to work around a 386BSD /bin/sh bug
-		:
-		dst=$1
-	    fi
-	    shift
-	    continue;;
-    esac
-done
-
-if [ x"$src" = x ]
-then
-	echo "install:	no input file specified"
-	exit 1
+doit=${DOITPROG-}
+if test -z "$doit"; then
+  doit_exec=exec
 else
-	:
+  doit_exec=$doit
 fi
 
-if [ x"$dir_arg" != x ]; then
-	dst=$src
-	src=""
-	
-	if [ -d $dst ]; then
-		instcmd=:
-		chmodcmd=""
-	else
-		instcmd=$mkdirprog
-	fi
-else
+# Put in absolute file names if you don't have them in your path;
+# or use environment vars.
+
+chgrpprog=${CHGRPPROG-chgrp}
+chmodprog=${CHMODPROG-chmod}
+chownprog=${CHOWNPROG-chown}
+cmpprog=${CMPPROG-cmp}
+cpprog=${CPPROG-cp}
+mkdirprog=${MKDIRPROG-mkdir}
+mvprog=${MVPROG-mv}
+rmprog=${RMPROG-rm}
+stripprog=${STRIPPROG-strip}
+
+posix_glob='?'
+initialize_posix_glob='
+  test "$posix_glob" != "?" || {
+    if (set -f) 2>/dev/null; then
+      posix_glob=
+    else
+      posix_glob=:
+    fi
+  }
+'
+
+posix_mkdir=
+
+# Desired mode of installed file.
+mode=0755
+
+chgrpcmd=
+chmodcmd=$chmodprog
+chowncmd=
+mvcmd=$mvprog
+rmcmd="$rmprog -f"
+stripcmd=
 
-# Waiting for this to be detected by the "$instcmd $src $dsttmp" command
-# might cause directories to be created, which would be especially bad 
-# if $src (and thus $dsttmp) contains '*'.
+src=
+dst=
+dir_arg=
+dst_arg=
 
-	if [ -f "$src" ] || [ -d "$src" ]
-	then
-		:
-	else
-		echo "install:  $src does not exist"
-		exit 1
-	fi
-	
-	if [ x"$dst" = x ]
-	then
-		echo "install:	no destination specified"
-		exit 1
-	else
-		:
-	fi
+copy_on_change=false
+no_target_directory=
 
-# If destination is a directory, append the input filename; if your system
-# does not like double slashes in filenames, you may need to add some logic
+usage="\
+Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE
+   or: $0 [OPTION]... SRCFILES... DIRECTORY
+   or: $0 [OPTION]... -t DIRECTORY SRCFILES...
+   or: $0 [OPTION]... -d DIRECTORIES...
 
-	if [ -d $dst ]
-	then
-		dst="$dst"/`basename $src`
-	else
-		:
-	fi
-fi
+In the 1st form, copy SRCFILE to DSTFILE.
+In the 2nd and 3rd, copy all SRCFILES to DIRECTORY.
+In the 4th, create DIRECTORIES.
 
-## this sed command emulates the dirname command
-dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'`
+Options:
+     --help     display this help and exit.
+     --version  display version info and exit.
 
-# Make sure that the destination directory exists.
-#  this part is taken from Noah Friedman's mkinstalldirs script
+  -c            (ignored)
+  -C            install only if different (preserve the last data modification time)
+  -d            create directories instead of installing files.
+  -g GROUP      $chgrpprog installed files to GROUP.
+  -m MODE       $chmodprog installed files to MODE.
+  -o USER       $chownprog installed files to USER.
+  -s            $stripprog installed files.
+  -t DIRECTORY  install into DIRECTORY.
+  -T            report an error if DSTFILE is a directory.
 
-# Skip lots of stat calls in the usual case.
-if [ ! -d "$dstdir" ]; then
-defaultIFS='
-	'
-IFS="${IFS-${defaultIFS}}"
+Environment variables override the default commands:
+  CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG
+  RMPROG STRIPPROG
+"
 
-oIFS="${IFS}"
-# Some sh's can't handle IFS=/ for some reason.
-IFS='%'
-set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'`
-IFS="${oIFS}"
+while test $# -ne 0; do
+  case $1 in
+    -c) ;;
 
-pathcomp=''
+    -C) copy_on_change=true;;
 
-while [ $# -ne 0 ] ; do
-	pathcomp="${pathcomp}${1}"
-	shift
+    -d) dir_arg=true;;
 
-	if [ ! -d "${pathcomp}" ] ;
-        then
-		$mkdirprog "${pathcomp}"
-	else
-		:
-	fi
+    -g) chgrpcmd="$chgrpprog $2"
+	shift;;
 
-	pathcomp="${pathcomp}/"
-done
-fi
+    --help) echo "$usage"; exit $?;;
 
-if [ x"$dir_arg" != x ]
-then
-	$doit $instcmd $dst &&
+    -m) mode=$2
+	case $mode in
+	  *' '* | *'	'* | *'
+'*	  | *'*'* | *'?'* | *'['*)
+	    echo "$0: invalid mode: $mode" >&2
+	    exit 1;;
+	esac
+	shift;;
 
-	if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else : ; fi &&
-	if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else : ; fi &&
-	if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else : ; fi &&
-	if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else : ; fi
-else
+    -o) chowncmd="$chownprog $2"
+	shift;;
 
-# If we're going to rename the final executable, determine the name now.
+    -s) stripcmd=$stripprog;;
 
-	if [ x"$transformarg" = x ] 
-	then
-		dstfile=`basename $dst`
-	else
-		dstfile=`basename $dst $transformbasename | 
-			sed $transformarg`$transformbasename
-	fi
+    -t) dst_arg=$2
+	shift;;
 
-# don't allow the sed command to completely eliminate the filename
+    -T) no_target_directory=true;;
 
-	if [ x"$dstfile" = x ] 
-	then
-		dstfile=`basename $dst`
-	else
-		:
-	fi
-
-# Make a temp file name in the proper directory.
+    --version) echo "$0 $scriptversion"; exit $?;;
 
-	dsttmp=$dstdir/#inst.$$#
+    --)	shift
+	break;;
 
-# Move or copy the file name to the temp name
+    -*)	echo "$0: invalid option: $1" >&2
+	exit 1;;
 
-	$doit $instcmd $src $dsttmp &&
-
-	trap "rm -f ${dsttmp}" 0 &&
+    *)  break;;
+  esac
+  shift
+done
 
-# and set any options; do chmod last to preserve setuid bits
+if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then
+  # When -d is used, all remaining arguments are directories to create.
+  # When -t is used, the destination is already specified.
+  # Otherwise, the last argument is the destination.  Remove it from $@.
+  for arg
+  do
+    if test -n "$dst_arg"; then
+      # $@ is not empty: it contains at least $arg.
+      set fnord "$@" "$dst_arg"
+      shift # fnord
+    fi
+    shift # arg
+    dst_arg=$arg
+  done
+fi
 
-# If any of these fail, we abort the whole thing.  If we want to
-# ignore errors from any of these, just make sure not to ignore
-# errors from the above "$doit $instcmd $src $dsttmp" command.
+if test $# -eq 0; then
+  if test -z "$dir_arg"; then
+    echo "$0: no input file specified." >&2
+    exit 1
+  fi
+  # It's OK to call `install-sh -d' without argument.
+  # This can happen when creating conditional directories.
+  exit 0
+fi
 
-	if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else :;fi &&
-	if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else :;fi &&
-	if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else :;fi &&
-	if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else :;fi &&
+if test -z "$dir_arg"; then
+  trap '(exit $?); exit' 1 2 13 15
+
+  # Set umask so as not to create temps with too-generous modes.
+  # However, 'strip' requires both read and write access to temps.
+  case $mode in
+    # Optimize common cases.
+    *644) cp_umask=133;;
+    *755) cp_umask=22;;
+
+    *[0-7])
+      if test -z "$stripcmd"; then
+	u_plus_rw=
+      else
+	u_plus_rw='% 200'
+      fi
+      cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;;
+    *)
+      if test -z "$stripcmd"; then
+	u_plus_rw=
+      else
+	u_plus_rw=,u+rw
+      fi
+      cp_umask=$mode$u_plus_rw;;
+  esac
+fi
 
-# Now rename the file to the real destination.
+for src
+do
+  # Protect names starting with `-'.
+  case $src in
+    -*) src=./$src;;
+  esac
+
+  if test -n "$dir_arg"; then
+    dst=$src
+    dstdir=$dst
+    test -d "$dstdir"
+    dstdir_status=$?
+  else
+
+    # Waiting for this to be detected by the "$cpprog $src $dsttmp" command
+    # might cause directories to be created, which would be especially bad
+    # if $src (and thus $dsttmp) contains '*'.
+    if test ! -f "$src" && test ! -d "$src"; then
+      echo "$0: $src does not exist." >&2
+      exit 1
+    fi
+
+    if test -z "$dst_arg"; then
+      echo "$0: no destination specified." >&2
+      exit 1
+    fi
+
+    dst=$dst_arg
+    # Protect names starting with `-'.
+    case $dst in
+      -*) dst=./$dst;;
+    esac
 
-	$doit $rmcmd -f $dstdir/$dstfile &&
-	$doit $mvcmd $dsttmp $dstdir/$dstfile 
+    # If destination is a directory, append the input filename; won't work
+    # if double slashes aren't ignored.
+    if test -d "$dst"; then
+      if test -n "$no_target_directory"; then
+	echo "$0: $dst_arg: Is a directory" >&2
+	exit 1
+      fi
+      dstdir=$dst
+      dst=$dstdir/`basename "$src"`
+      dstdir_status=0
+    else
+      # Prefer dirname, but fall back on a substitute if dirname fails.
+      dstdir=`
+	(dirname "$dst") 2>/dev/null ||
+	expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+	     X"$dst" : 'X\(//\)[^/]' \| \
+	     X"$dst" : 'X\(//\)$' \| \
+	     X"$dst" : 'X\(/\)' \| . 2>/dev/null ||
+	echo X"$dst" |
+	    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+		   s//\1/
+		   q
+		 }
+		 /^X\(\/\/\)[^/].*/{
+		   s//\1/
+		   q
+		 }
+		 /^X\(\/\/\)$/{
+		   s//\1/
+		   q
+		 }
+		 /^X\(\/\).*/{
+		   s//\1/
+		   q
+		 }
+		 s/.*/./; q'
+      `
+
+      test -d "$dstdir"
+      dstdir_status=$?
+    fi
+  fi
+
+  obsolete_mkdir_used=false
+
+  if test $dstdir_status != 0; then
+    case $posix_mkdir in
+      '')
+	# Create intermediate dirs using mode 755 as modified by the umask.
+	# This is like FreeBSD 'install' as of 1997-10-28.
+	umask=`umask`
+	case $stripcmd.$umask in
+	  # Optimize common cases.
+	  *[2367][2367]) mkdir_umask=$umask;;
+	  .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;;
+
+	  *[0-7])
+	    mkdir_umask=`expr $umask + 22 \
+	      - $umask % 100 % 40 + $umask % 20 \
+	      - $umask % 10 % 4 + $umask % 2
+	    `;;
+	  *) mkdir_umask=$umask,go-w;;
+	esac
+
+	# With -d, create the new directory with the user-specified mode.
+	# Otherwise, rely on $mkdir_umask.
+	if test -n "$dir_arg"; then
+	  mkdir_mode=-m$mode
+	else
+	  mkdir_mode=
+	fi
 
-fi &&
+	posix_mkdir=false
+	case $umask in
+	  *[123567][0-7][0-7])
+	    # POSIX mkdir -p sets u+wx bits regardless of umask, which
+	    # is incompatible with FreeBSD 'install' when (umask & 300) != 0.
+	    ;;
+	  *)
+	    tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$
+	    trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0
+
+	    if (umask $mkdir_umask &&
+		exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1
+	    then
+	      if test -z "$dir_arg" || {
+		   # Check for POSIX incompatibilities with -m.
+		   # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or
+		   # other-writeable bit of parent directory when it shouldn't.
+		   # FreeBSD 6.1 mkdir -m -p sets mode of existing directory.
+		   ls_ld_tmpdir=`ls -ld "$tmpdir"`
+		   case $ls_ld_tmpdir in
+		     d????-?r-*) different_mode=700;;
+		     d????-?--*) different_mode=755;;
+		     *) false;;
+		   esac &&
+		   $mkdirprog -m$different_mode -p -- "$tmpdir" && {
+		     ls_ld_tmpdir_1=`ls -ld "$tmpdir"`
+		     test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1"
+		   }
+		 }
+	      then posix_mkdir=:
+	      fi
+	      rmdir "$tmpdir/d" "$tmpdir"
+	    else
+	      # Remove any dirs left behind by ancient mkdir implementations.
+	      rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null
+	    fi
+	    trap '' 0;;
+	esac;;
+    esac
 
+    if
+      $posix_mkdir && (
+	umask $mkdir_umask &&
+	$doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir"
+      )
+    then :
+    else
+
+      # The umask is ridiculous, or mkdir does not conform to POSIX,
+      # or it failed possibly due to a race condition.  Create the
+      # directory the slow way, step by step, checking for races as we go.
+
+      case $dstdir in
+	/*) prefix='/';;
+	-*) prefix='./';;
+	*)  prefix='';;
+      esac
+
+      eval "$initialize_posix_glob"
+
+      oIFS=$IFS
+      IFS=/
+      $posix_glob set -f
+      set fnord $dstdir
+      shift
+      $posix_glob set +f
+      IFS=$oIFS
+
+      prefixes=
+
+      for d
+      do
+	test -z "$d" && continue
+
+	prefix=$prefix$d
+	if test -d "$prefix"; then
+	  prefixes=
+	else
+	  if $posix_mkdir; then
+	    (umask=$mkdir_umask &&
+	     $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break
+	    # Don't fail if two instances are running concurrently.
+	    test -d "$prefix" || exit 1
+	  else
+	    case $prefix in
+	      *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;;
+	      *) qprefix=$prefix;;
+	    esac
+	    prefixes="$prefixes '$qprefix'"
+	  fi
+	fi
+	prefix=$prefix/
+      done
+
+      if test -n "$prefixes"; then
+	# Don't fail if two instances are running concurrently.
+	(umask $mkdir_umask &&
+	 eval "\$doit_exec \$mkdirprog $prefixes") ||
+	  test -d "$dstdir" || exit 1
+	obsolete_mkdir_used=true
+      fi
+    fi
+  fi
+
+  if test -n "$dir_arg"; then
+    { test -z "$chowncmd" || $doit $chowncmd "$dst"; } &&
+    { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } &&
+    { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false ||
+      test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1
+  else
+
+    # Make a couple of temp file names in the proper directory.
+    dsttmp=$dstdir/_inst.$$_
+    rmtmp=$dstdir/_rm.$$_
+
+    # Trap to clean up those temp files at exit.
+    trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0
+
+    # Copy the file name to the temp name.
+    (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") &&
+
+    # and set any options; do chmod last to preserve setuid bits.
+    #
+    # If any of these fail, we abort the whole thing.  If we want to
+    # ignore errors from any of these, just make sure not to ignore
+    # errors from the above "$doit $cpprog $src $dsttmp" command.
+    #
+    { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } &&
+    { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } &&
+    { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } &&
+    { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } &&
+
+    # If -C, don't bother to copy if it wouldn't change the file.
+    if $copy_on_change &&
+       old=`LC_ALL=C ls -dlL "$dst"	2>/dev/null` &&
+       new=`LC_ALL=C ls -dlL "$dsttmp"	2>/dev/null` &&
+
+       eval "$initialize_posix_glob" &&
+       $posix_glob set -f &&
+       set X $old && old=:$2:$4:$5:$6 &&
+       set X $new && new=:$2:$4:$5:$6 &&
+       $posix_glob set +f &&
+
+       test "$old" = "$new" &&
+       $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1
+    then
+      rm -f "$dsttmp"
+    else
+      # Rename the file to the real destination.
+      $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null ||
+
+      # The rename failed, perhaps because mv can't rename something else
+      # to itself, or perhaps because mv is so ancient that it does not
+      # support -f.
+      {
+	# Now remove or move aside any old file at destination location.
+	# We try this two ways since rm can't unlink itself on some
+	# systems and the destination file might be busy for other
+	# reasons.  In this case, the final cleanup might fail but the new
+	# file should still install successfully.
+	{
+	  test ! -f "$dst" ||
+	  $doit $rmcmd -f "$dst" 2>/dev/null ||
+	  { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null &&
+	    { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; }
+	  } ||
+	  { echo "$0: cannot unlink or rename $dst" >&2
+	    (exit 1); exit 1
+	  }
+	} &&
+
+	# Now rename the file to the real destination.
+	$doit $mvcmd "$dsttmp" "$dst"
+      }
+    fi || exit 1
+
+    trap '' 0
+  fi
+done
 
-exit 0
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-end: "$"
+# End:
diff --git a/rt/lib/RT.pm b/rt/lib/RT.pm
index 42c9ea299..3ed85afc0 100644
--- a/rt/lib/RT.pm
+++ b/rt/lib/RT.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,61 +45,79 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
-package RT;
+
 use strict;
-use RT::I18N;
-use RT::CurrentUser;
-use RT::System;
-
-use vars qw($VERSION $System $SystemUser $Nobody $Handle $Logger
-        $CORE_CONFIG_FILE
-        $SITE_CONFIG_FILE
-        $BasePath
-        $EtcPath
-        $VarPath
-        $LocalPath
-        $LocalEtcPath
-        $LocalLexiconPath
-        $LogDir
-        $BinPath
-        $MasonComponentRoot
-        $MasonLocalComponentRoot
-        $MasonDataDir
-        $MasonSessionDir
-);
-
-$VERSION = '3.6.10';
-$CORE_CONFIG_FILE = "/opt/rt3/etc/RT_Config.pm";
-$SITE_CONFIG_FILE = "/opt/rt3/etc/RT_SiteConfig.pm";
-
-
-
-$BasePath = '/opt/rt3';
-
-$EtcPath = '/opt/rt3/etc';
-$BinPath = '/opt/rt3/bin';
-$VarPath = '/opt/rt3/var';
-$LocalPath = '/opt/rt3/local';
-$LocalEtcPath = '/opt/rt3/local/etc';
-$LocalLexiconPath = '/opt/rt3/local/po';
+use warnings;
+
+package RT;
+
+
+use File::Spec ();
+use Cwd ();
+
+use vars qw($Config $System $SystemUser $Nobody $Handle $Logger $_INSTALL_MODE);
+
+our $VERSION = '3.8.7';
+
+
+
+our $BasePath = '/opt/rt3';
+our $EtcPath = 'etc';
+our $BinPath = 'bin';
+our $SbinPath = 'sbin';
+our $VarPath = 'var';
+our $PluginPath = 'plugins';
+our $LocalPath = 'local';
+our $LocalEtcPath = 'local/etc';
+our $LocalLibPath        =    'local/lib';
+our $LocalLexiconPath = 'local/po';
+our $LocalPluginPath = $LocalPath."/plugins";
+
 
 # $MasonComponentRoot is where your rt instance keeps its mason html files
 
-$MasonComponentRoot = '/opt/rt3/share/html';
+our $MasonComponentRoot = 'share/html';
 
 # $MasonLocalComponentRoot is where your rt instance keeps its site-local
 # mason html files.
 
-$MasonLocalComponentRoot = '/opt/rt3/local/html';
+our $MasonLocalComponentRoot = 'local/html';
 
 # $MasonDataDir Where mason keeps its datafiles
 
-$MasonDataDir = '/opt/rt3/var/mason_data';
+our $MasonDataDir = 'var/mason_data';
 
 # RT needs to put session data (for preserving state between connections
 # via the web interface)
-$MasonSessionDir = '/opt/rt3/var/session_data';
+our $MasonSessionDir = 'var/session_data';
+
+unless (  File::Spec->file_name_is_absolute($EtcPath) ) {
+
+# if BasePath exists and is absolute, we won't infer it from $INC{'RT.pm'}.
+# otherwise RT.pm will make src dir(where we configure RT) be the BasePath 
+# instead of the --prefix one
+    unless ( -d $BasePath && File::Spec->file_name_is_absolute($BasePath) ) {
+        my $pm_path = ( File::Spec->splitpath( $INC{'RT.pm'} ) )[1];
 
+       # need rel2abs here is to make sure path is absolute, since $INC{'RT.pm'}
+       # is not always absolute
+        $BasePath =
+          File::Spec->rel2abs(
+            File::Spec->catdir( $pm_path, File::Spec->updir ) );
+    }
+
+    $BasePath = Cwd::realpath( $BasePath );
+
+    for my $path ( qw/EtcPath BinPath SbinPath VarPath LocalPath LocalEtcPath
+            LocalLibPath LocalLexiconPath PluginPath LocalPluginPath 
+            MasonComponentRoot MasonLocalComponentRoot MasonDataDir 
+            MasonSessionDir/ ) {
+        no strict 'refs';
+        # just change relative ones
+        $$path = File::Spec->catfile( $BasePath, $$path )
+          unless File::Spec->file_name_is_absolute( $$path );
+    }
+}
 
 
 =head1 NAME
@@ -112,12 +130,14 @@ A fully featured request tracker package
 
 =head1 DESCRIPTION
 
+=head2 INITIALIZATION
+
 =head2 LoadConfig
 
 Load RT's config file.  First, the site configuration file
-(C) is loaded, in order to establish overall site
+(F) is loaded, in order to establish overall site
 settings like hostname and name of RT instance.  Then, the core
-configuration file (C) is loaded to set fallback values
+configuration file (F) is loaded to set fallback values
 for all settings; it bases some values on settings from the site
 configuration file.
 
@@ -128,59 +148,33 @@ have not been set already.
 =cut
 
 sub LoadConfig {
-     local *Set = sub { $_[0] = $_[1] unless defined $_[0] }; 
-
-    my $username = getpwuid($>);
-    my $group = getgrgid($();
-    my $message = <LoadConfigs;
+    require RT::I18N;
 
     # RT::Essentials mistakenly recommends that WebPath be set to '/'.
     # If the user does that, do what they mean.
     $RT::WebPath = '' if ($RT::WebPath eq '/');
 
-    $ENV{'TZ'} = $RT::Timezone if ($RT::Timezone);
+    # fix relative LogDir and GnuPG homedir
+    unless ( File::Spec->file_name_is_absolute( $Config->Get('LogDir') ) ) {
+        $Config->Set( LogDir =>
+              File::Spec->catfile( $BasePath, $Config->Get('LogDir') ) );
+    }
 
+    my $gpgopts = $Config->Get('GnuPGOptions');
+    unless ( File::Spec->file_name_is_absolute( $gpgopts->{homedir} ) ) {
+        $gpgopts->{homedir} = File::Spec->catfile( $BasePath, $gpgopts->{homedir} );
+    }
+    
     RT::I18N->Init;
 }
 
 =head2 Init
 
-Conenct to the database, set up logging.
+L, L,
+L and L.
 
 =cut
 
@@ -188,41 +182,34 @@ sub Init {
 
     CheckPerlRequirements();
 
+    InitPluginPaths();
+
     #Get a database connection
     ConnectToDatabase();
-
-    #RT's system user is a genuine database user. its id lives here
-    $SystemUser = new RT::CurrentUser();
-    $SystemUser->LoadByName('RT_System');
-    
-    #RT's "nobody user" is a genuine database user. its ID lives here.
-    $Nobody = new RT::CurrentUser();
-    $Nobody->LoadByName('Nobody');
-  
-    $System = RT::System->new();
-
+    InitSystemObjects();
     InitClasses();
     InitLogging(); 
-}
+    InitPlugins();
+    RT->Config->PostLoadCheck;
 
+}
 
 =head2 ConnectToDatabase
 
-Get a database connection
+Get a database connection. See also .
 
 =cut
 
 sub ConnectToDatabase {
     require RT::Handle;
-    unless ($Handle && $Handle->dbh && $Handle->dbh->ping) {
-        $Handle = RT::Handle->new();
-    } 
-    $Handle->Connect();
+    $Handle = new RT::Handle unless $Handle;
+    $Handle->Connect;
+    return $Handle;
 }
 
 =head2 InitLogging
 
-Create the RT::Logger object. 
+Create the Logger object and set up signal handlers.
 
 =cut
 
@@ -231,112 +218,141 @@ sub InitLogging {
     # We have to set the record separator ($, man perlvar)
     # or Log::Dispatch starts getting
     # really pissy, as some other module we use unsets it.
-
     $, = '';
     use Log::Dispatch 1.6;
 
-    unless ($RT::Logger) {
+    my %level_to_num = (
+        map( { $_ => } 0..7 ),
+        debug     => 0,
+        info      => 1,
+        notice    => 2,
+        warning   => 3,
+        error     => 4, 'err' => 4,
+        critical  => 5, crit  => 5,
+        alert     => 6, 
+        emergency => 7, emerg => 7,
+    );
 
-    $RT::Logger = Log::Dispatch->new();
+    unless ( $RT::Logger ) {
 
-    my $simple_cb = sub {
-        # if this code throw any warning we can get segfault
-        no warnings;
+        $RT::Logger = Log::Dispatch->new;
 
-        my %p = @_;
+        my $stack_from_level;
+        if ( $stack_from_level = RT->Config->Get('LogStackTraces') ) {
+            # if option has old style '\d'(true) value
+            $stack_from_level = 0 if $stack_from_level =~ /^\d+$/;
+            $stack_from_level = $level_to_num{ $stack_from_level } || 0;
+        } else {
+            $stack_from_level = 99; # don't log
+        }
 
-        my $frame = 0; # stack frame index
-        # skip Log::* stack frames
-        $frame++ while( caller($frame) && caller($frame) =~ /^Log::/ );
+        my $simple_cb = sub {
+            # if this code throw any warning we can get segfault
+            no warnings;
+            my %p = @_;
+
+            # skip Log::* stack frames
+            my $frame = 0;
+            $frame++ while caller($frame) && caller($frame) =~ /^Log::/;
+            my ($package, $filename, $line) = caller($frame);
+
+            $p{'message'} =~ s/(?:\r*\n)+$//;
+            return "[". gmtime(time) ."] [". $p{'level'} ."]: "
+                . $p{'message'} ." ($filename:$line)\n";
+        };
+
+        my $syslog_cb = sub {
+            # if this code throw any warning we can get segfault
+            no warnings;
+            my %p = @_;
+
+            my $frame = 0; # stack frame index
+            # skip Log::* stack frames
+            $frame++ while caller($frame) && caller($frame) =~ /^Log::/;
+            my ($package, $filename, $line) = caller($frame);
+
+            # syswrite() cannot take utf8; turn it off here.
+            Encode::_utf8_off($p{message});
+
+            $p{message} =~ s/(?:\r*\n)+$//;
+            if ($p{level} eq 'debug') {
+                return "$p{message}\n";
+            } else {
+                return "$p{message} ($filename:$line)\n";
+            }
+        };
 
-        my ($package, $filename, $line) = caller($frame);
-        $p{message} =~ s/(?:\r*\n)+$//;
-        my $str = "[".gmtime(time)."] [".$p{level}."]: $p{message} ($filename:$line)\n";
+        my $stack_cb = sub {
+            no warnings;
+            my %p = @_;
+            return $p{'message'} unless $level_to_num{ $p{'level'} } >= $stack_from_level;
+            
+            require Devel::StackTrace;
+            my $trace = Devel::StackTrace->new( ignore_class => [ 'Log::Dispatch', 'Log::Dispatch::Base' ] );
+            return $p{'message'} . $trace->as_string;
 
-        if( $RT::LogStackTraces ) {
-            $str .= "\nStack trace:\n";
             # skip calling of the Log::* subroutins
-            $frame++ while( caller($frame) && (caller($frame))[3] =~ /^Log::/ );
+            my $frame = 0;
+            $frame++ while caller($frame) && caller($frame) =~ /^Log::/;
+            $frame++ while caller($frame) && (caller($frame))[3] =~ /^Log::/;
+
+            $p{'message'} .= "\nStack trace:\n";
             while( my ($package, $filename, $line, $sub) = caller($frame++) ) {
-                $str .= "\t". $sub ."() called at $filename:$line\n";
+                $p{'message'} .= "\t$sub(...) called at $filename:$line\n";
+            }
+            return $p{'message'};
+        };
+
+        if ( $Config->Get('LogToFile') ) {
+            my ($filename, $logdir) = (
+                $Config->Get('LogToFileNamed') || 'rt.log',
+                $Config->Get('LogDir') || File::Spec->catdir( $VarPath, 'log' ),
+            );
+            if ( $filename =~ m![/\\]! ) { # looks like an absolute path.
+                ($logdir) = $filename =~ m{^(.*[/\\])};
+            }
+            else {
+                $filename = File::Spec->catfile( $logdir, $filename );
             }
-        }
-        return $str;
-    };
-
-    my $syslog_cb = sub {
-        my %p = @_;
-
-        my $frame = 0; # stack frame index
-        # skip Log::* stack frames
-        $frame++ while( caller($frame) && caller($frame) =~ /^Log::/ );
-        my ($package, $filename, $line) = caller($frame);
 
-        # syswrite() cannot take utf8; turn it off here.
-        Encode::_utf8_off($p{message});
+            unless ( -d $logdir && ( ( -f $filename && -w $filename ) || -w $logdir ) ) {
+                # localizing here would be hard when we don't have a current user yet
+                die "Log file '$filename' couldn't be written or created.\n RT can't run.";
+            }
 
-        $p{message} =~ s/(?:\r*\n)+$//;
-        if ($p{level} eq 'debug') {
-            return "$p{message}\n"
-        } else {
-            return "$p{message} ($filename:$line)\n"
-        }
-    };
-    
-    if ($RT::LogToFile) {
-        my ($filename, $logdir);
-        if ($RT::LogToFileNamed =~ m![/\\]!) {
-            # looks like an absolute path.
-            $filename = $RT::LogToFileNamed;
-            ($logdir) = $RT::LogToFileNamed =~ m!^(.*[/\\])!;
+            require Log::Dispatch::File;
+            $RT::Logger->add( Log::Dispatch::File->new
+                           ( name=>'file',
+                             min_level=> $Config->Get('LogToFile'),
+                             filename=> $filename,
+                             mode=>'append',
+                             callbacks => [ $simple_cb, $stack_cb ],
+                           ));
         }
-        else {
-            $filename = "$RT::LogDir/$RT::LogToFileNamed";
-            $logdir = $RT::LogDir;
+        if ( $Config->Get('LogToScreen') ) {
+            require Log::Dispatch::Screen;
+            $RT::Logger->add( Log::Dispatch::Screen->new
+                         ( name => 'screen',
+                           min_level => $Config->Get('LogToScreen'),
+                           callbacks => [ $simple_cb, $stack_cb ],
+                           stderr => 1,
+                         ));
         }
-
-        unless ( -d $logdir && ( ( -f $filename && -w $filename ) || -w $logdir ) ) {
-            # localizing here would be hard when we don't have a current user yet
-            die "Log file $filename couldn't be written or created.\n RT can't run.";
+        if ( $Config->Get('LogToSyslog') ) {
+            require Log::Dispatch::Syslog;
+            $RT::Logger->add(Log::Dispatch::Syslog->new
+                         ( name => 'syslog',
+                           ident => 'RT',
+                           min_level => $Config->Get('LogToSyslog'),
+                           callbacks => [ $syslog_cb, $stack_cb ],
+                           stderr => 1,
+                           $Config->Get('LogToSyslogConf'),
+                         ));
         }
-
-        package Log::Dispatch::File;
-        require Log::Dispatch::File;
-        $RT::Logger->add(Log::Dispatch::File->new
-                       ( name=>'rtlog',
-                         min_level=> $RT::LogToFile,
-                         filename=> $filename,
-                         mode=>'append',
-                         callbacks => $simple_cb,
-                       ));
-    }
-    if ($RT::LogToScreen) {
-        package Log::Dispatch::Screen;
-        require Log::Dispatch::Screen;
-        $RT::Logger->add(Log::Dispatch::Screen->new
-                     ( name => 'screen',
-                       min_level => $RT::LogToScreen,
-                       callbacks => $simple_cb,
-                       stderr => 1,
-                     ));
-    }
-    if ($RT::LogToSyslog) {
-        package Log::Dispatch::Syslog;
-        require Log::Dispatch::Syslog;
-        $RT::Logger->add(Log::Dispatch::Syslog->new
-                     ( name => 'syslog',
-                       ident => 'RT',
-                       min_level => $RT::LogToSyslog,
-                       callbacks => $syslog_cb,
-                       stderr => 1,
-                       @RT::LogToSyslogConf
-                     ));
-    }
-
     }
 
-# {{{ Signal handlers
 
+# Signal handlers
 ## This is the default handling of warnings and die'ings in the code
 ## (including other used modules - maybe except for errors catched by
 ## Mason).  It will log all problems through the standard logging
@@ -355,16 +371,15 @@ sub InitLogging {
 
 #When we call die, trap it and log->crit with the value of the die.
 
-$SIG{__DIE__}  = sub {
-    unless ($^S || !defined $^S ) {
-        $RT::Handle->Rollback();
-        $RT::Logger->crit("$_[0]");
-    }
-    die $_[0];
-};
-
-# }}}
-
+    $SIG{__DIE__}  = sub {
+        # if we are not in eval and perl is not parsing code
+        # then rollback transactions and log RT error
+        unless ($^S || !defined $^S ) {
+            $RT::Handle->Rollback(1) if $RT::Handle;
+            $RT::Logger->crit("$_[0]") if $RT::Logger;
+        }
+        die $_[0];
+    };
 }
 
 
@@ -400,10 +415,9 @@ EOF
     }
 }
 
-
 =head2 InitClasses
 
-Load all modules that define base classes
+Load all modules that define base classes.
 
 =cut
 
@@ -426,6 +440,8 @@ sub InitClasses {
     require RT::ObjectCustomFields;
     require RT::ObjectCustomFieldValues;
     require RT::Attributes;
+    require RT::Dashboard;
+    require RT::Approval;
 
     # on a cold server (just after restart) people could have an object
     # in the session, as we deserialize it so we never call constructor
@@ -452,21 +468,196 @@ sub InitClasses {
     );
 }
 
-# }}}
+=head2 InitSystemObjects
+
+Initializes system objects: C<$RT::System>, C<$RT::SystemUser>
+and C<$RT::Nobody>.
+
+=cut
+
+sub InitSystemObjects {
+
+    #RT's system user is a genuine database user. its id lives here
+    require RT::CurrentUser;
+    $SystemUser = new RT::CurrentUser;
+    $SystemUser->LoadByName('RT_System');
+
+    #RT's "nobody user" is a genuine database user. its ID lives here.
+    $Nobody = new RT::CurrentUser;
+    $Nobody->LoadByName('Nobody');
+
+    require RT::System;
+    $System = RT::System->new( $SystemUser );
+}
+
+=head1 CLASS METHODS
+
+=head2 Config
+
+Returns the current L, but note that
+you must L first otherwise this method
+returns undef.
+
+Method can be called as class method.
+
+=cut
+
+sub Config { return $Config }
+
+=head2 DatabaseHandle
+
+Returns the current L.
+
+See also L.
+
+=cut
+
+sub DatabaseHandle { return $Handle }
+
+=head2 Logger
+
+Returns the logger. See also L.
+
+=cut
+
+sub Logger { return $Logger }
+
+=head2 System
+
+Returns the current L. See also
+L.
+
+=cut
+
+sub System { return $System }
+
+=head2 SystemUser
+
+Returns the system user's object, it's object of
+L class that represents the system. See also
+L.
+
+=cut
+
+sub SystemUser { return $SystemUser }
+
+=head2 Nobody
+
+Returns object of Nobody. It's object of L class
+that represents a user who can own ticket and nothing else. See
+also L.
+
+=cut
+
+sub Nobody { return $Nobody }
+
+=head2 Plugins
+
+Returns a listref of all Plugins currently configured for this RT instance.
+You can define plugins by adding them to the @Plugins list in your RT_SiteConfig
+
+=cut
+
+our @PLUGINS = ();
+sub Plugins {
+    my $self = shift;
+    unless (@PLUGINS) {
+        $self->InitPluginPaths;
+        @PLUGINS = $self->InitPlugins;
+    }
+    return \@PLUGINS;
+}
+
+=head2 PluginDirs
+
+Takes optional subdir (e.g. po, lib, etc.) and return plugins' dirs that exist.
+
+This code chacke plugins names or anything else and required when main config
+is loaded to load plugins' configs.
+
+=cut
+
+sub PluginDirs {
+    my $self = shift;
+    my $subdir = shift;
+
+    require RT::Plugin;
+
+    my @res;
+    foreach my $plugin (grep $_, RT->Config->Get('Plugins')) {
+        my $path = RT::Plugin->new( name => $plugin )->Path( $subdir );
+        next unless -d $path;
+        push @res, $path;
+    }
+    return @res;
+}
+
+=head2 InitPluginPaths
+
+Push plugins' lib paths into @INC right after F.
+In case F isn't in @INC, append them to @INC
+
+=cut
+
+sub InitPluginPaths {
+    my $self = shift || __PACKAGE__;
+
+    my @lib_dirs = $self->PluginDirs('lib');
+
+    my @tmp_inc;
+    my $added;
+    for (@INC) {
+        if ( Cwd::realpath($_) eq $RT::LocalLibPath) {
+            push @tmp_inc, $_, @lib_dirs;
+            $added = 1;
+        } else {
+            push @tmp_inc, $_;
+        }
+    }
+
+    # append @lib_dirs in case $RT::LocalLibPath isn't in @INC
+    push @tmp_inc, @lib_dirs unless $added;
+
+    my %seen;
+    @INC = grep !$seen{$_}++, @tmp_inc;
+}
+
+=head2 InitPlugins
+
+Initialze all Plugins found in the RT configuration file, setting up their lib and HTML::Mason component roots.
 
+=cut
+
+sub InitPlugins {
+    my $self    = shift;
+    my @plugins;
+    require RT::Plugin;
+    foreach my $plugin (grep $_, RT->Config->Get('Plugins')) {
+        $plugin->require;
+        die $UNIVERSAL::require::ERROR if ($UNIVERSAL::require::ERROR);
+        push @plugins, RT::Plugin->new(name =>$plugin);
+    }
+    return @plugins;
+}
 
-sub SystemUser {
-    return($SystemUser);
-}	
 
-sub Nobody {
-    return ($Nobody);
+sub InstallMode {
+    my $self = shift;
+    if (@_) {
+         $_INSTALL_MODE = shift;
+         if($_INSTALL_MODE) {
+             require RT::CurrentUser;
+            $SystemUser = RT::CurrentUser->new();
+         }
+    }
+    return $_INSTALL_MODE;
 }
 
+
 =head1 BUGS
 
-Please report them to rt-bugs@fsck.com, if you know what's broken and have at least 
-some idea of what needs to be fixed.
+Please report them to rt-bugs@bestpractical.com, if you know what's
+broken and have at least some idea of what needs to be fixed.
 
 If you're not sure what's going on, report them rt-devel@lists.bestpractical.com.
 
@@ -475,14 +666,6 @@ If you're not sure what's going on, report them rt-devel@lists.bestpractical.com
 L
 L
 
-=begin testing
-
-ok ($RT::Nobody->Name() eq 'Nobody', "Nobody is nobody");
-ok ($RT::Nobody->Name() ne 'root', "Nobody isn't named root");
-ok ($RT::SystemUser->Name() eq 'RT_System', "The system user is RT_System");
-ok ($RT::SystemUser->Name() ne 'noname', "The system user isn't noname");
-
-=end testing
 
 =cut
 
diff --git a/rt/lib/RT.pm.in b/rt/lib/RT.pm.in
index 18531109e..f0e56e83a 100644
--- a/rt/lib/RT.pm.in
+++ b/rt/lib/RT.pm.in
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,61 +45,79 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
-package RT;
+
 use strict;
-use RT::I18N;
-use RT::CurrentUser;
-use RT::System;
-
-use vars qw($VERSION $System $SystemUser $Nobody $Handle $Logger
-        $CORE_CONFIG_FILE
-        $SITE_CONFIG_FILE
-        $BasePath
-        $EtcPath
-        $VarPath
-        $LocalPath
-        $LocalEtcPath
-        $LocalLexiconPath
-        $LogDir
-        $BinPath
-        $MasonComponentRoot
-        $MasonLocalComponentRoot
-        $MasonDataDir
-        $MasonSessionDir
-);
-
-$VERSION = '@RT_VERSION_MAJOR@.@RT_VERSION_MINOR@.@RT_VERSION_PATCH@';
-$CORE_CONFIG_FILE = "@CONFIG_FILE_PATH@/RT_Config.pm";
-$SITE_CONFIG_FILE = "@CONFIG_FILE_PATH@/RT_SiteConfig.pm";
+use warnings;
+
+package RT;
+
+
+use File::Spec ();
+use Cwd ();
+
+use vars qw($Config $System $SystemUser $Nobody $Handle $Logger $_INSTALL_MODE);
+
+our $VERSION = '@RT_VERSION_MAJOR@.@RT_VERSION_MINOR@.@RT_VERSION_PATCH@';
 
 @DATABASE_ENV_PREF@
 
-$BasePath = '@RT_PATH@';
+our $BasePath = '@RT_PATH@';
+our $EtcPath = '@RT_ETC_PATH@';
+our $BinPath = '@RT_BIN_PATH@';
+our $SbinPath = '@RT_SBIN_PATH@';
+our $VarPath = '@RT_VAR_PATH@';
+our $PluginPath = '@RT_PLUGIN_PATH@';
+our $LocalPath = '@RT_LOCAL_PATH@';
+our $LocalEtcPath = '@LOCAL_ETC_PATH@';
+our $LocalLibPath        =    '@LOCAL_LIB_PATH@';
+our $LocalLexiconPath = '@LOCAL_LEXICON_PATH@';
+our $LocalPluginPath = $LocalPath."/plugins";
 
-$EtcPath = '@RT_ETC_PATH@';
-$BinPath = '@RT_BIN_PATH@';
-$VarPath = '@RT_VAR_PATH@';
-$LocalPath = '@RT_LOCAL_PATH@';
-$LocalEtcPath = '@LOCAL_ETC_PATH@';
-$LocalLexiconPath = '@LOCAL_LEXICON_PATH@';
 
 # $MasonComponentRoot is where your rt instance keeps its mason html files
 
-$MasonComponentRoot = '@MASON_HTML_PATH@';
+our $MasonComponentRoot = '@MASON_HTML_PATH@';
 
 # $MasonLocalComponentRoot is where your rt instance keeps its site-local
 # mason html files.
 
-$MasonLocalComponentRoot = '@MASON_LOCAL_HTML_PATH@';
+our $MasonLocalComponentRoot = '@MASON_LOCAL_HTML_PATH@';
 
 # $MasonDataDir Where mason keeps its datafiles
 
-$MasonDataDir = '@MASON_DATA_PATH@';
+our $MasonDataDir = '@MASON_DATA_PATH@';
 
 # RT needs to put session data (for preserving state between connections
 # via the web interface)
-$MasonSessionDir = '@MASON_SESSION_PATH@';
+our $MasonSessionDir = '@MASON_SESSION_PATH@';
+
+unless (  File::Spec->file_name_is_absolute($EtcPath) ) {
 
+# if BasePath exists and is absolute, we won't infer it from $INC{'RT.pm'}.
+# otherwise RT.pm will make src dir(where we configure RT) be the BasePath 
+# instead of the --prefix one
+    unless ( -d $BasePath && File::Spec->file_name_is_absolute($BasePath) ) {
+        my $pm_path = ( File::Spec->splitpath( $INC{'RT.pm'} ) )[1];
+
+       # need rel2abs here is to make sure path is absolute, since $INC{'RT.pm'}
+       # is not always absolute
+        $BasePath =
+          File::Spec->rel2abs(
+            File::Spec->catdir( $pm_path, File::Spec->updir ) );
+    }
+
+    $BasePath = Cwd::realpath( $BasePath );
+
+    for my $path ( qw/EtcPath BinPath SbinPath VarPath LocalPath LocalEtcPath
+            LocalLibPath LocalLexiconPath PluginPath LocalPluginPath 
+            MasonComponentRoot MasonLocalComponentRoot MasonDataDir 
+            MasonSessionDir/ ) {
+        no strict 'refs';
+        # just change relative ones
+        $$path = File::Spec->catfile( $BasePath, $$path )
+          unless File::Spec->file_name_is_absolute( $$path );
+    }
+}
 
 
 =head1 NAME
@@ -112,12 +130,14 @@ A fully featured request tracker package
 
 =head1 DESCRIPTION
 
+=head2 INITIALIZATION
+
 =head2 LoadConfig
 
 Load RT's config file.  First, the site configuration file
-(C) is loaded, in order to establish overall site
+(F) is loaded, in order to establish overall site
 settings like hostname and name of RT instance.  Then, the core
-configuration file (C) is loaded to set fallback values
+configuration file (F) is loaded to set fallback values
 for all settings; it bases some values on settings from the site
 configuration file.
 
@@ -128,59 +148,33 @@ have not been set already.
 =cut
 
 sub LoadConfig {
-     local *Set = sub { $_[0] = $_[1] unless defined $_[0] }; 
-
-    my $username = getpwuid($>);
-    my $group = getgrgid($();
-    my $message = <LoadConfigs;
+    require RT::I18N;
 
     # RT::Essentials mistakenly recommends that WebPath be set to '/'.
     # If the user does that, do what they mean.
     $RT::WebPath = '' if ($RT::WebPath eq '/');
 
-    $ENV{'TZ'} = $RT::Timezone if ($RT::Timezone);
+    # fix relative LogDir and GnuPG homedir
+    unless ( File::Spec->file_name_is_absolute( $Config->Get('LogDir') ) ) {
+        $Config->Set( LogDir =>
+              File::Spec->catfile( $BasePath, $Config->Get('LogDir') ) );
+    }
 
+    my $gpgopts = $Config->Get('GnuPGOptions');
+    unless ( File::Spec->file_name_is_absolute( $gpgopts->{homedir} ) ) {
+        $gpgopts->{homedir} = File::Spec->catfile( $BasePath, $gpgopts->{homedir} );
+    }
+    
     RT::I18N->Init;
 }
 
 =head2 Init
 
-Conenct to the database, set up logging.
+L, L,
+L and L.
 
 =cut
 
@@ -188,41 +182,34 @@ sub Init {
 
     CheckPerlRequirements();
 
+    InitPluginPaths();
+
     #Get a database connection
     ConnectToDatabase();
-
-    #RT's system user is a genuine database user. its id lives here
-    $SystemUser = new RT::CurrentUser();
-    $SystemUser->LoadByName('RT_System');
-    
-    #RT's "nobody user" is a genuine database user. its ID lives here.
-    $Nobody = new RT::CurrentUser();
-    $Nobody->LoadByName('Nobody');
-  
-    $System = RT::System->new();
-
+    InitSystemObjects();
     InitClasses();
     InitLogging(); 
-}
+    InitPlugins();
+    RT->Config->PostLoadCheck;
 
+}
 
 =head2 ConnectToDatabase
 
-Get a database connection
+Get a database connection. See also .
 
 =cut
 
 sub ConnectToDatabase {
     require RT::Handle;
-    unless ($Handle && $Handle->dbh && $Handle->dbh->ping) {
-        $Handle = RT::Handle->new();
-    } 
-    $Handle->Connect();
+    $Handle = new RT::Handle unless $Handle;
+    $Handle->Connect;
+    return $Handle;
 }
 
 =head2 InitLogging
 
-Create the RT::Logger object. 
+Create the Logger object and set up signal handlers.
 
 =cut
 
@@ -231,112 +218,141 @@ sub InitLogging {
     # We have to set the record separator ($, man perlvar)
     # or Log::Dispatch starts getting
     # really pissy, as some other module we use unsets it.
-
     $, = '';
     use Log::Dispatch 1.6;
 
-    unless ($RT::Logger) {
+    my %level_to_num = (
+        map( { $_ => } 0..7 ),
+        debug     => 0,
+        info      => 1,
+        notice    => 2,
+        warning   => 3,
+        error     => 4, 'err' => 4,
+        critical  => 5, crit  => 5,
+        alert     => 6, 
+        emergency => 7, emerg => 7,
+    );
 
-    $RT::Logger = Log::Dispatch->new();
+    unless ( $RT::Logger ) {
 
-    my $simple_cb = sub {
-        # if this code throw any warning we can get segfault
-        no warnings;
+        $RT::Logger = Log::Dispatch->new;
 
-        my %p = @_;
+        my $stack_from_level;
+        if ( $stack_from_level = RT->Config->Get('LogStackTraces') ) {
+            # if option has old style '\d'(true) value
+            $stack_from_level = 0 if $stack_from_level =~ /^\d+$/;
+            $stack_from_level = $level_to_num{ $stack_from_level } || 0;
+        } else {
+            $stack_from_level = 99; # don't log
+        }
 
-        my $frame = 0; # stack frame index
-        # skip Log::* stack frames
-        $frame++ while( caller($frame) && caller($frame) =~ /^Log::/ );
+        my $simple_cb = sub {
+            # if this code throw any warning we can get segfault
+            no warnings;
+            my %p = @_;
+
+            # skip Log::* stack frames
+            my $frame = 0;
+            $frame++ while caller($frame) && caller($frame) =~ /^Log::/;
+            my ($package, $filename, $line) = caller($frame);
+
+            $p{'message'} =~ s/(?:\r*\n)+$//;
+            return "[". gmtime(time) ."] [". $p{'level'} ."]: "
+                . $p{'message'} ." ($filename:$line)\n";
+        };
+
+        my $syslog_cb = sub {
+            # if this code throw any warning we can get segfault
+            no warnings;
+            my %p = @_;
+
+            my $frame = 0; # stack frame index
+            # skip Log::* stack frames
+            $frame++ while caller($frame) && caller($frame) =~ /^Log::/;
+            my ($package, $filename, $line) = caller($frame);
+
+            # syswrite() cannot take utf8; turn it off here.
+            Encode::_utf8_off($p{message});
+
+            $p{message} =~ s/(?:\r*\n)+$//;
+            if ($p{level} eq 'debug') {
+                return "$p{message}\n";
+            } else {
+                return "$p{message} ($filename:$line)\n";
+            }
+        };
 
-        my ($package, $filename, $line) = caller($frame);
-        $p{message} =~ s/(?:\r*\n)+$//;
-        my $str = "[".gmtime(time)."] [".$p{level}."]: $p{message} ($filename:$line)\n";
+        my $stack_cb = sub {
+            no warnings;
+            my %p = @_;
+            return $p{'message'} unless $level_to_num{ $p{'level'} } >= $stack_from_level;
+            
+            require Devel::StackTrace;
+            my $trace = Devel::StackTrace->new( ignore_class => [ 'Log::Dispatch', 'Log::Dispatch::Base' ] );
+            return $p{'message'} . $trace->as_string;
 
-        if( $RT::LogStackTraces ) {
-            $str .= "\nStack trace:\n";
             # skip calling of the Log::* subroutins
-            $frame++ while( caller($frame) && (caller($frame))[3] =~ /^Log::/ );
+            my $frame = 0;
+            $frame++ while caller($frame) && caller($frame) =~ /^Log::/;
+            $frame++ while caller($frame) && (caller($frame))[3] =~ /^Log::/;
+
+            $p{'message'} .= "\nStack trace:\n";
             while( my ($package, $filename, $line, $sub) = caller($frame++) ) {
-                $str .= "\t". $sub ."() called at $filename:$line\n";
+                $p{'message'} .= "\t$sub(...) called at $filename:$line\n";
+            }
+            return $p{'message'};
+        };
+
+        if ( $Config->Get('LogToFile') ) {
+            my ($filename, $logdir) = (
+                $Config->Get('LogToFileNamed') || 'rt.log',
+                $Config->Get('LogDir') || File::Spec->catdir( $VarPath, 'log' ),
+            );
+            if ( $filename =~ m![/\\]! ) { # looks like an absolute path.
+                ($logdir) = $filename =~ m{^(.*[/\\])};
+            }
+            else {
+                $filename = File::Spec->catfile( $logdir, $filename );
             }
-        }
-        return $str;
-    };
-
-    my $syslog_cb = sub {
-        my %p = @_;
-
-        my $frame = 0; # stack frame index
-        # skip Log::* stack frames
-        $frame++ while( caller($frame) && caller($frame) =~ /^Log::/ );
-        my ($package, $filename, $line) = caller($frame);
 
-        # syswrite() cannot take utf8; turn it off here.
-        Encode::_utf8_off($p{message});
+            unless ( -d $logdir && ( ( -f $filename && -w $filename ) || -w $logdir ) ) {
+                # localizing here would be hard when we don't have a current user yet
+                die "Log file '$filename' couldn't be written or created.\n RT can't run.";
+            }
 
-        $p{message} =~ s/(?:\r*\n)+$//;
-        if ($p{level} eq 'debug') {
-            return "$p{message}\n"
-        } else {
-            return "$p{message} ($filename:$line)\n"
+            require Log::Dispatch::File;
+            $RT::Logger->add( Log::Dispatch::File->new
+                           ( name=>'file',
+                             min_level=> $Config->Get('LogToFile'),
+                             filename=> $filename,
+                             mode=>'append',
+                             callbacks => [ $simple_cb, $stack_cb ],
+                           ));
         }
-    };
-    
-    if ($RT::LogToFile) {
-        my ($filename, $logdir);
-        if ($RT::LogToFileNamed =~ m![/\\]!) {
-            # looks like an absolute path.
-            $filename = $RT::LogToFileNamed;
-            ($logdir) = $RT::LogToFileNamed =~ m!^(.*[/\\])!;
+        if ( $Config->Get('LogToScreen') ) {
+            require Log::Dispatch::Screen;
+            $RT::Logger->add( Log::Dispatch::Screen->new
+                         ( name => 'screen',
+                           min_level => $Config->Get('LogToScreen'),
+                           callbacks => [ $simple_cb, $stack_cb ],
+                           stderr => 1,
+                         ));
         }
-        else {
-            $filename = "$RT::LogDir/$RT::LogToFileNamed";
-            $logdir = $RT::LogDir;
-        }
-
-        unless ( -d $logdir && ( ( -f $filename && -w $filename ) || -w $logdir ) ) {
-            # localizing here would be hard when we don't have a current user yet
-            die "Log file $filename couldn't be written or created.\n RT can't run.";
+        if ( $Config->Get('LogToSyslog') ) {
+            require Log::Dispatch::Syslog;
+            $RT::Logger->add(Log::Dispatch::Syslog->new
+                         ( name => 'syslog',
+                           ident => 'RT',
+                           min_level => $Config->Get('LogToSyslog'),
+                           callbacks => [ $syslog_cb, $stack_cb ],
+                           stderr => 1,
+                           $Config->Get('LogToSyslogConf'),
+                         ));
         }
-
-        package Log::Dispatch::File;
-        require Log::Dispatch::File;
-        $RT::Logger->add(Log::Dispatch::File->new
-                       ( name=>'rtlog',
-                         min_level=> $RT::LogToFile,
-                         filename=> $filename,
-                         mode=>'append',
-                         callbacks => $simple_cb,
-                       ));
-    }
-    if ($RT::LogToScreen) {
-        package Log::Dispatch::Screen;
-        require Log::Dispatch::Screen;
-        $RT::Logger->add(Log::Dispatch::Screen->new
-                     ( name => 'screen',
-                       min_level => $RT::LogToScreen,
-                       callbacks => $simple_cb,
-                       stderr => 1,
-                     ));
-    }
-    if ($RT::LogToSyslog) {
-        package Log::Dispatch::Syslog;
-        require Log::Dispatch::Syslog;
-        $RT::Logger->add(Log::Dispatch::Syslog->new
-                     ( name => 'syslog',
-                       ident => 'RT',
-                       min_level => $RT::LogToSyslog,
-                       callbacks => $syslog_cb,
-                       stderr => 1,
-                       @RT::LogToSyslogConf
-                     ));
-    }
-
     }
 
-# {{{ Signal handlers
 
+# Signal handlers
 ## This is the default handling of warnings and die'ings in the code
 ## (including other used modules - maybe except for errors catched by
 ## Mason).  It will log all problems through the standard logging
@@ -355,16 +371,15 @@ sub InitLogging {
 
 #When we call die, trap it and log->crit with the value of the die.
 
-$SIG{__DIE__}  = sub {
-    unless ($^S || !defined $^S ) {
-        $RT::Handle->Rollback();
-        $RT::Logger->crit("$_[0]");
-    }
-    die $_[0];
-};
-
-# }}}
-
+    $SIG{__DIE__}  = sub {
+        # if we are not in eval and perl is not parsing code
+        # then rollback transactions and log RT error
+        unless ($^S || !defined $^S ) {
+            $RT::Handle->Rollback(1) if $RT::Handle;
+            $RT::Logger->crit("$_[0]") if $RT::Logger;
+        }
+        die $_[0];
+    };
 }
 
 
@@ -400,10 +415,9 @@ EOF
     }
 }
 
-
 =head2 InitClasses
 
-Load all modules that define base classes
+Load all modules that define base classes.
 
 =cut
 
@@ -426,6 +440,8 @@ sub InitClasses {
     require RT::ObjectCustomFields;
     require RT::ObjectCustomFieldValues;
     require RT::Attributes;
+    require RT::Dashboard;
+    require RT::Approval;
 
     # on a cold server (just after restart) people could have an object
     # in the session, as we deserialize it so we never call constructor
@@ -452,21 +468,196 @@ sub InitClasses {
     );
 }
 
-# }}}
+=head2 InitSystemObjects
+
+Initializes system objects: C<$RT::System>, C<$RT::SystemUser>
+and C<$RT::Nobody>.
+
+=cut
+
+sub InitSystemObjects {
+
+    #RT's system user is a genuine database user. its id lives here
+    require RT::CurrentUser;
+    $SystemUser = new RT::CurrentUser;
+    $SystemUser->LoadByName('RT_System');
+
+    #RT's "nobody user" is a genuine database user. its ID lives here.
+    $Nobody = new RT::CurrentUser;
+    $Nobody->LoadByName('Nobody');
+
+    require RT::System;
+    $System = RT::System->new( $SystemUser );
+}
+
+=head1 CLASS METHODS
+
+=head2 Config
+
+Returns the current L, but note that
+you must L first otherwise this method
+returns undef.
+
+Method can be called as class method.
+
+=cut
+
+sub Config { return $Config }
+
+=head2 DatabaseHandle
+
+Returns the current L.
+
+See also L.
+
+=cut
+
+sub DatabaseHandle { return $Handle }
+
+=head2 Logger
+
+Returns the logger. See also L.
+
+=cut
+
+sub Logger { return $Logger }
+
+=head2 System
+
+Returns the current L. See also
+L.
+
+=cut
+
+sub System { return $System }
+
+=head2 SystemUser
+
+Returns the system user's object, it's object of
+L class that represents the system. See also
+L.
+
+=cut
+
+sub SystemUser { return $SystemUser }
+
+=head2 Nobody
+
+Returns object of Nobody. It's object of L class
+that represents a user who can own ticket and nothing else. See
+also L.
+
+=cut
+
+sub Nobody { return $Nobody }
+
+=head2 Plugins
+
+Returns a listref of all Plugins currently configured for this RT instance.
+You can define plugins by adding them to the @Plugins list in your RT_SiteConfig
+
+=cut
+
+our @PLUGINS = ();
+sub Plugins {
+    my $self = shift;
+    unless (@PLUGINS) {
+        $self->InitPluginPaths;
+        @PLUGINS = $self->InitPlugins;
+    }
+    return \@PLUGINS;
+}
+
+=head2 PluginDirs
+
+Takes optional subdir (e.g. po, lib, etc.) and return plugins' dirs that exist.
+
+This code chacke plugins names or anything else and required when main config
+is loaded to load plugins' configs.
+
+=cut
 
+sub PluginDirs {
+    my $self = shift;
+    my $subdir = shift;
 
-sub SystemUser {
-    return($SystemUser);
-}	
+    require RT::Plugin;
 
-sub Nobody {
-    return ($Nobody);
+    my @res;
+    foreach my $plugin (grep $_, RT->Config->Get('Plugins')) {
+        my $path = RT::Plugin->new( name => $plugin )->Path( $subdir );
+        next unless -d $path;
+        push @res, $path;
+    }
+    return @res;
 }
 
+=head2 InitPluginPaths
+
+Push plugins' lib paths into @INC right after F.
+In case F isn't in @INC, append them to @INC
+
+=cut
+
+sub InitPluginPaths {
+    my $self = shift || __PACKAGE__;
+
+    my @lib_dirs = $self->PluginDirs('lib');
+
+    my @tmp_inc;
+    my $added;
+    for (@INC) {
+        if ( Cwd::realpath($_) eq $RT::LocalLibPath) {
+            push @tmp_inc, $_, @lib_dirs;
+            $added = 1;
+        } else {
+            push @tmp_inc, $_;
+        }
+    }
+
+    # append @lib_dirs in case $RT::LocalLibPath isn't in @INC
+    push @tmp_inc, @lib_dirs unless $added;
+
+    my %seen;
+    @INC = grep !$seen{$_}++, @tmp_inc;
+}
+
+=head2 InitPlugins
+
+Initialze all Plugins found in the RT configuration file, setting up their lib and HTML::Mason component roots.
+
+=cut
+
+sub InitPlugins {
+    my $self    = shift;
+    my @plugins;
+    require RT::Plugin;
+    foreach my $plugin (grep $_, RT->Config->Get('Plugins')) {
+        $plugin->require;
+        die $UNIVERSAL::require::ERROR if ($UNIVERSAL::require::ERROR);
+        push @plugins, RT::Plugin->new(name =>$plugin);
+    }
+    return @plugins;
+}
+
+
+sub InstallMode {
+    my $self = shift;
+    if (@_) {
+         $_INSTALL_MODE = shift;
+         if($_INSTALL_MODE) {
+             require RT::CurrentUser;
+            $SystemUser = RT::CurrentUser->new();
+         }
+    }
+    return $_INSTALL_MODE;
+}
+
+
 =head1 BUGS
 
-Please report them to rt-bugs@fsck.com, if you know what's broken and have at least 
-some idea of what needs to be fixed.
+Please report them to rt-bugs@bestpractical.com, if you know what's
+broken and have at least some idea of what needs to be fixed.
 
 If you're not sure what's going on, report them rt-devel@lists.bestpractical.com.
 
@@ -475,14 +666,6 @@ If you're not sure what's going on, report them rt-devel@lists.bestpractical.com
 L
 L
 
-=begin testing
-
-ok ($RT::Nobody->Name() eq 'Nobody', "Nobody is nobody");
-ok ($RT::Nobody->Name() ne 'root', "Nobody isn't named root");
-ok ($RT::SystemUser->Name() eq 'RT_System', "The system user is RT_System");
-ok ($RT::SystemUser->Name() ne 'noname', "The system user isn't noname");
-
-=end testing
 
 =cut
 
diff --git a/rt/lib/RT/ACE.pm b/rt/lib/RT/ACE.pm
index 0cd12174d..7f21ba05e 100755
--- a/rt/lib/RT/ACE.pm
+++ b/rt/lib/RT/ACE.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,6 +45,7 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by )
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -68,11 +69,7 @@ RT::ACE
 =cut
 
 package RT::ACE;
-use RT::Record; 
-
-
-use vars qw( @ISA );
-@ISA= qw( RT::Record );
+use base 'RT::Record';
 
 sub _Init {
   my $self = shift; 
diff --git a/rt/lib/RT/ACE_Overlay.pm b/rt/lib/RT/ACE_Overlay.pm
index 1a245f31d..f2a2efdc0 100644
--- a/rt/lib/RT/ACE_Overlay.pm
+++ b/rt/lib/RT/ACE_Overlay.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,6 +45,7 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 =head1 SYNOPSIS
 
   use RT::ACE;
@@ -57,11 +58,6 @@
 
 =head1 METHODS
 
-=begin testing
-
-ok(require RT::ACE);
-
-=end testing
 
 =cut
 
@@ -89,15 +85,6 @@ use vars qw (
 # to real people or groups
 
 
-=begin testing
-
-my $Queue = RT::Queue->new($RT::SystemUser);
-
-is ($Queue->AvailableRights->{'DeleteTicket'} , 'Delete tickets', "Found the delete ticket right");
-is ($RT::System->AvailableRights->{'SuperUser'},  'Do anything and everything', "Found the superuser right");
-
-
-=end testing
 
 =cut
 
@@ -149,6 +136,14 @@ sub LoadByValues {
                  ObjectType    => undef,
                  @_ );
 
+    if ( $args{'RightName'} ) {
+        my $canonic_name = $self->CanonicalizeRightName( $args{'RightName'} );
+        unless ( $canonic_name ) {
+            return ( 0, $self->loc("Invalid right. Couldn't canonicalize right '[_1]'", $args{'RightName'}) );
+        }
+        $args{'RightName'} = $canonic_name;
+    }
+
     my $princ_obj;
     ( $princ_obj, $args{'PrincipalType'} ) =
       $self->_CanonicalizePrincipal( $args{'PrincipalId'},
@@ -216,11 +211,18 @@ PARAMS is a parameter hash with the following elements:
 
 sub Create {
     my $self = shift;
-    my %args = ( PrincipalId   => undef,
-                 PrincipalType => undef,
-                 RightName     => undef,
-                 Object        => undef,
-                 @_ );
+    my %args = (
+        PrincipalId   => undef,
+        PrincipalType => undef,
+        RightName     => undef,
+        Object        => undef,
+        @_
+    );
+
+    unless ( $args{'RightName'} ) {
+        return ( 0, $self->loc('No right specified') );
+    }
+
     #if we haven't specified any sort of right, we're talking about a global right
     if (!defined $args{'Object'} && !defined $args{'ObjectId'} && !defined $args{'ObjectType'}) {
         $args{'Object'} = $RT::System;
@@ -262,37 +264,23 @@ sub Create {
     # }}}
 
     # {{{ Canonicalize and check the right name
-    unless ( $args{'RightName'} ) {
-        return ( 0, $self->loc('Invalid right') );
+    my $canonic_name = $self->CanonicalizeRightName( $args{'RightName'} );
+    unless ( $canonic_name ) {
+        return ( 0, $self->loc("Invalid right. Couldn't canonicalize right '[_1]'", $args{'RightName'}) );
     }
-
-    $args{'RightName'} = $self->CanonicalizeRightName( $args{'RightName'} );
+    $args{'RightName'} = $canonic_name;
 
     #check if it's a valid RightName
-    if ( ref ($args{'Object'} eq 'RT::Queue'  )) {
-        unless ( exists $args{'Object'}->AvailableRights->{ $args{'RightName'} } ) {
-            $RT::Logger->warning("Couldn't validate right name". $args{'RightName'});
-            return ( 0, $self->loc('Invalid right') );
-        }
-    }
-    elsif ( ref ($args{'Object'} eq 'RT::Group'  )) {
-        unless ( exists $args{'Object'}->AvailableRights->{ $args{'RightName'} } ) {
-            $RT::Logger->warning("Couldn't validate group right name". $args{'RightName'});
-            return ( 0, $self->loc('Invalid right') );
-        }
-    }
-    elsif ( ref ($args{'Object'} eq 'RT::System'  )) {
-        my $q = RT::Queue->new($self->CurrentUser);
-        my $g = RT::Group->new($self->CurrentUser);
-
-        unless (( exists $g->AvailableRights->{ $args{'RightName'} } )
-        || ( exists $g->AvailableRights->{ $args{'RightName'} } )
-        || ( exists $RT::System->AvailableRights->{ $args{'RightName'} } ) ) {
-            $RT::Logger->warning("Couldn't validate system right name - ". $args{'RightName'});
+    if ( $args{'Object'}->can('AvailableRights') ) {
+        my $available = $args{'Object'}->AvailableRights;
+        unless ( grep $_ eq $args{'RightName'}, map $self->CanonicalizeRightName( $_ ), keys %$available ) {
+            $RT::Logger->warning(
+                "Couldn't validate right name '$args{'RightName'}'"
+                ." for object of ". ref( $args{'Object'} ) ." class"
+            );
             return ( 0, $self->loc('Invalid right') );
         }
     }
-
     # }}}
 
     # Make sure the right doesn't already exist.
@@ -318,7 +306,7 @@ sub Create {
     #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space. 
     RT::Principal->InvalidateACLCache();
 
-    if ( $id > 0 ) {
+    if ( $id ) {
         return ( $id, $self->loc('Right Granted') );
     }
     else {
@@ -340,216 +328,6 @@ or doesn't have the right to delegate rights.
 
 Always returns a tuple of (ReturnValue, Message)
 
-=begin testing
-
-use_ok(RT::User);
-my $user_a = RT::User->new($RT::SystemUser);
-$user_a->Create( Name => 'DelegationA', Privileged => 1);
-ok ($user_a->Id, "Created delegation user a");
-
-my $user_b = RT::User->new($RT::SystemUser);
-$user_b->Create( Name => 'DelegationB', Privileged => 1);
-ok ($user_b->Id, "Created delegation user b");
-
-
-use_ok(RT::Queue);
-my $q = RT::Queue->new($RT::SystemUser);
-$q->Create(Name =>'DelegationTest');
-ok ($q->Id, "Created a delegation test queue");
-
-
-#------ First, we test whether a user can delegate a right that's been granted to him personally 
-my ($val, $msg) = $user_a->PrincipalObj->GrantRight(Object => $RT::System, Right => 'AdminOwnPersonalGroups');
-ok($val, $msg);
-
-($val, $msg) = $user_a->PrincipalObj->GrantRight(Object =>$q, Right => 'OwnTicket');
-ok($val, $msg);
-
-ok($user_a->HasRight( Object => $RT::System, Right => 'AdminOwnPersonalGroups')    ,"user a has the right 'AdminOwnPersonalGroups' directly");
-
-my $a_delegates = RT::Group->new($user_a);
-$a_delegates->CreatePersonalGroup(Name => 'Delegates');
-ok( $a_delegates->Id   ,"user a creates a personal group 'Delegates'");
-ok( $a_delegates->AddMember($user_b->PrincipalId)   ,"user a adds user b to personal group 'delegates'");
-
-ok( !$user_b->HasRight(Right => 'OwnTicket', Object => $q)    ,"user b does not have the right to OwnTicket' in queue 'DelegationTest'");
-ok(  $user_a->HasRight(Right => 'OwnTicket', Object => $q)  ,"user a has the right to 'OwnTicket' in queue 'DelegationTest'");
-ok(!$user_a->HasRight( Object => $RT::System, Right => 'DelegateRights')    ,"user a does not have the right 'delegate rights'");
-
-
-my $own_ticket_ace = RT::ACE->new($user_a);
-my $user_a_equiv_group = RT::Group->new($user_a);
-$user_a_equiv_group->LoadACLEquivalenceGroup($user_a->PrincipalObj);
-ok ($user_a_equiv_group->Id, "Loaded the user A acl equivalence group");
-my $user_b_equiv_group = RT::Group->new($user_b);
-$user_b_equiv_group->LoadACLEquivalenceGroup($user_b->PrincipalObj);
-ok ($user_b_equiv_group->Id, "Loaded the user B acl equivalence group");
-$own_ticket_ace->LoadByValues( PrincipalType => 'Group', PrincipalId => $user_a_equiv_group->PrincipalId, Object=>$q, RightName => 'OwnTicket');
-
-ok ($own_ticket_ace->Id, "Found the ACE we want to test with for now");
-
-
-($val, $msg) = $own_ticket_ace->Delegate(PrincipalId => $a_delegates->PrincipalId)  ;
-ok( !$val ,"user a tries and fails to delegate the right 'ownticket' in queue 'DelegationTest' to personal group 'delegates' - $msg");
-
-
-($val, $msg) = $user_a->PrincipalObj->GrantRight( Right => 'DelegateRights');
-ok($val, "user a is granted the right to 'delegate rights' - $msg");
-
-ok($user_a->HasRight( Object => $RT::System, Right => 'DelegateRights')    ,"user a has the right 'AdminOwnPersonalGroups' directly");
-
-($val, $msg) = $own_ticket_ace->Delegate(PrincipalId => $a_delegates->PrincipalId) ;
-
-ok( $val    ,"user a tries and succeeds to delegate the right 'ownticket' in queue 'DelegationTest' to personal group 'delegates' - $msg");
-ok(  $user_b->HasRight(Right => 'OwnTicket', Object => $q)  ,"user b has the right to own tickets in queue 'DelegationTest'");
-my $delegated_ace = RT::ACE->new($user_a);
-$delegated_ace->LoadByValues ( Object => $q, RightName => 'OwnTicket', PrincipalType => 'Group',
-PrincipalId => $a_delegates->PrincipalId, DelegatedBy => $user_a->PrincipalId, DelegatedFrom => $own_ticket_ace->Id);
-ok ($delegated_ace->Id, "Found the delegated ACE");
-
-ok(    $a_delegates->DeleteMember($user_b->PrincipalId)  ,"user a removes b from pg 'delegates'");
-ok(  !$user_b->HasRight(Right => 'OwnTicket', Object => $q)  ,"user b does not have the right to own tickets in queue 'DelegationTest'");
-ok(  $a_delegates->AddMember($user_b->PrincipalId)    ,"user a adds user b to personal group 'delegates'");
-ok(   $user_b->HasRight(Right => 'OwnTicket', Object=> $q) ,"user b has the right to own tickets in queue 'DelegationTest'");
-ok(   $delegated_ace->Delete ,"user a revokes pg 'delegates' right to 'OwnTickets' in queue 'DelegationTest'");
-ok( ! $user_b->HasRight(Right => 'OwnTicket', Object => $q)   ,"user b does not have the right to own tickets in queue 'DelegationTest'");
-
-($val, $msg) = $own_ticket_ace->Delegate(PrincipalId => $a_delegates->PrincipalId)  ;
-ok(  $val  ,"user a delegates pg 'delegates' right to 'OwnTickets' in queue 'DelegationTest' - $msg");
-
-ok( $user_a->HasRight(Right => 'OwnTicket', Object => $q)    ,"user a does not have the right to own tickets in queue 'DelegationTest'");
-
-($val, $msg) = $user_a->PrincipalObj->RevokeRight(Object=>$q, Right => 'OwnTicket');
-ok($val, "Revoked user a's right to own tickets in queue 'DelegationTest". $msg);
-
-ok( !$user_a->HasRight(Right => 'OwnTicket', Object => $q)    ,"user a does not have the right to own tickets in queue 'DelegationTest'");
-
- ok( !$user_b->HasRight(Right => 'OwnTicket', Object => $q)   ,"user b does not have the right to own tickets in queue 'DelegationTest'");
-
-($val, $msg) = $user_a->PrincipalObj->GrantRight(Object=>$q, Right => 'OwnTicket');
-ok($val, $msg);
-
- ok( $user_a->HasRight(Right => 'OwnTicket', Object => $q)   ,"user a has the right to own tickets in queue 'DelegationTest'");
-
- ok(  !$user_b->HasRight(Right => 'OwnTicket', Object => $q)  ,"user b does not have the right to own tickets in queue 'DelegationTest'");
-
-# {{{ get back to a known clean state 
-($val, $msg) = $user_a->PrincipalObj->RevokeRight( Object => $q, Right => 'OwnTicket');
-ok($val, "Revoked user a's right to own tickets in queue 'DelegationTest -". $msg);
-ok( !$user_a->HasRight(Right => 'OwnTicket', Object => $q)    ,"make sure that user a can't own tickets in queue 'DelegationTest'");
-# }}}
-
-
-# {{{ Set up some groups and membership
-my $del1 = RT::Group->new($RT::SystemUser);
-($val, $msg) = $del1->CreateUserDefinedGroup(Name => 'Del1');
-ok( $val   ,"create a group del1 - $msg");
-
-my $del2 = RT::Group->new($RT::SystemUser);
-($val, $msg) = $del2->CreateUserDefinedGroup(Name => 'Del2');
-ok( $val   ,"create a group del2 - $msg");
-($val, $msg) = $del1->AddMember($del2->PrincipalId);
-ok( $val,"make del2 a member of del1 - $msg");
-
-my $del2a = RT::Group->new($RT::SystemUser);
-($val, $msg) = $del2a->CreateUserDefinedGroup(Name => 'Del2a');
-ok( $val   ,"create a group del2a - $msg");
-($val, $msg) = $del2->AddMember($del2a->PrincipalId);  
-ok($val    ,"make del2a a member of del2 - $msg");
-
-my $del2b = RT::Group->new($RT::SystemUser);
-($val, $msg) = $del2b->CreateUserDefinedGroup(Name => 'Del2b');
-ok( $val   ,"create a group del2b - $msg");
-($val, $msg) = $del2->AddMember($del2b->PrincipalId);  
-ok($val    ,"make del2b a member of del2 - $msg");
-
-($val, $msg) = $del2->AddMember($user_a->PrincipalId) ;
-ok($val,"make 'user a' a member of del2 - $msg");
-
-($val, $msg) = $del2b->AddMember($user_a->PrincipalId) ;
-ok($val,"make 'user a' a member of del2b - $msg");
-
-# }}}
-
-# {{{ Grant a right to a group and make sure that a submember can delegate the right and that it does not get yanked
-# when a user is removed as a submember, when they're a sumember through another path 
-($val, $msg) = $del1->PrincipalObj->GrantRight( Object=> $q, Right => 'OwnTicket');
-ok( $val   ,"grant del1  the right to 'OwnTicket' in queue 'DelegationTest' - $msg");
-
-ok(  $user_a->HasRight(Right => 'OwnTicket', Object => $q)  ,"make sure that user a can own tickets in queue 'DelegationTest'");
-
-my $group_ace= RT::ACE->new($user_a);
-$group_ace->LoadByValues( PrincipalType => 'Group', PrincipalId => $del1->PrincipalId, Object => $q, RightName => 'OwnTicket');
-
-ok ($group_ace->Id, "Found the ACE we want to test with for now");
-
-($val, $msg) = $group_ace->Delegate(PrincipalId => $a_delegates->PrincipalId);
-
-ok( $val   ,"user a tries and succeeds to delegate the right 'ownticket' in queue 'DelegationTest' to personal group 'delegates' - $msg");
-ok(  $user_b->HasRight(Right => 'OwnTicket', Object => $q)  ,"user b has the right to own tickets in queue 'DelegationTest'");
-
-
-($val, $msg) = $del2b->DeleteMember($user_a->PrincipalId);
-ok( $val   ,"remove user a from group del2b - $msg");
-ok(  $user_a->HasRight(Right => 'OwnTicket', Object => $q)  ,"user a has the right to own tickets in queue 'DelegationTest'");
-ok( $user_b->HasRight(Right => 'OwnTicket', Object => $q)    ,"user b has the right to own tickets in queue 'DelegationTest'");
-
-# }}}
-
-# {{{ When a  user is removed froom a group by the only path they're in there by, make sure the delegations go away
-($val, $msg) = $del2->DeleteMember($user_a->PrincipalId);
-ok( $val   ,"remove user a from group del2 - $msg");
-ok(  !$user_a->HasRight(Right => 'OwnTicket', Object => $q)  ,"user a does not have the right to own tickets in queue 'DelegationTest' ");
-ok(  !$user_b->HasRight(Right => 'OwnTicket', Object => $q)  ,"user b does not have the right to own tickets in queue 'DelegationTest' ");
-# }}}
-
-($val, $msg) = $del2->AddMember($user_a->PrincipalId);
-ok( $val   ,"make user a a member of group del2 - $msg");
-
-($val, $msg) = $del2->PrincipalObj->GrantRight(Object=>$q, Right => 'OwnTicket');
-ok($val, "grant the right 'own tickets' in queue 'DelegationTest' to group del2 - $msg");
-
-my $del2_right = RT::ACE->new($user_a);
-$del2_right->LoadByValues( PrincipalId => $del2->PrincipalId, PrincipalType => 'Group', Object => $q, RightName => 'OwnTicket');
-ok ($del2_right->Id, "Found the right");
-
-($val, $msg) = $del2_right->Delegate(PrincipalId => $a_delegates->PrincipalId);
-ok( $val   ,"user a tries and succeeds to delegate the right 'ownticket' in queue 'DelegationTest' gotten via del2 to personal group 'delegates' - $msg");
-
-# They have it via del1 and del2
-ok( $user_a->HasRight(Right => 'OwnTicket', Object => $q)   ,"user b has the right to own tickets in queue 'DelegationTest'");
-
-
-($val, $msg) = $del2->PrincipalObj->RevokeRight(Object=>$q, Right => 'OwnTicket');
-ok($val, "revoke the right 'own tickets' in queue 'DelegationTest' to group del2 - $msg");
-ok(  $user_a->HasRight(Right => 'OwnTicket', Object => $q)  ,"user a does has the right to own tickets in queue 'DelegationTest' via del1");
-ok(  !$user_b->HasRight(Right => 'OwnTicket', Object => $q)   ,"user b does not have the right to own tickets in queue 'DelegationTest'");
-
-($val, $msg) = $del2->PrincipalObj->GrantRight(Object=>$q, Right => 'OwnTicket');
-ok($val, "grant the right 'own tickets' in queue 'DelegationTest' to group del2 - $msg");
-
-
-$group_ace= RT::ACE->new($user_a);
-$group_ace->LoadByValues( PrincipalType => 'Group', PrincipalId => $del1->PrincipalId, Object=>$q, RightName => 'OwnTicket');
-
-ok ($group_ace->Id, "Found the ACE we want to test with for now");
-
-($val, $msg) = $group_ace->Delegate(PrincipalId => $a_delegates->PrincipalId);
-
-ok( $val   ,"user a tries and succeeds to delegate the right 'ownticket' in queue 'DelegationTest' to personal group 'delegates' - $msg");
-
-ok( $user_b->HasRight(Right => 'OwnTicket', Object => $q)    ,"user b has the right to own tickets in queue 'DelegationTest'");
-
-($val, $msg) = $del2->DeleteMember($user_a->PrincipalId);
-ok( $val   ,"remove user a from group del2 - $msg");
-
-ok(  !$user_a->HasRight(Right => 'OwnTicket', Object => $q)  ,"user a does not have the right to own tickets in queue 'DelegationTest'");
-
-ok(  !$user_b->HasRight(Right => 'OwnTicket', Object => $q)   ,"user b does not have the right to own tickets in queue 'DelegationTest'");
-
-
-
-=end testing
 
 =cut
 
@@ -762,6 +540,20 @@ sub _BootstrapCreate {
 
 # {{{ sub CanonicalizeRightName
 
+sub RightName {
+    my $self = shift;
+    my $val = $self->_Value('RightName');
+    return $val unless $val;
+
+    my $available = $self->Object->AvailableRights;
+    foreach my $right ( keys %$available ) {
+        return $right if $val eq $self->CanonicalizeRightName($right);
+    }
+
+    $RT::Logger->error("Invalid right. Couldn't canonicalize right '$val'");
+    return $val;
+}
+
 =head2 CanonicalizeRightName 
 
 Takes a queue or system right name in any case and returns it in
@@ -771,14 +563,7 @@ the correct case. If it's not found, will return undef.
 
 sub CanonicalizeRightName {
     my $self  = shift;
-    my $right = shift;
-    $right = lc $right;
-    if ( exists $LOWERCASERIGHTNAMES{"$right"} ) {
-        return ( $LOWERCASERIGHTNAMES{"$right"} );
-    }
-    else {
-        return (undef);
-    }
+    return $LOWERCASERIGHTNAMES{ lc shift };
 }
 
 # }}}
@@ -899,7 +684,7 @@ Returns a tuple of  (RT::Principal, PrincipalType)  for the principal we really
 sub _CanonicalizePrincipal {
     my $self       = shift;
     my $princ_id   = shift;
-    my $princ_type = shift;
+    my $princ_type = shift || '';
 
     my $princ_obj = RT::Principal->new($RT::SystemUser);
     $princ_obj->Load($princ_id);
diff --git a/rt/lib/RT/ACL.pm b/rt/lib/RT/ACL.pm
index 9641292e6..1dc66e8b6 100755
--- a/rt/lib/RT/ACL.pm
+++ b/rt/lib/RT/ACL.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,6 +45,7 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by )
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -71,12 +72,9 @@ use strict;
 
 package RT::ACL;
 
-use RT::SearchBuilder;
+use base 'RT::SearchBuilder';
 use RT::ACE;
 
-use vars qw( @ISA );
-@ISA= qw(RT::SearchBuilder);
-
 
 sub _Init {
     my $self = shift;
diff --git a/rt/lib/RT/ACL_Overlay.pm b/rt/lib/RT/ACL_Overlay.pm
index 1329df07c..d645e4063 100644
--- a/rt/lib/RT/ACL_Overlay.pm
+++ b/rt/lib/RT/ACL_Overlay.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,6 +45,7 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 =head1 NAME
 
   RT::ACL - collection of RT ACE objects
@@ -59,11 +60,6 @@ my $ACL = new RT::ACL($CurrentUser);
 
 =head1 METHODS
 
-=begin testing
-
-ok(require RT::ACL);
-
-=end testing
 
 =cut
 
@@ -318,7 +314,7 @@ sub _DoSearch {
     my $self = shift;
    # $RT::Logger->debug("Now in ".$self."->_DoSearch");
     my $return = $self->SUPER::_DoSearch(@_);
-  #  $RT::Logger->debug("In $self ->_DoSearch. return from SUPER::_DoSearch was $return\n");
+  #  $RT::Logger->debug("In $self ->_DoSearch. return from SUPER::_DoSearch was $return");
     $self->_BuildHash();
     return ($return);
 }
@@ -329,7 +325,8 @@ sub _BuildHash {
     my $self = shift;
 
     while (my $entry = $self->Next) {
-       my $hashkey = $entry->ObjectType . "-" .  $entry->ObjectId . "-" .  $entry->RightName . "-" .  $entry->PrincipalId . "-" .  $entry->PrincipalType;
+        my $hashkey = join '-', map $entry->__Value( $_ ),
+            qw(ObjectType ObjectId RightName PrincipalId PrincipalType);
 
         $self->{'as_hash'}->{"$hashkey"} =1;
 
diff --git a/rt/lib/RT/Action.pm b/rt/lib/RT/Action.pm
new file mode 100755
index 000000000..1918a7e37
--- /dev/null
+++ b/rt/lib/RT/Action.pm
@@ -0,0 +1,227 @@
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
+=head1 NAME
+
+  RT::Action - a generic baseclass for RT Actions
+
+=head1 SYNOPSIS
+
+  use RT::Action;
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+
+=cut
+
+package RT::Action;
+
+use strict;
+use Scalar::Util;
+
+use base qw/RT::Base/;
+
+# {{{ sub new 
+sub new  {
+  my $proto = shift;
+  my $class = ref($proto) || $proto;
+  my $self  = {};
+  bless ($self, $class);
+  $self->_Init(@_);
+  return $self;
+}
+# }}}
+
+# {{{ sub _Init 
+sub _Init  {
+  my $self = shift;
+  my %args = ( Argument => undef,
+               CurrentUser => undef,
+               ScripActionObj => undef,
+               ScripObj => undef,
+               TemplateObj => undef,
+               TicketObj => undef,
+               TransactionObj => undef,
+               Type => undef,
+
+               @_ );
+
+  $self->{'Argument'} = $args{'Argument'};
+  $self->CurrentUser( $args{'CurrentUser'});
+  $self->{'ScripActionObj'} = $args{'ScripActionObj'};
+  $self->{'ScripObj'} = $args{'ScripObj'};
+  $self->{'TemplateObj'} = $args{'TemplateObj'};
+  $self->{'TicketObj'} = $args{'TicketObj'};
+  $self->{'TransactionObj'} = $args{'TransactionObj'};
+  $self->{'Type'} = $args{'Type'};
+
+  Scalar::Util::weaken($self->{'ScripActionObj'});
+  Scalar::Util::weaken($self->{'ScripObj'});
+  Scalar::Util::weaken($self->{'TemplateObj'});
+  Scalar::Util::weaken($self->{'TicketObj'});
+  Scalar::Util::weaken($self->{'TransactionObj'});
+
+}
+# }}}
+
+# Access Scripwide data
+
+# {{{ sub Argument 
+sub Argument  {
+  my $self = shift;
+  return($self->{'Argument'});
+}
+# }}}
+
+# {{{ sub TicketObj
+sub TicketObj  {
+  my $self = shift;
+  return($self->{'TicketObj'});
+}
+# }}}
+
+# {{{ sub TransactionObj
+sub TransactionObj  {
+  my $self = shift;
+  return($self->{'TransactionObj'});
+}
+# }}}
+
+# {{{ sub TemplateObj
+sub TemplateObj  {
+  my $self = shift;
+  return($self->{'TemplateObj'});
+}
+# }}}
+
+# {{{ sub ScripObj
+sub ScripObj  {
+  my $self = shift;
+  return($self->{'ScripObj'});
+}
+# }}}
+
+# {{{ sub ScripActionObj
+sub ScripActionObj  {
+  my $self = shift;
+  return($self->{'ScripActionObj'});
+}
+# }}}
+
+# {{{ sub Type
+sub Type  {
+  my $self = shift;
+  return($self->{'Type'});
+}
+# }}}
+
+
+# Scrip methods
+
+#Do what we need to do and send it out.
+
+# {{{ sub Commit 
+sub Commit  {
+  my $self = shift;
+  return(0, $self->loc("Commit Stubbed"));
+}
+# }}}
+
+
+#What does this type of Action does
+
+# {{{ sub Describe 
+sub Describe  {
+  my $self = shift;
+  return $self->loc("No description for [_1]", ref $self);
+}
+# }}}
+
+
+#Parse the templates, get things ready to go.
+
+# {{{ sub Prepare 
+sub Prepare  {
+  my $self = shift;
+  return (0, $self->loc("Prepare Stubbed"));
+}
+# }}}
+
+
+#If this rule applies to this transaction, return true.
+
+# {{{ sub IsApplicable 
+sub IsApplicable  {
+  my $self = shift;
+  return(undef);
+}
+# }}}
+
+# {{{ sub DESTROY
+sub DESTROY {
+    my $self = shift;
+
+    # We need to clean up all the references that might maybe get
+    # oddly circular
+    $self->{'ScripActionObj'} = undef;
+    $self->{'ScripObj'} = undef;
+    $self->{'TemplateObj'} =undef
+    $self->{'TicketObj'} = undef;
+    $self->{'TransactionObj'} = undef;
+}
+
+# }}}
+
+eval "require RT::Action_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action_Vendor.pm});
+eval "require RT::Action_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action_Local.pm});
+
+1;
diff --git a/rt/lib/RT/Action/AutoOpen.pm b/rt/lib/RT/Action/AutoOpen.pm
index 004ed13cc..e1cf0ae7c 100644
--- a/rt/lib/RT/Action/AutoOpen.pm
+++ b/rt/lib/RT/Action/AutoOpen.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,28 +45,25 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
-# This Action will open the BASE if a dependent is resolved.
 
+# This Action will open the BASE if a dependent is resolved.
 package RT::Action::AutoOpen;
-require RT::Action::Generic;
 
 use strict;
-use vars qw/@ISA/;
-@ISA=qw(RT::Action::Generic);
+use warnings;
 
-#Do what we need to do and send it out.
+use base qw(RT::Action);
 
-#What does this type of Action does
+=head1 DESCRIPTION
 
-# {{{ sub Describe 
-sub Describe  {
-  my $self = shift;
-  return (ref $self );
-}
-# }}}
+Opens a ticket unless it's allready open, but only unless transaction
+L.
 
+Doesn't open a ticket if message's head has field C with
+C substring.
+
+=cut
 
-# {{{ sub Prepare 
 sub Prepare {
     my $self = shift;
 
@@ -83,22 +80,21 @@ sub Prepare {
 
     return 1;
 }
-# }}}
 
 sub Commit {
     my $self = shift;
-      my $oldstatus = $self->TicketObj->Status();
-        $self->TicketObj->__Set( Field => 'Status', Value => 'open' );
-        $self->TicketObj->_NewTransaction(
-                         Type     => 'Status',
-                         Field    => 'Status',
-                         OldValue => $oldstatus,
-                         NewValue => 'open',
-                         Data => 'Ticket auto-opened on incoming correspondence'
-        );
-
-
-    return(1);
+
+    my $oldstatus = $self->TicketObj->Status;
+    $self->TicketObj->__Set( Field => 'Status', Value => 'open' );
+    $self->TicketObj->_NewTransaction(
+        Type     => 'Status',
+        Field    => 'Status',
+        OldValue => $oldstatus,
+        NewValue => 'open',
+        Data     => 'Ticket auto-opened on incoming correspondence'
+    );
+
+    return 1;
 }
 
 eval "require RT::Action::AutoOpen_Vendor";
diff --git a/rt/lib/RT/Action/Autoreply.pm b/rt/lib/RT/Action/Autoreply.pm
index ea56b9f5b..3734d819a 100755
--- a/rt/lib/RT/Action/Autoreply.pm
+++ b/rt/lib/RT/Action/Autoreply.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,12 +45,13 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 package RT::Action::Autoreply;
-require RT::Action::SendEmail;
 
 use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Action::SendEmail);
+use warnings;
+
+use base qw(RT::Action::SendEmail);
 
 =head2 Prepare
 
@@ -95,43 +96,37 @@ Set this message\'s return address to the apropriate queue address
 
 sub SetReturnAddress {
     my $self = shift;
-    my %args = ( is_comment => 0,
-		 @_
-	       );
     
-    my $replyto;
-    if ($args{'is_comment'}) { 
-	$replyto = $self->TicketObj->QueueObj->CommentAddress || 
-		     $RT::CommentAddress;
-    }
-    else {
-	$replyto = $self->TicketObj->QueueObj->CorrespondAddress ||
-		     $RT::CorrespondAddress;
-    }
-    
-    unless ($self->TemplateObj->MIMEObj->head->get('From')) {
-	if ($RT::UseFriendlyFromLine) {
-	    my $friendly_name = $self->TicketObj->QueueObj->Description ||
+    my $friendly_name;
+
+	if (RT->Config->Get('UseFriendlyFromLine')) {
+	    $friendly_name = $self->TicketObj->QueueObj->Description ||
 		    $self->TicketObj->QueueObj->Name;
-	    $friendly_name =~ s/"/\\"/g;
-	    $self->SetHeader( 'From',
-		        sprintf($RT::FriendlyFromLineFormat, 
-                $self->MIMEEncodeString( $friendly_name, $RT::EmailOutputEncoding ), $replyto),
-	    );
 	}
-	else {
-	    $self->SetHeader( 'From', $replyto );
-	}
-    }
-    
-    unless ($self->TemplateObj->MIMEObj->head->get('Reply-To')) {
-	$self->SetHeader('Reply-To', "$replyto");
-    }
+
+    $self->SUPER::SetReturnAddress( @_, friendly_name => $friendly_name );
     
 }
   
 # }}}
 
+# {{{{ sub SetRTSpecialHeaders
+
+=head2 SetRTSpecialHeaders
+
+Set the C header to C, in accordance
+with RFC3834.
+
+=cut
+
+sub SetRTSpecialHeaders {
+    my $self = shift;
+    $self->SUPER::SetRTSpecialHeaders(@_);
+    $self->SetHeader( 'Auto-Submitted', 'auto-replied' );
+}
+
+# }}}
+
 eval "require RT::Action::Autoreply_Vendor";
 die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/Autoreply_Vendor.pm});
 eval "require RT::Action::Autoreply_Local";
diff --git a/rt/lib/RT/Action/CreateTickets.pm b/rt/lib/RT/Action/CreateTickets.pm
index 40d18d357..4883ae3a8 100644
--- a/rt/lib/RT/Action/CreateTickets.pm
+++ b/rt/lib/RT/Action/CreateTickets.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,13 +45,12 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 package RT::Action::CreateTickets;
-require RT::Action::Generic;
+use base 'RT::Action';
 
 use strict;
 use warnings;
-use vars qw/@ISA/;
-@ISA = qw(RT::Action::Generic);
 
 use MIME::Entity;
 
@@ -106,10 +105,12 @@ of perl inside the Text::Template using {} delimiters, but that
 such sections absolutely can not span a ===Create-Ticket boundary.
 
 After each ticket is created, it's stuffed into a hash called %Tickets
-so as to be available during the creation of other tickets during the same 
-ScripAction.  The hash is prepopulated with the ticket which triggered the 
-ScripAction as $Tickets{'TOP'}; you can also access that ticket using the
-shorthand TOP.
+so as to be available during the creation of other tickets during the
+same ScripAction, using the key 'create-identifier', where
+C is the id you put after C<===Create-Ticket:>.  The hash
+is prepopulated with the ticket which triggered the ScripAction as
+$Tickets{'TOP'}; you can also access that ticket using the shorthand
+TOP.
 
 A simple example:
 
@@ -164,8 +165,9 @@ A convoluted example
  ENDOFCONTENT
  ===Create-Ticket: two
  Subject: Manager approval
+ Type: approval
  Depended-On-By: TOP
- Refers-On: {$Tickets{"approval"}->Id}
+ Refers-To: {$Tickets{"create-approval"}->Id}
  Queue: ___Approvals
  Content-Type: text/plain
  Content: 
@@ -236,236 +238,6 @@ Refers-To, RefersTo, refersto, refers-to and r-e-f-er-s-tO will all
 be treated as the same thing.
 
 
-=begin testing
-
-ok (require RT::Action::CreateTickets);
-use_ok(RT::Scrip);
-use_ok(RT::Template);
-use_ok(RT::ScripAction);
-use_ok(RT::ScripCondition);
-use_ok(RT::Ticket);
-
-my $approvalsq = RT::Queue->new($RT::SystemUser);
-$approvalsq->Create(Name => 'Approvals');
-ok ($approvalsq->Id, "Created Approvals test queue");
-
-
-my $approvals = 
-'===Create-Ticket: approval
-Queue: ___Approvals
-Type: approval
-AdminCc: {join ("\nAdminCc: ",@admins) }
-Depended-On-By: {$Tickets{"TOP"}->Id}
-Refers-To: TOP 
-Subject: Approval for ticket: {$Tickets{"TOP"}->Id} - {$Tickets{"TOP"}->Subject}
-Due: {time + 86400}
-Content-Type: text/plain
-Content: Your approval is requested for the ticket {$Tickets{"TOP"}->Id}: {$Tickets{"TOP"}->Subject}
-Blah
-Blah
-ENDOFCONTENT
-===Create-Ticket: two
-Subject: Manager approval.
-Depended-On-By: approval
-Queue: ___Approvals
-Content-Type: text/plain
-Content: 
-Your minion approved ticket {$Tickets{"TOP"}->Id}. you ok with that?
-ENDOFCONTENT
-';
-
-ok ($approvals =~ /Content/, "Read in the approvals template");
-
-my $apptemp = RT::Template->new($RT::SystemUser);
-$apptemp->Create( Content => $approvals, Name => "Approvals", Queue => "0");
-
-ok ($apptemp->Id);
-
-my $q = RT::Queue->new($RT::SystemUser);
-$q->Create(Name => 'WorkflowTest');
-ok ($q->Id, "Created workflow test queue");
-
-my $scrip = RT::Scrip->new($RT::SystemUser);
-my ($sval, $smsg) =$scrip->Create( ScripCondition => 'On Transaction',
-                ScripAction => 'Create Tickets',
-                Template => 'Approvals',
-                Queue => $q->Id);
-ok ($sval, $smsg);
-ok ($scrip->Id, "Created the scrip");
-ok ($scrip->TemplateObj->Id, "Created the scrip template");
-ok ($scrip->ConditionObj->Id, "Created the scrip condition");
-ok ($scrip->ActionObj->Id, "Created the scrip action");
-
-my $t = RT::Ticket->new($RT::SystemUser);
-my($tid, $ttrans, $tmsg) = $t->Create(Subject => "Sample workflow test",
-           Owner => "root",
-           Queue => $q->Id);
-
-ok ($tid,$tmsg);
-
-my $deps = $t->DependsOn;
-is ($deps->Count, 1, "The ticket we created depends on one other ticket");
-my $dependson= $deps->First->TargetObj;
-ok ($dependson->Id, "It depends on a real ticket");
-unlike ($dependson->Subject, qr/{/, "The subject doesn't have braces in it. that means we're interpreting expressions");
-is ($t->ReferredToBy->Count,1, "It's only referred to by one other ticket");
-is ($t->ReferredToBy->First->BaseObj->Id,$t->DependsOn->First->TargetObj->Id, "The same ticket that depends on it refers to it.");
-use RT::Action::CreateTickets;
-my $action =  RT::Action::CreateTickets->new( CurrentUser => $RT::SystemUser);;
-
-# comma-delimited templates
-my $commas = <<"EOF";
-id,Queue,Subject,Owner,Content
-ticket1,General,"foo, bar",root,blah
-ticket2,General,foo bar,root,blah
-ticket3,General,foo' bar,root,blah'boo
-ticket4,General,foo' bar,,blah'boo
-EOF
-
-
-# Comma delimited templates with missing data
-my $sparse_commas = <<"EOF";
-id,Queue,Subject,Owner,Requestor
-ticket14,General,,,bobby
-ticket15,General,,,tommy
-ticket16,General,,suzie,tommy
-ticket17,General,Foo "bar" baz,suzie,tommy
-ticket18,General,'Foo "bar" baz',suzie,tommy
-ticket19,General,'Foo bar' baz,suzie,tommy
-EOF
-
-
-# tab-delimited templates
-my $tabs = <<"EOF";
-id\tQueue\tSubject\tOwner\tContent
-ticket10\tGeneral\t"foo' bar"\troot\tblah'
-ticket11\tGeneral\tfoo, bar\troot\tblah
-ticket12\tGeneral\tfoo' bar\troot\tblah'boo
-ticket13\tGeneral\tfoo' bar\t\tblah'boo
-EOF
-
-my %expected;
-
-$expected{ticket1} = <Parse(Content =>$commas);
-$action->Parse(Content =>$sparse_commas);
-$action->Parse(Content => $tabs);
-
-my %got;
-foreach (@{ $action->{'create_tickets'} }) {
-  $got{$_} = $action->{'templates'}->{$_};
-}
-
-foreach my $id ( sort keys %expected ) {
-    ok(exists($got{"create-$id"}), "template exists for $id");
-    is($got{"create-$id"}, $expected{$id}, "template is correct for $id");
-}
-
-=end testing
 
 
 =head1 AUTHOR
@@ -541,16 +313,16 @@ sub Prepare {
     my $self = shift;
 
     unless ( $self->TemplateObj ) {
-        $RT::Logger->warning("No template object handed to $self\n");
+        $RT::Logger->warning("No template object handed to $self");
     }
 
     unless ( $self->TransactionObj ) {
-        $RT::Logger->warning("No transaction object handed to $self\n");
+        $RT::Logger->warning("No transaction object handed to $self");
 
     }
 
     unless ( $self->TicketObj ) {
-        $RT::Logger->warning("No ticket object handed to $self\n");
+        $RT::Logger->warning("No ticket object handed to $self");
 
     }
 
@@ -575,7 +347,6 @@ sub CreateByTemplate {
     my @results;
 
     # XXX: cargo cult programming that works. i'll be back.
-    use bytes;
 
     local %T::Tickets = %T::Tickets;
     local $T::TOP     = $T::TOP;
@@ -637,7 +408,6 @@ sub UpdateByTemplate {
     my $top  = shift;
 
     # XXX: cargo cult programming that works. i'll be back.
-    use bytes;
 
     my @results;
     local %T::Tickets = %T::Tickets;
@@ -894,7 +664,7 @@ sub ParseLines {
             }
         );
 
-        $RT::Logger->debug("Workflow: yielding\n$content");
+        $RT::Logger->debug("Workflow: yielding $content");
 
         if ($err) {
             $RT::Logger->error( "Ticket creation failed: " . $err );
@@ -990,6 +760,7 @@ sub ParseLines {
         TimeLeft        => $args{'timeleft'},
         InitialPriority => $args{'initialpriority'} || 0,
         FinalPriority   => $args{'finalpriority'} || 0,
+        SquelchMailTo   => $args{'squelchmailto'},
         Type            => $args{'type'},
     );
 
@@ -1223,7 +994,7 @@ sub GetUpdateTemplate {
         my $mode   = $LINKTYPEMAP{$type}->{Mode};
         my $method = $LINKTYPEMAP{$type}->{Type};
 
-        my $links;
+        my $links = '';
         while ( my $link = $t->$method->Next ) {
             $links .= ", " if $links;
 
diff --git a/rt/lib/RT/Action/EscalatePriority.pm b/rt/lib/RT/Action/EscalatePriority.pm
index 46635df05..bf9de92c2 100644
--- a/rt/lib/RT/Action/EscalatePriority.pm
+++ b/rt/lib/RT/Action/EscalatePriority.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,6 +45,7 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 =head1 NAME
 
   RT::Action::EscalatePriority
@@ -71,11 +72,9 @@ as the ticket heads toward its due date.
 
 
 package RT::Action::EscalatePriority;
-require RT::Action::Generic;
+use base 'RT::Action';
 
 use strict;
-use vars qw/@ISA/;
-@ISA=qw(RT::Action::Generic);
 
 #Do what we need to do and send it out.
 
@@ -155,7 +154,7 @@ sub Commit {
    my ($val, $msg) = $self->TicketObj->SetPriority($self->{'prio'});
 
    unless ($val) {
-	$RT::Logger->debug($self . " $msg\n"); 
+	$RT::Logger->debug($self . " $msg"); 
    }
 }
 
diff --git a/rt/lib/RT/Action/ExtractSubjectTag.pm b/rt/lib/RT/Action/ExtractSubjectTag.pm
new file mode 100644
index 000000000..4a173ce76
--- /dev/null
+++ b/rt/lib/RT/Action/ExtractSubjectTag.pm
@@ -0,0 +1,103 @@
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
+package RT::Action::ExtractSubjectTag;
+use base 'RT::Action';
+use strict;
+
+sub Describe {
+    my $self = shift;
+    return ( ref $self );
+}
+
+sub Prepare {
+    return (1);
+}
+
+sub Commit {
+    my $self            = shift;
+    my $Transaction     = $self->TransactionObj;
+    my $FirstAttachment = $Transaction->Attachments->First;
+    return 1 unless ($FirstAttachment);
+
+    my $Ticket = $self->TicketObj;
+
+    my $TicketSubject      = $self->TicketObj->Subject;
+    my $origTicketSubject  = $TicketSubject;
+    my $TransactionSubject = $FirstAttachment->Subject;
+
+    my $match   = RT->Config->Get('ExtractSubjectTagMatch');
+    my $nomatch = RT->Config->Get('ExtractSubjectTagNoMatch');
+    TAGLIST: while ( $TransactionSubject =~ /($match)/g ) {
+        my $tag = $1;
+        next if $tag =~ /$nomatch/;
+        foreach my $subject_tag ( RT->System->SubjectTag ) {
+            if ($tag =~ /\[\Q$subject_tag\E\s+\#(\d+)\s*\]/) {
+                next TAGLIST;
+            }
+        }
+        $TicketSubject .= " $tag" unless ( $TicketSubject =~ /\Q$tag\E/ );
+    }
+
+    $self->TicketObj->SetSubject($TicketSubject)
+        if ( $TicketSubject ne $origTicketSubject );
+
+    return (1);
+}
+
+eval "require RT::Action::ExtractSubjectTag_Vendor";
+if ($@ && $@ !~ qr{^Can't locate RT/Action/ExtractSubjectTag_Vendor.pm}) {
+    die $@;
+};
+
+eval "require RT::Action::ExtractSubjectTag_Local";
+if ($@ && $@ !~ qr{^Can't locate RT/Action/ExtractSubjectTag_Local.pm}) {
+    die $@;
+};
+
+1;
diff --git a/rt/lib/RT/Action/Generic.pm b/rt/lib/RT/Action/Generic.pm
index 3232d4898..5e8ef32ce 100755
--- a/rt/lib/RT/Action/Generic.pm
+++ b/rt/lib/RT/Action/Generic.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,9 +45,10 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 =head1 NAME
 
-  RT::Action::Generic - a generic baseclass for RT Actions
+  RT::Action::Generic - deprecated, see RT::Action
 
 =head1 SYNOPSIS
 
@@ -55,177 +56,25 @@
 
 =head1 DESCRIPTION
 
-=head1 METHODS
-
-=begin testing
+This module is provided only for backwards compatibility.
 
-ok (require RT::Action::Generic);
+=head1 METHODS
 
-=end testing
 
 =cut
 
-package RT::Action::Generic;
-
 use strict;
-use Scalar::Util;
-
-use base qw/RT::Base/;
-
-# {{{ sub new 
-sub new  {
-  my $proto = shift;
-  my $class = ref($proto) || $proto;
-  my $self  = {};
-  bless ($self, $class);
-  $self->_Init(@_);
-  return $self;
-}
-# }}}
-
-# {{{ sub _Init 
-sub _Init  {
-  my $self = shift;
-  my %args = ( Argument => undef,
-               CurrentUser => undef,
-               ScripActionObj => undef,
-               ScripObj => undef,
-               TemplateObj => undef,
-               TicketObj => undef,
-               TransactionObj => undef,
-               Type => undef,
-
-               @_ );
-
-  $self->{'Argument'} = $args{'Argument'};
-  $self->CurrentUser( $args{'CurrentUser'});
-  $self->{'ScripActionObj'} = $args{'ScripActionObj'};
-  $self->{'ScripObj'} = $args{'ScripObj'};
-  $self->{'TemplateObj'} = $args{'TemplateObj'};
-  $self->{'TicketObj'} = $args{'TicketObj'};
-  $self->{'TransactionObj'} = $args{'TransactionObj'};
-  $self->{'Type'} = $args{'Type'};
-
-  Scalar::Util::weaken($self->{'ScripActionObj'});
-  Scalar::Util::weaken($self->{'ScripObj'});
-  Scalar::Util::weaken($self->{'TemplateObj'});
-  Scalar::Util::weaken($self->{'TicketObj'});
-  Scalar::Util::weaken($self->{'TransactionObj'});
-
-}
-# }}}
-
-# Access Scripwide data
-
-# {{{ sub Argument 
-sub Argument  {
-  my $self = shift;
-  return($self->{'Argument'});
-}
-# }}}
-
-# {{{ sub TicketObj
-sub TicketObj  {
-  my $self = shift;
-  return($self->{'TicketObj'});
-}
-# }}}
-
-# {{{ sub TransactionObj
-sub TransactionObj  {
-  my $self = shift;
-  return($self->{'TransactionObj'});
-}
-# }}}
-
-# {{{ sub TemplateObj
-sub TemplateObj  {
-  my $self = shift;
-  return($self->{'TemplateObj'});
-}
-# }}}
-
-# {{{ sub ScripObj
-sub ScripObj  {
-  my $self = shift;
-  return($self->{'ScripObj'});
-}
-# }}}
-
-# {{{ sub ScripActionObj
-sub ScripActionObj  {
-  my $self = shift;
-  return($self->{'ScripActionObj'});
-}
-# }}}
-
-# {{{ sub Type
-sub Type  {
-  my $self = shift;
-  return($self->{'Type'});
-}
-# }}}
-
-
-# Scrip methods
-
-#Do what we need to do and send it out.
-
-# {{{ sub Commit 
-sub Commit  {
-  my $self = shift;
-  return(0, $self->loc("Commit Stubbed"));
-}
-# }}}
-
-
-#What does this type of Action does
-
-# {{{ sub Describe 
-sub Describe  {
-  my $self = shift;
-  return $self->loc("No description for [_1]", ref $self);
-}
-# }}}
-
-
-#Parse the templates, get things ready to go.
-
-# {{{ sub Prepare 
-sub Prepare  {
-  my $self = shift;
-  return (0, $self->loc("Prepare Stubbed"));
-}
-# }}}
-
-
-#If this rule applies to this transaction, return true.
-
-# {{{ sub IsApplicable 
-sub IsApplicable  {
-  my $self = shift;
-  return(undef);
-}
-# }}}
-
-# {{{ sub DESTROY
-sub DESTROY {
-    my $self = shift;
-
-    # We need to clean up all the references that might maybe get
-    # oddly circular
-    $self->{'ScripActionObj'} = undef;
-    $self->{'ScripObj'} = undef;
-    $self->{'TemplateObj'} =undef
-    $self->{'TicketObj'} = undef;
-    $self->{'TransactionObj'} = undef;
-}
-
-# }}}
+use warnings;
+package RT::Action::Generic;
+use base 'RT::Action';
 
 eval "require RT::Action::Generic_Vendor";
 die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/Generic_Vendor.pm});
+warn "RT::Action::Generic has become RT::Action. Please adjust your deprecated RT::Action::Generic_Vendor file at " . $INC{"RT/Action/Generic_Vendor.pm"} if !$@;
+
 eval "require RT::Action::Generic_Local";
 die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/Generic_Local.pm});
+warn "RT::Action::Generic has become RT::Action. Please adjust your deprecated RT::Action::Generic_Local file at " . $INC{"RT/Action/Generic_Local.pm"} if !$@;
 
 1;
+
diff --git a/rt/lib/RT/Action/LinearEscalate.pm b/rt/lib/RT/Action/LinearEscalate.pm
new file mode 100755
index 000000000..9130f40ca
--- /dev/null
+++ b/rt/lib/RT/Action/LinearEscalate.pm
@@ -0,0 +1,279 @@
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
+=head1 NAME
+
+RT::Action::LinearEscalate - will move a ticket's priority toward its final priority.
+
+=head1 This vs. RT::Action::EscalatePriority
+
+This action doesn't change priority if due date is not set.
+
+This action honor the Starts date.
+
+This action can apply changes silently.
+
+This action can replace EscalatePriority completly. If you want to tickets
+that have been created without Due date then you can add scrip that sets
+default due date. For example a week then priorities of your tickets will
+escalate linearly during the week from intial value towards final.
+
+=head1 This vs. LinearEscalate from the CPAN
+
+This action is an integration of the module from the CPAN into RT's core
+that's happened in RT 3.8. If you're upgrading from 3.6 and have been using
+module from the CPAN with old version of RT then you should uninstall it
+and use this one.
+
+However, this action doesn't support control over config. Read 
+to find out ways to deal with it.
+
+=head1 DESCRIPTION
+
+LinearEscalate is a ScripAction that will move a ticket's priority
+from its initial priority to its final priority linearly as
+the ticket approaches its due date.
+
+It's intended to be called by an RT escalation tool. One such tool is called
+rt-crontool and is located in $RTHOME/bin (see C for more details).
+
+=head1 USAGE
+
+Once the ScripAction is installed, the following script in "cron" 
+will get tickets to where they need to be:
+
+    rt-crontool --search RT::Search::FromSQL --search-arg \
+    "(Status='new' OR Status='open' OR Status = 'stalled')" \
+    --action RT::Action::LinearEscalate
+
+The Starts date is associated with intial ticket's priority or
+the Created field if the former is not set. End of interval is
+the Due date. Tickets without due date B.
+
+=head1 CONFIGURATION
+
+Initial and Final priorities are controlled by queue's options
+and can be defined using the web UI via Configuration tab. This
+action should handle correctly situations when initial priority
+is greater than final.
+
+LinearEscalate's behavior can be controlled by two options:
+
+=over 4
+
+=item RecordTransaction - defaults to false and if option is true then
+causes the tool to create a transaction on the ticket when it is escalated.
+
+=item UpdateLastUpdated - which defaults to true and updates the LastUpdated
+field when the ticket is escalated, otherwise don't touch anything.
+
+=back
+
+You cannot set "UpdateLastUpdated" to false unless "RecordTransaction"
+is also false. Well, you can, but we'll just ignore you.
+
+You can set this options using either in F, as action
+argument in call to the rt-crontool or in DB if you want to use the action
+in scrips.
+
+From a shell you can use the following command:
+
+    rt-crontool --search RT::Search::FromSQL --search-arg \
+    "(Status='new' OR Status='open' OR Status = 'stalled')" \
+    --action RT::Action::LinearEscalate \
+    --action-arg "RecordTransaction: 1"
+
+This ScripAction uses RT's internal _Set or __Set calls to set ticket
+priority without running scrips or recording a transaction on each
+update, if it's been said to.
+
+=cut
+
+package RT::Action::LinearEscalate;
+
+use strict;
+use warnings;
+use base qw(RT::Action);
+
+our $VERSION = '0.06';
+
+#Do what we need to do and send it out.
+
+#What does this type of Action does
+
+sub Describe {
+    my $self = shift;
+    my $class = ref($self) || $self;
+    return "$class will move a ticket's priority toward its final priority.";
+}
+
+sub Prepare {
+    my $self = shift;
+
+    my $ticket = $self->TicketObj;
+
+    my $due = $ticket->DueObj->Unix;
+    unless ( $due > 0 ) {
+        $RT::Logger->debug('Due is not set. Not escalating.');
+        return 1;
+    }
+
+    my $priority_range = ($ticket->FinalPriority ||0) - ($ticket->InitialPriority ||0);
+    unless ( $priority_range ) {
+        $RT::Logger->debug('Final and Initial priorities are equal. Not escalating.');
+        return 1;
+    }
+
+    if ( $ticket->Priority >= $ticket->FinalPriority && $priority_range > 0 ) {
+        $RT::Logger->debug('Current priority is greater than final. Not escalating.');
+        return 1;
+    }
+    elsif ( $ticket->Priority <= $ticket->FinalPriority && $priority_range < 0 ) {
+        $RT::Logger->debug('Current priority is lower than final. Not escalating.');
+        return 1;
+    }
+
+    # TODO: compute the number of business days until the ticket is due
+
+    # now we know we have a due date. for every day that passes,
+    # increment priority according to the formula
+
+    my $starts         = $ticket->StartsObj->Unix;
+    $starts            = $ticket->CreatedObj->Unix unless $starts > 0;
+    my $now            = time;
+
+    # do nothing if we didn't reach starts or created date
+    if ( $starts > $now ) {
+        $RT::Logger->debug('Starts(Created) is in future. Not escalating.');
+        return 1;
+    }
+
+    $due = $starts + 1 if $due <= $starts; # +1 to avoid div by zero
+
+    my $percent_complete = ($now-$starts)/($due - $starts);
+
+    my $new_priority = int($percent_complete * $priority_range) + ($ticket->InitialPriority || 0);
+	$new_priority = $ticket->FinalPriority if $new_priority > $ticket->FinalPriority;
+    $self->{'new_priority'} = $new_priority;
+
+    return 1;
+}
+
+sub Commit {
+    my $self = shift;
+
+    my $new_value = $self->{'new_priority'};
+    return 1 unless defined $new_value;
+
+    my $ticket = $self->TicketObj;
+    # if the priority hasn't changed do nothing
+    return 1 if $ticket->Priority == $new_value;
+
+    # override defaults from argument
+    my ($record, $update) = (0, 1);
+    {
+        my $arg = $self->Argument || '';
+        if ( $arg =~ /RecordTransaction:\s*(\d+)/i ) {
+            $record = $1;
+            $RT::Logger->debug("Overrode RecordTransaction: $record");
+        } 
+        if ( $arg =~ /UpdateLastUpdated:\s*(\d+)/i ) {
+            $update = $1;
+            $RT::Logger->debug("Overrode UpdateLastUpdated: $update");
+        }
+        $update = 1 if $record;
+    }
+
+    $RT::Logger->debug(
+        'Linearly escalating priority of ticket #'. $ticket->Id
+        .' from '. $ticket->Priority .' to '. $new_value
+        .' and'. ($record? '': ' do not') .' record a transaction'
+        .' and'. ($update? '': ' do not') .' touch last updated field'
+    );
+
+    my ( $val, $msg );
+    unless ( $record ) {
+        unless ( $update ) {
+            ( $val, $msg ) = $ticket->__Set(
+                Field => 'Priority',
+                Value => $new_value,
+            );
+        }
+        else {
+            ( $val, $msg ) = $ticket->_Set(
+                Field => 'Priority',
+                Value => $new_value,
+                RecordTransaction => 0,
+            );
+        }
+    }
+    else {
+        ( $val, $msg ) = $ticket->SetPriority( $new_value );
+    }
+
+    unless ($val) {
+        $RT::Logger->error( "Couldn't set new priority value: $msg" );
+        return (0, $msg);
+    }
+    return 1;
+}
+
+eval "require RT::Action::LinearEscalate_Vendor";
+die $@ if ( $@ && $@ !~ qr{^Can't locate RT/Action/LinearEscalate_Vendor.pm} );
+eval "require RT::Action::LinearEscalate_Local";
+die $@ if ( $@ && $@ !~ qr{^Can't locate RT/Action/LinearEscalate_Local.pm} );
+
+1;
+
+=head1 AUTHORS
+
+Kevin Riggle Ekevinr@bestpractical.comE
+
+Ruslan Zakirov Eruz@bestpractical.comE
+
+=cut
diff --git a/rt/lib/RT/Action/Notify.pm b/rt/lib/RT/Action/Notify.pm
index 82cad1e58..30238fd61 100755
--- a/rt/lib/RT/Action/Notify.pm
+++ b/rt/lib/RT/Action/Notify.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,14 +45,16 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 #
 package RT::Action::Notify;
-require RT::Action::SendEmail;
-use Mail::Address;
+
 use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Action::SendEmail);
+use warnings;
 
+use base qw(RT::Action::SendEmail);
+
+use Email::Address;
 
 =head2 Prepare
 
@@ -67,8 +69,6 @@ sub Prepare {
     $self->SUPER::Prepare();
 }
 
-# {{{ sub SetRecipients
-
 =head2 SetRecipients
 
 Sets the recipients of this meesage to Owner, Requestor, AdminCc, Cc or All. 
@@ -79,73 +79,63 @@ Explicitly B notify the creator of the transaction by default
 sub SetRecipients {
     my $self = shift;
 
-    my $arg = $self->Argument;
+    my $ticket = $self->TicketObj;
 
+    my $arg = $self->Argument;
     $arg =~ s/\bAll\b/Owner,Requestor,AdminCc,Cc/;
 
     my ( @To, @PseudoTo, @Cc, @Bcc );
 
 
     if ( $arg =~ /\bOtherRecipients\b/ ) {
-        if ( $self->TransactionObj->Attachments->First ) {
-            my @cc_addresses = Mail::Address->parse($self->TransactionObj->Attachments->First->GetHeader('RT-Send-Cc'));
-            foreach my $addr (@cc_addresses) {
-                  push @Cc, $addr->address;
-            }
-            my @bcc_addresses = Mail::Address->parse($self->TransactionObj->Attachments->First->GetHeader('RT-Send-Bcc'));
-
-            foreach my $addr (@bcc_addresses) {
-                  push @Bcc, $addr->address;
-            }
-
+        if ( my $attachment = $self->TransactionObj->Attachments->First ) {
+            push @Cc, map { $_->address } Email::Address->parse(
+                $attachment->GetHeader('RT-Send-Cc')
+            );
+            push @Bcc, map { $_->address } Email::Address->parse(
+                $attachment->GetHeader('RT-Send-Bcc')
+            );
         }
     }
 
     if ( $arg =~ /\bRequestor\b/ ) {
-        push ( @To, $self->TicketObj->Requestors->MemberEmailAddresses  );
+        push @To, $ticket->Requestors->MemberEmailAddresses;
     }
 
-    
-
     if ( $arg =~ /\bCc\b/ ) {
 
         #If we have a To, make the Ccs, Ccs, otherwise, promote them to To
         if (@To) {
-            push ( @Cc, $self->TicketObj->Cc->MemberEmailAddresses );
-            push ( @Cc, $self->TicketObj->QueueObj->Cc->MemberEmailAddresses  );
+            push ( @Cc, $ticket->Cc->MemberEmailAddresses );
+            push ( @Cc, $ticket->QueueObj->Cc->MemberEmailAddresses  );
         }
         else {
-            push ( @Cc, $self->TicketObj->Cc->MemberEmailAddresses  );
-            push ( @To, $self->TicketObj->QueueObj->Cc->MemberEmailAddresses  );
+            push ( @Cc, $ticket->Cc->MemberEmailAddresses  );
+            push ( @To, $ticket->QueueObj->Cc->MemberEmailAddresses  );
         }
     }
 
-    if ( ( $arg =~ /\bOwner\b/ )
-        && ( $self->TicketObj->OwnerObj->id != $RT::Nobody->id ) )
-    {
-
-        # If we're not sending to Ccs or requestors, 
+    if ( $arg =~ /\bOwner\b/ && $ticket->OwnerObj->id != $RT::Nobody->id ) {
+        # If we're not sending to Ccs or requestors,
         # then the Owner can be the To.
         if (@To) {
-            push ( @Bcc, $self->TicketObj->OwnerObj->EmailAddress );
+            push ( @Bcc, $ticket->OwnerObj->EmailAddress );
         }
         else {
-            push ( @To, $self->TicketObj->OwnerObj->EmailAddress );
+            push ( @To, $ticket->OwnerObj->EmailAddress );
         }
 
     }
 
     if ( $arg =~ /\bAdminCc\b/ ) {
-        push ( @Bcc, $self->TicketObj->AdminCc->MemberEmailAddresses  );
-        push ( @Bcc, $self->TicketObj->QueueObj->AdminCc->MemberEmailAddresses  );
+        push ( @Bcc, $ticket->AdminCc->MemberEmailAddresses  );
+        push ( @Bcc, $ticket->QueueObj->AdminCc->MemberEmailAddresses  );
     }
 
-    if ($RT::UseFriendlyToLine) {
+    if ( RT->Config->Get('UseFriendlyToLine') ) {
         unless (@To) {
-            push (
-		@PseudoTo,
-		sprintf($RT::FriendlyToLineFormat, $arg, $self->TicketObj->id),
-	    );
+            push @PseudoTo,
+                sprintf RT->Config->Get('FriendlyToLineFormat'), $arg, $ticket->id;
         }
     }
 
@@ -154,7 +144,7 @@ sub SetRecipients {
     #Strip the sender out of the To, Cc and AdminCc and set the 
     # recipients fields used to build the message by the superclass.
     # unless a flag is set 
-    if ($RT::NotifyActor) {
+    if (RT->Config->Get('NotifyActor')) {
         @{ $self->{'To'} }  = @To;
         @{ $self->{'Cc'} }  = @Cc;
         @{ $self->{'Bcc'} } = @Bcc;
@@ -169,8 +159,6 @@ sub SetRecipients {
 
 }
 
-# }}}
-
 eval "require RT::Action::Notify_Vendor";
 die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/Notify_Vendor.pm});
 eval "require RT::Action::Notify_Local";
diff --git a/rt/lib/RT/Action/NotifyAsComment.pm b/rt/lib/RT/Action/NotifyAsComment.pm
index 215f453d3..b2eb5acd8 100755
--- a/rt/lib/RT/Action/NotifyAsComment.pm
+++ b/rt/lib/RT/Action/NotifyAsComment.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,13 +45,13 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 package RT::Action::NotifyAsComment;
 require RT::Action::Notify;
 
 use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Action::Notify);
-
+use warnings;
+use base qw(RT::Action::Notify);
 
 =head2 SetReturnAddress
 
diff --git a/rt/lib/RT/Action/NotifyGroup.pm b/rt/lib/RT/Action/NotifyGroup.pm
new file mode 100644
index 000000000..6b830cb86
--- /dev/null
+++ b/rt/lib/RT/Action/NotifyGroup.pm
@@ -0,0 +1,209 @@
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
+=head1 NAME
+
+RT::Action::NotifyGroup - RT Action that sends notifications to groups and/or users
+
+=head1 DESCRIPTION
+
+RT action module that allow you to notify particular groups and/or users.
+Distribution is shipped with C script that
+is command line tool for managing NotifyGroup scrip actions. For more
+more info see its documentation.
+
+=cut
+
+package RT::Action::NotifyGroup;
+
+use strict;
+use warnings;
+use base qw(RT::Action::Notify);
+
+require RT::User;
+require RT::Group;
+
+=head1 METHODS
+
+=head2 SetRecipients
+
+Sets the recipients of this message to Groups and/or Users.
+
+=cut
+
+sub SetRecipients {
+    my $self = shift;
+
+    my $arg = $self->Argument;
+    foreach( $self->__SplitArg( $arg ) ) {
+        $self->_HandleArgument( $_ );
+    }
+
+    my $creator = $self->TransactionObj->CreatorObj->EmailAddress();
+    unless( $RT::NotifyActor ) {
+        @{ $self->{'To'} } = grep ( !/^\Q$creator\E$/, @{ $self->{'To'} } );
+    }
+
+    $self->{'seen_ueas'} = {};
+
+    return 1;
+}
+
+sub _HandleArgument {
+    my $self = shift;
+    my $instance = shift;
+
+    if ( $instance !~ /\D/ ) {
+        my $obj = RT::Principal->new( $self->CurrentUser );
+        $obj->Load( $instance );
+        return $self->_HandlePrincipal( $obj );
+    }
+
+    my $group = RT::Group->new( $self->CurrentUser );
+    $group->LoadUserDefinedGroup( $instance );
+    # to check disabled and so on
+    return $self->_HandlePrincipal( $group->PrincipalObj )
+        if $group->id;
+
+    require Email::Address;
+
+    my $user = RT::User->new( $self->CurrentUser );
+    if ( $instance =~ /^$Email::Address::addr_spec$/ ) {
+        $user->LoadByEmail( $instance );
+        return $self->__PushUserAddress( $instance )
+            unless $user->id;
+    } else {
+        $user->Load( $instance );
+    }
+    return $self->_HandlePrincipal( $user->PrincipalObj )
+        if $user->id;
+
+    $RT::Logger->error(
+        "'$instance' is not principal id, group name, user name,"
+        ." user email address or any email address"
+    );
+
+    return;
+}
+
+sub _HandlePrincipal {
+    my $self = shift;
+    my $obj = shift;
+    unless( $obj->id ) {
+        $RT::Logger->error( "Couldn't load principal #$obj" );
+        return;
+    }
+    if( $obj->Disabled ) {
+        $RT::Logger->info( "Principal #$obj is disabled => skip" );
+        return;
+    }
+    if( !$obj->PrincipalType ) {
+        $RT::Logger->crit( "Principal #$obj has empty type" );
+    } elsif( lc $obj->PrincipalType eq 'user' ) {
+        $self->__HandleUserArgument( $obj->Object );
+    } elsif( lc $obj->PrincipalType eq 'group' ) {
+        $self->__HandleGroupArgument( $obj->Object );
+    } else {
+        $RT::Logger->info( "Principal #$obj has unsupported type" );
+    }
+    return;
+}
+
+sub __HandleUserArgument {
+    my $self = shift;
+    my $obj = shift;
+    
+    my $uea = $obj->EmailAddress;
+    unless( $uea ) {
+        $RT::Logger->warning( "User #". $obj->id ." has no email address" );
+        return;
+    }
+    $self->__PushUserAddress( $uea );
+}
+
+sub __HandleGroupArgument {
+    my $self = shift;
+    my $obj = shift;
+
+    my $members = $obj->UserMembersObj;
+    while( my $m = $members->Next ) {
+        $self->__HandleUserArgument( $m );
+    }
+}
+
+sub __SplitArg {
+    return grep length, map {s/^\s+//; s/\s+$//; $_} split /,/, $_[1];
+}
+
+sub __PushUserAddress {
+    my $self = shift;
+    my $uea = shift;
+    push @{ $self->{'To'} }, $uea unless $self->{'seen_ueas'}{ $uea }++;
+    return;
+}
+
+
+=head1 AUTHOR
+
+Ruslan U. Zakirov Eruz@bestpractical.comE
+
+L, F
+
+=cut
+
+eval "require RT::Action::NotifyGroup_Vendor";
+if ($@ && $@ !~ qr{^Can't locate RT/Action/NotifyGroup_Vendor.pm}) {
+    die $@;
+};
+
+eval "require RT::Action::NotifyGroup_Local";
+if ($@ && $@ !~ qr{^Can't locate RT/Action/NotifyGroup_Local.pm}) {
+    die $@;
+};
+
+1;
diff --git a/rt/lib/RT/Action/NotifyGroupAsComment.pm b/rt/lib/RT/Action/NotifyGroupAsComment.pm
new file mode 100644
index 000000000..bee0a01a1
--- /dev/null
+++ b/rt/lib/RT/Action/NotifyGroupAsComment.pm
@@ -0,0 +1,91 @@
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
+=head1 NAME
+
+RT::Action::NotifyGroupAsComment - RT Action that sends notifications to groups and/or users as comment
+
+=head1 DESCRIPTION
+
+This is subclass of L that send comments instead of replies.
+See C and L docs for more info.
+
+=cut
+
+package RT::Action::NotifyGroupAsComment;
+
+use strict;
+use warnings;
+
+use RT::Action::NotifyGroup;
+
+use base qw(RT::Action::NotifyGroup);
+
+sub SetReturnAddress {
+	my $self = shift;
+	$self->{'comment'} = 1;
+	return $self->SUPER::SetReturnAddress( @_, is_comment => 1 );
+}
+
+=head1 AUTHOR
+
+Ruslan U. Zakirov Eruz@bestpractical.comE
+
+=cut
+
+eval "require RT::Action::NotifyGroupAsComment_Vendor";
+if ($@ && $@ !~ qr{^Can't locate RT/Action/NotifyGroupAsComment_Vendor.pm}) {
+    die $@;
+};
+
+eval "require RT::Action::NotifyGroupAsComment_Local";
+if ($@ && $@ !~ qr{^Can't locate RT/Action/NotifyGroupAsComment_Local.pm}) {
+    die $@;
+};
+
+1;
diff --git a/rt/lib/RT/Action/RecordComment.pm b/rt/lib/RT/Action/RecordComment.pm
index c0256d6d7..bac17e96e 100644
--- a/rt/lib/RT/Action/RecordComment.pm
+++ b/rt/lib/RT/Action/RecordComment.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,11 +45,10 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 package RT::Action::RecordComment;
-require RT::Action::Generic;
+use base 'RT::Action';
 use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Action::Generic);
 
 =head1 NAME
 
diff --git a/rt/lib/RT/Action/RecordCorrespondence.pm b/rt/lib/RT/Action/RecordCorrespondence.pm
index 10a890e4e..044893b97 100644
--- a/rt/lib/RT/Action/RecordCorrespondence.pm
+++ b/rt/lib/RT/Action/RecordCorrespondence.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,11 +45,10 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 package RT::Action::RecordCorrespondence;
-require RT::Action::Generic;
+use base 'RT::Action';
 use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Action::Generic);
 
 =head1 NAME
 
diff --git a/rt/lib/RT/Action/ResolveMembers.pm b/rt/lib/RT/Action/ResolveMembers.pm
index fab049b0a..ff826ccc1 100644
--- a/rt/lib/RT/Action/ResolveMembers.pm
+++ b/rt/lib/RT/Action/ResolveMembers.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,15 +45,14 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 # This Action will resolve all members of a resolved group ticket
 
 package RT::Action::ResolveMembers;
-require RT::Action::Generic;
+use base 'RT::Action';
 require RT::Links;
 
 use strict;
-use vars qw/@ISA/;
-@ISA=qw(RT::Action::Generic);
 
 #Do what we need to do and send it out.
 
diff --git a/rt/lib/RT/Action/SendEmail.pm b/rt/lib/RT/Action/SendEmail.pm
index ed5ec4fd6..a09bd3e56 100755
--- a/rt/lib/RT/Action/SendEmail.pm
+++ b/rt/lib/RT/Action/SendEmail.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,20 +45,21 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 # Portions Copyright 2000 Tobias Brox 
 
 package RT::Action::SendEmail;
-require RT::Action::Generic;
 
 use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Action::Generic);
+use warnings;
 
-use MIME::Words qw(encode_mimeword);
+use base qw(RT::Action);
 
 use RT::EmailParser;
-use Mail::Address;
-use Date::Format qw(strftime);
+use RT::Interface::Email;
+use Email::Address;
+our @EMAIL_RECIPIENT_HEADERS = qw(To Cc Bcc);
+
 
 =head1 NAME
 
@@ -68,53 +69,85 @@ RT::Action::AutoReply is a good example subclass.
 
 =head1 SYNOPSIS
 
-  require RT::Action::SendEmail;
-  @ISA  = qw(RT::Action::SendEmail);
-
+  use base 'RT::Action::SendEmail';
 
 =head1 DESCRIPTION
 
 Basically, you create another module RT::Action::YourAction which ISA
 RT::Action::SendEmail.
 
-=begin testing
+=head1 METHODS
 
-ok (require RT::Action::SendEmail);
+=head2 CleanSlate
 
-=end testing
+Cleans class-wide options, like L or L.
 
+=cut
 
-=head1 AUTHOR
-
-Jesse Vincent  and Tobias Brox 
+sub CleanSlate {
+    my $self = shift;
+    $self->SquelchMailTo(undef);
+    $self->AttachTickets(undef);
+}
 
-=head1 SEE ALSO
+=head2 Commit
 
-perl(1).
+Sends the prepared message and writes outgoing record into DB if the feature is
+activated in the config.
 
 =cut
 
-# {{{ Scrip methods (_Init, Commit, Prepare, IsApplicable)
+sub Commit {
+    my $self = shift;
 
+    $self->DeferDigestRecipients() if RT->Config->Get('RecordOutgoingEmail');
+    my $message = $self->TemplateObj->MIMEObj;
 
-# {{{ sub Commit
+    my $orig_message;
+    if (   RT->Config->Get('RecordOutgoingEmail')
+        && RT->Config->Get('GnuPG')->{'Enable'} )
+    {
 
-sub Commit {
-    # DO NOT SHIFT @_ in this subroutine.  It breaks Hook::LexWrap's
-    # ability to pass @_ to a 'post' routine.
-    my $self = $_[0];
+        # it's hacky, but we should know if we're going to crypt things
+        my $attachment = $self->TransactionObj->Attachments->First;
+
+        my %crypt;
+        foreach my $argument (qw(Sign Encrypt)) {
+            if ( $attachment
+                && defined $attachment->GetHeader("X-RT-$argument") )
+            {
+                $crypt{$argument} = $attachment->GetHeader("X-RT-$argument");
+            } else {
+                $crypt{$argument} = $self->TicketObj->QueueObj->$argument();
+            }
+        }
+        if ( $crypt{'Sign'} || $crypt{'Encrypt'} ) {
+            $orig_message = $message->dup;
+        }
+    }
 
-    my ($ret) = $self->SendMessage( $self->TemplateObj->MIMEObj );
-    if ( $ret > 0 ) {
-        $self->RecordOutgoingMailTransaction( $self->TemplateObj->MIMEObj )
-            if ($RT::RecordOutgoingEmail);
+    my ($ret) = $self->SendMessage($message);
+    if ( $ret > 0 && RT->Config->Get('RecordOutgoingEmail') ) {
+        if ($orig_message) {
+            $message->attach(
+                Type        => 'application/x-rt-original-message',
+                Disposition => 'inline',
+                Data        => $orig_message->as_string,
+            );
+        }
+        $self->RecordOutgoingMailTransaction($message);
+        $self->RecordDeferredRecipients();
     }
-    return (abs $ret);
+
+
+    return ( abs $ret );
 }
 
-# }}}
+=head2 Prepare
+
+Builds an outgoing email we're going to send using scrip's template.
 
-# {{{ sub Prepare
+=cut
 
 sub Prepare {
     my $self = shift;
@@ -136,116 +169,116 @@ sub Prepare {
     $self->RemoveInappropriateRecipients();
 
     my %seen;
-    foreach my $type qw(To Cc Bcc) {
-        @{ $self->{ $type } } =
-            grep defined && length && !$seen{ lc $_ }++,
-                @{ $self->{ $type } };
+    foreach my $type (@EMAIL_RECIPIENT_HEADERS) {
+        @{ $self->{$type} }
+            = grep defined && length && !$seen{ lc $_ }++,
+            @{ $self->{$type} };
     }
 
     # Go add all the Tos, Ccs and Bccs that we need to to the message to
     # make it happy, but only if we actually have values in those arrays.
 
-    # TODO: We should be pulling the recipients out of the template and shove them into To, Cc and Bcc
+# TODO: We should be pulling the recipients out of the template and shove them into To, Cc and Bcc
 
-    $self->SetHeader( 'To', join ( ', ', @{ $self->{'To'} } ) )
-      if ( ! $MIMEObj->head->get('To') &&  $self->{'To'} && @{ $self->{'To'} } );
-    $self->SetHeader( 'Cc', join ( ', ', @{ $self->{'Cc'} } ) )
-      if ( !$MIMEObj->head->get('Cc') && $self->{'Cc'} && @{ $self->{'Cc'} } );
-    $self->SetHeader( 'Bcc', join ( ', ', @{ $self->{'Bcc'} } ) )
-      if ( !$MIMEObj->head->get('Bcc') && $self->{'Bcc'} && @{ $self->{'Bcc'} } );
+    for my $header (@EMAIL_RECIPIENT_HEADERS) {
 
-    # PseudoTo	(fake to headers) shouldn't get matched for message recipients.
+    $self->SetHeader( $header, join( ', ', @{ $self->{$header} } ) )
+        if ( !$MIMEObj->head->get($header)
+        && $self->{$header}
+        && @{ $self->{$header} } );
+}
+    # PseudoTo (fake to headers) shouldn't get matched for message recipients.
     # If we don't have any 'To' header (but do have other recipients), drop in
     # the pseudo-to header.
-    $self->SetHeader( 'To', join ( ', ', @{ $self->{'PseudoTo'} } ) )
-      if ( $self->{'PseudoTo'} && ( @{ $self->{'PseudoTo'} } )
-        and ( !$MIMEObj->head->get('To') ) ) and ( $MIMEObj->head->get('Cc') or $MIMEObj->head->get('Bcc'));
+    $self->SetHeader( 'To', join( ', ', @{ $self->{'PseudoTo'} } ) )
+        if $self->{'PseudoTo'}
+            && @{ $self->{'PseudoTo'} }
+            && !$MIMEObj->head->get('To')
+            && ( $MIMEObj->head->get('Cc') or $MIMEObj->head->get('Bcc') );
 
     # We should never have to set the MIME-Version header
     $self->SetHeader( 'MIME-Version', '1.0' );
 
     # fsck.com #5959: Since RT sends 8bit mail, we should say so.
-    $self->SetHeader( 'Content-Transfer-Encoding','8bit');
+    $self->SetHeader( 'Content-Transfer-Encoding', '8bit' );
 
     # For security reasons, we only send out textual mails.
-    my @parts = $MIMEObj;
-    while (my $part = shift @parts) {
-        if ($part->is_multipart) {
-            push @parts, $part->parts;
-        }
-        else {
-            if ( RT::I18N::IsTextualContentType( $part->mime_type ) ) {
-                $part->head->mime_attr( "Content-Type" => $part->mime_type )
-            } else {
-                $part->head->mime_attr( "Content-Type" => 'text/plain' );
-            }
-            $part->head->mime_attr( "Content-Type.charset" => 'utf-8' );
-        }
+    foreach my $part ( grep !$_->is_multipart, $MIMEObj->parts_DFS ) {
+        my $type = $part->mime_type || 'text/plain';
+        $type = 'text/plain' unless RT::I18N::IsTextualContentType($type);
+        $part->head->mime_attr( "Content-Type"         => $type );
+        $part->head->mime_attr( "Content-Type.charset" => 'utf-8' );
     }
 
-
-    RT::I18N::SetMIMEEntityToEncoding( $MIMEObj, $RT::EmailOutputEncoding, 'mime_words_ok' );
+    RT::I18N::SetMIMEEntityToEncoding( $MIMEObj,
+        RT->Config->Get('EmailOutputEncoding'),
+        'mime_words_ok', );
 
     # Build up a MIME::Entity that looks like the original message.
-    $self->AddAttachments() if ( $MIMEObj->head->get('RT-Attach-Message') );
+    $self->AddAttachments if ( $MIMEObj->head->get('RT-Attach-Message')
+                               && ( $MIMEObj->head->get('RT-Attach-Message') !~ /^(n|no|0|off|false)$/i ) );
+
+    $self->AddTickets;
+
+    my $attachment = $self->TransactionObj->Attachments->First;
+    if ($attachment
+        && !(
+               $attachment->GetHeader('X-RT-Encrypt')
+            || $self->TicketObj->QueueObj->Encrypt
+        )
+        )
+    {
+        $attachment->SetHeader( 'X-RT-Encrypt' => 1 )
+            if ( $attachment->GetHeader("X-RT-Incoming-Encryption") || '' ) eq
+            'Success';
+    }
 
     return $result;
-
 }
 
-# }}}
-
-# }}}
-
-
-
 =head2 To
 
-Returns an array of Mail::Address objects containing all the To: recipients for this notification
+Returns an array of L objects containing all the To: recipients for this notification
 
 =cut
 
 sub To {
     my $self = shift;
-    return ($self->_AddressesFromHeader('To'));
+    return ( $self->AddressesFromHeader('To') );
 }
 
 =head2 Cc
 
-Returns an array of Mail::Address objects containing all the Cc: recipients for this notification
+Returns an array of L objects containing all the Cc: recipients for this notification
 
 =cut
 
-sub Cc { 
+sub Cc {
     my $self = shift;
-    return ($self->_AddressesFromHeader('Cc'));
+    return ( $self->AddressesFromHeader('Cc') );
 }
 
 =head2 Bcc
 
-Returns an array of Mail::Address objects containing all the Bcc: recipients for this notification
+Returns an array of L objects containing all the Bcc: recipients for this notification
 
 =cut
 
-
 sub Bcc {
     my $self = shift;
-    return ($self->_AddressesFromHeader('Bcc'));
+    return ( $self->AddressesFromHeader('Bcc') );
 
 }
 
-sub _AddressesFromHeader  {
-    my $self = shift;
-    my $field = shift;
-    my $header = $self->TemplateObj->MIMEObj->head->get($field);
-    my @addresses = Mail::Address->parse($header);
+sub AddressesFromHeader {
+    my $self      = shift;
+    my $field     = shift;
+    my $header    = $self->TemplateObj->MIMEObj->head->get($field);
+    my @addresses = Email::Address->parse($header);
 
     return (@addresses);
 }
 
-
-# {{{ SendMessage
-
 =head2 SendMessage MIMEObj
 
 sends the message using RT's preferred API.
@@ -254,6 +287,7 @@ TODO: Break this out to a separate module
 =cut
 
 sub SendMessage {
+
     # DO NOT SHIFT @_ in this subroutine.  It breaks Hook::LexWrap's
     # ability to pass @_ to a 'post' routine.
     my ( $self, $MIMEObj ) = @_;
@@ -262,36 +296,36 @@ sub SendMessage {
     chomp $msgid;
 
     $self->ScripActionObj->{_Message_ID}++;
-    
-    $RT::Logger->info( $msgid . " #"
-        . $self->TicketObj->id . "/"
-        . $self->TransactionObj->id
-        . " - Scrip "
-        . $self->ScripObj->id . " "
-        . $self->ScripObj->Description );
-
-    #If we don't have any recipients to send to, don't send a message;
-    unless ( $MIMEObj->head->get('To')
-        || $MIMEObj->head->get('Cc')
-        || $MIMEObj->head->get('Bcc') )
-    {
-        $RT::Logger->info( $msgid . " No recipients found. Not sending.\n" );
-        return (-1);
-    }
 
-    unless ($MIMEObj->head->get('Date')) {
-        # We coerce localtime into an array since strftime has a flawed prototype that only accepts
-        # a list
-      $MIMEObj->head->replace(Date => strftime('%a, %d %b %Y %H:%M:%S %z', @{[localtime()]}));
-    }
+    $RT::Logger->info( $msgid . " #"
+            . $self->TicketObj->id . "/"
+            . $self->TransactionObj->id
+            . " - Scrip "
+            . ($self->ScripObj->id || '#rule'). " "
+            . ( $self->ScripObj->Description || '' ) );
+
+    my $status = RT::Interface::Email::SendEmail(
+        Entity      => $MIMEObj,
+        Ticket      => $self->TicketObj,
+        Transaction => $self->TransactionObj,
+    );
 
-    return (0) unless ($self->OutputMIMEObject($MIMEObj));
+     
+    return $status unless ($status > 0 || exists $self->{'Deferred'});
 
     my $success = $msgid . " sent ";
-    foreach( qw(To Cc Bcc) ) {
+    foreach (@EMAIL_RECIPIENT_HEADERS) {
         my $recipients = $MIMEObj->head->get($_);
-        $success .= " $_: ". $recipients if $recipients;
+        $success .= " $_: " . $recipients if $recipients;
     }
+
+    if( exists $self->{'Deferred'} ) {
+        for (qw(daily weekly susp)) {
+            $success .= "\nBatched email $_ for: ". join(", ", keys %{ $self->{'Deferred'}{ $_ } } )
+                if exists $self->{'Deferred'}{ $_ };
+        }
+    }
+
     $success =~ s/\n//g;
 
     $RT::Logger->info($success);
@@ -299,140 +333,166 @@ sub SendMessage {
     return (1);
 }
 
+=head2 AddAttachments
 
-=head2 OutputMIMEObject MIME::Entity
-
-Sends C as an email message according to RT's mailer configuration.
-
-=cut 
-
+Takes any attachments to this transaction and attaches them to the message
+we're building.
 
+=cut
 
-sub OutputMIMEObject {
+sub AddAttachments {
     my $self = shift;
-    my $MIMEObj = shift;
-    
-    my $msgid = $MIMEObj->head->get('Message-ID');
-    chomp $msgid;
-    
-    my $SendmailArguments = $RT::SendmailArguments;
-    if (defined $RT::VERPPrefix && defined $RT::VERPDomain) {
-      my $EnvelopeFrom = $self->TransactionObj->CreatorObj->EmailAddress;
-      $EnvelopeFrom =~ s/@/=/g;
-      $EnvelopeFrom =~ s/\s//g;
-      $SendmailArguments .= " -f ${RT::VERPPrefix}${EnvelopeFrom}\@${RT::VERPDomain}";
-    }
-
 
-    if ( $RT::MailCommand eq 'sendmailpipe' ) {
-        eval {
-            # don't ignore CHLD signal to get proper exit code
-            local $SIG{'CHLD'} = 'DEFAULT';
+    my $MIMEObj = $self->TemplateObj->MIMEObj;
 
-            my $mail;
-            unless( open $mail, "|$RT::SendmailPath $SendmailArguments" ) {
-                die "Couldn't run $RT::SendmailPath: $!";
-            }
+    $MIMEObj->head->delete('RT-Attach-Message');
 
-            # if something wrong with $mail->print we will get PIPE signal, handle it
-            local $SIG{'PIPE'} = sub { die "$RT::SendmailPath closed pipe" };
-            $MIMEObj->print($mail);
+    my $attachments = RT::Attachments->new($RT::SystemUser);
+    $attachments->Limit(
+        FIELD => 'TransactionId',
+        VALUE => $self->TransactionObj->Id
+    );
 
-            unless ( close $mail ) {
-                die "Close failed: $!" if $!; # system error
-                # sendmail exit statuses mostly errors with data not software
-                # TODO: status parsing: core dump, exit on signal or EX_*
-                $RT::Logger->warning( "$RT::SendmailPath exitted with status $?" );
-            }
-        };
-        if ($@) {
-            $RT::Logger->crit( $msgid . "Could not send mail: " . $@ );
-            return 0;
-        }
-    }
-    else {
-        my @mailer_args = ($RT::MailCommand);
-        my $method = 'send';
+    # Don't attach anything blank
+    $attachments->LimitNotEmpty;
+    $attachments->OrderBy( FIELD => 'id' );
 
-        local $ENV{MAILADDRESS};
+    # We want to make sure that we don't include the attachment that's
+    # being used as the "Content" of this message" unless that attachment's
+    # content type is not like text/...
+    my $transaction_content_obj = $self->TransactionObj->ContentObj;
 
-        if ( $RT::MailCommand eq 'sendmail' ) {
-            push @mailer_args, split(/\s+/, $SendmailArguments);
-        }
-        elsif ( $RT::MailCommand eq 'smtp' ) {
-            $ENV{MAILADDRESS} = $RT::SMTPFrom || $MIMEObj->head->get('From');
-            push @mailer_args, ( Host  => $RT::SMTPServer );
-            push @mailer_args, ( Debug => $RT::SMTPDebug );
-            $method = 'smtpsend';
-        }
-        else {
-            push @mailer_args, $RT::MailParams;
+    if (   $transaction_content_obj
+        && $transaction_content_obj->ContentType =~ m{text/}i )
+    {
+        # If this was part of a multipart/alternative, skip all of the kids
+        my $parent = $transaction_content_obj->ParentObj;
+        if ($parent and $parent->Id and $parent->ContentType eq "multipart/alternative") {
+            $attachments->Limit(
+                ENTRYAGGREGATOR => 'AND',
+                FIELD           => 'parent',
+                OPERATOR        => '!=',
+                VALUE           => $parent->Id,
+            );
+        } else {
+            $attachments->Limit(
+                ENTRYAGGREGATOR => 'AND',
+                FIELD           => 'id',
+                OPERATOR        => '!=',
+                VALUE           => $transaction_content_obj->Id,
+            );
         }
+    }
 
-        unless ( $MIMEObj->$method(@mailer_args) ) {
-            $RT::Logger->crit( $msgid . "Could not send mail." );
-            return (0);
+    # attach any of this transaction's attachments
+    my $seen_attachment = 0;
+    while ( my $attach = $attachments->Next ) {
+        if ( !$seen_attachment ) {
+            $MIMEObj->make_multipart( 'mixed', Force => 1 );
+            $seen_attachment = 1;
         }
+        $self->AddAttachment($attach);
     }
-    return 1;
 }
 
-# }}}
-
-# {{{ AddAttachments 
+=head2 AddAttachment $attachment
 
-=head2 AddAttachments
-
-Takes any attachments to this transaction and attaches them to the message
+Takes one attachment object of L class and attaches it to the message
 we're building.
 
 =cut
 
+sub AddAttachment {
+    my $self    = shift;
+    my $attach  = shift;
+    my $MIMEObj = shift || $self->TemplateObj->MIMEObj;
+
+    $MIMEObj->attach(
+        Type     => $attach->ContentType,
+        Charset  => $attach->OriginalEncoding,
+        Data     => $attach->OriginalContent,
+        Filename => $self->MIMEEncodeString( $attach->Filename ),
+        'RT-Attachment:' => $self->TicketObj->Id . "/"
+            . $self->TransactionObj->Id . "/"
+            . $attach->id,
+        Encoding => '-SUGGEST',
+    );
+}
 
-sub AddAttachments {
-    my $self = shift;
+=head2 AttachTickets [@IDs]
 
-    my $MIMEObj = $self->TemplateObj->MIMEObj;
+Returns or set list of ticket's IDs that should be attached to an outgoing message.
 
-    $MIMEObj->head->delete('RT-Attach-Message');
+B this method works as a class method and setup things global, so you have to
+clean list by passing undef as argument.
 
-    my $attachments = RT::Attachments->new($RT::SystemUser);
-    $attachments->Limit(
-        FIELD => 'TransactionId',
-        VALUE => $self->TransactionObj->Id
-    );
-    $attachments->OrderBy( FIELD => 'id');
-
-    my $transaction_content_obj = $self->TransactionObj->ContentObj;
+=cut
 
-    # attach any of this transaction's attachments
-    while ( my $attach = $attachments->Next ) {
+{
+    my $list = [];
 
-        # Don't attach anything blank
-        next unless ( $attach->ContentLength );
-
-# We want to make sure that we don't include the attachment that's being used as the "Content" of this message.
-        next
-          if ( $transaction_content_obj
-            && $transaction_content_obj->Id == $attach->Id
-            && $transaction_content_obj->ContentType =~ qr{text/plain}i );
-        $MIMEObj->make_multipart('mixed');
-        $MIMEObj->attach(
-            Type     => $attach->ContentType,
-            Charset  => $attach->OriginalEncoding,
-            Data     => $attach->OriginalContent,
-            Filename => $self->MIMEEncodeString( $attach->Filename,
-                $RT::EmailOutputEncoding ),
-            'RT-Attachment:' => $self->TicketObj->Id."/".$self->TransactionObj->Id."/".$attach->id,
-            Encoding => '-SUGGEST'
-        );
+    sub AttachTickets {
+        my $self = shift;
+        $list = [ grep defined, @_ ] if @_;
+        return @$list;
     }
+}
+
+=head2 AddTickets
+
+Attaches tickets to the current message, list of tickets' ids get from
+L method.
 
+=cut
+
+sub AddTickets {
+    my $self = shift;
+    $self->AddTicket($_) foreach $self->AttachTickets;
+    return;
 }
 
-# }}}
+=head2 AddTicket $ID
 
-# {{{ RecordOutgoingMailTransaction
+Attaches a ticket with ID to the message.
+
+Each ticket is attached as multipart entity and all its messages and attachments
+are attached as sub entities in order of creation, but only if transaction type
+is Create or Correspond.
+
+=cut
+
+sub AddTicket {
+    my $self = shift;
+    my $tid  = shift;
+
+    # XXX: we need a current user here, but who is current user?
+    my $attachs   = RT::Attachments->new($RT::SystemUser);
+    my $txn_alias = $attachs->TransactionAlias;
+    $attachs->Limit( ALIAS => $txn_alias, FIELD => 'Type', VALUE => 'Create' );
+    $attachs->Limit(
+        ALIAS => $txn_alias,
+        FIELD => 'Type',
+        VALUE => 'Correspond'
+    );
+    $attachs->LimitByTicket($tid);
+    $attachs->LimitNotEmpty;
+    $attachs->OrderBy( FIELD => 'Created' );
+
+    my $ticket_mime = MIME::Entity->build(
+        Type        => 'multipart/mixed',
+        Top         => 0,
+        Description => "ticket #$tid",
+    );
+    while ( my $attachment = $attachs->Next ) {
+        $self->AddAttachment( $attachment, $ticket_mime );
+    }
+    if ( $ticket_mime->parts ) {
+        my $email_mime = $self->TemplateObj->MIMEObj;
+        $email_mime->make_multipart;
+        $email_mime->add_part($ticket_mime);
+    }
+    return;
+}
 
 =head2 RecordOutgoingMailTransaction MIMEObj
 
@@ -440,12 +500,9 @@ Record a transaction in RT with this outgoing message for future record-keeping
 
 =cut
 
-
-
 sub RecordOutgoingMailTransaction {
-    my $self = shift;
+    my $self    = shift;
     my $MIMEObj = shift;
-           
 
     my @parts = $MIMEObj->parts;
     my @attachments;
@@ -453,26 +510,28 @@ sub RecordOutgoingMailTransaction {
     foreach my $part (@parts) {
         my $attach = $part->head->get('RT-Attachment');
         if ($attach) {
-            $RT::Logger->debug("We found an attachment. we want to not record it.");
+            $RT::Logger->debug(
+                "We found an attachment. we want to not record it.");
             push @attachments, $attach;
         } else {
             $RT::Logger->debug("We found a part. we want to record it.");
             push @keep, $part;
         }
     }
-    $MIMEObj->parts(\@keep);
+    $MIMEObj->parts( \@keep );
     foreach my $attachment (@attachments) {
-        $MIMEObj->head->add('RT-Attachment', $attachment);
+        $MIMEObj->head->add( 'RT-Attachment', $attachment );
     }
 
     RT::I18N::SetMIMEEntityToEncoding( $MIMEObj, 'utf-8', 'mime_words_ok' );
 
-    my $transaction = RT::Transaction->new($self->TransactionObj->CurrentUser);
+    my $transaction
+        = RT::Transaction->new( $self->TransactionObj->CurrentUser );
 
-    # XXX: TODO -> Record attachments as references to things in the attachments table, maybe.
+# XXX: TODO -> Record attachments as references to things in the attachments table, maybe.
 
     my $type;
-    if ($self->TransactionObj->Type eq 'Comment') {
+    if ( $self->TransactionObj->Type eq 'Comment' ) {
         $type = 'CommentEmailRecord';
     } else {
         $type = 'EmailRecord';
@@ -489,19 +548,15 @@ sub RecordOutgoingMailTransaction {
         ActivateScrips => 0
     );
 
-    if( $id ) {
-	$self->{'OutgoingMailTransaction'} = $id;
+    if ($id) {
+        $self->{'OutgoingMailTransaction'} = $id;
     } else {
-        $RT::Logger->warning( "Could not record outgoing message transaction: $msg" );
+        $RT::Logger->warning(
+            "Could not record outgoing message transaction: $msg");
     }
     return $id;
 }
 
-# }}}
-#
-
-# {{{ sub SetRTSpecialHeaders
-
 =head2 SetRTSpecialHeaders 
 
 This routine adds all the random headers that RT wants in a mail message
@@ -514,161 +569,266 @@ sub SetRTSpecialHeaders {
 
     $self->SetSubject();
     $self->SetSubjectToken();
-    $self->SetHeaderAsEncoding( 'Subject', $RT::EmailOutputEncoding )
-      if ($RT::EmailOutputEncoding);
+    $self->SetHeaderAsEncoding( 'Subject',
+        RT->Config->Get('EmailOutputEncoding') )
+        if ( RT->Config->Get('EmailOutputEncoding') );
     $self->SetReturnAddress();
     $self->SetReferencesHeaders();
 
-    unless ($self->TemplateObj->MIMEObj->head->get('Message-ID')) {
-      # Get Message-ID for this txn
-      my $msgid = "";
-      $msgid = $self->TransactionObj->Message->First->GetHeader("RT-Message-ID")
-        || $self->TransactionObj->Message->First->GetHeader("Message-ID")
-        if $self->TransactionObj->Message && $self->TransactionObj->Message->First;
+    unless ( $self->TemplateObj->MIMEObj->head->get('Message-ID') ) {
 
-      # If there is one, and we can parse it, then base our Message-ID on it
-      if ($msgid 
-          and $msgid =~ s/<(rt-.*?-\d+-\d+)\.(\d+)-\d+-\d+\@\Q$RT::Organization\E>$/
+        # Get Message-ID for this txn
+        my $msgid = "";
+        if ( my $msg = $self->TransactionObj->Message->First ) {
+            $msgid = $msg->GetHeader("RT-Message-ID")
+                || $msg->GetHeader("Message-ID");
+        }
+
+        # If there is one, and we can parse it, then base our Message-ID on it
+        if (    $msgid
+            and $msgid
+            =~ s/<(rt-.*?-\d+-\d+)\.(\d+)-\d+-\d+\@\QRT->Config->Get('Organization')\E>$/
                          "<$1." . $self->TicketObj->id
                           . "-" . $self->ScripObj->id
                           . "-" . $self->ScripActionObj->{_Message_ID}
-                          . "@" . $RT::Organization . ">"/eg
-          and $2 == $self->TicketObj->id) {
-        $self->SetHeader( "Message-ID" => $msgid );
-      } else {
-        $self->SetHeader( 'Message-ID',
-            "TicketObj->id . "-"
-            . $self->ScripObj->id . "-"  # Scrip
-            . $self->ScripActionObj->{_Message_ID} . "@"  # Email sent
-            . $RT::Organization
-            . ">" );
-      }
+                          . "@" . RT->Config->Get('Organization') . ">"/eg
+            and $2 == $self->TicketObj->id
+            )
+        {
+            $self->SetHeader( "Message-ID" => $msgid );
+        } else {
+            $self->SetHeader(
+                'Message-ID' => RT::Interface::Email::GenMessageId(
+                    Ticket      => $self->TicketObj,
+                    Scrip       => $self->ScripObj,
+                    ScripAction => $self->ScripActionObj
+                ),
+            );
+        }
     }
 
     $self->SetHeader( 'Precedence', "bulk" )
-      unless ( $self->TemplateObj->MIMEObj->head->get("Precedence") );
+        unless ( $self->TemplateObj->MIMEObj->head->get("Precedence") );
 
-    $self->SetHeader( 'X-RT-Loop-Prevention', $RT::rtname );
+    $self->SetHeader( 'X-RT-Loop-Prevention', RT->Config->Get('rtname') );
     $self->SetHeader( 'RT-Ticket',
-        $RT::rtname . " #" . $self->TicketObj->id() );
+        RT->Config->Get('rtname') . " #" . $self->TicketObj->id() );
     $self->SetHeader( 'Managed-by',
         "RT $RT::VERSION (http://www.bestpractical.com/rt/)" );
 
-    $self->SetHeader( 'RT-Originator',
-        $self->TransactionObj->CreatorObj->EmailAddress );
+# XXX, TODO: use /ShowUser/ShowUserEntry(or something like that) when it would be
+#            refactored into user's method.
+    if ( my $email = $self->TransactionObj->CreatorObj->EmailAddress ) {
+        $self->SetHeader( 'RT-Originator', $email );
+    }
 
 }
 
-# }}}
 
+sub DeferDigestRecipients {
+    my $self = shift;
+    $RT::Logger->debug( "Calling SetRecipientDigests for transaction " . $self->TransactionObj . ", id " . $self->TransactionObj->id );
+
+    # The digest attribute will be an array of notifications that need to
+    # be sent for this transaction.  The array will have the following
+    # format for its objects.
+    # $digest_hash -> {daily|weekly|susp} -> address -> {To|Cc|Bcc}
+    #                                     -> sent -> {true|false}
+    # The "sent" flag will be used by the cron job to indicate that it has
+    # run on this transaction.
+    # In a perfect world we might move this hash construction to the
+    # extension module itself.
+    my $digest_hash = {};
+
+    foreach my $mailfield (@EMAIL_RECIPIENT_HEADERS) {
+        # If we have a "PseudoTo", the "To" contains it, so we don't need to access it
+        next if ( ( $self->{'PseudoTo'} && @{ $self->{'PseudoTo'} } ) && ( $mailfield eq 'To' ) );
+        $RT::Logger->debug( "Working on mailfield $mailfield; recipients are " . join( ',', @{ $self->{$mailfield} } ) );
+
+        # Store the 'daily digest' folk in an array.
+        my ( @send_now, @daily_digest, @weekly_digest, @suspended );
+
+        # Have to get the list of addresses directly from the MIME header
+        # at this point.
+        $RT::Logger->debug( $self->TemplateObj->MIMEObj->head->as_string );
+        foreach my $rcpt ( map { $_->address } $self->AddressesFromHeader($mailfield) ) {
+            next unless $rcpt;
+            my $user_obj = RT::User->new($RT::SystemUser);
+            $user_obj->LoadByEmail($rcpt);
+            if  ( ! $user_obj->id ) {
+                # If there's an email address in here without an associated
+                # RT user, pass it on through.
+                $RT::Logger->debug( "User $rcpt is not associated with an RT user object.  Send mail.");
+                push( @send_now, $rcpt );
+                next;
+            }
 
-# }}}
+            my $mailpref = RT->Config->Get( 'EmailFrequency', $user_obj ) || '';
+            $RT::Logger->debug( "Got user mail preference '$mailpref' for user $rcpt");
 
-# {{{ RemoveInappropriateRecipients
+            if ( $mailpref =~ /daily/i ) { push( @daily_digest, $rcpt ) }
+            elsif ( $mailpref =~ /weekly/i ) { push( @weekly_digest, $rcpt ) }
+            elsif ( $mailpref =~ /suspend/i ) { push( @suspended, $rcpt ) }
+            else { push( @send_now, $rcpt ) }
+        }
 
-=head2 RemoveInappropriateRecipients
+        # Reset the relevant mail field.
+        $RT::Logger->debug( "Removing deferred recipients from $mailfield: line");
+        if (@send_now) {
+            $self->SetHeader( $mailfield, join( ', ', @send_now ) );
+        } else {    # No recipients!  Remove the header.
+            $self->TemplateObj->MIMEObj->head->delete($mailfield);
+        }
 
-Remove addresses that are RT addresses or that are on this transaction's blacklist
+        # Push the deferred addresses into the appropriate field in
+        # our attribute hash, with the appropriate mail header.
+        $RT::Logger->debug(
+            "Setting deferred recipients for attribute creation");
+        $digest_hash->{'daily'}->{$_} = {'header' => $mailfield , _sent => 0}  for (@daily_digest);
+        $digest_hash->{'weekly'}->{$_} ={'header' =>  $mailfield, _sent => 0}  for (@weekly_digest);
+        $digest_hash->{'susp'}->{$_} = {'header' => $mailfield, _sent =>0 }  for (@suspended);
+    }
 
-=cut
+    if ( scalar keys %$digest_hash ) {
 
-sub RemoveInappropriateRecipients {
+        # Save the hash so that we can add it as an attribute to the
+        # outgoing email transaction.
+        $self->{'Deferred'} = $digest_hash;
+    } else {
+        $RT::Logger->debug( "No recipients found for deferred delivery on "
+                . "transaction #"
+                . $self->TransactionObj->id );
+    }
+}
+
+
+    
+sub RecordDeferredRecipients {
     my $self = shift;
+    return unless exists $self->{'Deferred'};
+
+    my $txn_id = $self->{'OutgoingMailTransaction'};
+    return unless $txn_id;
 
-    my $msgid = $self->TemplateObj->MIMEObj->head->get  ('Message-Id');
+    my $txn_obj = RT::Transaction->new( $self->CurrentUser );
+    $txn_obj->Load( $txn_id );
+    my( $ret, $msg ) = $txn_obj->AddAttribute(
+        Name => 'DeferredRecipients',
+        Content => $self->{'Deferred'}
+    );
+    $RT::Logger->warning( "Unable to add deferred recipients to outgoing transaction: $msg" ) 
+        unless $ret;
 
+    return ($ret,$msg);
+}
 
+=head2 SquelchMailTo [@ADDRESSES]
 
-    my @blacklist;
+Mark ADDRESSES to be removed from list of the recipients. Returns list of the addresses.
+To empty list pass undefined argument.
+
+B that this method can be called as class method and works globaly. Don't forget to
+clean this list when blocking is not required anymore, pass undef to do this.
+
+=cut
 
-    my @types = qw/To Cc Bcc/;
+{
+    my $squelch = [];
 
-    # Weed out any RT addresses. We really don't want to talk to ourselves!
-    foreach my $type (@types) {
-        @{ $self->{$type} } =
-          RT::EmailParser::CullRTAddresses( "", @{ $self->{$type} } );
+    sub SquelchMailTo {
+        my $self = shift;
+        if (@_) {
+            $squelch = [ grep defined, @_ ];
+        }
+        return @$squelch;
     }
+}
+
+=head2 RemoveInappropriateRecipients
+
+Remove addresses that are RT addresses or that are on this transaction's blacklist
+
+=cut
+
+sub RemoveInappropriateRecipients {
+    my $self = shift;
+
+    my @blacklist = ();
 
     # If there are no recipients, don't try to send the message.
     # If the transaction has content and has the header RT-Squelch-Replies-To
 
-    if ( $self->TransactionObj->Attachments->First() ) {
-        if (
-            $self->TransactionObj->Attachments->First->GetHeader(
-                'RT-DetectedAutoGenerated')
-          )
-        {
+    my $msgid = $self->TemplateObj->MIMEObj->head->get('Message-Id');
+    if ( my $attachment = $self->TransactionObj->Attachments->First ) {
+
+        if ( $attachment->GetHeader('RT-DetectedAutoGenerated') ) {
 
             # What do we want to do with this? It's probably (?) a bounce
             # caused by one of the watcher addresses being broken.
             # Default ("true") is to redistribute, for historical reasons.
 
-            if ( !$RT::RedistributeAutoGeneratedMessages ) {
+            if ( !RT->Config->Get('RedistributeAutoGeneratedMessages') ) {
 
                 # Don't send to any watchers.
-                @{ $self->{'To'} }  = ();
-                @{ $self->{'Cc'} }  = ();
-                @{ $self->{'Bcc'} } = ();
-
-                $RT::Logger->info( $msgid . " The incoming message was autogenerated. Not redistributing this message based on site configuration.\n");
-            }
-            elsif ( $RT::RedistributeAutoGeneratedMessages eq 'privileged' ) {
+                @{ $self->{$_} } = () for (@EMAIL_RECIPIENT_HEADERS);
+                $RT::Logger->info( $msgid
+                        . " The incoming message was autogenerated. "
+                        . "Not redistributing this message based on site configuration."
+                );
+            } elsif ( RT->Config->Get('RedistributeAutoGeneratedMessages') eq
+                'privileged' )
+            {
 
                 # Only send to "privileged" watchers.
-                #
-
-                foreach my $type (@types) {
-
+                foreach my $type (@EMAIL_RECIPIENT_HEADERS) {
                     foreach my $addr ( @{ $self->{$type} } ) {
                         my $user = RT::User->new($RT::SystemUser);
                         $user->LoadByEmail($addr);
-                        @{ $self->{$type} } =
-                          grep ( !/^\Q$addr\E$/, @{ $self->{$type} } )
-                          if ( !$user->Privileged );
-
+                        push @blacklist, $addr if ( !$user->Privileged );
                     }
                 }
-                $RT::Logger->info( $msgid . " The incoming message was autogenerated. Not redistributing this message to unprivileged users based on site configuration.\n");
-
+                $RT::Logger->info( $msgid
+                        . " The incoming message was autogenerated. "
+                        . "Not redistributing this message to unprivileged users based on site configuration."
+                );
             }
-
         }
 
-        my $squelch =
-          $self->TransactionObj->Attachments->First->GetHeader(
-            'RT-Squelch-Replies-To');
-
-        if ($squelch) {
-            @blacklist = split( /,/, $squelch );
+        if ( my $squelch = $attachment->GetHeader('RT-Squelch-Replies-To') ) {
+            push @blacklist, split( /,/, $squelch );
         }
     }
 
-    # Let's grab the SquelchMailTo attribue and push those entries into the @blacklist
-    my @non_recipients = $self->TicketObj->SquelchMailTo;
-    foreach my $attribute (@non_recipients) {
-        push @blacklist, $attribute->Content;
-    }
+# Let's grab the SquelchMailTo attribue and push those entries into the @blacklist
+    push @blacklist, map $_->Content, $self->TicketObj->SquelchMailTo;
+    push @blacklist, $self->SquelchMailTo;
 
     # Cycle through the people we're sending to and pull out anyone on the
     # system blacklist
 
-    foreach my $person_to_yank (@blacklist) {
-        $person_to_yank =~ s/\s//g;
-        foreach my $type (@types) {
-            @{ $self->{$type} } =
-              grep ( !/^\Q$person_to_yank\E$/, @{ $self->{$type} } );
+    # Trim leading and trailing spaces. 
+    @blacklist = map { RT::User->CanonicalizeEmailAddress( $_->address ) } Email::Address->parse(join(', ', grep {defined} @blacklist));
+
+    foreach my $type (@EMAIL_RECIPIENT_HEADERS) {
+        my @addrs;
+        foreach my $addr ( @{ $self->{$type} } ) {
+
+         # Weed out any RT addresses. We really don't want to talk to ourselves!
+         # If we get a reply back, that means it's not an RT address
+            if ( !RT::EmailParser->CullRTAddresses($addr) ) {
+                $RT::Logger->info( $msgid . "$addr appears to point to this RT instance. Skipping" );
+                next;
+            }
+            if ( grep /^\Q$addr\E$/, @blacklist ) {
+                $RT::Logger->info( $msgid . "$addr was blacklisted for outbound mail on this transaction. Skipping");
+                next;
+            }
+            push @addrs, $addr;
         }
+        @{ $self->{$type} } = @addrs;
     }
 }
 
-# }}}
-# {{{ sub SetReturnAddress
-
 =head2 SetReturnAddress is_comment => BOOLEAN
 
 Calculate and set From and Reply-To headers based on the is_comment flag.
@@ -680,6 +840,7 @@ sub SetReturnAddress {
     my $self = shift;
     my %args = (
         is_comment => 0,
+        friendly_name => undef,
         @_
     );
 
@@ -689,33 +850,35 @@ sub SetReturnAddress {
 
     if ( $args{'is_comment'} ) {
         $replyto = $self->TicketObj->QueueObj->CommentAddress
-          || $RT::CommentAddress;
-    }
-    else {
+            || RT->Config->Get('CommentAddress');
+    } else {
         $replyto = $self->TicketObj->QueueObj->CorrespondAddress
-          || $RT::CorrespondAddress;
+            || RT->Config->Get('CorrespondAddress');
     }
 
     unless ( $self->TemplateObj->MIMEObj->head->get('From') ) {
-        if ($RT::UseFriendlyFromLine) {
-            my $friendly_name = $self->TransactionObj->CreatorObj->RealName
-                || $self->TransactionObj->CreatorObj->Name;
-            if ( $friendly_name =~ /^"(.*)"$/ ) {    # a quoted string
-                $friendly_name = $1;
+        if ( RT->Config->Get('UseFriendlyFromLine') ) {
+            my $friendly_name = $args{friendly_name};
+
+            unless ( $friendly_name ) {
+                $friendly_name = $self->TransactionObj->CreatorObj->FriendlyName;
+                if ( $friendly_name =~ /^"(.*)"$/ ) {    # a quoted string
+                    $friendly_name = $1;
+                }
             }
 
             $friendly_name =~ s/"/\\"/g;
             $self->SetHeader(
                 'From',
                 sprintf(
-                    $RT::FriendlyFromLineFormat,
-                    $self->MIMEEncodeString( $friendly_name,
-                        $RT::EmailOutputEncoding ),
+                    RT->Config->Get('FriendlyFromLineFormat'),
+                    $self->MIMEEncodeString(
+                        $friendly_name, RT->Config->Get('EmailOutputEncoding')
+                    ),
                     $replyto
                 ),
             );
-        }
-        else {
+        } else {
             $self->SetHeader( 'From', $replyto );
         }
     }
@@ -726,10 +889,6 @@ sub SetReturnAddress {
 
 }
 
-# }}}
-
-# {{{ sub SetHeader
-
 =head2 SetHeader FIELD, VALUE
 
 Set the FIELD of the current MIME object into VALUE.
@@ -743,20 +902,16 @@ sub SetHeader {
 
     chomp $val;
     chomp $field;
-    $self->TemplateObj->MIMEObj->head->fold_length( $field, 10000 );
-    $self->TemplateObj->MIMEObj->head->replace( $field,     $val );
-    return $self->TemplateObj->MIMEObj->head->get($field);
+    my $head = $self->TemplateObj->MIMEObj->head;
+    $head->fold_length( $field, 10000 );
+    $head->replace( $field, $val );
+    return $head->get($field);
 }
 
-# }}}
-
-
-# {{{ sub SetSubject
-
 =head2 SetSubject
 
-This routine sets the subject. it does not add the rt tag. that gets done elsewhere
-If $self->{'Subject'} is already defined, it uses that. otherwise, it tries to get
+This routine sets the subject. it does not add the rt tag. That gets done elsewhere
+If subject is already defined via template, it uses that. otherwise, it tries to get
 the transaction's subject.
 
 =cut 
@@ -765,39 +920,29 @@ sub SetSubject {
     my $self = shift;
     my $subject;
 
-    my $message = $self->TransactionObj->Attachments;
     if ( $self->TemplateObj->MIMEObj->head->get('Subject') ) {
         return ();
     }
+
+    my $message = $self->TransactionObj->Attachments;
+    $message->RowsPerPage(1);
     if ( $self->{'Subject'} ) {
         $subject = $self->{'Subject'};
+    } elsif ( my $first = $message->First ) {
+        my $tmp = $first->GetHeader('Subject');
+        $subject = defined $tmp ? $tmp : $self->TicketObj->Subject;
+    } else {
+        $subject = $self->TicketObj->Subject;
     }
-    elsif ( ( $message->First() ) && ( $message->First->Headers ) ) {
-        my $header = $message->First->Headers();
-        $header =~ s/\n\s+/ /g;
-        if ( $header =~ /^Subject: (.*?)$/m ) {
-            $subject = $1;
-        }
-        else {
-            $subject = $self->TicketObj->Subject();
-        }
-
-    }
-    else {
-        $subject = $self->TicketObj->Subject();
-    }
+    $subject = '' unless defined $subject;
+    chomp $subject;
 
-    $subject =~ s/(\r\n|\n|\s)/ /gi;
+    $subject =~ s/(\r\n|\n|\s)/ /g;
 
-    chomp $subject;
     $self->SetHeader( 'Subject', $subject );
 
 }
 
-# }}}
-
-# {{{ sub SetSubjectToken
-
 =head2 SetSubjectToken
 
 This routine fixes the RT tag in the subject. It's unlikely that you want to overwrite this.
@@ -806,22 +951,16 @@ This routine fixes the RT tag in the subject. It's unlikely that you want to ove
 
 sub SetSubjectToken {
     my $self = shift;
-    my $sub  = $self->TemplateObj->MIMEObj->head->get('Subject');
-    my $id   = $self->TicketObj->id;
-
-    my $token_re = $RT::EmailSubjectTagRegex;
-    $token_re = qr/\Q$RT::rtname\E/o unless $token_re;
-    return if $sub =~ /\[$token_re\s+#$id\]/;
 
-    $sub =~ s/(\r\n|\n|\s)/ /gi;
-    chomp $sub;
-    $self->TemplateObj->MIMEObj->head->replace(
-        Subject => "[$RT::rtname #$id] $sub",
+    my $head = $self->TemplateObj->MIMEObj->head;
+    $head->replace(
+        Subject => RT::Interface::Email::AddSubjectTag(
+            Encode::decode_utf8( $head->get('Subject') ),
+            $self->TicketObj,
+        ),
     );
 }
 
-# }}}
-
 =head2 SetReferencesHeaders
 
 Set References and In-Reply-To headers for this message.
@@ -829,18 +968,14 @@ Set References and In-Reply-To headers for this message.
 =cut
 
 sub SetReferencesHeaders {
-
     my $self = shift;
     my ( @in_reply_to, @references, @msgid );
 
-    my $attachments = $self->TransactionObj->Message;
-
-    if ( my $top = $attachments->First() ) {
-        @in_reply_to = split(/\s+/m, $top->GetHeader('In-Reply-To') || '');  
-        @references = split(/\s+/m, $top->GetHeader('References') || '' );  
-        @msgid = split(/\s+/m, $top->GetHeader('Message-ID') || ''); 
-    }
-    else {
+    if ( my $top = $self->TransactionObj->Message->First ) {
+        @in_reply_to = split( /\s+/m, $top->GetHeader('In-Reply-To') || '' );
+        @references  = split( /\s+/m, $top->GetHeader('References')  || '' );
+        @msgid       = split( /\s+/m, $top->GetHeader('Message-ID')  || '' );
+    } else {
         return (undef);
     }
 
@@ -848,53 +983,54 @@ sub SetReferencesHeaders {
     # the RT Web UI, and hence we want to *not* append its Message-ID
     # to the References and In-Reply-To.  OR it came from an outside
     # source, and we should treat it as per the RFC
-    if ( "@msgid" =~ /<(rt-.*?-\d+-\d+)\.(\d+-0-0)\@$RT::Organization>/) {
+    my $org = RT->Config->Get('Organization');
+    if ( "@msgid" =~ /<(rt-.*?-\d+-\d+)\.(\d+)-0-0\@\Q$org\E>/ ) {
+
+        # Make all references which are internal be to version which we
+        # have sent out
 
-      # Make all references which are internal be to version which we
-      # have sent out
-      for (@references, @in_reply_to) {
-        s/<(rt-.*?-\d+-\d+)\.(\d+-0-0)\@$RT::Organization>$/
+        for ( @references, @in_reply_to ) {
+            s/<(rt-.*?-\d+-\d+)\.(\d+-0-0)\@\Q$org\E>$/
           "<$1." . $self->TicketObj->id .
              "-" . $self->ScripObj->id .
              "-" . $self->ScripActionObj->{_Message_ID} .
-             "@" . $RT::Organization . ">"/eg
-      }
+             "@" . $org . ">"/eg
+        }
 
-      # In reply to whatever the internal message was in reply to
-      $self->SetHeader( 'In-Reply-To', join( " ",  ( @in_reply_to )));
+        # In reply to whatever the internal message was in reply to
+        $self->SetHeader( 'In-Reply-To', join( " ", (@in_reply_to) ) );
 
-      # Default the references to whatever we're in reply to
-      @references = @in_reply_to unless @references;
+        # Default the references to whatever we're in reply to
+        @references = @in_reply_to unless @references;
 
-      # References are unchanged from internal
+        # References are unchanged from internal
     } else {
-      # In reply to that message
-      $self->SetHeader( 'In-Reply-To', join( " ",  ( @msgid )));
 
-      # Default the references to whatever we're in reply to
-      @references = @in_reply_to unless @references;
+        # In reply to that message
+        $self->SetHeader( 'In-Reply-To', join( " ", (@msgid) ) );
 
-      # Push that message onto the end of the references
-      push @references, @msgid;
+        # Default the references to whatever we're in reply to
+        @references = @in_reply_to unless @references;
+
+        # Push that message onto the end of the references
+        push @references, @msgid;
     }
 
     # Push pseudo-ref to the front
     my $pseudo_ref = $self->PseudoReference;
-    @references = ($pseudo_ref, grep { $_ ne $pseudo_ref } @references);
+    @references = ( $pseudo_ref, grep { $_ ne $pseudo_ref } @references );
 
     # If there are more than 10 references headers, remove all but the
     # first four and the last six (Gotta keep this from growing
     # forever)
-    splice(@references, 4, -6) if ($#references >= 10);
+    splice( @references, 4, -6 ) if ( $#references >= 10 );
 
     # Add on the references
-    $self->SetHeader( 'References', join( " ",   @references) );
+    $self->SetHeader( 'References', join( " ", @references ) );
     $self->TemplateObj->MIMEObj->head->fold_length( 'References', 80 );
 
 }
 
-# }}}
-
 =head2 PseudoReference
 
 Returns a fake Message-ID: header for the ticket to allow a base level of threading
@@ -904,13 +1040,13 @@ Returns a fake Message-ID: header for the ticket to allow a base level of thread
 sub PseudoReference {
 
     my $self = shift;
-    my $pseudo_ref =  'TicketObj->id .'@'.$RT::Organization .'>';
+    my $pseudo_ref
+        = 'TicketObj->id . '@'
+        . RT->Config->Get('Organization') . '>';
     return $pseudo_ref;
 }
 
-
-# {{{ SetHeadingAsEncoding
-
 =head2 SetHeaderAsEncoding($field_name, $charset_encoding)
 
 This routine converts the field into specified charset encoding.
@@ -921,86 +1057,37 @@ sub SetHeaderAsEncoding {
     my $self = shift;
     my ( $field, $enc ) = ( shift, shift );
 
-    if ($field eq 'From' and $RT::SMTPFrom) {
-        $self->TemplateObj->MIMEObj->head->replace( $field, $RT::SMTPFrom );
-	return;
-    }
-
-    my $value = $self->TemplateObj->MIMEObj->head->get($field);
-
-    $value =  $self->MIMEEncodeString($value, $enc);
+    my $head = $self->TemplateObj->MIMEObj->head;
 
-    $self->TemplateObj->MIMEObj->head->replace( $field, $value );
+    if ( lc($field) eq 'from' and RT->Config->Get('SMTPFrom') ) {
+        $head->replace( $field, RT->Config->Get('SMTPFrom') );
+        return;
+    }
 
+    my $value = $head->get( $field );
+    $value = $self->MIMEEncodeString( $value, $enc );
+    $head->replace( $field, $value );
 
-} 
-# }}}
+}
 
-# {{{ MIMEEncodeString
+=head2 MIMEEncodeString
 
-=head2 MIMEEncodeString STRING ENCODING
+Takes a perl string and optional encoding pass it over
+L.
 
-Takes a string and a possible encoding and returns the string wrapped in MIME goo.
+Basicly encode a string using B encoding according to RFC2047.
 
 =cut
 
 sub MIMEEncodeString {
-    my  $self = shift;
-    my $value = shift;
-    # using RFC2047 notation, sec 2.
-    # encoded-word = "=?" charset "?" encoding "?" encoded-text "?="
-    my $charset = shift;
-    my $encoding = 'B';
-    # An 'encoded-word' may not be more than 75 characters long
-    #
-    # MIME encoding increases 4/3*(number of bytes), and always in multiples
-    # of 4. Thus we have to find the best available value of bytes available
-    # for each chunk.
-    #
-    # First we get the integer max which max*4/3 would fit on space.
-    # Then we find the greater multiple of 3 lower or equal than $max.
-    my $max = int(((75-length('=?'.$charset.'?'.$encoding.'?'.'?='))*3)/4);
-    $max = int($max/3)*3;
-
-    chomp $value;
-
-    if ( $max <= 0 ) {
-      # gives an error...
-      $RT::Logger->crit("Can't encode! Charset or encoding too big.\n");
-      return ($value);
-    }
-
-    return ($value) unless $value =~ /[^\x20-\x7e]/;
-
-    $value =~ s/\s*$//;
-
-    # we need perl string to split thing char by char
-    Encode::_utf8_on($value) unless Encode::is_utf8( $value );
-
-    my ($tmp, @chunks) = ('', ());
-    while ( length $value ) {
-        my $char = substr($value, 0, 1, '');
-        my $octets = Encode::encode( $charset, $char );
-        if ( length($tmp) + length($octets) > $max ) {
-            push @chunks, $tmp;
-            $tmp = '';
-        }
-        $tmp .= $octets;
-    }
-    push @chunks, $tmp if length $tmp;
-
-    # encode an join chuncks
-    $value = join "\n ",
-               map encode_mimeword( $_, $encoding, $charset ), @chunks ;
-    return($value); 
+    my $self  = shift;
+    return RT::Interface::Email::EncodeToMIME( String => $_[0], Charset => $_[1] );
 }
 
-# }}}
-
 eval "require RT::Action::SendEmail_Vendor";
-die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/SendEmail_Vendor.pm});
+die $@ if ( $@ && $@ !~ qr{^Can't locate RT/Action/SendEmail_Vendor.pm} );
 eval "require RT::Action::SendEmail_Local";
-die $@ if ($@ && $@ !~ qr{^Can't locate RT/Action/SendEmail_Local.pm});
+die $@ if ( $@ && $@ !~ qr{^Can't locate RT/Action/SendEmail_Local.pm} );
 
 1;
 
diff --git a/rt/lib/RT/Action/SetPriority.pm b/rt/lib/RT/Action/SetPriority.pm
index b4c8ee199..9b0838926 100644
--- a/rt/lib/RT/Action/SetPriority.pm
+++ b/rt/lib/RT/Action/SetPriority.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,12 +45,11 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 package RT::Action::SetPriority;
-require RT::Action::Generic;
+use base 'RT::Action';
 
 use strict;
-use vars qw/@ISA/;
-@ISA=qw(RT::Action::Generic);
 
 #Do what we need to do and send it out.
 
diff --git a/rt/lib/RT/Action/UserDefined.pm b/rt/lib/RT/Action/UserDefined.pm
index 7bf6eee51..80ef49224 100644
--- a/rt/lib/RT/Action/UserDefined.pm
+++ b/rt/lib/RT/Action/UserDefined.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,14 +45,11 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
- 
 
 package RT::Action::UserDefined;
-use RT::Action::Generic;
+use base 'RT::Action';
 
 use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Action::Generic);
 
 =head2 Prepare
 
diff --git a/rt/lib/RT/Approval.pm b/rt/lib/RT/Approval.pm
new file mode 100644
index 000000000..c381ba1d2
--- /dev/null
+++ b/rt/lib/RT/Approval.pm
@@ -0,0 +1,74 @@
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
+package RT::Approval;
+use strict;
+use warnings;
+
+use RT::Ruleset;
+
+RT::Ruleset->Add(
+    Name => 'Approval',
+    Rules => [
+        'RT::Approval::Rule::NewPending',
+        'RT::Approval::Rule::Rejected',
+        'RT::Approval::Rule::Passed',
+        'RT::Approval::Rule::Created',
+    ]);
+
+eval "require RT::Approval_Vendor";
+if ($@ && $@ !~ qr{^Can't locate RT/Approval_Vendor.pm}) {
+    die $@;
+};
+
+eval "require RT::Approval_Local";
+if ($@ && $@ !~ qr{^Can't locate RT/Approval_Local.pm}) {
+    die $@;
+};
+
+1;
diff --git a/rt/lib/RT/Approval/Rule.pm b/rt/lib/RT/Approval/Rule.pm
new file mode 100644
index 000000000..37ca478d4
--- /dev/null
+++ b/rt/lib/RT/Approval/Rule.pm
@@ -0,0 +1,85 @@
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
+package RT::Approval::Rule;
+use strict;
+use warnings;
+
+use base 'RT::Rule';
+
+use constant _Queue => '___Approvals';
+
+sub Prepare {
+    my $self = shift;
+    return unless $self->SUPER::Prepare();
+    $self->TicketObj->Type eq 'approval';
+}
+
+sub GetTemplate {
+    my ($self, $template_name, %args) = @_;
+    my $template = RT::Template->new($self->CurrentUser);
+    $template->Load($template_name) or return;
+    my ($result, $msg) = $template->Parse(%args);
+
+    # XXX: error handling
+
+    return $template;
+}
+
+eval "require RT::Approval::Rule_Vendor";
+if ($@ && $@ !~ qr{^Can't locate RT/Approval/Rule_Vendor.pm}) {
+    die $@;
+};
+
+eval "require RT::Approval::Rule_Local";
+if ($@ && $@ !~ qr{^Can't locate RT/Approval/Rule_Local.pm}) {
+    die $@;
+};
+
+1;
+
diff --git a/rt/lib/RT/Approval/Rule/Created.pm b/rt/lib/RT/Approval/Rule/Created.pm
new file mode 100644
index 000000000..73ba2db32
--- /dev/null
+++ b/rt/lib/RT/Approval/Rule/Created.pm
@@ -0,0 +1,71 @@
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
+package RT::Approval::Rule::Created;
+use strict;
+use warnings;
+use base 'RT::Approval::Rule';
+
+use constant _Stage => 'TransactionBatch';
+
+use constant Description => "Notify Owner of their ticket has been approved by some or all approvers"; # loc
+
+sub Prepare {
+    my $self = shift;
+    return unless $self->SUPER::Prepare();
+
+    $self->TransactionObj->Type eq 'Create' &&
+    !$self->TicketObj->HasUnresolvedDependencies( Type => 'approval' );
+}
+
+sub Commit {
+    my $self = shift;
+    $self->RunScripAction('Open Tickets' => 'Blank');
+}
+
+1;
diff --git a/rt/lib/RT/Approval/Rule/NewPending.pm b/rt/lib/RT/Approval/Rule/NewPending.pm
new file mode 100644
index 000000000..5a6f1ed16
--- /dev/null
+++ b/rt/lib/RT/Approval/Rule/NewPending.pm
@@ -0,0 +1,97 @@
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
+package RT::Approval::Rule::NewPending;
+use strict;
+use warnings;
+use base 'RT::Approval::Rule';
+
+use constant Description => "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval"; # loc
+
+sub Prepare {
+    my $self = shift;
+    return unless $self->SUPER::Prepare();
+
+    $self->OnStatusChange('open') and
+    eval { $T::Approving = ($self->TicketObj->AllDependedOnBy( Type => 'ticket' ))[0] }
+}
+
+sub Commit {
+    my $self = shift;
+    my ($top) = $self->TicketObj->AllDependedOnBy( Type => 'ticket' );
+    my $t = $self->TicketObj->Transactions;
+    my $to;
+    while ( my $o = $t->Next ) {
+        $to = $o, last if $o->Type eq 'Create';
+    }
+
+    # XXX: this makes the owner incorrect so notify owner won't work
+    # local $self->{TicketObj} = $top;
+
+    # first txn entry of the approval ticket
+    local $self->{TransactionObj} = $to;
+    $self->RunScripAction('Notify Owner', 'New Pending Approval', @_);
+
+    return;
+
+    # this generates more correct content of the message, but not sure
+    # if ccmessageto is the right way to do this.
+    my $template = $self->GetTemplate('New Pending Approval',
+                                      TicketObj => $top,
+                                      TransactionObj => $to)
+        or return;
+
+    my ( $result, $msg ) = $template->Parse(
+        TicketObj => $top,
+    );
+    $self->TicketObj->Comment( CcMessageTo => $self->TicketObj->OwnerObj->EmailAddress,
+                               MIMEObj => $template->MIMEObj );
+
+}
+
+1;
diff --git a/rt/lib/RT/Approval/Rule/Passed.pm b/rt/lib/RT/Approval/Rule/Passed.pm
new file mode 100644
index 000000000..3134019bf
--- /dev/null
+++ b/rt/lib/RT/Approval/Rule/Passed.pm
@@ -0,0 +1,110 @@
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
+package RT::Approval::Rule::Passed;
+use strict;
+use warnings;
+use base 'RT::Approval::Rule';
+
+use constant Description => "Notify Owner of their ticket has been approved by some or all approvers"; # loc
+
+sub Prepare {
+    my $self = shift;
+    return unless $self->SUPER::Prepare();
+
+    $self->OnStatusChange('resolved');
+}
+
+sub Commit {
+    my $self = shift;
+    my $note;
+    my $t = $self->TicketObj->Transactions;
+
+    while ( my $o = $t->Next ) {
+        next unless $o->Type eq 'Correspond';
+        $note .= $o->Content . "\n" if $o->ContentObj;
+    }
+
+    my ($top) = $self->TicketObj->AllDependedOnBy( Type => 'ticket' );
+    my $links  = $self->TicketObj->DependedOnBy;
+
+    while ( my $link = $links->Next ) {
+        my $obj = $link->BaseObj;
+        next unless $obj->Type eq 'approval';
+
+        for my $other ($obj->AllDependsOn( Type => 'approval' )) {
+            if ( $other->QueueObj->IsActiveStatus( $other->Status ) ) {
+                $other->__Set(
+                    Field => 'Status',
+                    Value => 'deleted',
+                );
+            }
+
+        }
+        $obj->SetStatus( Status => 'open', Force => 1 );
+    }
+
+    my $passed = !$top->HasUnresolvedDependencies( Type => 'approval' );
+    my $template = $self->GetTemplate(
+        $passed ? 'All Approvals Passed' : 'Approval Passed',
+        TicketObj => $top,
+        Approval => $self->TicketObj,
+        Notes => $note,
+    ) or die;
+
+    $top->Correspond( MIMEObj => $template->MIMEObj );
+
+    if ($passed) {
+        $self->RunScripAction('Notify Owner', 'Approval Ready for Owner',
+                              TicketObj => $top);
+    }
+
+    return;
+}
+
+1;
diff --git a/rt/lib/RT/Approval/Rule/Rejected.pm b/rt/lib/RT/Approval/Rule/Rejected.pm
new file mode 100644
index 000000000..7353f635d
--- /dev/null
+++ b/rt/lib/RT/Approval/Rule/Rejected.pm
@@ -0,0 +1,114 @@
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
+package RT::Approval::Rule::Rejected;
+use strict;
+use warnings;
+use base 'RT::Approval::Rule';
+
+use constant Description => "If an approval is rejected, reject the original and delete pending approvals"; # loc
+
+sub Prepare {
+    my $self = shift;
+    return unless $self->SUPER::Prepare();
+
+    return (0)
+        unless $self->OnStatusChange('rejected') or $self->OnStatusChange('deleted')
+}
+
+sub Commit {    # XXX: from custom prepare code
+    my $self = shift;
+    if ( my ($rejected) =
+        $self->TicketObj->AllDependedOnBy( Type => 'ticket' ) ) {
+        my $note = '';
+        if ( RT->Config->Get('ApprovalRejectionNotes') ) {
+            my $t = $self->TicketObj->Transactions;
+            while ( my $o = $t->Next ) {
+                next unless $o->Type eq 'Correspond';
+                $note .= $o->Content . "\n" if $o->ContentObj;
+            }
+        }
+
+        my $template = $self->GetTemplate('Approval Rejected',
+                                          TicketObj => $rejected,
+                                          Approval  => $self->TicketObj,
+                                          Notes     => $note);
+
+        $rejected->Correspond( MIMEObj => $template->MIMEObj );
+        $rejected->SetStatus(
+            Status => 'rejected',
+            Force  => 1,
+        );
+    }
+    my $links = $self->TicketObj->DependedOnBy;
+    foreach my $link ( @{ $links->ItemsArrayRef } ) {
+        my $obj = $link->BaseObj;
+        if ( $obj->QueueObj->IsActiveStatus( $obj->Status ) ) {
+            if ( $obj->Type eq 'approval' ) {
+                $obj->SetStatus(
+                    Status => 'deleted',
+                    Force  => 1,
+                );
+            }
+        }
+    }
+
+    $links = $self->TicketObj->DependsOn;
+    foreach my $link ( @{ $links->ItemsArrayRef } ) {
+        my $obj = $link->TargetObj;
+        if ( $obj->QueueObj->IsActiveStatus( $obj->Status ) ) {
+            $obj->SetStatus(
+                Status => 'deleted',
+                Force  => 1,
+            );
+        }
+    }
+
+}
+
+1;
diff --git a/rt/lib/RT/Attachment.pm b/rt/lib/RT/Attachment.pm
index f0a19874c..4327238e6 100755
--- a/rt/lib/RT/Attachment.pm
+++ b/rt/lib/RT/Attachment.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,6 +45,7 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by )
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
diff --git a/rt/lib/RT/Attachment_Overlay.pm b/rt/lib/RT/Attachment_Overlay.pm
index 7ab6d0ae9..1d508c0fe 100644
--- a/rt/lib/RT/Attachment_Overlay.pm
+++ b/rt/lib/RT/Attachment_Overlay.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,10 +45,10 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
-=head1 SYNOPSIS
 
-  use RT::Attachment;
+=head1 SYNOPSIS
 
+    use RT::Attachment;
 
 =head1 DESCRIPTION
 
@@ -56,15 +56,9 @@ This module should never be instantiated directly by client code. it's an intern
 module which should only be instantiated through exported APIs in Ticket, Queue and other 
 similar objects.
 
-
 =head1 METHODS
 
 
-=begin testing
-
-ok (require RT::Attachment);
-
-=end testing
 
 =cut
 
@@ -74,13 +68,12 @@ package RT::Attachment;
 use strict;
 no warnings qw(redefine);
 
+use RT::Transaction;
 use MIME::Base64;
 use MIME::QuotedPrint;
 
-
-# {{{ sub _OverlayAccessible 
 sub _OverlayAccessible {
-    {
+  {
     TransactionId   => { 'read'=>1, 'public'=>1, 'write' => 0 },
     MessageId       => { 'read'=>1, 'write' => 0 },
     Parent          => { 'read'=>1, 'write' => 0 },
@@ -94,32 +87,6 @@ sub _OverlayAccessible {
     Created         => { 'read'=>1, 'auto'=>1, },
   };
 }
-# }}}
-
-# {{{ sub TransactionObj 
-
-=head2 TransactionObj
-
-Returns the transaction object asscoiated with this attachment.
-
-=cut
-
-sub TransactionObj {
-    require RT::Transaction;
-    my $self=shift;
-    unless (exists $self->{_TransactionObj}) {
-	$self->{_TransactionObj}=RT::Transaction->new($self->CurrentUser);
-	$self->{_TransactionObj}->Load($self->TransactionId);
-    }
-    unless ($self->{_TransactionObj}->Id) {
-        $RT::Logger->crit("Attachment ".$self->id." can't find transaction ".$self->TransactionId." which it is ostensibly part of. That's bad");
-    }
-    return $self->{_TransactionObj};
-}
-
-# }}}
-
-# {{{ sub Create 
 
 =head2 Create
 
@@ -139,20 +106,19 @@ sub Create {
                  Attachment    => undef,
                  @_ );
 
-    #For ease of reference
+    # For ease of reference
     my $Attachment = $args{'Attachment'};
 
-    #if we didn't specify a ticket, we need to bail
-    if ( $args{'TransactionId'} == 0 ) {
-        $RT::Logger->crit( "RT::Attachment->Create couldn't, as you didn't specify a transaction\n" );
+    # if we didn't specify a ticket, we need to bail
+    unless ( $args{'TransactionId'} ) {
+        $RT::Logger->crit( "RT::Attachment->Create couldn't, as you didn't specify a transaction" );
         return (0);
-
     }
 
-    #If we possibly can, collapse it to a singlepart
+    # If we possibly can, collapse it to a singlepart
     $Attachment->make_singlepart;
 
-    #Get the subject
+    # Get the subject
     my $Subject = $Attachment->head->get( 'subject', 0 );
     defined($Subject) or $Subject = '';
     chomp($Subject);
@@ -161,27 +127,30 @@ sub Create {
     my $MessageId = $Attachment->head->get( 'Message-ID', 0 );
     defined($MessageId) or $MessageId = '';
     chomp ($MessageId);
-    $MessageId =~ s/^<(.*)>$/$1/go;
-
+    $MessageId =~ s/^<(.*?)>$/$1/o;
 
     #Get the filename
     my $Filename = $Attachment->head->recommended_filename;
 
+    # MIME::Head doesn't support perl strings well and can return
+    # octets which later will be double encoded in low-level code
+    my $head = $Attachment->head->as_string;
+    utf8::decode( $head );
+
     # If a message has no bodyhandle, that means that it has subparts (or appears to)
     # and we should act accordingly.  
     unless ( defined $Attachment->bodyhandle ) {
-
-        my $id = $self->SUPER::Create(
+        my ($id) = $self->SUPER::Create(
             TransactionId => $args{'TransactionId'},
-            Parent        => 0,
+            Parent        => $args{'Parent'},
             ContentType   => $Attachment->mime_type,
-            Headers       => $Attachment->head->as_string,
+            Headers       => $head,
             MessageId     => $MessageId,
-            Subject       => $Subject
+            Subject       => $Subject,
         );
-        
+
         unless ($id) {
-            $RT::Logger->crit("Attachment insert failed - ".$RT::Handle->dbh->errstr);
+            $RT::Logger->crit("Attachment insert failed - ". $RT::Handle->dbh->errstr);
         }
 
         foreach my $part ( $Attachment->parts ) {
@@ -192,7 +161,7 @@ sub Create {
                 Attachment    => $part,
             );
             unless ($id) {
-                $RT::Logger->crit("Attachment insert failed - ".$RT::Handle->dbh->errstr);
+                $RT::Logger->crit("Attachment insert failed: ". $RT::Handle->dbh->errstr);
             }
         }
         return ($id);
@@ -201,69 +170,115 @@ sub Create {
     #If it's not multipart
     else {
 
-        my ($ContentEncoding, $Body) = $self->_EncodeLOB( $Attachment->bodyhandle->as_string,
-                                                          $Attachment->mime_type 
-                                                        );
+        my ($ContentEncoding, $Body) = $self->_EncodeLOB(
+            $Attachment->bodyhandle->as_string,
+            $Attachment->mime_type
+        );
+
         my $id = $self->SUPER::Create(
             TransactionId   => $args{'TransactionId'},
             ContentType     => $Attachment->mime_type,
             ContentEncoding => $ContentEncoding,
             Parent          => $args{'Parent'},
-            Headers         => $Attachment->head->as_string,
+            Headers         => $head,
             Subject         => $Subject,
             Content         => $Body,
             Filename        => $Filename,
             MessageId       => $MessageId,
         );
+
         unless ($id) {
-            $RT::Logger->crit("Attachment insert failed - ".$RT::Handle->dbh->errstr);
+            $RT::Logger->crit("Attachment insert failed: ". $RT::Handle->dbh->errstr);
         }
-
-        return ($id);
+        return $id;
     }
 }
 
-# }}}
-
-
 =head2 Import
 
 Create an attachment exactly as specified in the named parameters.
 
 =cut
 
-
 sub Import {
     my $self = shift;
-    my %args = ( ContentEncoding => 'none',
+    my %args = ( ContentEncoding => 'none', @_ );
 
-		 @_ );
+    ( $args{'ContentEncoding'}, $args{'Content'} ) =
+        $self->_EncodeLOB( $args{'Content'}, $args{'MimeType'} );
 
+    return ( $self->SUPER::Create(%args) );
+}
 
- ($args{'ContentEncoding'}, $args{'Content'}) = $self->_EncodeLOB($args{'Content'}, $args{'MimeType'});
+=head2 TransactionObj
 
-    return($self->SUPER::Create(%args));
+Returns the transaction object asscoiated with this attachment.
+
+=cut
+
+sub TransactionObj {
+    my $self = shift;
+
+    unless ( $self->{_TransactionObj} ) {
+        $self->{_TransactionObj} = RT::Transaction->new( $self->CurrentUser );
+        $self->{_TransactionObj}->Load( $self->TransactionId );
+    }
+
+    unless ($self->{_TransactionObj}->Id) {
+        $RT::Logger->crit(  "Attachment ". $self->id
+                           ." can't find transaction ". $self->TransactionId
+                           ." which it is ostensibly part of. That's bad");
+    }
+    return $self->{_TransactionObj};
 }
 
-# {{{ sub Content
+=head2 ParentObj
 
-=head2 Content
+Returns a parent's L object if this attachment
+has a parent, otherwise returns undef.
 
-Returns the attachment's content. if it's base64 encoded, decode it 
-before returning it.
+=cut
+
+sub ParentObj {
+    my $self = shift;
+    return undef unless $self->Parent;
+
+    my $parent = RT::Attachment->new( $self->CurrentUser );
+    $parent->LoadById( $self->Parent );
+    return $parent;
+}
+
+=head2 Children
+
+Returns an L object which is preloaded with
+all attachments objects with this attachment\'s Id as their
+C.
 
 =cut
 
-sub Content {
-  my $self = shift;
-   $self->_DecodeLOB($self->ContentType, $self->ContentEncoding, $self->_Value('Content', decode_utf8 => 0));
+sub Children {
+    my $self = shift;
+    
+    my $kids = RT::Attachments->new( $self->CurrentUser );
+    $kids->ChildrenOf( $self->Id );
+    return($kids);
 }
 
+=head2 Content
 
-# }}}
+Returns the attachment's content. if it's base64 encoded, decode it 
+before returning it.
 
+=cut
 
-# {{{ sub OriginalContent
+sub Content {
+    my $self = shift;
+    return $self->_DecodeLOB(
+        $self->ContentType,
+        $self->ContentEncoding,
+        $self->_Value('Content', decode_utf8 => 0),
+    );
+}
 
 =head2 OriginalContent
 
@@ -274,43 +289,37 @@ original encoding.
 =cut
 
 sub OriginalContent {
-  my $self = shift;
-
-  return $self->Content unless RT::I18N::IsTextualContentType($self->ContentType);
-
-  my $enc = $self->OriginalEncoding;
-
-  my $content;
-  if ( $self->ContentEncoding eq 'none' || ! $self->ContentEncoding ) {
-      $content = $self->_Value('Content', decode_utf8 => 0);
-  } elsif ( $self->ContentEncoding eq 'base64' ) {
-      $content = MIME::Base64::decode_base64($self->_Value('Content', decode_utf8 => 0));
-  } elsif ( $self->ContentEncoding eq 'quoted-printable' ) {
-      $content = MIME::QuotedPrint::decode($self->_Value('Content', decode_utf8 => 0));
-  } else {
-      return( $self->loc("Unknown ContentEncoding [_1]", $self->ContentEncoding));
-  }
-
-  # Turn *off* the SvUTF8 bits here so decode_utf8 and from_to below can work.
-  local $@;
-  Encode::_utf8_off($content);
-
-  if (!$enc || $enc eq '' ||  $enc eq 'utf8' || $enc eq 'utf-8') {
-    # If we somehow fail to do the decode, at least push out the raw bits
-    eval {return( Encode::decode_utf8($content))} || return ($content);
-  }
-  
-  eval { Encode::from_to($content, 'utf8' => $enc) } if $enc;
-  if ($@) {
-	$RT::Logger->error("Could not convert attachment from assumed utf8 to '$enc' :".$@);
-  }
-  return $content;
-}
+    my $self = shift;
 
-# }}}
+    return $self->Content unless RT::I18N::IsTextualContentType($self->ContentType);
+    my $enc = $self->OriginalEncoding;
 
+    my $content;
+    if ( !$self->ContentEncoding || $self->ContentEncoding eq 'none' ) {
+        $content = $self->_Value('Content', decode_utf8 => 0);
+    } elsif ( $self->ContentEncoding eq 'base64' ) {
+        $content = MIME::Base64::decode_base64($self->_Value('Content', decode_utf8 => 0));
+    } elsif ( $self->ContentEncoding eq 'quoted-printable' ) {
+        $content = MIME::QuotedPrint::decode($self->_Value('Content', decode_utf8 => 0));
+    } else {
+        return( $self->loc("Unknown ContentEncoding [_1]", $self->ContentEncoding));
+    }
+
+    # Turn *off* the SvUTF8 bits here so decode_utf8 and from_to below can work.
+    local $@;
+    Encode::_utf8_off($content);
 
-# {{{ sub OriginalEncoding
+    if (!$enc || $enc eq '' ||  $enc eq 'utf8' || $enc eq 'utf-8') {
+        # If we somehow fail to do the decode, at least push out the raw bits
+        eval { return( Encode::decode_utf8($content)) } || return ($content);
+    }
+
+    eval { Encode::from_to($content, 'utf8' => $enc) } if $enc;
+    if ($@) {
+        $RT::Logger->error("Could not convert attachment from assumed utf8 to '$enc' :".$@);
+    }
+    return $content;
+}
 
 =head2 OriginalEncoding
 
@@ -319,35 +328,34 @@ Returns the attachment's original encoding.
 =cut
 
 sub OriginalEncoding {
-  my $self = shift;
-  return $self->GetHeader('X-RT-Original-Encoding');
+    my $self = shift;
+    return $self->GetHeader('X-RT-Original-Encoding');
 }
 
-# }}}
-
-# {{{ sub Children
-
-=head2 Children
+=head2 ContentLength
 
-  Returns an RT::Attachments object which is preloaded with all Attachments objects with this Attachment\'s Id as their 'Parent'
+Returns length of L in bytes.
 
 =cut
 
-sub Children {
+sub ContentLength {
     my $self = shift;
-    
-    my $kids = new RT::Attachments($self->CurrentUser);
-    $kids->ChildrenOf($self->Id);
-    return($kids);
-}
-
-# }}}
 
-# {{{ UTILITIES
+    return undef unless $self->TransactionObj->CurrentUserCanSee;
 
-# {{{ sub Quote 
+    my $len = $self->GetHeader('Content-Length');
+    unless ( defined $len ) {
+        use bytes;
+        no warnings 'uninitialized';
+        $len = length($self->Content);
+        $self->SetHeader('Content-Length' => $len);
+    }
+    return $len;
+}
 
+=head2 Quote
 
+=cut
 
 sub Quote {
     my $self=shift;
@@ -399,9 +407,64 @@ sub Quote {
 
     return (\$body, $max);
 }
-# }}}
 
-# {{{ sub NiceHeaders - pulls out only the most relevant headers
+=head2 ContentAsMIME
+
+Returns MIME entity built from this attachment.
+
+=cut
+
+sub ContentAsMIME {
+    my $self = shift;
+
+    my $entity = new MIME::Entity;
+    foreach my $header ($self->SplitHeaders) {
+        my ($h_key, $h_val) = split /:/, $header, 2;
+        $entity->head->add( $h_key, RT::Interface::Email::EncodeToMIME( String => $h_val ) );
+    }
+
+    use MIME::Body;
+    $entity->bodyhandle(
+        MIME::Body::Scalar->new( $self->OriginalContent )
+    );
+
+    return $entity;
+}
+
+
+=head2 Addresses
+
+Returns a hashref of all addresses related to this attachment.
+The keys of the hash are C, C, C, C, C
+and C. The values are references to lists of
+L objects.
+
+=cut
+
+sub Addresses {
+    my $self = shift;
+
+    my %data = ();
+    my $current_user_address = lc $self->CurrentUser->EmailAddress;
+    my $correspond = lc $self->TransactionObj->TicketObj->QueueObj->CorrespondAddress;
+    my $comment = lc $self->TransactionObj->TicketObj->QueueObj->CommentAddress;
+    foreach my $hdr (qw(From To Cc Bcc RT-Send-Cc RT-Send-Bcc)) {
+        my @Addresses;
+        my $line      = $self->GetHeader($hdr);
+        
+        foreach my $AddrObj ( Email::Address->parse( $line )) {
+            my $address = $AddrObj->address;
+            $address = lc RT::User->CanonicalizeEmailAddress($address);
+            next if ( $current_user_address eq $address );
+            next if ( $comment              eq $address );
+            next if ( $correspond           eq $address );
+            next if ( RT::EmailParser->IsRTAddress($address) );
+            push @Addresses, $AddrObj ;
+        }
+		$data{$hdr} = \@Addresses;
+    }
+	return \%data;
+}
 
 =head2 NiceHeaders
 
@@ -420,34 +483,37 @@ sub NiceHeaders {
     }
     return $hdrs;
 }
-# }}}
-
-# {{{ sub Headers
 
 =head2 Headers
 
 Returns this object's headers as a string.  This method specifically
 removes the RT-Send-Bcc: header, so as to never reveal to whom RT sent a Bcc.
 We need to record the RT-Send-Cc and RT-Send-Bcc values so that we can actually send
-out mail. (The mailing rules are separated from the ticket update code by
-an abstraction barrier that makes it impossible to pass this data directly
+out mail. The mailing rules are separated from the ticket update code by
+an abstraction barrier that makes it impossible to pass this data directly.
 
 =cut
 
 sub Headers {
-    my $self = shift;
-    my $hdrs="";
-    my @headers =  grep { !/^RT-Send-Bcc/i } $self->_SplitHeaders;
-    return join("\n",@headers);
-
+    return join("\n", $_[0]->SplitHeaders);
 }
 
+=head2 EncodedHeaders
 
-# }}}
+Takes encoding as argument and returns the attachment's headers as octets in encoded
+using the encoding.
 
-# {{{ sub GetHeader
+This is not protection using quoted printable or base64 encoding.
 
-=head2 GetHeader ( 'Tag')
+=cut
+
+sub EncodedHeaders {
+    my $self = shift;
+    my $encoding = shift || 'utf8';
+    return Encode::encode( $encoding, $self->Headers );
+}
+
+=head2 GetHeader $TAG
 
 Returns the value of the header Tag as a string. This bypasses the weeding out
 done in Headers() above.
@@ -458,17 +524,52 @@ sub GetHeader {
     my $self = shift;
     my $tag = shift;
     foreach my $line ($self->_SplitHeaders) {
-        if ($line =~ /^\Q$tag\E:\s+(.*)$/si) { #if we find the header, return its value
-            return ($1);
-        }
+        next unless $line =~ /^\Q$tag\E:\s+(.*)$/si;
+
+        #if we find the header, return its value
+        return ($1);
     }
     
     # we found no header. return an empty string
     return undef;
 }
-# }}}
 
-# {{{ sub SetHeader
+=head2 DelHeader $TAG
+
+Delete a field from the attachment's headers.
+
+=cut
+
+sub DelHeader {
+    my $self = shift;
+    my $tag = shift;
+
+    my $newheader = '';
+    foreach my $line ($self->_SplitHeaders) {
+        next if $line =~ /^\Q$tag\E:\s+(.*)$/is;
+	$newheader .= "$line\n";
+    }
+    return $self->__Set( Field => 'Headers', Value => $newheader);
+}
+
+=head2 AddHeader $TAG, $VALUE, ...
+
+Add one or many fields to the attachment's headers.
+
+=cut
+
+sub AddHeader {
+    my $self = shift;
+
+    my $newheader = $self->__Value( 'Headers' );
+    while ( my ($tag, $value) = splice @_, 0, 2 ) {
+        $value = '' unless defined $value;
+        $value =~ s/\s+$//s;
+        $value =~ s/\r+\n/\n /g;
+        $newheader .= "$tag: $value\n";
+    }
+    return $self->__Set( Field => 'Headers', Value => $newheader);
+}
 
 =head2 SetHeader ( 'Tag', 'Value' )
 
@@ -479,8 +580,8 @@ Replace or add a Header to the attachment's headers.
 sub SetHeader {
     my $self = shift;
     my $tag = shift;
-    my $newheader = '';
 
+    my $newheader = '';
     foreach my $line ($self->_SplitHeaders) {
         if (defined $tag and $line =~ /^\Q$tag\E:\s+(.*)$/i) {
 	    $newheader .= "$tag: $_[0]\n";
@@ -494,80 +595,26 @@ sub SetHeader {
     $newheader .= "$tag: $_[0]\n" if defined $tag;
     $self->__Set( Field => 'Headers', Value => $newheader);
 }
-# }}}
 
-# {{{ sub _Value 
+=head2 SplitHeaders
 
-=head2 _Value
+Returns an array of this attachment object's headers, with one header 
+per array entry. Multiple lines are folded.
 
-Takes the name of a table column.
-Returns its value as a string, if the user passes an ACL check
+B returns C field.
 
 =cut
 
-sub _Value {
-
-    my $self  = shift;
-    my $field = shift;
-
-    #if the field is public, return it.
-    if ( $self->_Accessible( $field, 'public' ) ) {
-        return ( $self->__Value( $field, @_ ) );
-    }
-
-    #If it's a comment, we need to be extra special careful
-    elsif ( $self->TransactionObj->Type =~ /^Comment/ ) {
-        if ( $self->TransactionObj->CurrentUserHasRight('ShowTicketComments') )
-        {
-            return ( $self->__Value( $field, @_ ) );
-        }
-    }
-    elsif ( $self->TransactionObj->CurrentUserHasRight('ShowTicket') ) {
-        return ( $self->__Value( $field, @_ ) );
-    }
-
-    #if they ain't got rights to see, don't let em
-    else {
-        return (undef);
-    }
-
+sub SplitHeaders {
+    my $self = shift;
+    return (grep !/^RT-Send-Bcc/i, $self->_SplitHeaders(@_) );
 }
 
-# }}}
-
 =head2 _SplitHeaders
 
 Returns an array of this attachment object's headers, with one header 
 per array entry. multiple lines are folded.
 
-=begin testing
-
-my $test1 = "From: jesse";
-my @headers = RT::Attachment->_SplitHeaders($test1);
-is ($#headers, 0, $test1 );
-
-my $test2 = qq{From: jesse
-To: bobby
-Subject: foo
-};
-
-@headers = RT::Attachment->_SplitHeaders($test2);
-is ($#headers, 2, "testing a bunch of singline multiple headers" );
-
-
-my $test3 = qq{From: jesse
-To: bobby,
- Suzie,
-    Sally,
-    Joey: bizzy,
-Subject: foo
-};
-
-@headers = RT::Attachment->_SplitHeaders($test3);
-is ($#headers, 2, "testing a bunch of singline multiple headers" );
-
-
-=end testing
 
 =cut
 
@@ -583,35 +630,130 @@ sub _SplitHeaders {
 }
 
 
-sub ContentLength {
+sub Encrypt {
     my $self = shift;
 
-    unless ( (($self->TransactionObj->CurrentUserHasRight('ShowTicketComments')) and
-	     ($self->TransactionObj->Type eq 'Comment') )  or
-	    ($self->TransactionObj->CurrentUserHasRight('ShowTicket'))) {
-	return undef;
+    my $txn = $self->TransactionObj;
+    return (0, $self->loc('Permission Denied')) unless $txn->CurrentUserCanSee;
+    return (0, $self->loc('Permission Denied'))
+        unless $txn->TicketObj->CurrentUserHasRight('ModifyTicket');
+    return (0, $self->loc('GnuPG integration is disabled'))
+        unless RT->Config->Get('GnuPG')->{'Enable'};
+    return (0, $self->loc('Attachments encryption is disabled'))
+        unless RT->Config->Get('GnuPG')->{'AllowEncryptDataInDB'};
+
+    require RT::Crypt::GnuPG;
+
+    my $type = $self->ContentType;
+    if ( $type =~ /^x-application-rt\/gpg-encrypted/i ) {
+        return (1, $self->loc('Already encrypted'));
+    } elsif ( $type =~ /^multipart\//i ) {
+        return (1, $self->loc('No need to encrypt'));
+    } else {
+        $type = qq{x-application-rt\/gpg-encrypted; original-type="$type"};
     }
 
-    if (my $len = $self->GetHeader('Content-Length')) {
-	return $len;
+    my $queue = $txn->TicketObj->QueueObj;
+    my $encrypt_for;
+    foreach my $address ( grep $_,
+        $queue->CorrespondAddress,
+        $queue->CommentAddress,
+        RT->Config->Get('CorrespondAddress'),
+        RT->Config->Get('CommentAddress'),
+    ) {
+        my %res = RT::Crypt::GnuPG::GetKeysInfo( $address, 'private' );
+        next if $res{'exit_code'} || !$res{'info'};
+        %res = RT::Crypt::GnuPG::GetKeysForEncryption( $address );
+        next if $res{'exit_code'} || !$res{'info'};
+        $encrypt_for = $address;
+    }
+    unless ( $encrypt_for ) {
+        return (0, $self->loc('No key suitable for encryption'));
     }
 
-    {
-	use bytes;
-	my $len = length($self->Content);
-	$self->SetHeader('Content-Length' => $len);
-	return $len;
+    $self->__Set( Field => 'ContentType', Value => $type );
+    $self->SetHeader( 'Content-Type' => $type );
+
+    my $content = $self->Content;
+    my %res = RT::Crypt::GnuPG::SignEncryptContent(
+        Content => \$content,
+        Sign => 0,
+        Encrypt => 1,
+        Recipients => [ $encrypt_for ],
+    );
+    if ( $res{'exit_code'} ) {
+        return (0, $self->loc('GnuPG error. Contact with administrator'));
     }
+
+    my ($status, $msg) = $self->__Set( Field => 'Content', Value => $content );
+    unless ( $status ) {
+        return ($status, $self->loc("Couldn't replace content with encrypted data: [_1]", $msg));
+    }
+    return (1, $self->loc('Successfuly encrypted data'));
 }
 
-# }}}
+sub Decrypt {
+    my $self = shift;
+
+    my $txn = $self->TransactionObj;
+    return (0, $self->loc('Permission Denied')) unless $txn->CurrentUserCanSee;
+    return (0, $self->loc('Permission Denied'))
+        unless $txn->TicketObj->CurrentUserHasRight('ModifyTicket');
+    return (0, $self->loc('GnuPG integration is disabled'))
+        unless RT->Config->Get('GnuPG')->{'Enable'};
+
+    require RT::Crypt::GnuPG;
+
+    my $type = $self->ContentType;
+    if ( $type =~ /^x-application-rt\/gpg-encrypted/i ) {
+        ($type) = ($type =~ /original-type="(.*)"/i);
+        $type ||= 'application/octeat-stream';
+    } else {
+        return (1, $self->loc('Is not encrypted'));
+    }
+    $self->__Set( Field => 'ContentType', Value => $type );
+    $self->SetHeader( 'Content-Type' => $type );
+
+    my $content = $self->Content;
+    my %res = RT::Crypt::GnuPG::DecryptContent( Content => \$content, );
+    if ( $res{'exit_code'} ) {
+        return (0, $self->loc('GnuPG error. Contact with administrator'));
+    }
+
+    my ($status, $msg) = $self->__Set( Field => 'Content', Value => $content );
+    unless ( $status ) {
+        return ($status, $self->loc("Couldn't replace content with decrypted data: [_1]", $msg));
+    }
+    return (1, $self->loc('Successfuly decrypted data'));
+}
+
+=head2 _Value
+
+Takes the name of a table column.
+Returns its value as a string, if the user passes an ACL check
+
+=cut
+
+sub _Value {
+    my $self  = shift;
+    my $field = shift;
+
+    #if the field is public, return it.
+    if ( $self->_Accessible( $field, 'public' ) ) {
+        return ( $self->__Value( $field, @_ ) );
+    }
+
+    return undef unless $self->TransactionObj->CurrentUserCanSee;
+    return $self->__Value( $field, @_ );
+}
 
-# Transactions don't change. by adding this cache congif directiove, we don't lose pathalogically on long tickets.
+# Transactions don't change. by adding this cache congif directiove,
+# we don't lose pathalogically on long tickets.
 sub _CacheConfig {
     {
-        'cache_p'         => 1,
-          'fast_update_p' => 1,
-          'cache_for_sec' => 180,
+        'cache_p'       => 1,
+        'fast_update_p' => 1,
+        'cache_for_sec' => 180,
     }
 }
 
diff --git a/rt/lib/RT/Attachments.pm b/rt/lib/RT/Attachments.pm
index 44115492f..416cde6ba 100755
--- a/rt/lib/RT/Attachments.pm
+++ b/rt/lib/RT/Attachments.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,6 +45,7 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by )
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
diff --git a/rt/lib/RT/Attachments_Overlay.pm b/rt/lib/RT/Attachments_Overlay.pm
index cedceac52..83cc96d21 100644
--- a/rt/lib/RT/Attachments_Overlay.pm
+++ b/rt/lib/RT/Attachments_Overlay.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,6 +45,7 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 =head1 NAME
 
   RT::Attachments - a collection of RT::Attachment objects
@@ -62,11 +63,6 @@ should only be accessed through exported APIs in Ticket, Queue and other similar
 =head1 METHODS
 
 
-=begin testing
-
-ok (require RT::Attachments);
-
-=end testing
 
 =cut
 
@@ -76,20 +72,48 @@ package RT::Attachments;
 use strict;
 no warnings qw(redefine);
 
-# {{{ sub _Init  
+use RT::Attachment;
+
 sub _Init   {
-  my $self = shift;
- 
-  $self->{'table'} = "Attachments";
-  $self->{'primary_key'} = "id";
-  $self->OrderBy ( FIELD => 'id',
-                   ORDER => 'ASC');
-  return ( $self->SUPER::_Init(@_));
+    my $self = shift;
+    $self->{'table'} = "Attachments";
+    $self->{'primary_key'} = "id";
+    $self->OrderBy(
+        FIELD => 'id',
+        ORDER => 'ASC',
+    );
+    return $self->SUPER::_Init( @_ );
 }
-# }}}
 
+sub CleanSlate {
+    my $self = shift;
+    delete $self->{_sql_transaction_alias};
+    return $self->SUPER::CleanSlate( @_ );
+}
+
+
+=head2 TransactionAlias
 
-# {{{ sub ContentType
+Returns alias for transactions table with applied join condition.
+Always return the same alias, so if you want to build some complex
+or recursive joining then you have to create new alias youself.
+
+=cut
+
+sub TransactionAlias {
+    my $self = shift;
+    return $self->{'_sql_transaction_alias'}
+        if $self->{'_sql_transaction_alias'};
+
+    my $res = $self->NewAlias('Transactions');
+    $self->Limit(
+        ENTRYAGGREGATOR => 'AND',
+        FIELD           => 'TransactionId',
+        VALUE           => $res . '.id',
+        QUOTEVALUE      => 0,
+    );
+    return $self->{'_sql_transaction_alias'} = $res;
+}
 
 =head2 ContentType (VALUE => 'text/plain', ENTRYAGGREGATOR => 'OR', OPERATOR => '=' ) 
 
@@ -99,20 +123,16 @@ Limit result set to attachments of ContentType 'TYPE'...
 
 
 sub ContentType  {
-  my $self = shift;
-  my %args = ( VALUE => 'text/plain',
-	       OPERATOR => '=',
-	       ENTRYAGGREGATOR => 'OR',
-	       @_);
-
-  $self->Limit ( FIELD => 'ContentType',
-		 VALUE => $args{'VALUE'},
-		 OPERATOR => $args{'OPERATOR'},
-		 ENTRYAGGREGATOR => $args{'ENTRYAGGREGATOR'});
+    my $self = shift;
+    my %args = (
+        VALUE           => 'text/plain',
+	    OPERATOR        => '=',
+	    ENTRYAGGREGATOR => 'OR',
+	    @_
+    );
+
+    return $self->Limit ( %args, FIELD => 'ContentType' );
 }
-# }}}
-
-# {{{ sub ChildrenOf 
 
 =head2 ChildrenOf ID
 
@@ -122,52 +142,101 @@ Limit result set to children of Attachment ID
 
 
 sub ChildrenOf  {
-  my $self = shift;
-  my $attachment = shift;
-  $self->Limit ( FIELD => 'Parent',
-		 VALUE => $attachment);
+    my $self = shift;
+    my $attachment = shift;
+    return $self->Limit(
+        FIELD => 'Parent',
+        VALUE => $attachment
+    );
+}
+
+=head2 LimitNotEmpty
+
+Limit result set to attachments with not empty content.
+
+=cut
+
+sub LimitNotEmpty {
+    my $self = shift;
+    $self->Limit(
+        ENTRYAGGREGATOR => 'AND',
+        FIELD           => 'Content',
+        OPERATOR        => 'IS NOT',
+        VALUE           => 'NULL',
+        QUOTEVALUE      => 0,
+    );
+
+    # http://rt3.fsck.com/Ticket/Display.html?id=12483
+    if ( RT->Config->Get('DatabaseType') ne 'Oracle' ) {
+        $self->Limit(
+            ENTRYAGGREGATOR => 'AND',
+            FIELD           => 'Content',
+            OPERATOR        => '!=',
+            VALUE           => '',
+        );
+    }
+    return;
+}
+
+=head2 LimitByTicket $ticket_id
+
+Limit result set to attachments of a ticket.
+
+=cut
+
+sub LimitByTicket {
+    my $self = shift;
+    my $tid = shift;
+
+    my $transactions = $self->TransactionAlias;
+    $self->Limit(
+        ENTRYAGGREGATOR => 'AND',
+        ALIAS           => $transactions,
+        FIELD           => 'ObjectType',
+        VALUE           => 'RT::Ticket',
+    );
+
+    my $tickets = $self->NewAlias('Tickets');
+    $self->Limit(
+        ENTRYAGGREGATOR => 'AND',
+        ALIAS           => $tickets,
+        FIELD           => 'id',
+        VALUE           => $transactions . '.ObjectId',
+        QUOTEVALUE      => 0,
+    );
+    $self->Limit(
+        ENTRYAGGREGATOR => 'AND',
+        ALIAS           => $tickets,
+        FIELD           => 'EffectiveId',
+        VALUE           => $tid,
+    );
+    return;
 }
-# }}}
 
 # {{{ sub NewItem 
 sub NewItem  {
   my $self = shift;
-
-  use RT::Attachment;
-  my $item = new RT::Attachment($self->CurrentUser);
-  return($item);
+  return RT::Attachment->new( $self->CurrentUser );
 }
 # }}}
 
 # {{{ sub Next
 sub Next {
     my $self = shift;
- 	
-    my $Attachment = $self->SUPER::Next();
-    if ((defined($Attachment)) and (ref($Attachment))) {
-	if ($Attachment->TransactionObj->__Value('Type') =~ /^Comment/ && 
-	    $Attachment->TransactionObj->TicketObj->CurrentUserHasRight('ShowTicketComments')) {
-	    return($Attachment);
-	} elsif ($Attachment->TransactionObj->__Value('Type') !~ /^Comment/ && 
-		 $Attachment->TransactionObj->TicketObj->CurrentUserHasRight('ShowTicket')) {
-	    return($Attachment);
-	}
-
-	#If the user doesn't have the right to show this ticket
-	else {	
-	    return($self->Next());
-	}
+
+    my $Attachment = $self->SUPER::Next;
+    return $Attachment unless $Attachment;
+
+    my $txn = $Attachment->TransactionObj;
+    if ( $txn->__Value('Type') eq 'Comment' ) {
+        return $Attachment if $txn->CurrentUserHasRight('ShowTicketComments');
+    } elsif ( $txn->CurrentUserHasRight('ShowTicket') ) {
+        return $Attachment;
     }
 
-    #if there never was any ticket
-    else {
-	return(undef);
-    }	
+    # If the user doesn't have the right to show this ticket
+    return $self->Next;
 }
 # }}}
 
-  1;
-
-
-
-
+1;
diff --git a/rt/lib/RT/Attribute.pm b/rt/lib/RT/Attribute.pm
index e513c287c..dcdfd7f45 100644
--- a/rt/lib/RT/Attribute.pm
+++ b/rt/lib/RT/Attribute.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,6 +45,7 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by )
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
diff --git a/rt/lib/RT/Attribute_Overlay.pm b/rt/lib/RT/Attribute_Overlay.pm
index 72071f562..4d201da7a 100644
--- a/rt/lib/RT/Attribute_Overlay.pm
+++ b/rt/lib/RT/Attribute_Overlay.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,6 +45,7 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 package RT::Attribute;
 
 use strict;
@@ -271,55 +272,23 @@ sub _SerializeContent {
 sub SetContent {
     my $self = shift;
     my $content = shift;
-    
+
     # Call __Value to avoid ACL check.
-    if ($self->__Value('ContentType') eq 'storable') {
-    # We eval the serialization because it will lose on a coderef.
-    eval  {$content = $self->_SerializeContent($content); };
-    if ($@) {
-        $RT::Logger->error("For some reason, content couldn't be frozen");
-        return(0, $@);
-    }
+    if ( $self->__Value('ContentType') eq 'storable' ) {
+        # We eval the serialization because it will lose on a coderef.
+        $content = eval { $self->_SerializeContent($content) };
+        if ($@) {
+            $RT::Logger->error("Content couldn't be frozen: $@");
+            return(0, "Content couldn't be frozen");
+        }
     }
-    return ($self->SUPER::SetContent($content));
+    return $self->SUPER::SetContent( $content );
 }
 
 =head2 SubValue KEY
 
 Returns the subvalue for $key.
 
-=begin testing
-
-my $user = $RT::SystemUser;
-my ($id, $msg) =  $user->AddAttribute(Name => 'SavedSearch', Content => { Query => 'Foo'} );
-ok ($id, $msg);
-my $attr = RT::Attribute->new($RT::SystemUser);
-$attr->Load($id);
-ok($attr->Name eq 'SavedSearch');
-$attr->SetSubValues( Format => 'baz');
-
-my $format = $attr->SubValue('Format');
-is ($format , 'baz');
-
-$attr->SetSubValues( Format => 'bar');
-$format = $attr->SubValue('Format');
-is ($format , 'bar');
-
-$attr->DeleteAllSubValues();
-$format = $attr->SubValue('Format');
-is ($format, undef);
-
-$attr->SetSubValues(Format => 'This is a format');
-
-my $attr2 = RT::Attribute->new($RT::SystemUser);
-$attr2->Load($id);
-is ($attr2->SubValue('Format'), 'This is a format');
-$attr2->Delete;
-my $attr3 = RT::Attribute->new($RT::SystemUser);
-my ($id) = $attr3->Load($id);
-is ($id, 0);
-
-=end testing
 
 =cut
 
diff --git a/rt/lib/RT/Attributes.pm b/rt/lib/RT/Attributes.pm
index 12f659fa0..b96b3e26a 100644
--- a/rt/lib/RT/Attributes.pm
+++ b/rt/lib/RT/Attributes.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,6 +45,7 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by )
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
diff --git a/rt/lib/RT/Attributes_Overlay.pm b/rt/lib/RT/Attributes_Overlay.pm
index e0c2f5a04..ebe8c4cf1 100644
--- a/rt/lib/RT/Attributes_Overlay.pm
+++ b/rt/lib/RT/Attributes_Overlay.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,14 +45,15 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 =head1 NAME
 
-  RT::Attributes - collection of RT::Attribute objects
+RT::Attributes - collection of RT::Attribute objects
 
 =head1 SYNOPSIS
 
-  use RT::Attributes;
-my $Attributes = new RT::Attributes($CurrentUser);
+    use RT::Attributes;
+    my $Attributes = new RT::Attributes($CurrentUser);
 
 =head1 DESCRIPTION
 
@@ -154,22 +155,28 @@ the matching name.
 
 sub DeleteEntry {
     my $self = shift;
-    my %args = ( Name => undef,
-                 Content => undef,
-                 id => undef,
-                 @_);
+    my %args = (
+        Name    => undef,
+        Content => undef,
+        id      => undef,
+        @_
+    );
     my $found = 0;
-    foreach my $attr ($self->Named($args{'Name'})){ 
-      if ((!defined $args{'id'} and !defined $args{'Content'})
-          or (defined $args{'id'} and $attr->id eq $args{'id'})
-          or (defined $args{'Content'} and $attr->Content eq $args{'Content'})) {
-        my ($id, $msg) = $attr->Delete;
-        return ($id, $msg) unless $id;
-        $found = 1;
-      }
+    foreach my $attr ( $self->Named( $args{'Name'} ) ) {
+        if ( ( !defined $args{'id'} and !defined $args{'Content'} )
+             or ( defined $args{'id'} and $attr->id eq $args{'id'} )
+             or ( defined $args{'Content'} and $attr->Content eq $args{'Content'} ) )
+        {
+            my ($id, $msg) = $attr->Delete;
+            return ($id, $msg) unless $id;
+            $found = 1;
+        }
     }
     return (0, "No entry found") unless $found;
-    $self->_DoSearch();
+    $self->RedoSearch;
+    # XXX: above string must work but because of bug in DBIx::SB it doesn't,
+    # to reproduce delete string below and run t/api/attribute-tests.t
+    $self->_DoSearch;
     return (1, $self->loc('Attribute Deleted'));
 }
 
diff --git a/rt/lib/RT/Base.pm b/rt/lib/RT/Base.pm
index 9a3ab6964..f276aa24e 100644
--- a/rt/lib/RT/Base.pm
+++ b/rt/lib/RT/Base.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,9 +45,10 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 package RT::Base;
-use Carp;
-use Scalar::Util;
+use Carp ();
+use Scalar::Util ();
 
 use strict;
 use vars qw(@EXPORT);
@@ -101,8 +102,8 @@ sub CurrentUser {
     unless ( ref $self->{'user'} && $self->{'user'}->isa('RT::CurrentUser') ) {
         my $msg = "$self was created without a CurrentUser."
             ." Any RT object which is subclass of RT::Base must be created"
-            ." with a RT::CurrentUser or a RT::User obejct as the first argument.";
-        $msg .= "\n". Carp::cluck() if @_;
+            ." with a RT::CurrentUser or a RT::User object as the first argument.";
+        $msg .= "\n". Carp::longmess() if @_;
 
         $RT::Logger->error( $msg );
         return $self->{'user'} = undef;
@@ -146,7 +147,6 @@ sub loc {
         return $user->loc(@_);
     }
     else {
-        use Carp;
         Carp::confess("No currentuser");
         return ("Critical error:$self has no CurrentUser", $self);
     }
@@ -158,7 +158,6 @@ sub loc_fuzzy {
         return $user->loc_fuzzy(@_);
     }
     else {
-        use Carp;
         Carp::confess("No currentuser");
         return ("Critical error:$self has no CurrentUser", $self);
     }
diff --git a/rt/lib/RT/CachedGroupMember.pm b/rt/lib/RT/CachedGroupMember.pm
index 933c13bf9..1c9188f62 100644
--- a/rt/lib/RT/CachedGroupMember.pm
+++ b/rt/lib/RT/CachedGroupMember.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,6 +45,7 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by )
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
diff --git a/rt/lib/RT/CachedGroupMember_Overlay.pm b/rt/lib/RT/CachedGroupMember_Overlay.pm
index ffbbc8daf..a292afb77 100644
--- a/rt/lib/RT/CachedGroupMember_Overlay.pm
+++ b/rt/lib/RT/CachedGroupMember_Overlay.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,6 +45,7 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 package RT::CachedGroupMember;
 
 use strict;
@@ -148,6 +149,8 @@ sub Create {
         }
     }
 
+    return $id if $args{'Member'}->id == $args{'Group'}->id;
+
     if ( $args{'Member'}->IsGroup() ) {
         my $GroupMembers = $args{'Member'}->Object->MembersObj();
         while ( my $member = $GroupMembers->Next() ) {
@@ -215,7 +218,7 @@ sub Delete {
 
     # Unless $self->GroupObj still has the member recursively $self->MemberObj
     # (Since we deleted the database row above, $self no longer counts)
-    unless ( $self->GroupObj->Object->HasMemberRecursively( $self->MemberObj ) ) {
+    unless ( $self->GroupObj->Object->HasMemberRecursively( $self->MemberId ) ) {
 
 
         #   Find all ACEs granted to $self->GroupId
@@ -260,7 +263,7 @@ sub SetDisabled {
     my $val = shift;
  
     # if it's already disabled, we're good.
-    return {1} if ($self->__Value('Disabled') == $val);
+    return (1) if ( $self->__Value('Disabled') == $val);
     my $err = $self->SUPER::SetDisabled($val);
     my ($retval, $msg) = $err->as_array();
     unless ($retval) {
@@ -286,7 +289,7 @@ sub SetDisabled {
 
     # Unless $self->GroupObj still has the member recursively $self->MemberObj
     # (Since we SetDisabledd the database row above, $self no longer counts)
-    unless ( $self->GroupObj->Object->HasMemberRecursively( $self->MemberObj ) ) {
+    unless ( $self->GroupObj->Object->HasMemberRecursively( $self->MemberId ) ) {
         #   Find all ACEs granted to $self->GroupId
         my $acl = RT::ACL->new($RT::SystemUser);
         $acl->LimitToPrincipal( Id => $self->GroupId );
diff --git a/rt/lib/RT/CachedGroupMembers.pm b/rt/lib/RT/CachedGroupMembers.pm
index a7448d1d6..992856c36 100644
--- a/rt/lib/RT/CachedGroupMembers.pm
+++ b/rt/lib/RT/CachedGroupMembers.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,6 +45,7 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by )
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
diff --git a/rt/lib/RT/CachedGroupMembers_Overlay.pm b/rt/lib/RT/CachedGroupMembers_Overlay.pm
index c3b4fdd0d..9331553d3 100644
--- a/rt/lib/RT/CachedGroupMembers_Overlay.pm
+++ b/rt/lib/RT/CachedGroupMembers_Overlay.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,6 +45,7 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 =head1 NAME
 
   RT::CachedGroupMembers - a collection of RT::GroupMember objects
@@ -59,11 +60,6 @@
 =head1 METHODS
 
 
-=begin testing
-
-ok (require RT::CachedGroupMembers);
-
-=end testing
 
 =cut
 
diff --git a/rt/lib/RT/Condition.pm b/rt/lib/RT/Condition.pm
new file mode 100755
index 000000000..be7c4c56d
--- /dev/null
+++ b/rt/lib/RT/Condition.pm
@@ -0,0 +1,233 @@
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
+=head1 NAME
+
+  RT::Condition - generic baseclass for scrip condition;
+
+=head1 SYNOPSIS
+
+    use RT::Condition;
+    my $foo = RT::Condition->new( 
+		TransactionObj => $tr, 
+		TicketObj => $ti, 
+		ScripObj => $scr, 
+		Argument => $arg, 
+		Type => $type);
+
+    if ($foo->IsApplicable) {
+ 	   # do something
+    }
+
+
+=head1 DESCRIPTION
+
+
+=head1 METHODS
+
+
+
+
+=cut
+
+package RT::Condition;
+
+use strict;
+use warnings;
+
+use base qw/RT::Base/;
+
+# {{{ sub new 
+sub new  {
+  my $proto = shift;
+  my $class = ref($proto) || $proto;
+  my $self  = {};
+  bless ($self, $class);
+  $self->_Init(@_);
+  return $self;
+}
+# }}}
+
+# {{{ sub _Init 
+sub _Init  {
+  my $self = shift;
+  my %args = ( TransactionObj => undef,
+	       TicketObj => undef,
+	       ScripObj => undef,
+	       TemplateObj => undef,
+	       Argument => undef,
+	       ApplicableTransTypes => undef,
+           CurrentUser => undef,
+	       @_ );
+  
+  $self->{'Argument'} = $args{'Argument'};
+  $self->{'ScripObj'} = $args{'ScripObj'};
+  $self->{'TicketObj'} = $args{'TicketObj'};
+  $self->{'TransactionObj'} = $args{'TransactionObj'};
+  $self->{'ApplicableTransTypes'} = $args{'ApplicableTransTypes'};
+  $self->CurrentUser($args{'CurrentUser'});
+}
+# }}}
+
+# Access Scripwide data
+
+# {{{ sub Argument 
+
+=head2 Argument
+
+Return the optional argument associated with this ScripCondition
+
+=cut
+
+sub Argument  {
+  my $self = shift;
+  return($self->{'Argument'});
+}
+# }}}
+
+# {{{ sub TicketObj
+
+=head2 TicketObj
+
+Return the ticket object we're talking about
+
+=cut
+
+sub TicketObj  {
+  my $self = shift;
+  return($self->{'TicketObj'});
+}
+# }}}
+
+# {{{ sub ScripObj
+
+=head2 ScripObj
+
+Return the Scrip object we're talking about
+
+=cut
+
+sub ScripObj  {
+  my $self = shift;
+  return($self->{'ScripObj'});
+}
+# }}}
+# {{{ sub TransactionObj
+
+=head2 TransactionObj
+
+Return the transaction object we're talking about
+
+=cut
+
+sub TransactionObj  {
+  my $self = shift;
+  return($self->{'TransactionObj'});
+}
+# }}}
+
+# {{{ sub Type
+
+=head2 Type 
+
+
+
+=cut
+
+sub ApplicableTransTypes  {
+  my $self = shift;
+  return($self->{'ApplicableTransTypes'});
+}
+# }}}
+
+
+# Scrip methods
+
+
+#What does this type of Action does
+
+# {{{ sub Describe 
+sub Describe  {
+  my $self = shift;
+  return ($self->loc("No description for [_1]", ref $self));
+}
+# }}}
+
+
+#Parse the templates, get things ready to go.
+
+#If this rule applies to this transaction, return true.
+
+# {{{ sub IsApplicable 
+sub IsApplicable  {
+  my $self = shift;
+  return(undef);
+}
+# }}}
+
+# {{{ sub DESTROY
+sub DESTROY {
+    my $self = shift;
+
+    # We need to clean up all the references that might maybe get
+    # oddly circular
+    $self->{'TemplateObj'} =undef
+    $self->{'TicketObj'} = undef;
+    $self->{'TransactionObj'} = undef;
+    $self->{'ScripObj'} = undef;
+     
+}
+
+# }}}
+
+eval "require RT::Condition_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition_Vendor.pm});
+eval "require RT::Condition_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition_Local.pm});
+
+1;
diff --git a/rt/lib/RT/Condition/AnyTransaction.pm b/rt/lib/RT/Condition/AnyTransaction.pm
index 9b1bb8cfb..1b90aa53e 100644
--- a/rt/lib/RT/Condition/AnyTransaction.pm
+++ b/rt/lib/RT/Condition/AnyTransaction.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,14 +45,11 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
- 
 
 package RT::Condition::AnyTransaction;
-require RT::Condition::Generic;
+use base 'RT::Condition';
 
 use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Condition::Generic);
 
 
 =head2 IsApplicable
diff --git a/rt/lib/RT/Condition/BeforeDue.pm b/rt/lib/RT/Condition/BeforeDue.pm
index c42e07b26..b392f38b7 100644
--- a/rt/lib/RT/Condition/BeforeDue.pm
+++ b/rt/lib/RT/Condition/BeforeDue.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,15 +45,13 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 package RT::Condition::BeforeDue;
-require RT::Condition::Generic;
+use base 'RT::Condition';
 
 use RT::Date;
 
 use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Condition::Generic);
-
 
 sub IsApplicable {
     my $self = shift;
diff --git a/rt/lib/RT/Condition/CloseTicket.pm b/rt/lib/RT/Condition/CloseTicket.pm
new file mode 100644
index 000000000..ded04482f
--- /dev/null
+++ b/rt/lib/RT/Condition/CloseTicket.pm
@@ -0,0 +1,84 @@
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
+package RT::Condition::CloseTicket;
+
+use strict;
+use warnings;
+
+use base 'RT::Condition';
+
+
+=head2 IsApplicable
+
+If the ticket was closed, ie status was changed from any active status to
+an inactive. See F for C and C
+options.
+
+=cut
+
+sub IsApplicable {
+    my $self = shift;
+
+    my $txn = $self->TransactionObj;
+    return 0 unless $txn->Type eq "Status" ||
+        ( $txn->Type eq "Set" && $txn->Field eq "Status" );
+
+    my $queue = $self->TicketObj->QueueObj;
+    return 0 unless $queue->IsActiveStatus( $txn->OldValue );
+    return 0 unless $queue->IsInactiveStatus( $txn->NewValue );
+
+    return 1;
+}
+
+eval "require RT::Condition::CloseTicket_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/CloseTicket_Vendor.pm});
+eval "require RT::Condition::CloseTicket_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/CloseTicket_Local.pm});
+
+1;
diff --git a/rt/lib/RT/Condition/Generic.pm b/rt/lib/RT/Condition/Generic.pm
index da6ec476c..08baeda25 100755
--- a/rt/lib/RT/Condition/Generic.pm
+++ b/rt/lib/RT/Condition/Generic.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,191 +45,36 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 =head1 NAME
 
-  RT::Condition::Generic - ;
+  RT::Condition::Generic - deprecated, see RT::Condition
 
 =head1 SYNOPSIS
 
-    use RT::Condition::Generic;
-    my $foo = RT::Condition::Generic->new( 
-		TransactionObj => $tr, 
-		TicketObj => $ti, 
-		ScripObj => $scr, 
-		Argument => $arg, 
-		Type => $type);
-
-    if ($foo->IsApplicable) {
- 	   # do something
-    }
-
+  use RT::Condition::Generic;
 
 =head1 DESCRIPTION
 
+This module is provided only for backwards compatibility.
 
 =head1 METHODS
 
 
-=begin testing
-
-ok (require RT::Condition::Generic);
-
-=end testing
-
-
 =cut
 
-package RT::Condition::Generic;
-
 use strict;
-use base qw/RT::Base/;
-
-# {{{ sub new 
-sub new  {
-  my $proto = shift;
-  my $class = ref($proto) || $proto;
-  my $self  = {};
-  bless ($self, $class);
-  $self->_Init(@_);
-  return $self;
-}
-# }}}
-
-# {{{ sub _Init 
-sub _Init  {
-  my $self = shift;
-  my %args = ( TransactionObj => undef,
-	       TicketObj => undef,
-	       ScripObj => undef,
-	       TemplateObj => undef,
-	       Argument => undef,
-	       ApplicableTransTypes => undef,
-           CurrentUser => undef,
-	       @_ );
-  
-  $self->{'Argument'} = $args{'Argument'};
-  $self->{'ScripObj'} = $args{'ScripObj'};
-  $self->{'TicketObj'} = $args{'TicketObj'};
-  $self->{'TransactionObj'} = $args{'TransactionObj'};
-  $self->{'ApplicableTransTypes'} = $args{'ApplicableTransTypes'};
-  $self->CurrentUser($args{'CurrentUser'});
-}
-# }}}
-
-# Access Scripwide data
-
-# {{{ sub Argument 
-
-=head2 Argument
-
-Return the optional argument associated with this ScripCondition
-
-=cut
-
-sub Argument  {
-  my $self = shift;
-  return($self->{'Argument'});
-}
-# }}}
-
-# {{{ sub TicketObj
-
-=head2 TicketObj
-
-Return the ticket object we're talking about
-
-=cut
-
-sub TicketObj  {
-  my $self = shift;
-  return($self->{'TicketObj'});
-}
-# }}}
-
-# {{{ sub ScripObj
-
-=head2 ScripObj
-
-Return the Scrip object we're talking about
-
-=cut
-
-sub ScripObj  {
-  my $self = shift;
-  return($self->{'ScripObj'});
-}
-# }}}
-# {{{ sub TransactionObj
-
-=head2 TransactionObj
-
-Return the transaction object we're talking about
-
-=cut
-
-sub TransactionObj  {
-  my $self = shift;
-  return($self->{'TransactionObj'});
-}
-# }}}
-
-# {{{ sub Type
-
-=head2 Type 
-
-
-
-=cut
-
-sub ApplicableTransTypes  {
-  my $self = shift;
-  return($self->{'ApplicableTransTypes'});
-}
-# }}}
-
-
-# Scrip methods
-
-
-#What does this type of Action does
-
-# {{{ sub Describe 
-sub Describe  {
-  my $self = shift;
-  return ($self->loc("No description for [_1]", ref $self));
-}
-# }}}
-
-
-#Parse the templates, get things ready to go.
-
-#If this rule applies to this transaction, return true.
-
-# {{{ sub IsApplicable 
-sub IsApplicable  {
-  my $self = shift;
-  return(undef);
-}
-# }}}
-
-# {{{ sub DESTROY
-sub DESTROY {
-    my $self = shift;
-
-    # We need to clean up all the references that might maybe get
-    # oddly circular
-    $self->{'TemplateObj'} =undef
-    $self->{'TicketObj'} = undef;
-    $self->{'TransactionObj'} = undef;
-    $self->{'ScripObj'} = undef;
-     
-}
-
-# }}}
+use warnings;
+package RT::Condition::Generic;
+use base 'RT::Condition';
 
 eval "require RT::Condition::Generic_Vendor";
 die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/Generic_Vendor.pm});
+warn "RT::Condition::Generic has become RT::Condition. Please adjust your RT::Condition::Generic_Vendor file at " . $INC{"RT/Condition/Generic_Vendor.pm"} if !$@;
+
 eval "require RT::Condition::Generic_Local";
 die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/Generic_Local.pm});
+warn "RT::Condition::Generic has become RT::Condition. Please adjust your RT::Condition::Generic_Local file at " . $INC{"RT/Condition/Generic_Local.pm"} if !$@;
 
 1;
+
diff --git a/rt/lib/RT/Condition/Overdue.pm b/rt/lib/RT/Condition/Overdue.pm
index 4fb7f0d50..44d5f22e6 100644
--- a/rt/lib/RT/Condition/Overdue.pm
+++ b/rt/lib/RT/Condition/Overdue.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,8 +45,6 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
- 
-
 
 =head1 NAME
 
@@ -59,11 +57,8 @@ Returns true if the ticket we're operating on is overdue
 =cut
 
 package RT::Condition::Overdue;
-require RT::Condition::Generic;
-
+use base 'RT::Condition';
 use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Condition::Generic);
 
 
 =head2 IsApplicable
diff --git a/rt/lib/RT/Condition/OwnerChange.pm b/rt/lib/RT/Condition/OwnerChange.pm
index 2e10602dc..da9025304 100644
--- a/rt/lib/RT/Condition/OwnerChange.pm
+++ b/rt/lib/RT/Condition/OwnerChange.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,69 +45,23 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
- 
-
 
 package RT::Condition::OwnerChange;
-require RT::Condition::Generic;
-
+use base 'RT::Condition';
 use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Condition::Generic);
 
 
 =head2 IsApplicable
 
 If we're changing the owner return true, otherwise return false
 
-=begin testing
-
-my $q = RT::Queue->new($RT::SystemUser);
-$q->Create(Name =>'ownerChangeTest');
-
-ok($q->Id, "Created a scriptest queue");
-
-my $s1 = RT::Scrip->new($RT::SystemUser);
-my ($val, $msg) =$s1->Create( Queue => $q->Id,
-             ScripAction => 'User Defined',
-             ScripCondition => 'On Owner Change',
-             CustomIsApplicableCode => '',
-             CustomPrepareCode => 'return 1',
-             CustomCommitCode => '
-                    $self->TicketObj->SetPriority($self->TicketObj->Priority+1);
-                return(1);
-            ',
-             Template => 'Blank'
-    );
-ok($val,$msg);
-
-my $ticket = RT::Ticket->new($RT::SystemUser);
-my ($tv,$ttv,$tm) = $ticket->Create(Queue => $q->Id,
-                                    Subject => "hair on fire",
-                                    InitialPriority => '20'
-                                    );
-ok($tv, $tm);
-ok($ticket->SetOwner('root'));
-is ($ticket->Priority , '21', "Ticket priority is set right");
-ok($ticket->Steal);
-is ($ticket->Priority , '22', "Ticket priority is set right");
-ok($ticket->Untake);
-is ($ticket->Priority , '23', "Ticket priority is set right");
-ok($ticket->Take);
-is ($ticket->Priority , '24', "Ticket priority is set right");
-
-
-
-
-
-=end testing
 
 
 =cut
 
 sub IsApplicable {
     my $self = shift;
-    if ($self->TransactionObj->Field eq 'Owner') {
+    if ( ( $self->TransactionObj->Field || '' ) eq 'Owner' ) {
 	return(1);
     } 
     else {
diff --git a/rt/lib/RT/Condition/PriorityChange.pm b/rt/lib/RT/Condition/PriorityChange.pm
index 533cc4b31..268587a52 100644
--- a/rt/lib/RT/Condition/PriorityChange.pm
+++ b/rt/lib/RT/Condition/PriorityChange.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,15 +45,10 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
- 
-
 
 package RT::Condition::PriorityChange;
-require RT::Condition::Generic;
-
+use base 'RT::Condition';
 use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Condition::Generic);
 
 
 =head2 IsApplicable
diff --git a/rt/lib/RT/Condition/PriorityExceeds.pm b/rt/lib/RT/Condition/PriorityExceeds.pm
index 5f92957be..20089dbc3 100644
--- a/rt/lib/RT/Condition/PriorityExceeds.pm
+++ b/rt/lib/RT/Condition/PriorityExceeds.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,16 +45,10 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
- 
-
 
 package RT::Condition::PriorityExceeds;
-require RT::Condition::Generic;
-
+use base 'RT::Condition';
 use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Condition::Generic);
-
 
 =head2 IsApplicable
 
diff --git a/rt/lib/RT/Condition/QueueChange.pm b/rt/lib/RT/Condition/QueueChange.pm
index d5fbeecce..250a2de23 100644
--- a/rt/lib/RT/Condition/QueueChange.pm
+++ b/rt/lib/RT/Condition/QueueChange.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,16 +45,10 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
- 
-
 
 package RT::Condition::QueueChange;
-require RT::Condition::Generic;
-
+use base 'RT::Condition';
 use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Condition::Generic);
-
 
 =head2 IsApplicable
 
diff --git a/rt/lib/RT/Condition/ReopenTicket.pm b/rt/lib/RT/Condition/ReopenTicket.pm
new file mode 100644
index 000000000..1b62845f0
--- /dev/null
+++ b/rt/lib/RT/Condition/ReopenTicket.pm
@@ -0,0 +1,89 @@
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
+package RT::Condition::ReopenTicket;
+
+use strict;
+use warnings;
+
+use base 'RT::Condition';
+
+
+=head2 IsApplicable
+
+If the ticket was repopened, ie status was changed from any inactive status to
+an active. See F for C and C
+options.
+
+=cut
+
+sub IsApplicable {
+    my $self = shift;
+
+    my $txn = $self->TransactionObj;
+    return 0 unless $txn->Type eq "Status" ||
+        ( $txn->Type eq "Set" && $txn->Field eq "Status" );
+
+    my $queue = $self->TicketObj->QueueObj;
+    return 0 unless $queue->IsInactiveStatus( $txn->OldValue );
+    return 0 unless $queue->IsActiveStatus( $txn->NewValue );
+
+    $RT::Logger->debug("Condition 'On Reopen' triggered "
+        ."for ticket #". $self->TicketObj->id
+        ." transaction #". $txn->id
+    );
+
+    return 1;
+}
+
+eval "require RT::Condition::ReopenTicket_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/ReopenTicket_Vendor.pm});
+eval "require RT::Condition::ReopenTicket_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/ReopenTicket_Local.pm});
+
+1;
diff --git a/rt/lib/RT/Condition/StatusChange.pm b/rt/lib/RT/Condition/StatusChange.pm
index 20da9e728..285b71da6 100644
--- a/rt/lib/RT/Condition/StatusChange.pm
+++ b/rt/lib/RT/Condition/StatusChange.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,15 +45,10 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
- 
-
 
 package RT::Condition::StatusChange;
-require RT::Condition::Generic;
-
+use base 'RT::Condition';
 use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Condition::Generic);
 
 
 =head2 IsApplicable
diff --git a/rt/lib/RT/Condition/UserDefined.pm b/rt/lib/RT/Condition/UserDefined.pm
index f4d2e270c..f339e9a80 100644
--- a/rt/lib/RT/Condition/UserDefined.pm
+++ b/rt/lib/RT/Condition/UserDefined.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,15 +45,10 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
- 
 
 package RT::Condition::UserDefined;
-
-use RT::Condition::Generic;
-
+use base 'RT::Condition';
 use strict;
-use vars qw/@ISA/;
-@ISA = qw(RT::Condition::Generic);
 
 
 =head2 IsApplicable
@@ -64,6 +59,7 @@ This happens on every transaction. it's always applicable
 
 sub IsApplicable {
     my $self = shift;
+    local $@;
     my $retval = eval $self->ScripObj->CustomIsApplicableCode;
     if ($@) {
         $RT::Logger->error("Scrip ".$self->ScripObj->Id. " IsApplicable failed: ".$@);
diff --git a/rt/lib/RT/Config.pm b/rt/lib/RT/Config.pm
new file mode 100644
index 000000000..76c45dcbd
--- /dev/null
+++ b/rt/lib/RT/Config.pm
@@ -0,0 +1,894 @@
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
+package RT::Config;
+
+use strict;
+use warnings;
+
+use File::Spec ();
+
+=head1 NAME
+
+    RT::Config - RT's config
+
+=head1 SYNOPSYS
+
+    # get config object
+    use RT::Config;
+    my $config = new RT::Config;
+    $config->LoadConfigs;
+
+    # get or set option
+    my $rt_web_path = $config->Get('WebPath');
+    $config->Set(EmailOutputEncoding => 'latin1');
+
+    # get config object from RT package
+    use RT;
+    RT->LoadConfig;
+    my $config = RT->Config;
+
+=head1 DESCRIPTION
+
+C class provide access to RT's and RT extensions' config files.
+
+RT uses two files for site configuring:
+
+First file is F - core config file. This file is shipped
+with RT distribution and contains default values for all available options.
+B
+
+Second file is F - site config file. You can use it
+to customize your RT instance. In this file you can override any option
+listed in core config file.
+
+RT extensions could also provide thier config files. Extensions should
+use F<< _Config.pm >> and F<< _SiteConfig.pm >> names for
+config files, where  is extension name.
+
+B: All options from RT's config and extensions' configs are saved
+in one place and thus extension could override RT's options, but it is not
+recommended.
+
+=cut
+
+=head2 %META
+
+Hash of Config options that may be user overridable
+or may require more logic than should live in RT_*Config.pm
+
+Keyed by config name, there are several properties that
+can be set for each config optin:
+
+ Section     - What header this option should be grouped
+               under on the user Settings page
+ Overridable - Can users change this option
+ SortOrder   - Within a Section, how should the options be sorted
+               for display to the user
+ Widget      - Mason component path to widget that should be used 
+               to display this config option
+ WidgetArguments - An argument hash passed to the WIdget
+    Description - Friendly description to show the user
+    Values      - Arrayref of options (for select Widget)
+    ValuesLabel - Hashref, key is the Value from the Values
+                  list, value is a user friendly description
+                  of the value
+    Callback    - subref that receives no arguments.  It returns
+                  a hashref of items that are added to the rest
+                  of the WidgetArguments
+ PostLoadCheck - subref passed the RT::Config object and the current
+                 setting of the config option.  Can make further checks
+                 (such as seeing if a library is installed) and then change
+                 the setting of this or other options in the Config using 
+                 the RT::Config option.
+
+=cut
+
+our %META = (
+    # General user overridable options
+    DefaultQueue => {
+        Section         => 'General',
+        Overridable     => 1,
+        SortOrder       => 1,
+        Widget          => '/Widgets/Form/Select',
+        WidgetArguments => {
+            Description => 'Default queue',    #loc
+            Callback    => sub {
+                my $ret = { Values => [], ValuesLabel => {}};
+                my $q = new RT::Queues($HTML::Mason::Commands::session{'CurrentUser'});
+                $q->UnLimit;
+                while (my $queue = $q->Next) {
+                    next unless $queue->CurrentUserHasRight("CreateTicket");
+                    push @{$ret->{Values}}, $queue->Id;
+                    $ret->{ValuesLabel}{$queue->Id} = $queue->Name;
+                }
+                return $ret;
+            },
+        }
+    },
+    UsernameFormat => {
+        Section         => 'General',
+        Overridable     => 1,
+        SortOrder       => 2,
+        Widget          => '/Widgets/Form/Select',
+        WidgetArguments => {
+            Description => 'Username format', # loc
+            Values      => [qw(concise verbose)],
+            ValuesLabel => {
+                concise => 'Short usernames', # loc_left_pair
+                verbose => 'Name and email address', # loc_left_pair
+            },
+        },
+    },
+    WebDefaultStylesheet => {
+        Section         => 'General',                #loc
+        Overridable     => 1,
+        SortOrder       => 3,
+        Widget          => '/Widgets/Form/Select',
+        WidgetArguments => {
+            Description => 'Theme',                  #loc
+            # XXX: we need support for 'get values callback'
+            Values => [qw(3.5-default 3.4-compat web2)],
+        },
+    },
+    MessageBoxRichText => {
+        Section => 'General',
+        Overridable => 1,
+        SortOrder => 4,
+        Widget => '/Widgets/Form/Boolean',
+        WidgetArguments => {
+            Description => 'WYSIWYG message composer' # loc
+        }
+    },
+    MessageBoxRichTextHeight => {
+        Section => 'General',
+        Overridable => 1,
+        SortOrder => 5,
+        Widget => '/Widgets/Form/Integer',
+        WidgetArguments => {
+            Description => 'WYSIWYG composer height', # loc
+        }
+    },
+    MessageBoxWidth => {
+        Section         => 'General',
+        Overridable     => 1,
+        SortOrder       => 6,
+        Widget          => '/Widgets/Form/Integer',
+        WidgetArguments => {
+            Description => 'Message box width',           #loc
+        },
+    },
+    MessageBoxHeight => {
+        Section         => 'General',
+        Overridable     => 1,
+        SortOrder       => 7,
+        Widget          => '/Widgets/Form/Integer',
+        WidgetArguments => {
+            Description => 'Message box height',          #loc
+        },
+    },
+    SearchResultsRefreshInterval => {
+        Section         => 'General',                       #loc
+        Overridable     => 1,
+        SortOrder       => 8,
+        Widget          => '/Widgets/Form/Select',
+        WidgetArguments => {
+            Description => 'Search results refresh interval',                            #loc
+            Values      => [qw(0 120 300 600 1200 3600 7200)],
+            ValuesLabel => {
+                0 => "Don't refresh search results.",                      #loc
+                120 => "Refresh search results every 2 minutes.",          #loc
+                300 => "Refresh search results every 5 minutes.",          #loc
+                600 => "Refresh search results every 10 minutes.",         #loc
+                1200 => "Refresh search results every 20 minutes.",        #loc
+                3600 => "Refresh search results every 60 minutes.",        #loc
+                7200 => "Refresh search results every 120 minutes.",       #loc
+            },  
+        },  
+    },
+
+    # User overridable options for RT at a glance
+    DefaultSummaryRows => {
+        Section         => 'RT at a glance',    #loc
+        Overridable     => 1,
+        SortOrder       => 1,
+        Widget          => '/Widgets/Form/Integer',
+        WidgetArguments => {
+            Description => 'Number of search results',    #loc
+        },
+    },
+    HomePageRefreshInterval => {
+        Section         => 'RT at a glance',                       #loc
+        Overridable     => 1,
+        SortOrder       => 2,
+        Widget          => '/Widgets/Form/Select',
+        WidgetArguments => {
+            Description => 'Home page refresh interval',                #loc
+            Values      => [qw(0 120 300 600 1200 3600 7200)],
+            ValuesLabel => {
+                0 => "Don't refresh home page.",                  #loc
+                120 => "Refresh home page every 2 minutes.",      #loc
+                300 => "Refresh home page every 5 minutes.",      #loc
+                600 => "Refresh home page every 10 minutes.",     #loc
+                1200 => "Refresh home page every 20 minutes.",    #loc
+                3600 => "Refresh home page every 60 minutes.",    #loc
+                7200 => "Refresh home page every 120 minutes.",   #loc
+            },  
+        },  
+    },
+
+    # User overridable options for Ticket displays
+    MaxInlineBody => {
+        Section         => 'Ticket display',              #loc
+        Overridable     => 1,
+        SortOrder       => 1,
+        Widget          => '/Widgets/Form/Integer',
+        WidgetArguments => {
+            Description => 'Maximum inline message length',    #loc
+            Hints =>
+            "Length in characters; Use '0' to show all messages inline, regardless of length" #loc
+        },
+    },
+    OldestTransactionsFirst => {
+        Section         => 'Ticket display',
+        Overridable     => 1,
+        SortOrder       => 2,
+        Widget          => '/Widgets/Form/Boolean',
+        WidgetArguments => {
+            Description => 'Show oldest history first',    #loc
+        },
+    },
+    ShowUnreadMessageNotifications => { 
+        Section         => 'Ticket display',
+        Overridable     => 1,
+        SortOrder       => 3,
+        Widget          => '/Widgets/Form/Boolean',
+        WidgetArguments => {
+            Description => 'Notify me of unread messages',    #loc
+        },
+
+    },
+    PlainTextPre => {
+        Section         => 'Ticket display',
+        Overridable     => 1,
+        SortOrder       => 4,
+        Widget          => '/Widgets/Form/Boolean',
+        WidgetArguments => {
+            Description => 'add 
 tag around plain text attachments', #loc
+            Hints       => "Use this to protect the format of plain text" #loc
+        },
+    },
+    PlainTextMono => {
+        Section         => 'Ticket display',
+        Overridable     => 1,
+        SortOrder       => 5,
+        Widget          => '/Widgets/Form/Boolean',
+        WidgetArguments => {
+            Description => 'display wrapped and formatted plain text attachments', #loc
+            Hints => 'Use css rules to display text monospaced and with formatting preserved, but wrap as needed.  This does not work well with IE6 and you should use the previous option', #loc
+        },
+    },
+
+    # User overridable locale options
+    DateTimeFormat => {
+        Section         => 'Locale',                       #loc
+        Overridable     => 1,
+        Widget          => '/Widgets/Form/Select',
+        WidgetArguments => {
+            Description => 'Date format',                            #loc
+            Callback => sub { my $ret = { Values => [], ValuesLabel => {}};
+                              my $date = new RT::Date($HTML::Mason::Commands::session{'CurrentUser'});
+                              $date->Set;
+                              foreach my $value ($date->Formatters) {
+                                 push @{$ret->{Values}}, $value;
+                                 $ret->{ValuesLabel}{$value} = $date->$value();
+                              }
+                              return $ret;
+            },
+        },
+    },
+
+    # User overridable mail options
+    EmailFrequency => {
+        Section         => 'Mail',                                     #loc
+        Overridable     => 1,
+        Default     => 'Individual messages',
+        Widget          => '/Widgets/Form/Select',
+        WidgetArguments => {
+            Description => 'Email delivery',    #loc
+            Values      => [
+            'Individual messages',    #loc
+            'Daily digest',           #loc
+            'Weekly digest',          #loc
+            'Suspended'               #loc
+            ]
+        }
+    },
+
+    # this tends to break extensions that stash links in ticket update pages
+    Organization => {
+        Type            => 'SCALAR',
+        PostLoadCheck   => sub {
+            my ($self,$value) = @_;
+            $RT::Logger->error("your \$Organization setting ($value) appears to contain whitespace.  Please fix this.")
+                if $value =~ /\s/;;
+        },
+    },
+
+    # Internal config options
+    DisableGraphViz => {
+        Type            => 'SCALAR',
+        PostLoadCheck   => sub {
+            my $self  = shift;
+            my $value = shift;
+            return if $value;
+            return if $INC{'GraphViz.pm'};
+            local $@;
+            return if eval {require GraphViz; 1};
+            $RT::Logger->debug("You've enabled GraphViz, but we couldn't load the module: $@");
+            $self->Set( DisableGraphViz => 1 );
+        },
+    },
+    DisableGD => {
+        Type            => 'SCALAR',
+        PostLoadCheck   => sub {
+            my $self  = shift;
+            my $value = shift;
+            return if $value;
+            return if $INC{'GD.pm'};
+            local $@;
+            return if eval {require GD; 1};
+            $RT::Logger->debug("You've enabled GD, but we couldn't load the module: $@");
+            $self->Set( DisableGD => 1 );
+        },
+    },
+    MailPlugins  => { Type => 'ARRAY' },
+    Plugins      => { Type => 'ARRAY' },
+    GnuPG        => { Type => 'HASH' },
+    GnuPGOptions => { Type => 'HASH',
+        PostLoadCheck => sub {
+            my $self = shift;
+            my $gpg = $self->Get('GnuPG');
+            return unless $gpg->{'Enable'};
+            my $gpgopts = $self->Get('GnuPGOptions');
+            unless (-d $gpgopts->{homedir}  && -r _ ) { # no homedir, no gpg
+                $RT::Logger->debug(
+                    "RT's GnuPG libraries couldn't successfully read your".
+                    " configured GnuPG home directory (".$gpgopts->{homedir}
+                    ."). PGP support has been disabled");
+                $gpg->{'Enable'} = 0;
+                return;
+            }
+
+
+            require RT::Crypt::GnuPG;
+            unless (RT::Crypt::GnuPG->Probe()) {
+                $RT::Logger->debug(
+                    "RT's GnuPG libraries couldn't successfully execute gpg.".
+                    " PGP support has been disabled");
+                $gpg->{'Enable'} = 0;
+            }
+        }
+    },
+);
+my %OPTIONS = ();
+
+=head1 METHODS
+
+=head2 new
+
+Object constructor returns new object. Takes no arguments.
+
+=cut
+
+sub new {
+    my $proto = shift;
+    my $class = ref($proto) ? ref($proto) : $proto;
+    my $self  = bless {}, $class;
+    $self->_Init(@_);
+    return $self;
+}
+
+sub _Init {
+    return;
+}
+
+=head2 InitConfig
+
+Do nothin right now.
+
+=cut
+
+sub InitConfig {
+    my $self = shift;
+    my %args = ( File => '', @_ );
+    $args{'File'} =~ s/(?<=Config)(?=\.pm$)/Meta/;
+    return 1;
+}
+
+=head2 LoadConfigs
+
+Load all configs. First of all load RT's config then load
+extensions' config files in alphabetical order.
+Takes no arguments.
+
+=cut
+
+sub LoadConfigs {
+    my $self    = shift;
+
+    $self->InitConfig( File => 'RT_Config.pm' );
+    $self->LoadConfig( File => 'RT_Config.pm' );
+
+    my @configs = $self->Configs;
+    $self->InitConfig( File => $_ ) foreach @configs;
+    $self->LoadConfig( File => $_ ) foreach @configs;
+    return;
+}
+
+=head1 LoadConfig
+
+Takes param hash with C field.
+First, the site configuration file is loaded, in order to establish
+overall site settings like hostname and name of RT instance.
+Then, the core configuration file is loaded to set fallback values
+for all settings; it bases some values on settings from the site
+configuration file.
+
+B that core config file don't change options if site config
+has set them so to add value to some option instead of
+overriding you have to copy original value from core config file.
+
+=cut
+
+sub LoadConfig {
+    my $self = shift;
+    my %args = ( File => '', @_ );
+    $args{'File'} =~ s/(?_LoadConfig( %args, File => $site_config );
+    } else {
+        $self->_LoadConfig(%args);
+    }
+    $args{'File'} =~ s/Site(?=Config\.pm$)//;
+    $self->_LoadConfig(%args);
+    return 1;
+}
+
+sub _LoadConfig {
+    my $self = shift;
+    my %args = ( File => '', @_ );
+
+    my ($is_ext, $is_site);
+    if ( $args{'File'} eq ($ENV{RT_SITE_CONFIG}||'') ) {
+        ($is_ext, $is_site) = ('', 1);
+    } else {
+        $is_ext = $args{'File'} =~ /^(?!RT_)(?:(.*)_)(?:Site)?Config/ ? $1 : '';
+        $is_site = $args{'File'} =~ /SiteConfig/ ? 1 : 0;
+    }
+
+    eval {
+        package RT;
+        local *Set = sub(\[$@%]@) {
+            my ( $opt_ref, @args ) = @_;
+            my ( $pack, $file, $line ) = caller;
+            return $self->SetFromConfig(
+                Option     => $opt_ref,
+                Value      => [@args],
+                Package    => $pack,
+                File       => $file,
+                Line       => $line,
+                SiteConfig => $is_site,
+                Extension  => $is_ext,
+            );
+        };
+        my @etc_dirs = ($RT::LocalEtcPath);
+        push @etc_dirs, RT->PluginDirs('etc') if $is_ext;
+        push @etc_dirs, $RT::EtcPath, @INC;
+        local @INC = @etc_dirs;
+        require $args{'File'};
+    };
+    if ($@) {
+        return 1 if $is_site && $@ =~ qr{^Can't locate \Q$args{File}};
+        if ( $is_site || $@ !~ qr{^Can't locate \Q$args{File}} ) {
+            die qq{Couldn't load RT config file $args{'File'}:\n\n$@};
+        }
+
+        my $username = getpwuid($>);
+        my $group    = getgrgid($();
+
+        my ( $file_path, $fileuid, $filegid );
+        foreach ( $RT::LocalEtcPath, $RT::EtcPath, @INC ) {
+            my $tmp = File::Spec->catfile( $_, $args{File} );
+            ( $fileuid, $filegid ) = ( stat($tmp) )[ 4, 5 ];
+            if ( defined $fileuid ) {
+                $file_path = $tmp;
+                last;
+            }
+        }
+        unless ($file_path) {
+            die
+                qq{Couldn't load RT config file $args{'File'} as user $username / group $group.\n}
+                . qq{The file couldn't be found in $RT::LocalEtcPath and $RT::EtcPath.\n$@};
+        }
+
+        my $message = <Options( Overridable => undef ) ) {
+        $META{$o}->{'PostLoadCheck'}->( $self, $self->Get($o) );
+    }
+}
+
+=head2 Configs
+
+Returns list of config files found in local etc, plugins' etc
+and main etc directories.
+
+=cut
+
+sub Configs {
+    my $self    = shift;
+
+    my @configs = ();
+    foreach my $path ( $RT::LocalEtcPath, RT->PluginDirs('etc'), $RT::EtcPath ) {
+        my $mask = File::Spec->catfile( $path, "*_Config.pm" );
+        my @files = glob $mask;
+        @files = grep !/^RT_Config\.pm$/,
+            grep $_ && /^\w+_Config\.pm$/,
+            map { s/^.*[\\\/]//; $_ } @files;
+        push @configs, sort @files;
+    }
+
+    my %seen;
+    @configs = grep !$seen{$_}++, @configs;
+    return @configs;
+}
+
+=head2 Get
+
+Takes name of the option as argument and returns its current value.
+
+In the case of a user-overridable option, first checks the user's
+preferences before looking for site-wide configuration.
+
+Returns values from RT_SiteConfig, RT_Config and then the %META hash
+of configuration variables's "Default" for this config variable,
+in that order.
+
+Returns different things in scalar and array contexts. For scalar
+options it's not that important, however for arrays and hash it's.
+In scalar context returns references to arrays and hashes.
+
+Use C perl's op to force context, especially when you use
+C<(..., Argument => RT->Config->Get('ArrayOpt'), ...)>
+as perl's '=>' op doesn't change context of the right hand argument to
+scalar. Instead use C<(..., Argument => scalar RT->Config->Get('ArrayOpt'), ...)>.
+
+It's also important for options that have no default value(no default
+in F). If you don't force scalar context then you'll
+get empty list and all your named args will be messed up. For example
+C<(arg1 => 1, arg2 => RT->Config->Get('OptionDoesNotExist'), arg3 => 3)>
+will result in C<(arg1 => 1, arg2 => 'arg3', 3)> what is most probably
+unexpected, or C<(arg1 => 1, arg2 => RT->Config->Get('ArrayOption'), arg3 => 3)>
+will result in C<(arg1 => 1, arg2 => 'element of option', 'another_one' => ..., 'arg3', 3)>.
+
+=cut
+
+sub Get {
+    my ( $self, $name, $user ) = @_;
+
+    my $res;
+    if ( $user && $user->id && $META{$name}->{'Overridable'} ) {
+        $user = $user->UserObj if $user->isa('RT::CurrentUser');
+        my $prefs = $user->Preferences($RT::System);
+        $res = $prefs->{$name} if $prefs;
+    }
+    $res = $OPTIONS{$name}           unless defined $res;
+    $res = $META{$name}->{'Default'} unless defined $res;
+    return $self->_ReturnValue( $res, $META{$name}->{'Type'} || 'SCALAR' );
+}
+
+=head2 Set
+
+Set option's value to new value. Takes name of the option and new value.
+Returns old value.
+
+The new value should be scalar, array or hash depending on type of the option.
+If the option is not defined in meta or the default RT config then it is of
+scalar type.
+
+=cut
+
+sub Set {
+    my ( $self, $name ) = ( shift, shift );
+
+    my $old = $OPTIONS{$name};
+    my $type = $META{$name}->{'Type'} || 'SCALAR';
+    if ( $type eq 'ARRAY' ) {
+        $OPTIONS{$name} = [@_];
+        { no warnings 'once'; no strict 'refs'; @{"RT::$name"} = (@_); }
+    } elsif ( $type eq 'HASH' ) {
+        $OPTIONS{$name} = {@_};
+        { no warnings 'once'; no strict 'refs'; %{"RT::$name"} = (@_); }
+    } else {
+        $OPTIONS{$name} = shift;
+        {no warnings 'once'; no strict 'refs'; ${"RT::$name"} = $OPTIONS{$name}; }
+    }
+    $META{$name}->{'Type'} = $type;
+    return $self->_ReturnValue( $old, $type );
+}
+
+sub _ReturnValue {
+    my ( $self, $res, $type ) = @_;
+    return $res unless wantarray;
+
+    if ( $type eq 'ARRAY' ) {
+        return @{ $res || [] };
+    } elsif ( $type eq 'HASH' ) {
+        return %{ $res || {} };
+    }
+    return $res;
+}
+
+sub SetFromConfig {
+    my $self = shift;
+    my %args = (
+        Option     => undef,
+        Value      => [],
+        Package    => 'RT',
+        File       => '',
+        Line       => 0,
+        SiteConfig => 1,
+        Extension  => 0,
+        @_
+    );
+
+    unless ( $args{'File'} ) {
+        ( $args{'Package'}, $args{'File'}, $args{'Line'} ) = caller(1);
+    }
+
+    my $opt = $args{'Option'};
+
+    my $type;
+    my $name = $self->__GetNameByRef($opt);
+    if ($name) {
+        $type = ref $opt;
+        $name =~ s/.*:://;
+    } else {
+        $name = $$opt;
+        $type = $META{$name}->{'Type'} || 'SCALAR';
+    }
+
+    # if option is already set we have to check where
+    # it comes from and may be ignore it
+    if ( exists $OPTIONS{$name} ) {
+        if ( $args{'SiteConfig'} && $args{'Extension'} ) {
+            # if it's site config of an extension then it can only
+            # override options that came from its main config
+            if ( $args{'Extension'} ne $META{$name}->{'Source'}{'Extension'} ) {
+                my %source = %{ $META{$name}->{'Source'} };
+                warn
+                    "Change of config option '$name' at $args{'File'} line $args{'Line'} has been ignored."
+                    ." This option earlier has been set in $source{'File'} line $source{'Line'}."
+                    ." To overide this option use ". ($source{'Extension'}||'RT')
+                    ." site config."
+                ;
+                return 1;
+            }
+        } elsif ( !$args{'SiteConfig'} && $META{$name}->{'Source'}{'SiteConfig'} ) {
+            # if it's core config then we can override any option that came from another
+            # core config, but not site config
+
+            my %source = %{ $META{$name}->{'Source'} };
+            if ( $source{'Extension'} ne $args{'Extension'} ) {
+                # as a site config is loaded earlier then its base config
+                # then we warn only on different extensions, for example
+                # RTIR's options is set in main site config or RTFM's
+                warn
+                    "Change of config option '$name' at $args{'File'} line $args{'Line'} has been ignored."
+                    ." It's may be ok, but we want you to be aware."
+                    ." This option earlier has been set in $source{'File'} line $source{'Line'}."
+                ;
+            }
+
+            return 1;
+        }
+    }
+
+    $META{$name}->{'Type'} = $type;
+    foreach (qw(Package File Line SiteConfig Extension)) {
+        $META{$name}->{'Source'}->{$_} = $args{$_};
+    }
+    $self->Set( $name, @{ $args{'Value'} } );
+
+    return 1;
+}
+
+{
+    my $last_pack = '';
+
+    sub __GetNameByRef {
+        my $self = shift;
+        my $ref  = shift;
+        my $pack = shift;
+        if ( !$pack && $last_pack ) {
+            my $tmp = $self->__GetNameByRef( $ref, $last_pack );
+            return $tmp if $tmp;
+        }
+        $pack ||= 'main::';
+        $pack .= '::' unless substr( $pack, -2 ) eq '::';
+
+        my %ref_sym = (
+            SCALAR => '$',
+            ARRAY  => '@',
+            HASH   => '%',
+            CODE   => '&',
+        );
+        no strict 'refs';
+        my $name = undef;
+
+        # scan $pack's nametable(hash)
+        foreach my $k ( keys %{$pack} ) {
+
+            # hash for main:: has reference on itself
+            next if $k eq 'main::';
+
+            # if entry has trailing '::' then
+            # it is link to other name space
+            if ( $k =~ /::$/ ) {
+                $name = $self->__GetNameByRef( $ref, $k );
+                return $name if $name;
+            }
+
+            # entry of the table with references to
+            # SCALAR, ARRAY... and other types with
+            # the same name
+            my $entry = ${$pack}{$k};
+            next unless $entry;
+
+            # get entry for type we are looking for
+            # XXX skip references to scalars or other references.
+            # Otherwie 5.10 goes boom. may be we should skip any
+            # reference
+            return if ref($entry) eq 'SCALAR' || ref($entry) eq 'REF';
+            my $entry_ref = *{$entry}{ ref($ref) };
+            next unless $entry_ref;
+
+            # if references are equal then we've found
+            if ( $entry_ref == $ref ) {
+                $last_pack = $pack;
+                return ( $ref_sym{ ref($ref) } || '*' ) . $pack . $k;
+            }
+        }
+        return '';
+    }
+}
+
+=head2 Metadata
+
+
+=head2 Meta
+
+=cut
+
+sub Meta {
+    return $META{ $_[1] };
+}
+
+sub Sections {
+    my $self = shift;
+    my %seen;
+    return sort
+        grep !$seen{$_}++,
+        map $_->{'Section'} || 'General',
+        values %META;
+}
+
+sub Options {
+    my $self = shift;
+    my %args = ( Section => undef, Overridable => 1, Sorted => 1, @_ );
+    my @res  = keys %META;
+    
+    @res = grep( ( $META{$_}->{'Section'} || 'General' ) eq $args{'Section'},
+        @res 
+    ) if defined $args{'Section'};
+
+    if ( defined $args{'Overridable'} ) {
+        @res
+            = grep( ( $META{$_}->{'Overridable'} || 0 ) == $args{'Overridable'},
+            @res );
+    }
+
+    if ( $args{'Sorted'} ) {
+        @res = sort {
+            ($META{$a}->{SortOrder}||9999) <=> ($META{$b}->{SortOrder}||9999)
+            || $a cmp $b 
+        } @res;
+    } else {
+        @res = sort { $a cmp $b } @res;
+    }
+    return @res;
+}
+
+eval "require RT::Config_Vendor";
+if ($@ && $@ !~ qr{^Can't locate RT/Config_Vendor.pm}) {
+    die $@;
+};
+
+eval "require RT::Config_Local";
+if ($@ && $@ !~ qr{^Can't locate RT/Config_Local.pm}) {
+    die $@;
+};
+
+1;
diff --git a/rt/lib/RT/Crypt/GnuPG.pm b/rt/lib/RT/Crypt/GnuPG.pm
new file mode 100644
index 000000000..5581df153
--- /dev/null
+++ b/rt/lib/RT/Crypt/GnuPG.pm
@@ -0,0 +1,2450 @@
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
+use strict;
+use warnings;
+
+package RT::Crypt::GnuPG;
+
+use IO::Handle;
+use GnuPG::Interface;
+use RT::EmailParser ();
+use RT::Util 'safe_run_child';
+
+=head1 NAME
+
+RT::Crypt::GnuPG - encrypt/decrypt and sign/verify email messages with the GNU Privacy Guard (GPG)
+
+=head1 DESCRIPTION
+
+This module provides support for encryption and signing of outgoing messages, 
+as well as the decryption and verification of incoming email.
+
+=head1 CONFIGURATION
+
+You can control the configuration of this subsystem from RT's configuration file.
+Some options are available via the web interface, but to enable this functionality, you
+MUST start in the configuration file.
+
+There are two hashes, GnuPG and GnuPGOptions in the configuration file. The 
+first one controls RT specific options. It enables you to enable/disable facility 
+or change the format of messages. The second one is a hash with options for the 
+'gnupg' utility. You can use it to define a keyserver, enable auto-retrieval keys 
+and set almost any option 'gnupg' supports on your system.
+
+=head2 %GnuPG
+
+=head3 Enabling GnuPG
+
+Set to true value to enable this subsystem:
+
+    Set( %GnuPG,
+        Enable => 1,
+        ... other options ...
+    );
+
+However, note that you B add the 'Auth::GnuPG' email filter to enable
+the handling of incoming encrypted/signed messages.
+
+=head3 Format of outgoing messages
+
+Format of outgoing messages can be controlled using the 'OutgoingMessagesFormat'
+option in the RT config:
+
+    Set( %GnuPG,
+        ... other options ...
+        OutgoingMessagesFormat => 'RFC',
+        ... other options ...
+    );
+
+or
+
+    Set( %GnuPG,
+        ... other options ...
+        OutgoingMessagesFormat => 'Inline',
+        ... other options ...
+    );
+
+This framework implements two formats of signing and encrypting of email messages:
+
+=over
+
+=item RFC
+
+This format is also known as GPG/MIME and described in RFC3156 and RFC1847.
+Technique described in these RFCs is well supported by many mail user
+agents (MUA), but some MUAs support only inline signatures and encryption,
+so it's possible to use inline format (see below).
+
+=item Inline
+
+This format doesn't take advantage of MIME, but some mail clients do
+not support GPG/MIME.
+
+We sign text parts using clear signatures. For each attachments another
+attachment with a signature is added with '.sig' extension.
+
+Encryption of text parts is implemented using inline format, other parts
+are replaced with attachments with the filename extension '.pgp'.
+
+This format is discouraged because modern mail clients typically don't support
+it well.
+
+=back
+
+=head3 Encrypting data in the database
+
+You can allow users to encrypt data in the database using
+option C. By default it's disabled.
+Users must have rights to see and modify tickets to use
+this feature.
+
+=head2 %GnuPGOptions
+
+Use this hash to set options of the 'gnupg' program. You can define almost any
+option you want which  gnupg supports, but never try to set options which
+change output format or gnupg's commands, such as --sign (command),
+--list-options (option) and other.
+
+Some GnuPG options take arguments while others take none. (Such as  --use-agent).
+For options without specific value use C as hash value.
+To disable these option just comment them out or delete them from the hash
+
+    Set(%GnuPGOptions,
+        'option-with-value' => 'value',
+        'enabled-option-without-value' => undef,
+        # 'commented-option' => 'value or undef',
+    );
+
+B that options may contain '-' character and such options B be
+quoted, otherwise you can see quite cryptic error 'gpg: Invalid option "--0"'.
+
+=over
+
+=item --homedir
+
+The GnuPG home directory, by default it is set to F.
+
+You can manage this data with the 'gpg' commandline utility 
+using the GNUPGHOME environment variable or --homedir option. 
+Other utilities may be used as well.
+
+In a standard installation, access to this directory should be granted to
+the web server user which is running RT's web interface, but if you're running
+cronjobs or other utilities that access RT directly via API and may generate
+encrypted/signed notifications then the users you execute these scripts under
+must have access too. 
+
+However, granting access to the dir to many users makes your setup less secure,
+some features, such as auto-import of keys, may not be available if you do not.
+To enable this features and suppress warnings about permissions on
+the dir use --no-permission-warning.
+
+=item --digest-algo
+
+This option is required in advance when RFC format for outgoing messages is
+used. We can not get default algorithm from gpg program so RT uses 'SHA1' by
+default. You may want to override it. You can use MD5, SHA1, RIPEMD160,
+SHA256 or other, however use `gpg --version` command to get information about
+supported algorithms by your gpg. These algorithms are listed as hash-functions.
+
+=item --use-agent
+
+This option lets you use GPG Agent to cache the passphrase of RT's key. See
+L
+for information about GPG Agent.
+
+=item --passphrase
+
+This option lets you set the passphrase of RT's key directly. This option is
+special in that it isn't passed directly to GPG, but is put into a file that
+GPG then reads (which is more secure). The downside is that anyone who has read
+access to your RT_SiteConfig.pm file can see the passphrase, thus we recommend
+the --use-agent option instead.
+
+=item other
+
+Read `man gpg` to get list of all options this program support.
+
+=back
+
+=head2 Per-queue options
+
+Using the web interface it's possible to enable signing and/or encrypting by
+default. As an administrative user of RT, open 'Configuration' then 'Queues',
+and select a queue. On the page you can see information about the queue's keys 
+at the bottom and two checkboxes to choose default actions.
+
+As well, encryption is enabled for autoreplies and other notifications when
+an encypted message enters system via mailgate interface even if queue's
+option is disabled.
+
+=head2 Handling incoming messages
+
+To enable handling of encrypted and signed message in the RT you should add
+'Auth::GnuPG' mail plugin.
+
+    Set(@MailPlugins, 'Auth::MailFrom', 'Auth::GnuPG', ...other filter...);
+
+See also `perldoc lib/RT/Interface/Email/Auth/GnuPG.pm`.
+
+=head2 Errors handling
+
+There are several global templates created in the database by default. RT
+uses these templates to send error messages to users or RT's owner. These 
+templates have 'Error:' or 'Error to RT owner:' prefix in the name. You can 
+adjust the text of the messages using the web interface.
+
+Note that C<$TicketObj>, C<$TransactionObj> and other variable usually available
+in RT's templates are not available in these templates, but each template
+used for errors reporting has set of available data structures you can use to
+build better messages. See default templates and descriptions below.
+
+As well, you can disable particular notification by deleting content of
+a template. You can delete a template too, but in this case you'll see
+error messages in the logs when RT can not load template you've deleted.
+
+=head3 Problems with public keys
+
+Template 'Error: public key' is used to inform the user that RT has problems with
+his public key and won't be able to send him encrypted content. There are several 
+reasons why RT can't use a key. However, the actual reason is not sent to the user, 
+but sent to RT owner using 'Error to RT owner: public key'.
+
+The possible reasons: "Not Found", "Ambiguous specification", "Wrong
+key usage", "Key revoked", "Key expired", "No CRL known", "CRL too
+old", "Policy mismatch", "Not a secret key", "Key not trusted" or
+"No specific reason given".
+
+Due to limitations of GnuPG, it's impossible to encrypt to an untrusted key,
+unless 'always trust' mode is enabled.
+
+In the 'Error: public key' template there are a few additional variables available:
+
+=over 4
+
+=item $Message - user friendly error message
+
+=item $Reason - short reason as listed above
+
+=item $Recipient - recipient's identification
+
+=item $AddressObj - L object containing recipient's email address
+
+=back
+
+A message can have several invalid recipients, to avoid sending many emails
+to the RT owner the system sends one message to the owner, grouped by
+recipient. In the 'Error to RT owner: public key' template a C<@BadRecipients>
+array is available where each element is a hash reference that describes one
+recipient using the same fields as described above. So it's something like:
+
+    @BadRecipients = (
+        { Message => '...', Reason => '...', Recipient => '...', ...},
+        { Message => '...', Reason => '...', Recipient => '...', ...},
+        ...
+    )
+
+=head3 Private key doesn't exist
+
+Template 'Error: no private key' is used to inform the user that
+he sent an encrypted email, but we have no private key to decrypt
+it.
+
+In this template C<$Message> object of L class
+available. It's the message RT received.
+
+=head3 Invalid data
+
+Template 'Error: bad GnuPG data' used to inform the user that a
+message he sent has invalid data and can not be handled.
+
+There are several reasons for this error, but most of them are data
+corruption or absence of expected information.
+
+In this template C<@Messages> array is available and contains list
+of error messages.
+
+=head1 FOR DEVELOPERS
+
+=head2 Documentation and references
+
+* RFC1847 - Security Multiparts for MIME: Multipart/Signed and Multipart/Encrypted.
+Describes generic MIME security framework, "mulitpart/signed" and "multipart/encrypted"
+MIME types.
+
+* RFC3156 - MIME Security with Pretty Good Privacy (PGP),
+updates RFC2015.
+
+=cut
+
+# gnupg options supported by GnuPG::Interface
+# other otions should be handled via extra_args argument
+my %supported_opt = map { $_ => 1 } qw(
+       always_trust
+       armor
+       batch
+       comment
+       compress_algo
+       default_key
+       encrypt_to
+       extra_args
+       force_v3_sigs
+       homedir
+       logger_fd
+       no_greeting
+       no_options
+       no_verbose
+       openpgp
+       options
+       passphrase_fd
+       quiet
+       recipients
+       rfc1991
+       status_fd
+       textmode
+       verbose
+);
+
+# DEV WARNING: always pass all STD* handles to GnuPG interface even if we don't
+# need them, just pass 'new IO::Handle' and then close it after safe_run_child.
+# we don't want to leak anything into FCGI/Apache/MP handles, this break things.
+# So code should look like:
+#        my $handles = GnuPG::Handles->new(
+#            stdin  => ($handle{'stdin'}  = new IO::Handle),
+#            stdout => ($handle{'stdout'} = new IO::Handle),
+#            stderr => ($handle{'stderr'}  = new IO::Handle),
+#            ...
+#        );
+
+=head2 SignEncrypt Entity => MIME::Entity, [ Encrypt => 1, Sign => 1, ... ]
+
+Signs and/or encrypts an email message with GnuPG utility.
+
+=over
+
+=item Signing
+
+During signing you can pass C argument to set key we sign with this option
+overrides gnupg's C option. If C argument is not provided
+then address of a message sender is used.
+
+As well you can pass C, but if value is undefined then L
+called to get it.
+
+=item Encrypting
+
+During encryption you can pass a C array, otherwise C, C and
+C fields of the message are used to fetch the list.
+
+=back
+
+Returns a hash with the following keys:
+
+* exit_code
+* error
+* logger
+* status
+* message
+
+=cut
+
+sub SignEncrypt {
+    my %args = (@_);
+
+    my $entity = $args{'Entity'};
+    if ( $args{'Sign'} && !defined $args{'Signer'} ) {
+        $args{'Signer'} = UseKeyForSigning()
+            || (Email::Address->parse( $entity->head->get( 'From' ) ))[0]->address;
+    }
+    if ( $args{'Encrypt'} && !$args{'Recipients'} ) {
+        my %seen;
+        $args{'Recipients'} = [
+            grep $_ && !$seen{ $_ }++, map $_->address,
+            map Email::Address->parse( $entity->head->get( $_ ) ),
+            qw(To Cc Bcc)
+        ];
+    }
+    
+    my $format = lc RT->Config->Get('GnuPG')->{'OutgoingMessagesFormat'} || 'RFC';
+    if ( $format eq 'inline' ) {
+        return SignEncryptInline( %args );
+    } else {
+        return SignEncryptRFC3156( %args );
+    }
+}
+
+sub SignEncryptRFC3156 {
+    my %args = (
+        Entity => undef,
+
+        Sign => 1,
+        Signer => undef,
+        Passphrase => undef,
+
+        Encrypt => 1,
+        Recipients => undef,
+
+        @_
+    );
+
+    my $gnupg = new GnuPG::Interface;
+    my %opt = RT->Config->Get('GnuPGOptions');
+
+    # handling passphrase in GnuPGOptions
+    $args{'Passphrase'} = delete $opt{'passphrase'}
+        if !defined $args{'Passphrase'};
+
+    $opt{'digest-algo'} ||= 'SHA1';
+    $opt{'default_key'} = $args{'Signer'}
+        if $args{'Sign'} && $args{'Signer'};
+    $gnupg->options->hash_init(
+        _PrepareGnuPGOptions( %opt ),
+        armor => 1,
+        meta_interactive => 0,
+    );
+
+    my $entity = $args{'Entity'};
+
+    if ( $args{'Sign'} && !defined $args{'Passphrase'} ) {
+        $args{'Passphrase'} = GetPassphrase( Address => $args{'Signer'} );
+    }
+
+    my %res;
+    if ( $args{'Sign'} && !$args{'Encrypt'} ) {
+        # required by RFC3156(Ch. 5) and RFC1847(Ch. 2.1)
+        foreach ( grep !$_->is_multipart, $entity->parts_DFS ) {
+            my $tenc = $_->head->mime_encoding;
+            unless ( $tenc =~ m/^(?:7bit|quoted-printable|base64)$/i ) {
+                $_->head->mime_attr( 'Content-Transfer-Encoding'
+                    => $_->effective_type =~ m{^text/}? 'quoted-printable': 'base64'
+                );
+            }
+        }
+
+        my ($handles, $handle_list) = _make_gpg_handles(stdin =>IO::Handle::CRLF->new );
+        my %handle = %$handle_list;
+
+        $gnupg->passphrase( $args{'Passphrase'} );
+
+        eval {
+            local $SIG{'CHLD'} = 'DEFAULT';
+            my $pid = safe_run_child { $gnupg->detach_sign( handles => $handles ) };
+            $entity->make_multipart( 'mixed', Force => 1 );
+            {
+                local $SIG{'PIPE'} = 'IGNORE';
+                $entity->parts(0)->print( $handle{'stdin'} );
+                close $handle{'stdin'};
+            }
+            waitpid $pid, 0;
+        };
+        my $err = $@;
+        my @signature = readline $handle{'stdout'};
+        close $handle{'stdout'};
+
+        $res{'exit_code'} = $?;
+        foreach ( qw(stderr logger status) ) {
+            $res{$_} = do { local $/; readline $handle{$_} };
+            delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
+            close $handle{$_};
+        }
+        $RT::Logger->debug( $res{'status'} ) if $res{'status'};
+        $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
+        $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
+        if ( $err || $res{'exit_code'} ) {
+            $res{'message'} = $err? $err : "gpg exitted with error code ". ($res{'exit_code'} >> 8);
+            return %res;
+        }
+
+        # setup RFC1847(Ch.2.1) requirements
+        my $protocol = 'application/pgp-signature';
+        $entity->head->mime_attr( 'Content-Type' => 'multipart/signed' );
+        $entity->head->mime_attr( 'Content-Type.protocol' => $protocol );
+        $entity->head->mime_attr( 'Content-Type.micalg'   => 'pgp-'. lc $opt{'digest-algo'} );
+        $entity->attach(
+            Type        => $protocol,
+            Disposition => 'inline',
+            Data        => \@signature,
+            Encoding    => '7bit',
+        );
+    }
+    if ( $args{'Encrypt'} ) {
+        my %seen;
+        $gnupg->options->push_recipients( $_ ) foreach 
+            map UseKeyForEncryption($_) || $_,
+            grep !$seen{ $_ }++, map $_->address,
+            map Email::Address->parse( $entity->head->get( $_ ) ),
+            qw(To Cc Bcc);
+
+        my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
+        binmode $tmp_fh, ':raw';
+
+        my ($handles, $handle_list) = _make_gpg_handles(stdout => $tmp_fh);
+        my %handle = %$handle_list;
+        $handles->options( 'stdout'  )->{'direct'} = 1;
+        $gnupg->passphrase( $args{'Passphrase'} ) if $args{'Sign'};
+
+        eval {
+            local $SIG{'CHLD'} = 'DEFAULT';
+            my $pid = safe_run_child { $args{'Sign'}
+                ? $gnupg->sign_and_encrypt( handles => $handles )
+                : $gnupg->encrypt( handles => $handles ) };
+            $entity->make_multipart( 'mixed', Force => 1 );
+            {
+                local $SIG{'PIPE'} = 'IGNORE';
+                $entity->parts(0)->print( $handle{'stdin'} );
+                close $handle{'stdin'};
+            }
+            waitpid $pid, 0;
+        };
+
+        $res{'exit_code'} = $?;
+        foreach ( qw(stderr logger status) ) {
+            $res{$_} = do { local $/; readline $handle{$_} };
+            delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
+            close $handle{$_};
+        }
+        $RT::Logger->debug( $res{'status'} ) if $res{'status'};
+        $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
+        $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
+        if ( $@ || $? ) {
+            $res{'message'} = $@? $@: "gpg exited with error code ". ($? >> 8);
+            return %res;
+        }
+
+        my $protocol = 'application/pgp-encrypted';
+        $entity->parts([]);
+        $entity->head->mime_attr( 'Content-Type' => 'multipart/encrypted' );
+        $entity->head->mime_attr( 'Content-Type.protocol' => $protocol );
+        $entity->attach(
+            Type        => $protocol,
+            Disposition => 'inline',
+            Data        => ['Version: 1',''],
+            Encoding    => '7bit',
+        );
+        $entity->attach(
+            Type        => 'application/octet-stream',
+            Disposition => 'inline',
+            Path        => $tmp_fn,
+            Filename    => '',
+            Encoding    => '7bit',
+        );
+        $entity->parts(-1)->bodyhandle->{'_dirty_hack_to_save_a_ref_tmp_fh'} = $tmp_fh;
+    }
+    return %res;
+}
+
+sub SignEncryptInline {
+    my %args = ( @_ );
+
+    my $entity = $args{'Entity'};
+
+    my %res;
+    $entity->make_singlepart;
+    if ( $entity->is_multipart ) {
+        foreach ( $entity->parts ) {
+            %res = SignEncryptInline( @_, Entity => $_ );
+            return %res if $res{'exit_code'};
+        }
+        return %res;
+    }
+
+    return _SignEncryptTextInline( @_ )
+        if $entity->effective_type =~ /^text\//i;
+
+    return _SignEncryptAttachmentInline( @_ );
+}
+
+sub _SignEncryptTextInline {
+    my %args = (
+        Entity => undef,
+
+        Sign => 1,
+        Signer => undef,
+        Passphrase => undef,
+
+        Encrypt => 1,
+        Recipients => undef,
+
+        @_
+    );
+    return unless $args{'Sign'} || $args{'Encrypt'};
+
+    my $gnupg = new GnuPG::Interface;
+    my %opt = RT->Config->Get('GnuPGOptions');
+
+    # handling passphrase in GnupGOptions
+    $args{'Passphrase'} = delete $opt{'passphrase'}
+        if !defined($args{'Passphrase'});
+
+    $opt{'digest-algo'} ||= 'SHA1';
+    $opt{'default_key'} = $args{'Signer'}
+        if $args{'Sign'} && $args{'Signer'};
+    $gnupg->options->hash_init(
+        _PrepareGnuPGOptions( %opt ),
+        armor => 1,
+        meta_interactive => 0,
+    );
+
+    if ( $args{'Sign'} && !defined $args{'Passphrase'} ) {
+        $args{'Passphrase'} = GetPassphrase( Address => $args{'Signer'} );
+    }
+
+    if ( $args{'Encrypt'} ) {
+        $gnupg->options->push_recipients( $_ ) foreach 
+            map UseKeyForEncryption($_) || $_,
+            @{ $args{'Recipients'} || [] };
+    }
+
+    my %res;
+
+    my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
+    binmode $tmp_fh, ':raw';
+
+    my ($handles, $handle_list) = _make_gpg_handles(stdout => $tmp_fh);
+    my %handle = %$handle_list;
+
+    $handles->options( 'stdout'  )->{'direct'} = 1;
+    $gnupg->passphrase( $args{'Passphrase'} ) if $args{'Sign'};
+
+    my $entity = $args{'Entity'};
+    eval {
+        local $SIG{'CHLD'} = 'DEFAULT';
+        my $method = $args{'Sign'} && $args{'Encrypt'}
+            ? 'sign_and_encrypt'
+            : ($args{'Sign'}? 'clearsign': 'encrypt');
+        my $pid = safe_run_child { $gnupg->$method( handles => $handles ) };
+        {
+            local $SIG{'PIPE'} = 'IGNORE';
+            $entity->bodyhandle->print( $handle{'stdin'} );
+            close $handle{'stdin'};
+        }
+        waitpid $pid, 0;
+    };
+    $res{'exit_code'} = $?;
+    my $err = $@;
+
+    foreach ( qw(stderr logger status) ) {
+        $res{$_} = do { local $/; readline $handle{$_} };
+        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
+        close $handle{$_};
+    }
+    $RT::Logger->debug( $res{'status'} ) if $res{'status'};
+    $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
+    $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
+    if ( $err || $res{'exit_code'} ) {
+        $res{'message'} = $err? $err : "gpg exitted with error code ". ($res{'exit_code'} >> 8);
+        return %res;
+    }
+
+    $entity->bodyhandle( new MIME::Body::File $tmp_fn );
+    $entity->{'__store_tmp_handle_to_avoid_early_cleanup'} = $tmp_fh;
+
+    return %res;
+}
+
+sub _SignEncryptAttachmentInline {
+    my %args = (
+        Entity => undef,
+
+        Sign => 1,
+        Signer => undef,
+        Passphrase => undef,
+
+        Encrypt => 1,
+        Recipients => undef,
+
+        @_
+    );
+    return unless $args{'Sign'} || $args{'Encrypt'};
+
+    my $gnupg = new GnuPG::Interface;
+    my %opt = RT->Config->Get('GnuPGOptions');
+
+    # handling passphrase in GnupGOptions
+    $args{'Passphrase'} = delete $opt{'passphrase'}
+        if !defined($args{'Passphrase'});
+
+    $opt{'digest-algo'} ||= 'SHA1';
+    $opt{'default_key'} = $args{'Signer'}
+        if $args{'Sign'} && $args{'Signer'};
+    $gnupg->options->hash_init(
+        _PrepareGnuPGOptions( %opt ),
+        armor => 1,
+        meta_interactive => 0,
+    );
+
+    if ( $args{'Sign'} && !defined $args{'Passphrase'} ) {
+        $args{'Passphrase'} = GetPassphrase( Address => $args{'Signer'} );
+    }
+
+    my $entity = $args{'Entity'};
+    if ( $args{'Encrypt'} ) {
+        $gnupg->options->push_recipients( $_ ) foreach
+            map UseKeyForEncryption($_) || $_,
+            @{ $args{'Recipients'} || [] };
+    }
+
+    my %res;
+
+    my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
+    binmode $tmp_fh, ':raw';
+
+    my ($handles, $handle_list) = _make_gpg_handles(stdout => $tmp_fh);
+    my %handle = %$handle_list;
+    $handles->options( 'stdout'  )->{'direct'} = 1;
+    $gnupg->passphrase( $args{'Passphrase'} ) if $args{'Sign'};
+
+    eval {
+        local $SIG{'CHLD'} = 'DEFAULT';
+        my $method = $args{'Sign'} && $args{'Encrypt'}
+            ? 'sign_and_encrypt'
+            : ($args{'Sign'}? 'detach_sign': 'encrypt');
+        my $pid = safe_run_child { $gnupg->$method( handles => $handles ) };
+        {
+            local $SIG{'PIPE'} = 'IGNORE';
+            $entity->bodyhandle->print( $handle{'stdin'} );
+            close $handle{'stdin'};
+        }
+        waitpid $pid, 0;
+    };
+    $res{'exit_code'} = $?;
+    my $err = $@;
+
+    foreach ( qw(stderr logger status) ) {
+        $res{$_} = do { local $/; readline $handle{$_} };
+        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
+        close $handle{$_};
+    }
+    $RT::Logger->debug( $res{'status'} ) if $res{'status'};
+    $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
+    $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
+    if ( $err || $res{'exit_code'} ) {
+        $res{'message'} = $err? $err : "gpg exitted with error code ". ($res{'exit_code'} >> 8);
+        return %res;
+    }
+
+    my $filename = $entity->head->recommended_filename || 'no_name';
+    if ( $args{'Sign'} && !$args{'Encrypt'} ) {
+        $entity->make_multipart;
+        $entity->attach(
+            Type     => 'application/octet-stream',
+            Path     => $tmp_fn,
+            Filename => "$filename.sig",
+            Disposition => 'attachment',
+        );
+    } else {
+        $entity->bodyhandle( new MIME::Body::File $tmp_fn );
+        $entity->effective_type('application/octet-stream');
+        $entity->head->mime_attr( $_ => "$filename.pgp" )
+            foreach (qw(Content-Type.name Content-Disposition.filename));
+
+    }
+    $entity->{'__store_tmp_handle_to_avoid_early_cleanup'} = $tmp_fh;
+
+    return %res;
+}
+
+sub SignEncryptContent {
+    my %args = (
+        Content => undef,
+
+        Sign => 1,
+        Signer => undef,
+        Passphrase => undef,
+
+        Encrypt => 1,
+        Recipients => undef,
+
+        @_
+    );
+    return unless $args{'Sign'} || $args{'Encrypt'};
+
+    my $gnupg = new GnuPG::Interface;
+    my %opt = RT->Config->Get('GnuPGOptions');
+
+    # handling passphrase in GnupGOptions
+    $args{'Passphrase'} = delete $opt{'passphrase'}
+        if !defined($args{'Passphrase'});
+
+    $opt{'digest-algo'} ||= 'SHA1';
+    $opt{'default_key'} = $args{'Signer'}
+        if $args{'Sign'} && $args{'Signer'};
+    $gnupg->options->hash_init(
+        _PrepareGnuPGOptions( %opt ),
+        armor => 1,
+        meta_interactive => 0,
+    );
+
+    if ( $args{'Sign'} && !defined $args{'Passphrase'} ) {
+        $args{'Passphrase'} = GetPassphrase( Address => $args{'Signer'} );
+    }
+
+    if ( $args{'Encrypt'} ) {
+        $gnupg->options->push_recipients( $_ ) foreach 
+            map UseKeyForEncryption($_) || $_,
+            @{ $args{'Recipients'} || [] };
+    }
+
+    my %res;
+
+    my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
+    binmode $tmp_fh, ':raw';
+
+    my ($handles, $handle_list) = _make_gpg_handles(stdout => $tmp_fh);
+    my %handle = %$handle_list;
+    $handles->options( 'stdout'  )->{'direct'} = 1;
+    $gnupg->passphrase( $args{'Passphrase'} ) if $args{'Sign'};
+
+    eval {
+        local $SIG{'CHLD'} = 'DEFAULT';
+        my $method = $args{'Sign'} && $args{'Encrypt'}
+            ? 'sign_and_encrypt'
+            : ($args{'Sign'}? 'clearsign': 'encrypt');
+        my $pid = safe_run_child { $gnupg->$method( handles => $handles ) };
+        {
+            local $SIG{'PIPE'} = 'IGNORE';
+            $handle{'stdin'}->print( ${ $args{'Content'} } );
+            close $handle{'stdin'};
+        }
+        waitpid $pid, 0;
+    };
+    $res{'exit_code'} = $?;
+    my $err = $@;
+
+    foreach ( qw(stderr logger status) ) {
+        $res{$_} = do { local $/; readline $handle{$_} };
+        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
+        close $handle{$_};
+    }
+    $RT::Logger->debug( $res{'status'} ) if $res{'status'};
+    $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
+    $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
+    if ( $err || $res{'exit_code'} ) {
+        $res{'message'} = $err? $err : "gpg exitted with error code ". ($res{'exit_code'} >> 8);
+        return %res;
+    }
+
+    ${ $args{'Content'} } = '';
+    seek $tmp_fh, 0, 0;
+    while (1) {
+        my $status = read $tmp_fh, my $buf, 4*1024;
+        unless ( defined $status ) {
+            $RT::Logger->crit( "couldn't read message: $!" );
+        } elsif ( !$status ) {
+            last;
+        }
+        ${ $args{'Content'} } .= $buf;
+    }
+
+    return %res;
+}
+
+sub FindProtectedParts {
+    my %args = ( Entity => undef, CheckBody => 1, @_ );
+    my $entity = $args{'Entity'};
+
+    # inline PGP block, only in singlepart
+    unless ( $entity->is_multipart ) {
+        my $io = $entity->open('r');
+        unless ( $io ) {
+            $RT::Logger->warning( "Entity of type ". $entity->effective_type ." has no body" );
+            return ();
+        }
+        while ( defined($_ = $io->getline) ) {
+            next unless /^-----BEGIN PGP (SIGNED )?MESSAGE-----/;
+            my $type = $1? 'signed': 'encrypted';
+            $RT::Logger->debug("Found $type inline part");
+            return {
+                Type    => $type,
+                Format  => 'Inline',
+                Data  => $entity,
+            };
+        }
+        $io->close;
+        return ();
+    }
+
+    # RFC3156, multipart/{signed,encrypted}
+    if ( ( my $type = $entity->effective_type ) =~ /^multipart\/(?:encrypted|signed)$/ ) {
+        unless ( $entity->parts == 2 ) {
+            $RT::Logger->error( "Encrypted or signed entity must has two subparts. Skipped" );
+            return ();
+        }
+
+        my $protocol = $entity->head->mime_attr( 'Content-Type.protocol' );
+        unless ( $protocol ) {
+            $RT::Logger->error( "Entity is '$type', but has no protocol defined. Skipped" );
+            return ();
+        }
+
+        if ( $type eq 'multipart/encrypted' ) {
+            unless ( $protocol eq 'application/pgp-encrypted' ) {
+                $RT::Logger->info( "Skipping protocol '$protocol', only 'application/pgp-encrypted' is supported" );
+                return ();
+            }
+            $RT::Logger->debug("Found encrypted according to RFC3156 part");
+            return {
+                Type    => 'encrypted',
+                Format  => 'RFC3156',
+                Top   => $entity,
+                Data  => $entity->parts(1),
+                Info    => $entity->parts(0),
+            };
+        } else {
+            unless ( $protocol eq 'application/pgp-signature' ) {
+                $RT::Logger->info( "Skipping protocol '$protocol', only 'application/pgp-signature' is supported" );
+                return ();
+            }
+            $RT::Logger->debug("Found signed according to RFC3156 part");
+            return {
+                Type      => 'signed',
+                Format    => 'RFC3156',
+                Top     => $entity,
+                Data    => $entity->parts(0),
+                Signature => $entity->parts(1),
+            };
+        }
+    }
+
+    # attachments signed with signature in another part
+    my @file_indices;
+    foreach my $i ( 0 .. $entity->parts - 1 ) {
+        my $part = $entity->parts($i);
+
+        # we can not associate a signature within an attachment
+        # without file names
+        my $fname = $part->head->recommended_filename;
+        next unless $fname;
+
+        if ( $part->effective_type eq 'application/pgp-signature' ) {
+            push @file_indices, $i;
+        }
+        elsif ( $fname =~ /\.sig$/i && $part->effective_type eq 'application/octet-stream' ) {
+            push @file_indices, $i;
+        }
+    }
+
+    my (@res, %skip);
+    foreach my $i ( @file_indices ) {
+        my $sig_part = $entity->parts($i);
+        $skip{"$sig_part"}++;
+        my $sig_name = $sig_part->head->recommended_filename;
+        my ($file_name) = $sig_name =~ /^(.*?)(?:\.sig)?$/;
+
+        my ($data_part_idx) =
+            grep $file_name eq ($entity->parts($_)->head->recommended_filename||''),
+            grep $sig_part  ne  $entity->parts($_),
+                0 .. $entity->parts - 1;
+        unless ( defined $data_part_idx ) {
+            $RT::Logger->error("Found $sig_name attachment, but didn't find $file_name");
+            next;
+        }
+        my $data_part_in = $entity->parts($data_part_idx);
+
+        $skip{"$data_part_in"}++;
+        $RT::Logger->debug("Found signature (in '$sig_name') of attachment '$file_name'");
+        push @res, {
+            Type      => 'signed',
+            Format    => 'Attachment',
+            Top       => $entity,
+            Data      => $data_part_in,
+            Signature => $sig_part,
+        };
+    }
+
+    # attachments with inline encryption
+    my @encrypted_indices =
+        grep {($entity->parts($_)->head->recommended_filename || '') =~ /\.pgp$/}
+            0 .. $entity->parts - 1;
+
+    foreach my $i ( @encrypted_indices ) {
+        my $part = $entity->parts($i);
+        $skip{"$part"}++;
+        $RT::Logger->debug("Found encrypted attachment '". $part->head->recommended_filename ."'");
+        push @res, {
+            Type      => 'encrypted',
+            Format    => 'Attachment',
+            Top     => $entity,
+            Data    => $part,
+        };
+    }
+
+    push @res, FindProtectedParts( Entity => $_ )
+        foreach grep !$skip{"$_"}, $entity->parts;
+
+    return @res;
+}
+
+=head2 VerifyDecrypt Entity => undef, [ Detach => 1, Passphrase => undef, SetStatus => 1 ]
+
+=cut
+
+sub VerifyDecrypt {
+    my %args = ( Entity => undef, Detach => 1, SetStatus => 1, @_ );
+    my @protected = FindProtectedParts( Entity => $args{'Entity'} );
+    my @res;
+    # XXX: detaching may brake nested signatures
+    foreach my $item( grep $_->{'Type'} eq 'signed', @protected ) {
+        if ( $item->{'Format'} eq 'RFC3156' ) {
+            push @res, { VerifyRFC3156( %$item, SetStatus => $args{'SetStatus'} ) };
+            if ( $args{'Detach'} ) {
+                $item->{'Top'}->parts( [ $item->{'Data'} ] );
+                $item->{'Top'}->make_singlepart;
+            }
+            $item->{'Top'}->head->set( 'X-RT-GnuPG-Status' => $res[-1]->{'status'} )
+                if $args{'SetStatus'};
+        } elsif ( $item->{'Format'} eq 'Inline' ) {
+            push @res, { VerifyInline( %$item ) };
+            $item->{'Data'}->head->set( 'X-RT-GnuPG-Status' => $res[-1]->{'status'} )
+                if $args{'SetStatus'};
+        } elsif ( $item->{'Format'} eq 'Attachment' ) {
+            push @res, { VerifyAttachment( %$item ) };
+            if ( $args{'Detach'} ) {
+                $item->{'Top'}->parts( [ grep "$_" ne $item->{'Signature'}, $item->{'Top'}->parts ] );
+                $item->{'Top'}->make_singlepart;
+            }
+            $item->{'Data'}->head->set( 'X-RT-GnuPG-Status' => $res[-1]->{'status'} )
+                if $args{'SetStatus'};
+        }
+    }
+    foreach my $item( grep $_->{'Type'} eq 'encrypted', @protected ) {
+        if ( $item->{'Format'} eq 'RFC3156' ) {
+            push @res, { DecryptRFC3156( %$item ) };
+            $item->{'Top'}->head->set( 'X-RT-GnuPG-Status' => $res[-1]->{'status'} )
+                if $args{'SetStatus'};
+        } elsif ( $item->{'Format'} eq 'Inline' ) {
+            push @res, { DecryptInline( %$item ) };
+            $item->{'Data'}->head->set( 'X-RT-GnuPG-Status' => $res[-1]->{'status'} )
+                if $args{'SetStatus'};
+        } elsif ( $item->{'Format'} eq 'Attachment' ) {
+            push @res, { DecryptAttachment( %$item ) };
+            $item->{'Data'}->head->set( 'X-RT-GnuPG-Status' => $res[-1]->{'status'} )
+                if $args{'SetStatus'};
+        }
+    }
+    return @res;
+}
+
+sub VerifyInline { return DecryptInline( @_ ) }
+
+sub VerifyAttachment {
+    my %args = ( Data => undef, Signature => undef, Top => undef, @_ );
+
+    my $gnupg = new GnuPG::Interface;
+    my %opt = RT->Config->Get('GnuPGOptions');
+    $opt{'digest-algo'} ||= 'SHA1';
+    $gnupg->options->hash_init(
+        _PrepareGnuPGOptions( %opt ),
+        meta_interactive => 0,
+    );
+
+    foreach ( $args{'Data'}, $args{'Signature'} ) {
+        next unless $_->bodyhandle->is_encoded;
+
+        require RT::EmailParser;
+        RT::EmailParser->_DecodeBody($_);
+    }
+
+    my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
+    binmode $tmp_fh, ':raw';
+    $args{'Data'}->bodyhandle->print( $tmp_fh );
+    $tmp_fh->flush;
+
+    my ($handles, $handle_list) = _make_gpg_handles();
+    my %handle = %$handle_list;
+
+    my %res;
+    eval {
+        local $SIG{'CHLD'} = 'DEFAULT';
+        my $pid = safe_run_child { $gnupg->verify(
+            handles => $handles, command_args => [ '-', $tmp_fn ]
+        ) };
+        {
+            local $SIG{'PIPE'} = 'IGNORE';
+            $args{'Signature'}->bodyhandle->print( $handle{'stdin'} );
+            close $handle{'stdin'};
+        }
+        waitpid $pid, 0;
+    };
+    $res{'exit_code'} = $?;
+    foreach ( qw(stderr logger status) ) {
+        $res{$_} = do { local $/; readline $handle{$_} };
+        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
+        close $handle{$_};
+    }
+    $RT::Logger->debug( $res{'status'} ) if $res{'status'};
+    $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
+    $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
+    if ( $@ || $? ) {
+        $res{'message'} = $@? $@: "gpg exitted with error code ". ($? >> 8);
+    }
+    return %res;
+}
+
+sub VerifyRFC3156 {
+    my %args = ( Data => undef, Signature => undef, Top => undef, @_ );
+
+    my $gnupg = new GnuPG::Interface;
+    my %opt = RT->Config->Get('GnuPGOptions');
+    $opt{'digest-algo'} ||= 'SHA1';
+    $gnupg->options->hash_init(
+        _PrepareGnuPGOptions( %opt ),
+        meta_interactive => 0,
+    );
+
+    my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
+    binmode $tmp_fh, ':raw:eol(CRLF?)';
+    $args{'Data'}->print( $tmp_fh );
+    $tmp_fh->flush;
+
+    my ($handles, $handle_list) = _make_gpg_handles();
+    my %handle = %$handle_list;
+
+    my %res;
+    eval {
+        local $SIG{'CHLD'} = 'DEFAULT';
+        my $pid = safe_run_child { $gnupg->verify(
+            handles => $handles, command_args => [ '-', $tmp_fn ]
+        ) };
+        {
+            local $SIG{'PIPE'} = 'IGNORE';
+            $args{'Signature'}->bodyhandle->print( $handle{'stdin'} );
+            close $handle{'stdin'};
+        }
+        waitpid $pid, 0;
+    };
+    $res{'exit_code'} = $?;
+    foreach ( qw(stderr logger status) ) {
+        $res{$_} = do { local $/; readline $handle{$_} };
+        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
+        close $handle{$_};
+    }
+    $RT::Logger->debug( $res{'status'} ) if $res{'status'};
+    $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
+    $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
+    if ( $@ || $? ) {
+        $res{'message'} = $@? $@: "gpg exitted with error code ". ($? >> 8);
+    }
+    return %res;
+}
+
+sub DecryptRFC3156 {
+    my %args = (
+        Data => undef,
+        Info => undef,
+        Top => undef,
+        Passphrase => undef,
+        @_
+    );
+
+    my $gnupg = new GnuPG::Interface;
+    my %opt = RT->Config->Get('GnuPGOptions');
+
+    # handling passphrase in GnupGOptions
+    $args{'Passphrase'} = delete $opt{'passphrase'}
+        if !defined($args{'Passphrase'});
+
+    $opt{'digest-algo'} ||= 'SHA1';
+    $gnupg->options->hash_init(
+        _PrepareGnuPGOptions( %opt ),
+        meta_interactive => 0,
+    );
+
+    if ( $args{'Data'}->bodyhandle->is_encoded ) {
+        require RT::EmailParser;
+        RT::EmailParser->_DecodeBody($args{'Data'});
+    }
+
+    $args{'Passphrase'} = GetPassphrase()
+        unless defined $args{'Passphrase'};
+
+    my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
+    binmode $tmp_fh, ':raw';
+
+    my ($handles, $handle_list) = _make_gpg_handles(stdout => $tmp_fh);
+    my %handle = %$handle_list;
+    $handles->options( 'stdout' )->{'direct'} = 1;
+
+    my %res;
+    eval {
+        local $SIG{'CHLD'} = 'DEFAULT';
+        $gnupg->passphrase( $args{'Passphrase'} );
+        my $pid = safe_run_child { $gnupg->decrypt( handles => $handles ) };
+        {
+            local $SIG{'PIPE'} = 'IGNORE';
+            $args{'Data'}->bodyhandle->print( $handle{'stdin'} );
+            close $handle{'stdin'}
+        }
+
+        waitpid $pid, 0;
+    };
+    $res{'exit_code'} = $?;
+    foreach ( qw(stderr logger status) ) {
+        $res{$_} = do { local $/; readline $handle{$_} };
+        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
+        close $handle{$_};
+    }
+    $RT::Logger->debug( $res{'status'} ) if $res{'status'};
+    $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
+    $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
+
+    # if the decryption is fine but the signature is bad, then without this
+    # status check we lose the decrypted text
+    # XXX: add argument to the function to control this check
+    if ( $res{'status'} !~ /DECRYPTION_OKAY/ ) {
+        if ( $@ || $? ) {
+            $res{'message'} = $@? $@: "gpg exitted with error code ". ($? >> 8);
+            return %res;
+        }
+    }
+
+    seek $tmp_fh, 0, 0;
+    my $parser = new RT::EmailParser;
+    my $decrypted = $parser->ParseMIMEEntityFromFileHandle( $tmp_fh, 0 );
+    $decrypted->{'__store_link_to_object_to_avoid_early_cleanup'} = $parser;
+    $args{'Top'}->parts( [] );
+    $args{'Top'}->add_part( $decrypted );
+    $args{'Top'}->make_singlepart;
+    return %res;
+}
+
+sub DecryptInline {
+    my %args = (
+        Data => undef,
+        Passphrase => undef,
+        @_
+    );
+
+    my $gnupg = new GnuPG::Interface;
+    my %opt = RT->Config->Get('GnuPGOptions');
+
+    # handling passphrase in GnuPGOptions
+    $args{'Passphrase'} = delete $opt{'passphrase'}
+        if !defined($args{'Passphrase'});
+
+    $opt{'digest-algo'} ||= 'SHA1';
+    $gnupg->options->hash_init(
+        _PrepareGnuPGOptions( %opt ),
+        meta_interactive => 0,
+    );
+
+    if ( $args{'Data'}->bodyhandle->is_encoded ) {
+        require RT::EmailParser;
+        RT::EmailParser->_DecodeBody($args{'Data'});
+    }
+
+    $args{'Passphrase'} = GetPassphrase()
+        unless defined $args{'Passphrase'};
+
+    my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
+    binmode $tmp_fh, ':raw';
+
+    my $io = $args{'Data'}->open('r');
+    unless ( $io ) {
+        die "Entity has no body, never should happen";
+    }
+
+    my ($had_literal, $in_block) = ('', 0);
+    my ($block_fh, $block_fn) = File::Temp::tempfile( UNLINK => 1 );
+    binmode $block_fh, ':raw';
+
+    my %res;
+    while ( defined(my $str = $io->getline) ) {
+        if ( $in_block && $str =~ /^-----END PGP (?:MESSAGE|SIGNATURE)-----/ ) {
+            print $block_fh $str;
+            $in_block--;
+            next if $in_block > 0;
+
+            seek $block_fh, 0, 0;
+
+            my ($res_fh, $res_fn);
+            ($res_fh, $res_fn, %res) = _DecryptInlineBlock(
+                %args,
+                GnuPG => $gnupg,
+                BlockHandle => $block_fh,
+            );
+            return %res unless $res_fh;
+
+            print $tmp_fh "-----BEGIN OF PGP PROTECTED PART-----\n" if $had_literal;
+            while (my $buf = <$res_fh> ) {
+                print $tmp_fh $buf;
+            }
+            print $tmp_fh "-----END OF PART-----\n" if $had_literal;
+
+            ($block_fh, $block_fn) = File::Temp::tempfile( UNLINK => 1 );
+            binmode $block_fh, ':raw';
+            $in_block = 0;
+        }
+        elsif ( $str =~ /^-----BEGIN PGP (SIGNED )?MESSAGE-----/ ) {
+            $in_block++;
+            print $block_fh $str;
+        }
+        elsif ( $in_block ) {
+            print $block_fh $str;
+        }
+        else {
+            print $tmp_fh $str;
+            $had_literal = 1 if /\S/s;
+        }
+    }
+    $io->close;
+
+    seek $tmp_fh, 0, 0;
+    $args{'Data'}->bodyhandle( new MIME::Body::File $tmp_fn );
+    $args{'Data'}->{'__store_tmp_handle_to_avoid_early_cleanup'} = $tmp_fh;
+    return %res;
+}
+
+sub _DecryptInlineBlock {
+    my %args = (
+        GnuPG => undef,
+        BlockHandle => undef,
+        Passphrase => undef,
+        @_
+    );
+    my $gnupg = $args{'GnuPG'};
+
+    my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
+    binmode $tmp_fh, ':raw';
+
+    my ($handles, $handle_list) = _make_gpg_handles(
+            stdin => $args{'BlockHandle'}, 
+            stdout => $tmp_fh);
+    my %handle = %$handle_list;
+    $handles->options( 'stdout' )->{'direct'} = 1;
+    $handles->options( 'stdin' )->{'direct'} = 1;
+
+    my %res;
+    eval {
+        local $SIG{'CHLD'} = 'DEFAULT';
+        $gnupg->passphrase( $args{'Passphrase'} );
+        my $pid = safe_run_child { $gnupg->decrypt( handles => $handles ) };
+        waitpid $pid, 0;
+    };
+    $res{'exit_code'} = $?;
+    foreach ( qw(stderr logger status) ) {
+        $res{$_} = do { local $/; readline $handle{$_} };
+        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
+        close $handle{$_};
+    }
+    $RT::Logger->debug( $res{'status'} ) if $res{'status'};
+    $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
+    $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
+
+    # if the decryption is fine but the signature is bad, then without this
+    # status check we lose the decrypted text
+    # XXX: add argument to the function to control this check
+    if ( $res{'status'} !~ /DECRYPTION_OKAY/ ) {
+        if ( $@ || $? ) {
+            $res{'message'} = $@? $@: "gpg exitted with error code ". ($? >> 8);
+            return (undef, undef, %res);
+        }
+    }
+
+    seek $tmp_fh, 0, 0;
+    return ($tmp_fh, $tmp_fn, %res);
+}
+
+sub DecryptAttachment {
+    my %args = (
+        Top  => undef,
+        Data => undef,
+        Passphrase => undef,
+        @_
+    );
+
+    my $gnupg = new GnuPG::Interface;
+    my %opt = RT->Config->Get('GnuPGOptions');
+
+    # handling passphrase in GnuPGOptions
+    $args{'Passphrase'} = delete $opt{'passphrase'}
+        if !defined($args{'Passphrase'});
+
+    $opt{'digest-algo'} ||= 'SHA1';
+    $gnupg->options->hash_init(
+        _PrepareGnuPGOptions( %opt ),
+        meta_interactive => 0,
+    );
+
+    if ( $args{'Data'}->bodyhandle->is_encoded ) {
+        require RT::EmailParser;
+        RT::EmailParser->_DecodeBody($args{'Data'});
+    }
+
+    $args{'Passphrase'} = GetPassphrase()
+        unless defined $args{'Passphrase'};
+
+    my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
+    binmode $tmp_fh, ':raw';
+    $args{'Data'}->bodyhandle->print( $tmp_fh );
+    seek $tmp_fh, 0, 0;
+
+    my ($res_fh, $res_fn, %res) = _DecryptInlineBlock(
+        %args,
+        GnuPG => $gnupg,
+        BlockHandle => $tmp_fh,
+    );
+    return %res unless $res_fh;
+
+    $args{'Data'}->bodyhandle( new MIME::Body::File $res_fn );
+    $args{'Data'}->{'__store_tmp_handle_to_avoid_early_cleanup'} = $res_fh;
+
+    my $filename = $args{'Data'}->head->recommended_filename;
+    $filename =~ s/\.pgp$//i;
+    $args{'Data'}->head->mime_attr( $_ => $filename )
+        foreach (qw(Content-Type.name Content-Disposition.filename));
+
+    return %res;
+}
+
+sub DecryptContent {
+    my %args = (
+        Content => undef,
+        Passphrase => undef,
+        @_
+    );
+
+    my $gnupg = new GnuPG::Interface;
+    my %opt = RT->Config->Get('GnuPGOptions');
+
+    # handling passphrase in GnupGOptions
+    $args{'Passphrase'} = delete $opt{'passphrase'}
+        if !defined($args{'Passphrase'});
+
+    $opt{'digest-algo'} ||= 'SHA1';
+    $gnupg->options->hash_init(
+        _PrepareGnuPGOptions( %opt ),
+        meta_interactive => 0,
+    );
+
+    $args{'Passphrase'} = GetPassphrase()
+        unless defined $args{'Passphrase'};
+
+    my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
+    binmode $tmp_fh, ':raw';
+
+    my ($handles, $handle_list) = _make_gpg_handles(
+            stdout => $tmp_fh);
+    my %handle = %$handle_list;
+    $handles->options( 'stdout' )->{'direct'} = 1;
+
+    my %res;
+    eval {
+        local $SIG{'CHLD'} = 'DEFAULT';
+        $gnupg->passphrase( $args{'Passphrase'} );
+        my $pid = safe_run_child { $gnupg->decrypt( handles => $handles ) };
+        {
+            local $SIG{'PIPE'} = 'IGNORE';
+            print { $handle{'stdin'} } ${ $args{'Content'} };
+            close $handle{'stdin'};
+        }
+
+        waitpid $pid, 0;
+    };
+    $res{'exit_code'} = $?;
+    foreach ( qw(stderr logger status) ) {
+        $res{$_} = do { local $/; readline $handle{$_} };
+        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
+        close $handle{$_};
+    }
+    $RT::Logger->debug( $res{'status'} ) if $res{'status'};
+    $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
+    $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
+
+    # if the decryption is fine but the signature is bad, then without this
+    # status check we lose the decrypted text
+    # XXX: add argument to the function to control this check
+    if ( $res{'status'} !~ /DECRYPTION_OKAY/ ) {
+        if ( $@ || $? ) {
+            $res{'message'} = $@? $@: "gpg exitted with error code ". ($? >> 8);
+            return %res;
+        }
+    }
+
+    ${ $args{'Content'} } = '';
+    seek $tmp_fh, 0, 0;
+    while (1) {
+        my $status = read $tmp_fh, my $buf, 4*1024;
+        unless ( defined $status ) {
+            $RT::Logger->crit( "couldn't read message: $!" );
+        } elsif ( !$status ) {
+            last;
+        }
+        ${ $args{'Content'} } .= $buf;
+    }
+
+    return %res;
+}
+
+=head2 GetPassphrase [ Address => undef ]
+
+Returns passphrase, called whenever it's required with Address as a named argument.
+
+=cut
+
+sub GetPassphrase {
+    my %args = ( Address => undef, @_ );
+    return 'test';
+}
+
+=head2 ParseStatus
+
+Takes a string containing output of gnupg status stream. Parses it and returns
+array of hashes. Each element of array is a hash ref and represents line or
+group of lines in the status message.
+
+All hashes have Operation, Status and Message elements.
+
+=over
+
+=item Operation
+
+Classification of operations gnupg performs. Now we have support
+for Sign, Encrypt, Decrypt, Verify, PassphraseCheck, RecipientsCheck and Data
+values.
+
+=item Status
+
+Informs about success. Value is 'DONE' on success, other values means that
+an operation failed, for example 'ERROR', 'BAD', 'MISSING' and may be other.
+
+=item Message
+
+User friendly message.
+
+=back
+
+This parser is based on information from GnuPG distribution, see also
+F in the RT distribution.
+
+=cut
+
+my %REASON_CODE_TO_TEXT = (
+    NODATA => {
+        1 => "No armored data",
+        2 => "Expected a packet, but did not found one",
+        3 => "Invalid packet found",
+        4 => "Signature expected, but not found",
+    },
+    INV_RECP => {
+        0 => "No specific reason given",
+        1 => "Not Found",
+        2 => "Ambigious specification",
+        3 => "Wrong key usage",
+        4 => "Key revoked",
+        5 => "Key expired",
+        6 => "No CRL known",
+        7 => "CRL too old",
+        8 => "Policy mismatch",
+        9 => "Not a secret key",
+        10 => "Key not trusted",
+    },
+    ERRSIG => {
+        0 => 'not specified',
+        4 => 'unknown algorithm',
+        9 => 'missing public key',
+    },
+);
+
+sub ReasonCodeToText {
+    my $keyword = shift;
+    my $code = shift;
+    return $REASON_CODE_TO_TEXT{ $keyword }{ $code }
+        if exists $REASON_CODE_TO_TEXT{ $keyword }{ $code };
+    return 'unknown';
+}
+
+my %simple_keyword = (
+    NO_RECP => {
+        Operation => 'RecipientsCheck',
+        Status    => 'ERROR',
+        Message   => 'No recipients',
+    },
+    UNEXPECTED => {
+        Operation => 'Data',
+        Status    => 'ERROR',
+        Message   => 'Unexpected data has been encountered',
+    },
+    BADARMOR => {
+        Operation => 'Data',
+        Status    => 'ERROR',
+        Message   => 'The ASCII armor is corrupted',
+    },
+);
+
+# keywords we parse
+my %parse_keyword = map { $_ => 1 } qw(
+    USERID_HINT
+    SIG_CREATED GOODSIG BADSIG ERRSIG
+    END_ENCRYPTION
+    DECRYPTION_FAILED DECRYPTION_OKAY
+    BAD_PASSPHRASE GOOD_PASSPHRASE
+    NO_SECKEY NO_PUBKEY
+    NO_RECP INV_RECP NODATA UNEXPECTED
+);
+
+# keywords we ignore without any messages as we parse them using other
+# keywords as starting point or just ignore as they are useless for us
+my %ignore_keyword = map { $_ => 1 } qw(
+    NEED_PASSPHRASE MISSING_PASSPHRASE BEGIN_SIGNING PLAINTEXT PLAINTEXT_LENGTH
+    BEGIN_ENCRYPTION SIG_ID VALIDSIG
+    ENC_TO BEGIN_DECRYPTION END_DECRYPTION GOODMDC
+    TRUST_UNDEFINED TRUST_NEVER TRUST_MARGINAL TRUST_FULLY TRUST_ULTIMATE
+);
+
+sub ParseStatus {
+    my $status = shift;
+    return () unless $status;
+
+    my @status;
+    while ( $status =~ /\[GNUPG:\]\s*(.*?)(?=\[GNUPG:\]|\z)/igms ) {
+        push @status, $1; $status[-1] =~ s/\s+/ /g; $status[-1] =~ s/\s+$//;
+    }
+    $status = join "\n", @status;
+    study $status;
+
+    my @res;
+    my (%user_hint, $latest_user_main_key);
+    for ( my $i = 0; $i < @status; $i++ ) {
+        my $line = $status[$i];
+        my ($keyword, $args) = ($line =~ /^(\S+)\s*(.*)$/s);
+        if ( $simple_keyword{ $keyword } ) {
+            push @res, $simple_keyword{ $keyword };
+            $res[-1]->{'Keyword'} = $keyword;
+            next;
+        }
+        unless ( $parse_keyword{ $keyword } ) {
+            $RT::Logger->warning("Skipped $keyword") unless $ignore_keyword{ $keyword };
+            next;
+        }
+
+        if ( $keyword eq 'USERID_HINT' ) {
+            my %tmp = _ParseUserHint($status, $line);
+            $latest_user_main_key = $tmp{'MainKey'};
+            if ( $user_hint{ $tmp{'MainKey'} } ) {
+                while ( my ($k, $v) = each %tmp ) {
+                    $user_hint{ $tmp{'MainKey'} }->{$k} = $v;
+                }
+            } else {
+                $user_hint{ $tmp{'MainKey'} } = \%tmp;
+            }
+            next;
+        }
+        elsif ( $keyword eq 'BAD_PASSPHRASE' || $keyword eq 'GOOD_PASSPHRASE' ) {
+            my $key_id = $args;
+            my %res = (
+                Operation => 'PassphraseCheck',
+                Status    => $keyword eq 'BAD_PASSPHRASE'? 'BAD' : 'DONE',
+                Key       => $key_id,
+            );
+            $res{'Status'} = 'MISSING' if $status[ $i - 1 ] =~ /^MISSING_PASSPHRASE/;
+            foreach my $line ( reverse @status[ 0 .. $i-1 ] ) {
+                next unless $line =~ /^NEED_PASSPHRASE\s+(\S+)\s+(\S+)\s+(\S+)/;
+                next if $key_id && $2 ne $key_id;
+                @res{'MainKey', 'Key', 'KeyType'} = ($1, $2, $3);
+                last;
+            }
+            $res{'Message'} = ucfirst( lc( $res{'Status'} eq 'DONE'? 'GOOD': $res{'Status'} ) ) .' passphrase';
+            $res{'User'} = ( $user_hint{ $res{'MainKey'} } ||= {} ) if $res{'MainKey'};
+            if ( exists $res{'User'}->{'EmailAddress'} ) {
+                $res{'Message'} .= ' for '. $res{'User'}->{'EmailAddress'};
+            } else {
+                $res{'Message'} .= " for '0x$key_id'";
+            }
+            push @res, \%res;
+        }
+        elsif ( $keyword eq 'END_ENCRYPTION' ) {
+            my %res = (
+                Operation => 'Encrypt',
+                Status    => 'DONE',
+                Message   => 'Data has been encrypted',
+            );
+            foreach my $line ( reverse @status[ 0 .. $i-1 ] ) {
+                next unless $line =~ /^BEGIN_ENCRYPTION\s+(\S+)\s+(\S+)/;
+                @res{'MdcMethod', 'SymAlgo'} = ($1, $2);
+                last;
+            }
+            push @res, \%res;
+        }
+        elsif ( $keyword eq 'DECRYPTION_FAILED' || $keyword eq 'DECRYPTION_OKAY' ) {
+            my %res = ( Operation => 'Decrypt' );
+            @res{'Status', 'Message'} = 
+                $keyword eq 'DECRYPTION_FAILED'
+                ? ('ERROR', 'Decryption failed')
+                : ('DONE',  'Decryption process succeeded');
+
+            foreach my $line ( reverse @status[ 0 .. $i-1 ] ) {
+                next unless $line =~ /^ENC_TO\s+(\S+)\s+(\S+)\s+(\S+)/;
+                my ($key, $alg, $key_length) = ($1, $2, $3);
+
+                my %encrypted_to = (
+                    Message   => "The message is encrypted to '0x$key'",
+                    User      => ( $user_hint{ $key } ||= {} ),
+                    Key       => $key,
+                    KeyLength => $key_length,
+                    Algorithm => $alg,
+                );
+
+                push @{ $res{'EncryptedTo'} ||= [] }, \%encrypted_to;
+            }
+
+            push @res, \%res;
+        }
+        elsif ( $keyword eq 'NO_SECKEY' || $keyword eq 'NO_PUBKEY' ) {
+            my ($key) = split /\s+/, $args;
+            my $type = $keyword eq 'NO_SECKEY'? 'secret': 'public';
+            my %res = (
+                Operation => 'KeyCheck',
+                Status    => 'MISSING',
+                Message   => ucfirst( $type ) ." key '0x$key' is not available",
+                Key       => $key,
+                KeyType   => $type,
+            );
+            $res{'User'} = ( $user_hint{ $key } ||= {} );
+            $res{'User'}{ ucfirst( $type ). 'KeyMissing' } = 1;
+            push @res, \%res;
+        }
+        # GOODSIG, BADSIG, VALIDSIG, TRUST_*
+        elsif ( $keyword eq 'GOODSIG' ) {
+            my %res = (
+                Operation  => 'Verify',
+                Status     => 'DONE',
+                Message    => 'The signature is good',
+            );
+            @res{qw(Key UserString)} = split /\s+/, $args, 2;
+            $res{'Message'} .= ', signed by '. $res{'UserString'};
+
+            foreach my $line ( @status[ $i .. $#status ] ) {
+                next unless $line =~ /^TRUST_(\S+)/;
+                $res{'Trust'} = $1;
+                last;
+            }
+            $res{'Message'} .= ', trust level is '. lc( $res{'Trust'} || 'unknown');
+
+            foreach my $line ( @status[ $i .. $#status ] ) {
+                next unless $line =~ /^VALIDSIG\s+(.*)/;
+                @res{ qw(
+                    Fingerprint
+                    CreationDate
+                    Timestamp
+                    ExpireTimestamp
+                    Version
+                    Reserved
+                    PubkeyAlgo
+                    HashAlgo
+                    Class
+                    PKFingerprint
+                    Other
+                ) } = split /\s+/, $1, 10;
+                last;
+            }
+            push @res, \%res;
+        }
+        elsif ( $keyword eq 'BADSIG' ) {
+            my %res = (
+                Operation  => 'Verify',
+                Status     => 'BAD',
+                Message    => 'The signature has not been verified okay',
+            );
+            @res{qw(Key UserString)} = split /\s+/, $args, 2;
+            push @res, \%res;
+        }
+        elsif ( $keyword eq 'ERRSIG' ) {
+            my %res = (
+                Operation => 'Verify',
+                Status    => 'ERROR',
+                Message   => 'Not possible to check the signature',
+            );
+            @res{qw(Key PubkeyAlgo HashAlgo Class Timestamp ReasonCode Other)}
+                = split /\s+/, $args, 7;
+
+            $res{'Reason'} = ReasonCodeToText( $keyword, $res{'ReasonCode'} );
+            $res{'Message'} .= ", the reason is ". $res{'Reason'};
+
+            push @res, \%res;
+        }
+        elsif ( $keyword eq 'SIG_CREATED' ) {
+            # SIG_CREATED      
+            my @props = split /\s+/, $args;
+            push @res, {
+                Operation      => 'Sign',
+                Status         => 'DONE',
+                Message        => "Signed message",
+                Type           => $props[0],
+                PubKeyAlgo     => $props[1],
+                HashKeyAlgo    => $props[2],
+                Class          => $props[3],
+                Timestamp      => $props[4],
+                KeyFingerprint => $props[5],
+                User           => $user_hint{ $latest_user_main_key },
+            };
+            $res[-1]->{Message} .= ' by '. $user_hint{ $latest_user_main_key }->{'EmailAddress'}
+                if $user_hint{ $latest_user_main_key };
+        }
+        elsif ( $keyword eq 'INV_RECP' ) {
+            my ($rcode, $recipient) = split /\s+/, $args, 2;
+            my $reason = ReasonCodeToText( $keyword, $rcode );
+            push @res, {
+                Operation  => 'RecipientsCheck',
+                Status     => 'ERROR',
+                Message    => "Recipient '$recipient' is unusable, the reason is '$reason'",
+                Recipient  => $recipient,
+                ReasonCode => $rcode,
+                Reason     => $reason,
+            };
+        }
+        elsif ( $keyword eq 'NODATA' ) {
+            my $rcode = (split /\s+/, $args)[0];
+            my $reason = ReasonCodeToText( $keyword, $rcode );
+            push @res, {
+                Operation  => 'Data',
+                Status     => 'ERROR',
+                Message    => "No data has been found. The reason is '$reason'",
+                ReasonCode => $rcode,
+                Reason     => $reason,
+            };
+        }
+        else {
+            $RT::Logger->warning("Keyword $keyword is unknown");
+            next;
+        }
+        $res[-1]{'Keyword'} = $keyword if @res && !$res[-1]{'Keyword'};
+    }
+    return @res;
+}
+
+sub _ParseUserHint {
+    my ($status, $hint) = (@_);
+    my ($main_key_id, $user_str) = ($hint =~ /^USERID_HINT\s+(\S+)\s+(.*)$/);
+    return () unless $main_key_id;
+    return (
+        MainKey      => $main_key_id,
+        String       => $user_str,
+        EmailAddress => (map $_->address, Email::Address->parse( $user_str ))[0],
+    );
+}
+
+sub _PrepareGnuPGOptions {
+    my %opt = @_;
+    my %res = map { lc $_ => $opt{ $_ } } grep $supported_opt{ lc $_ }, keys %opt;
+    $res{'extra_args'} ||= [];
+    foreach my $o ( grep !$supported_opt{ lc $_ }, keys %opt ) {
+        push @{ $res{'extra_args'} }, '--'. lc $o;
+        push @{ $res{'extra_args'} }, $opt{ $o }
+            if defined $opt{ $o };
+    }
+    return %res;
+}
+
+{ my %key;
+# no args -> clear
+# one arg -> return preferred key
+# many -> set
+sub UseKeyForEncryption {
+    unless ( @_ ) {
+        %key = ();
+    } elsif ( @_ > 1 ) {
+        %key = (%key, @_);
+        $key{ lc($_) } = delete $key{ $_ } foreach grep lc ne $_, keys %key;
+    } else {
+        return $key{ $_[0] };
+    }
+    return ();
+} }
+
+=head2 UseKeyForSigning
+
+Returns or sets identifier of the key that should be used for signing.
+
+Returns the current value when called without arguments.
+
+Sets new value when called with one argument and unsets if it's undef.
+
+=cut
+
+{ my $key;
+sub UseKeyForSigning {
+    if ( @_ ) {
+        $key = $_[0];
+    }
+    return $key;
+} }
+
+=head2 GetKeysForEncryption
+
+Takes identifier and returns keys suitable for encryption.
+
+B that keys for which trust level is not set are
+also listed.
+
+=cut
+
+sub GetKeysForEncryption {
+    my $key_id = shift;
+    my %res = GetKeysInfo( $key_id, 'public', @_ );
+    return %res if $res{'exit_code'};
+    return %res unless $res{'info'};
+
+    foreach my $key ( splice @{ $res{'info'} } ) {
+        # skip disabled keys
+        next if $key->{'Capabilities'} =~ /D/;
+        # skip keys not suitable for encryption
+        next unless $key->{'Capabilities'} =~ /e/i;
+        # skip disabled, expired, revoke and keys with no trust,
+        # but leave keys with unknown trust level
+        next if $key->{'TrustLevel'} < 0;
+
+        push @{ $res{'info'} }, $key;
+    }
+    delete $res{'info'} unless @{ $res{'info'} };
+    return %res;
+}
+
+sub GetKeysForSigning {
+    my $key_id = shift;
+    return GetKeysInfo( $key_id, 'private', @_ );
+}
+
+sub CheckRecipients {
+    my @recipients = (@_);
+
+    my ($status, @issues) = (1, ());
+
+    my %seen;
+    foreach my $address ( grep !$seen{ lc $_ }++, map $_->address, @recipients ) {
+        my %res = GetKeysForEncryption( $address );
+        if ( $res{'info'} && @{ $res{'info'} } == 1 && $res{'info'}[0]{'TrustLevel'} > 0 ) {
+            # good, one suitable and trusted key 
+            next;
+        }
+        my $user = RT::User->new( $RT::SystemUser );
+        $user->LoadByEmail( $address );
+        # it's possible that we have no User record with the email
+        $user = undef unless $user->id;
+
+        if ( my $fpr = UseKeyForEncryption( $address ) ) {
+            if ( $res{'info'} && @{ $res{'info'} } ) {
+                next if
+                    grep lc $_->{'Fingerprint'} eq lc $fpr,
+                    grep $_->{'TrustLevel'} > 0,
+                    @{ $res{'info'} };
+            }
+
+            $status = 0;
+            my %issue = (
+                EmailAddress => $address,
+                $user? (User => $user) : (),
+                Keys => undef,
+            );
+            $issue{'Message'} = "Selected key either is not trusted or doesn't exist anymore."; #loc
+            push @issues, \%issue;
+            next;
+        }
+
+        my $prefered_key;
+        $prefered_key = $user->PreferredKey if $user;
+        #XXX: prefered key is not yet implemented...
+
+        # classify errors
+        $status = 0;
+        my %issue = (
+            EmailAddress => $address,
+            $user? (User => $user) : (),
+            Keys => undef,
+        );
+
+        unless ( $res{'info'} && @{ $res{'info'} } ) {
+            # no key
+            $issue{'Message'} = "There is no key suitable for encryption."; #loc
+        }
+        elsif ( @{ $res{'info'} } == 1 && !$res{'info'}[0]{'TrustLevel'} ) {
+            # trust is not set
+            $issue{'Message'} = "There is one suitable key, but trust level is not set."; #loc
+        }
+        else {
+            # multiple keys
+            $issue{'Message'} = "There are several keys suitable for encryption."; #loc
+        }
+        push @issues, \%issue;
+    }
+    return ($status, @issues);
+}
+
+sub GetPublicKeyInfo {
+    return GetKeyInfo( shift, 'public', @_ );
+}
+
+sub GetPrivateKeyInfo {
+    return GetKeyInfo( shift, 'private', @_ );
+}
+
+sub GetKeyInfo {
+    my %res = GetKeysInfo(@_);
+    $res{'info'} = $res{'info'}->[0];
+    return %res;
+}
+
+sub GetKeysInfo {
+    my $email = shift;
+    my $type = shift || 'public';
+    my $force = shift;
+
+    unless ( $email ) {
+        return (exit_code => 0) unless $force;
+    }
+
+    my $gnupg = new GnuPG::Interface;
+    my %opt = RT->Config->Get('GnuPGOptions');
+    $opt{'digest-algo'} ||= 'SHA1';
+    $opt{'with-colons'} = undef; # parseable format
+    $opt{'fingerprint'} = undef; # show fingerprint
+    $opt{'fixed-list-mode'} = undef; # don't merge uid with keys
+    $gnupg->options->hash_init(
+        _PrepareGnuPGOptions( %opt ),
+        armor => 1,
+        meta_interactive => 0,
+    );
+
+    my %res;
+
+    my ($handles, $handle_list) = _make_gpg_handles();
+    my %handle = %$handle_list;
+
+    eval {
+        local $SIG{'CHLD'} = 'DEFAULT';
+        my $method = $type eq 'private'? 'list_secret_keys': 'list_public_keys';
+        my $pid = safe_run_child { $gnupg->$method( handles => $handles, $email? (command_args => $email) : () ) };
+        close $handle{'stdin'};
+        waitpid $pid, 0;
+    };
+
+    my @info = readline $handle{'stdout'};
+    close $handle{'stdout'};
+
+    $res{'exit_code'} = $?;
+    foreach ( qw(stderr logger status) ) {
+        $res{$_} = do { local $/; readline $handle{$_} };
+        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
+        close $handle{$_};
+    }
+    $RT::Logger->debug( $res{'status'} ) if $res{'status'};
+    $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
+    $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
+    if ( $@ || $? ) {
+        $res{'message'} = $@? $@: "gpg exitted with error code ". ($? >> 8);
+        return %res;
+    }
+
+    @info = ParseKeysInfo( @info );
+    $res{'info'} = \@info;
+    return %res;
+}
+
+sub ParseKeysInfo {
+    my @lines = @_;
+
+    my %gpg_opt = RT->Config->Get('GnuPGOptions');
+
+    my @res = ();
+    foreach my $line( @lines ) {
+        chomp $line;
+        my $tag;
+        ($tag, $line) = split /:/, $line, 2;
+        if ( $tag eq 'pub' ) {
+            my %info;
+            @info{ qw(
+                TrustChar KeyLength Algorithm Key
+                Created Expire Empty OwnerTrustChar
+                Empty Empty Capabilities Other
+            ) } = split /:/, $line, 12;
+
+            # workaround gnupg's wierd behaviour, --list-keys command report calculated trust levels
+            # for any model except 'always', so you can change models and see changes, but not for 'always'
+            # we try to handle it in a simple way - we set ultimate trust for any key with trust
+            # level >= 0 if trust model is 'always'
+            my $always_trust;
+            $always_trust = 1 if exists $gpg_opt{'always-trust'};
+            $always_trust = 1 if exists $gpg_opt{'trust-model'} && $gpg_opt{'trust-model'} eq 'always';
+            @info{qw(Trust TrustTerse TrustLevel)} = 
+                _ConvertTrustChar( $info{'TrustChar'} );
+            if ( $always_trust && $info{'TrustLevel'} >= 0 ) {
+                @info{qw(Trust TrustTerse TrustLevel)} = 
+                    _ConvertTrustChar( 'u' );
+            }
+
+            @info{qw(OwnerTrust OwnerTrustTerse OwnerTrustLevel)} = 
+                _ConvertTrustChar( $info{'OwnerTrustChar'} );
+            $info{ $_ } = _ParseDate( $info{ $_ } )
+                foreach qw(Created Expire);
+            push @res, \%info;
+        }
+        elsif ( $tag eq 'sec' ) {
+            my %info;
+            @info{ qw(
+                Empty KeyLength Algorithm Key
+                Created Expire Empty OwnerTrustChar
+                Empty Empty Capabilities Other
+            ) } = split /:/, $line, 12;
+            @info{qw(OwnerTrust OwnerTrustTerse OwnerTrustLevel)} = 
+                _ConvertTrustChar( $info{'OwnerTrustChar'} );
+            $info{ $_ } = _ParseDate( $info{ $_ } )
+                foreach qw(Created Expire);
+            push @res, \%info;
+        }
+        elsif ( $tag eq 'uid' ) {
+            my %info;
+            @info{ qw(Trust Created Expire String) }
+                = (split /:/, $line)[0,4,5,8];
+            $info{ $_ } = _ParseDate( $info{ $_ } )
+                foreach qw(Created Expire);
+            push @{ $res[-1]{'User'} ||= [] }, \%info;
+        }
+        elsif ( $tag eq 'fpr' ) {
+            $res[-1]{'Fingerprint'} = (split /:/, $line, 10)[8];
+        }
+    }
+    return @res;
+}
+
+{
+    my %verbose = (
+        # deprecated
+        d   => [
+            "The key has been disabled", #loc
+            "key disabled", #loc
+            "-2"
+        ],
+
+        r   => [
+            "The key has been revoked", #loc
+            "key revoked", #loc
+            -3,
+        ],
+
+        e   => [ "The key has expired", #loc
+            "key expired", #loc
+            '-4',
+        ],
+
+        n   => [ "Don't trust this key at all", #loc
+            'none', #loc
+            -1,
+        ],
+
+        #gpupg docs says that '-' and 'q' may safely be treated as the same value
+        '-' => [
+            'Unknown (no trust value assigned)', #loc
+            'not set',
+            0,
+        ],
+        q   => [
+            'Unknown (no trust value assigned)', #loc
+            'not set',
+            0,
+        ],
+        o   => [
+            'Unknown (this value is new to the system)', #loc
+            'unknown',
+            0,
+        ],
+
+        m   => [
+            "There is marginal trust in this key", #loc
+            'marginal', #loc
+            1,
+        ],
+        f   => [
+            "The key is fully trusted", #loc
+            'full', #loc
+            2,
+        ],
+        u   => [
+            "The key is ultimately trusted", #loc
+            'ultimate', #loc
+            3,
+        ],
+    );
+
+    sub _ConvertTrustChar {
+        my $value = shift;
+        return @{ $verbose{'-'} } unless $value;
+        $value = substr $value, 0, 1;
+        return @{ $verbose{ $value } || $verbose{'o'} };
+    }
+}
+
+sub _ParseDate {
+    my $value = shift;
+    # never
+    return $value unless $value;
+
+    require RT::Date;
+    my $obj = RT::Date->new( $RT::SystemUser );
+    # unix time
+    if ( $value =~ /^\d+$/ ) {
+        $obj->Set( Value => $value );
+    } else {
+        $obj->Set( Format => 'unknown', Value => $value, Timezone => 'utc' );
+    }
+    return $obj;
+}
+
+sub DeleteKey {
+    my $key = shift;
+
+    my $gnupg = new GnuPG::Interface;
+    my %opt = RT->Config->Get('GnuPGOptions');
+    $gnupg->options->hash_init(
+        _PrepareGnuPGOptions( %opt ),
+        meta_interactive => 0,
+    );
+
+    my ($handles, $handle_list) = _make_gpg_handles();
+    my %handle = %$handle_list;
+
+    eval {
+        local $SIG{'CHLD'} = 'DEFAULT';
+        local @ENV{'LANG', 'LC_ALL'} = ('C', 'C');
+        my $pid = safe_run_child { $gnupg->wrap_call(
+            handles => $handles,
+            commands => ['--delete-secret-and-public-key'],
+            command_args => [$key],
+        ) };
+        close $handle{'stdin'};
+        while ( my $str = readline $handle{'status'} ) {
+            if ( $str =~ /^\[GNUPG:\]\s*GET_BOOL delete_key\..*/ ) {
+                print { $handle{'command'} } "y\n";
+            }
+        }
+        waitpid $pid, 0;
+    };
+    my $err = $@;
+    close $handle{'stdout'};
+
+    my %res;
+    $res{'exit_code'} = $?;
+    foreach ( qw(stderr logger status) ) {
+        $res{$_} = do { local $/; readline $handle{$_} };
+        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
+        close $handle{$_};
+    }
+    $RT::Logger->debug( $res{'status'} ) if $res{'status'};
+    $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
+    $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
+    if ( $err || $res{'exit_code'} ) {
+        $res{'message'} = $err? $err : "gpg exitted with error code ". ($res{'exit_code'} >> 8);
+    }
+    return %res;
+}
+
+sub ImportKey {
+    my $key = shift;
+
+    my $gnupg = new GnuPG::Interface;
+    my %opt = RT->Config->Get('GnuPGOptions');
+    $gnupg->options->hash_init(
+        _PrepareGnuPGOptions( %opt ),
+        meta_interactive => 0,
+    );
+
+    my ($handles, $handle_list) = _make_gpg_handles();
+    my %handle = %$handle_list;
+
+    eval {
+        local $SIG{'CHLD'} = 'DEFAULT';
+        local @ENV{'LANG', 'LC_ALL'} = ('C', 'C');
+        my $pid = safe_run_child { $gnupg->wrap_call(
+            handles => $handles,
+            commands => ['--import'],
+        ) };
+        print { $handle{'stdin'} } $key;
+        close $handle{'stdin'};
+        waitpid $pid, 0;
+    };
+    my $err = $@;
+    close $handle{'stdout'};
+
+    my %res;
+    $res{'exit_code'} = $?;
+    foreach ( qw(stderr logger status) ) {
+        $res{$_} = do { local $/; readline $handle{$_} };
+        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
+        close $handle{$_};
+    }
+    $RT::Logger->debug( $res{'status'} ) if $res{'status'};
+    $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
+    $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
+    if ( $err || $res{'exit_code'} ) {
+        $res{'message'} = $err? $err : "gpg exitted with error code ". ($res{'exit_code'} >> 8);
+    }
+    return %res;
+}
+
+=head2 KEY
+
+Signs a small message with the key, to make sure the key exists and 
+we have a useable passphrase. The first argument MUST be a key identifier
+of the signer: either email address, key id or finger print.
+
+Returns a true value if all went well.
+
+=cut
+
+sub DrySign {
+    my $from = shift;
+
+    my $mime = MIME::Entity->build(
+        Type    => "text/plain",
+        From    => 'nobody@localhost',
+        To      => 'nobody@localhost',
+        Subject => "dry sign",
+        Data    => ['t'],
+    );
+
+    my %res = SignEncrypt(
+        Sign    => 1,
+        Encrypt => 0,
+        Entity  => $mime,
+        Signer  => $from,
+    );
+
+    return $res{exit_code} == 0;
+}
+
+1;
+
+=head2 Probe
+
+This routine returns true if RT's GnuPG support is configured and working 
+properly (and false otherwise).
+
+
+=cut
+
+
+sub Probe {
+    my $gnupg = new GnuPG::Interface;
+    my %opt = RT->Config->Get('GnuPGOptions');
+    $gnupg->options->hash_init(
+        _PrepareGnuPGOptions( %opt ),
+        armor => 1,
+        meta_interactive => 0,
+    );
+
+    my ($handles, $handle_list) = _make_gpg_handles();
+    my %handle = %$handle_list;
+
+    local $@;
+    eval {
+        local $SIG{'CHLD'} = 'DEFAULT';
+        my $pid = safe_run_child { $gnupg->wrap_call( commands => ['--version' ], handles => $handles ) };
+        close $handle{'stdin'};
+        waitpid $pid, 0;
+    };
+    if ( $@ ) {
+        $RT::Logger->debug(
+            "Probe for GPG failed."
+            ." Couldn't run `gpg --version`: ". $@
+        );
+        return 0;
+    }
+
+# on some systems gpg exits with code 2, but still 100% functional,
+# it's general error system error or incorrect command, command is correct,
+# but there is no way to get actuall error
+    if ( $? && ($? >> 8) != 2 ) {
+        $RT::Logger->debug(
+            "Probe for GPG failed."
+            ." Process exitted with code ". ($? >> 8)
+            . ($? & 127 ? (" as recieved signal ". ($? & 127)) : '')
+        );
+        return 0;
+    }
+    return 1;
+}
+
+
+sub _make_gpg_handles {
+    my %handle_map = (
+        stdin  => IO::Handle->new(),
+        stdout => IO::Handle->new(),
+        stderr => IO::Handle->new(),
+        logger => IO::Handle->new(),
+        status => IO::Handle->new(),
+        command => IO::Handle->new(),
+
+
+            @_);
+
+    my $handles = GnuPG::Handles->new(%handle_map);
+    return ($handles, \%handle_map);
+}
+
+eval "require RT::Crypt::GnuPG_Vendor";
+if ($@ && $@ !~ qr{^Can't locate RT/Crypt/GnuPG_Vendor.pm}) {
+    die $@;
+};
+
+eval "require RT::Crypt::GnuPG_Local";
+if ($@ && $@ !~ qr{^Can't locate RT/Crypt/GnuPG_Local.pm}) {
+    die $@;
+};
+
+# helper package to avoid using temp file
+package IO::Handle::CRLF;
+
+use base qw(IO::Handle);
+
+sub print {
+    my ($self, @args) = (@_);
+    s/\r*\n/\x0D\x0A/g foreach @args;
+    return $self->SUPER::print( @args );
+}
+
+1;
diff --git a/rt/lib/RT/CurrentUser.pm b/rt/lib/RT/CurrentUser.pm
index 3193034a5..b674d4e60 100755
--- a/rt/lib/RT/CurrentUser.pm
+++ b/rt/lib/RT/CurrentUser.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,39 +45,55 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 =head1 NAME
 
   RT::CurrentUser - an RT object representing the current user
 
 =head1 SYNOPSIS
 
-  use RT::CurrentUser
+    use RT::CurrentUser;
+
+    # laod
+    my $current_user = new RT::CurrentUser;
+    $current_user->Load(...);
+    # or
+    my $current_user = RT::CurrentUser->new( $user_obj );
+    # or
+    my $current_user = RT::CurrentUser->new( $address || $name || $id );
+
+    # manipulation
+    $current_user->UserObj->SetName('new_name');
 
 
 =head1 DESCRIPTION
 
+B subclass of L class. Used to define the current
+user. You should pass an instance of this class to constructors of
+many RT classes, then the instance used to check ACLs and localize
+strings.
 
 =head1 METHODS
 
+See also L for a list of methods this class has.
 
-=begin testing
+=head2 new
 
-ok (require RT::CurrentUser);
-
-=end testing
+Returns new CurrentUser object. Unlike all other classes of RT it takes
+either subclass of C class object or scalar value that is
+passed to Load method.
 
 =cut
 
 
 package RT::CurrentUser;
 
-use RT::Record;
 use RT::I18N;
 
 use strict;
-use base qw/RT::Record/;
+use warnings;
 
-# {{{ sub _Init 
+use base qw/RT::User/;
 
 #The basic idea here is that $self->CurrentUser is always supposed
 # to be a CurrentUser object. but that's hard to do when we're trying to load
@@ -89,107 +105,69 @@ sub _Init {
 
     $self->{'table'} = "Users";
 
-    if ( defined($User) ) {
-
-        if (   UNIVERSAL::isa( $User, 'RT::User' )
-            || UNIVERSAL::isa( $User, 'RT::CurrentUser' ) )
-        {
-            $self->Load( $User->id );
+    if ( defined $User ) {
 
+        if ( UNIVERSAL::isa( $User, 'RT::User' ) ) {
+            $self->LoadById( $User->id );
         }
-        elsif ( ref($User) ) {
+        elsif ( ref $User ) {
             $RT::Logger->crit(
                 "RT::CurrentUser->new() called with a bogus argument: $User");
         }
         else {
-            $self->Load($User);
+            $self->Load( $User );
         }
     }
 
-    $self->_BuildTableAttributes();
+    $self->_BuildTableAttributes;
 
 }
-# }}}
 
-# {{{ sub Create
+=head2 Create, Delete and Set*
+
+As stated above it's a subclass of L, but this class is read-only
+and calls to these methods are illegal. Return 'permission denied' message
+and log an error.
+
+=cut
 
 sub Create {
     my $self = shift;
+    $RT::Logger->error('RT::CurrentUser is read-only, RT::User for manipulation');
     return (0, $self->loc('Permission Denied'));
 }
 
-# }}}
-
-# {{{ sub Delete
-
 sub Delete {
     my $self = shift;
+    $RT::Logger->error('RT::CurrentUser is read-only, RT::User for manipulation');
     return (0, $self->loc('Permission Denied'));
 }
 
-# }}}
-
-# {{{ sub UserObj
-
-=head2 UserObj
-
-  Returns the RT::User object associated with this CurrentUser object.
-
-=cut
-
-sub UserObj {
+sub _Set {
     my $self = shift;
-    
-	use RT::User;
-	my $user = RT::User->new($self);
-
-	unless ($user->Load($self->Id)) {
-	    $RT::Logger->err($self->loc("Couldn't load [_1] from the users database.\n", $self->Id));
-	}
-    return ($user);
+    $RT::Logger->error('RT::CurrentUser is read-only, RT::User for manipulation');
+    return (0, $self->loc('Permission Denied'));
 }
-# }}}
-
-# {{{ sub PrincipalObj 
 
-=head2 PrincipalObj
+=head2 UserObj
 
-    Returns this user's principal object.  this is just a helper routine for
-    $self->UserObj->PrincipalObj
+Returns the L object associated with this CurrentUser object.
 
 =cut
 
-sub PrincipalObj {
+sub UserObj {
     my $self = shift;
-    return($self->UserObj->PrincipalObj);
-}
-
 
-# }}}
-
-
-# {{{ sub PrincipalId 
-
-=head2 PrincipalId
-
-    Returns this user's principal Id.  this is just a helper routine for
-    $self->UserObj->PrincipalId
-
-=cut
-
-sub PrincipalId {
-    my $self = shift;
-    return($self->UserObj->PrincipalId);
+    my $user = RT::User->new( $self );
+    unless ( $user->LoadById( $self->Id ) ) {
+        $RT::Logger->error(
+            $self->loc("Couldn't load [_1] from the users database.\n", $self->Id)
+        );
+    }
+    return $user;
 }
 
-
-# }}}
-
-
-# {{{ sub _Accessible 
-
-
- sub _CoreAccessible  {
+sub _CoreAccessible  {
      {
          Name           => { 'read' => 1 },
            Gecos        => { 'read' => 1 },
@@ -200,29 +178,6 @@ sub PrincipalId {
      };
   
 }
-# }}}
-
-# {{{ sub LoadByEmail
-
-=head2 LoadByEmail
-
-Loads a User into this CurrentUser object.
-Takes the email address of the user to load.
-
-=cut
-
-sub LoadByEmail  {
-    my $self = shift;
-    my $identifier = shift;
-
-    $identifier = RT::User::CanonicalizeEmailAddress(undef, $identifier);
-        
-    $self->LoadByCol("EmailAddress",$identifier);
-    
-}
-# }}}
-
-# {{{ sub LoadByGecos
 
 =head2 LoadByGecos
 
@@ -233,14 +188,8 @@ Takes a unix username as its only argument.
 
 sub LoadByGecos  {
     my $self = shift;
-    my $identifier = shift;
-        
-    $self->LoadByCol("Gecos",$identifier);
-    
+    return $self->LoadByCol( "Gecos", shift );
 }
-# }}}
-
-# {{{ sub LoadByName
 
 =head2 LoadByName
 
@@ -251,154 +200,50 @@ Takes a Name.
 
 sub LoadByName {
     my $self = shift;
-    my $identifier = shift;
-    $self->LoadByCol("Name",$identifier);
-    
-}
-# }}}
-
-# {{{ sub Load 
-
-=head2 Load
-
-Loads a User into this CurrentUser object.
-Takes either an integer (users id column reference) or a Name
-The latter is deprecated. Instead, you should use LoadByName.
-Formerly, this routine also took email addresses. 
-
-=cut
-
-sub Load  {
-  my $self = shift;
-  my $identifier = shift;
-
-  #if it's an int, load by id. otherwise, load by name.
-  if ($identifier !~ /\D/) {
-    $self->SUPER::LoadById($identifier);
-  }
-
-  elsif (UNIVERSAL::isa($identifier,"RT::User")) {
-         # DWIM if they pass a user in
-         $self->SUPER::LoadById($identifier->Id);
-  } 
-  else {
-      # This is a bit dangerous, we might get false authen if somebody
-      # uses ambigous userids or real names:
-      $self->LoadByCol("Name",$identifier);
-  }
-}
-
-# }}}
-
-# {{{ sub IsPassword
-
-=head2 IsPassword
-
-Takes a password as a string.  Passes it off to IsPassword in this
-user's UserObj.  If it is the user's password and the user isn't
-disabled, returns 1.
-
-Otherwise, returns undef.
-
-=cut
-
-sub IsPassword { 
-  my $self = shift;
-  my $value = shift;
-  
-  return ($self->UserObj->IsPassword($value)); 
-}
-
-# }}}
-
-# {{{ sub Privileged
-
-=head2 Privileged
-
-Returns true if the current user can be granted rights and be
-a member of groups.
-
-=cut
-
-sub Privileged {
-    my $self = shift;
-    return ($self->UserObj->Privileged());
-}
-
-# }}}
-
-
-# {{{ sub HasRight
-
-=head2 HasRight
-
-calls $self->UserObj->HasRight with the arguments passed in
-
-=cut
-
-sub HasRight {
-  my $self = shift;
-  return ($self->UserObj->HasRight(@_));
+    return $self->LoadByCol( "Name", shift );
 }
 
-# }}}
-
-# {{{ Localization
-
 =head2 LanguageHandle
 
 Returns this current user's langauge handle. Should take a language
 specification. but currently doesn't
 
-=begin testing
-
-ok (my $cu = RT::CurrentUser->new('root'));
-ok (my $lh = $cu->LanguageHandle('en-us'));
-ok (defined $lh);
-ok ($lh->isa('Locale::Maketext'));
-is ($cu->loc('TEST_STRING'), "Concrete Mixer", "Localized TEST_STRING into English");
-ok ($lh = $cu->LanguageHandle('fr'));
-SKIP: {
-    skip "fr locale is not loaded", 1 unless grep $_ eq 'fr', @RT::LexiconLanguages;
-    is ($cu->loc('Before'), "Avant", "Localized TEST_STRING into Frenc");
-}
-
-=end testing
-
 =cut 
 
 sub LanguageHandle {
     my $self = shift;
-    if (   ( !defined $self->{'LangHandle'} )
-        || ( !UNIVERSAL::can( $self->{'LangHandle'}, 'maketext' ) )
-        || (@_) ) {
-        if ( !$RT::SystemUser or ($self->id || 0) == $RT::SystemUser->id() ) {
-            @_ = qw(en-US);
+    if (   !defined $self->{'LangHandle'}
+        || !UNIVERSAL::can( $self->{'LangHandle'}, 'maketext' )
+        || @_ )
+    {
+        if ( my $lang = $self->Lang ) {
+            push @_, $lang;
         }
-
-        elsif ( $self->Lang ) {
-            push @_, $self->Lang;
+        elsif ( $self->id && ($self->id == ($RT::SystemUser->id||0) || $self->id == ($RT::Nobody->id||0)) ) {
+            # don't use ENV magic for system users
+            push @_, 'en';
         }
+
         $self->{'LangHandle'} = RT::I18N->get_handle(@_);
     }
 
     # Fall back to english.
     unless ( $self->{'LangHandle'} ) {
-        die "We couldn't get a dictionary. Nye mogu naidti slovar. No puedo encontrar dictionario.";
+        die "We couldn't get a dictionary. Ne mogu naidti slovar. No puedo encontrar dictionario.";
     }
-    return ( $self->{'LangHandle'} );
+    return $self->{'LangHandle'};
 }
 
 sub loc {
     my $self = shift;
-    return '' if $_[0] eq '';
+    return '' if !defined $_[0] || $_[0] eq '';
 
     my $handle = $self->LanguageHandle;
 
     if (@_ == 1) {
-	# pre-scan the lexicon hashes to return _AUTO keys verbatim,
-	# to keep locstrings containing '[' and '~' from tripping over Maketext
-	return $_[0] unless grep { exists $_->{$_[0]} } @{ $handle->_lex_refs };
+        # pre-scan the lexicon hashes to return _AUTO keys verbatim,
+        # to keep locstrings containing '[' and '~' from tripping over Maketext
+        return $_[0] unless grep exists $_->{$_[0]}, @{ $handle->_lex_refs };
     }
 
     return $handle->maketext(@_);
@@ -406,20 +251,17 @@ sub loc {
 
 sub loc_fuzzy {
     my $self = shift;
-    return '' if (!$_[0] ||  $_[0] eq '');
+    return '' if !defined $_[0] || $_[0] eq '';
 
     # XXX: work around perl's deficiency when matching utf8 data
     return $_[0] if Encode::is_utf8($_[0]);
-    my $result = $self->LanguageHandle->maketext_fuzzy(@_);
 
-    return($result);
+    return $self->LanguageHandle->maketext_fuzzy( @_ );
 }
-# }}}
-
 
 =head2 CurrentUser
 
-Return  the current currentuser object
+Return the current currentuser object
 
 =cut
 
@@ -437,9 +279,9 @@ representing whether the authentication succeeded.
 If both $nonce and $created are specified, validate $password against:
 
     encode_base64(sha1(
-	$nonce .
-	$created .
-	sha1_hex( "$username:$realm:$server_pass" )
+        $nonce .
+        $created .
+        sha1_hex( "$username:$realm:$server_pass" )
     ))
 
 where $server_pass is the md5_hex(password) digest stored in the
@@ -458,9 +300,9 @@ sub Authenticate {
     my $username = $self->UserObj->Name or return;
     my $server_pass = $self->UserObj->__Value('Password') or return;
     my $auth_digest = MIME::Base64::encode_base64(Digest::SHA1::sha1(
-	$nonce .
-	$created .
-	Digest::MD5::md5_hex("$username:$realm:$server_pass")
+        $nonce .
+        $created .
+        Digest::MD5::md5_hex("$username:$realm:$server_pass")
     ));
 
     chomp($password);
@@ -469,13 +311,9 @@ sub Authenticate {
     return ($password eq $auth_digest);
 }
 
-# }}}
-
-
 eval "require RT::CurrentUser_Vendor";
 die $@ if ($@ && $@ !~ qr{^Can't locate RT/CurrentUser_Vendor.pm});
 eval "require RT::CurrentUser_Local";
 die $@ if ($@ && $@ !~ qr{^Can't locate RT/CurrentUser_Local.pm});
 
 1;
- 
diff --git a/rt/lib/RT/CustomField.pm b/rt/lib/RT/CustomField.pm
index ba51d5b1c..995728f67 100644
--- a/rt/lib/RT/CustomField.pm
+++ b/rt/lib/RT/CustomField.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,6 +45,7 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by )
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
@@ -92,7 +93,7 @@ Create takes a hash of values and creates a row in the database:
   varchar(200) 'Name'.
   varchar(200) 'Type'.
   int(11) 'MaxValues'.
-  varchar(255) 'Pattern'.
+  longtext 'Pattern'.
   smallint(6) 'Repeated'.
   varchar(255) 'Description'.
   int(11) 'SortOrder'.
@@ -119,8 +120,10 @@ sub Create {
                 SortOrder => '0',
                 LookupType => '',
                 Disabled => '0',
+                LinkToValue => '',
+                IncludeContentForValue => '',
 
-		  @_);
+                  @_);
     $self->SUPER::Create(
                          Name => $args{'Name'},
                          Type => $args{'Type'},
@@ -131,6 +134,8 @@ sub Create {
                          SortOrder => $args{'SortOrder'},
                          LookupType => $args{'LookupType'},
                          Disabled => $args{'Disabled'},
+                         LinkToValue => $args{'LinkToValue'},
+                         IncludeContentForValue => $args{'IncludeContentForValue'}
 );
 
 }
@@ -203,7 +208,7 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
 =head2 Pattern
 
 Returns the current value of Pattern. 
-(In the database, Pattern is stored as varchar(255).)
+(In the database, Pattern is stored as longtext.)
 
 
 
@@ -212,7 +217,7 @@ Returns the current value of Pattern.
 
 Set Pattern to VALUE. 
 Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
-(In the database, Pattern will be stored as a varchar(255).)
+(In the database, Pattern will be stored as a longtext.)
 
 
 =cut
@@ -357,7 +362,7 @@ sub _CoreAccessible {
         MaxValues => 
 		{read => 1, write => 1, sql_type => 4, length => 11,  is_blob => 0,  is_numeric => 1,  type => 'int(11)', default => ''},
         Pattern => 
-		{read => 1, write => 1, sql_type => 12, length => 255,  is_blob => 0,  is_numeric => 0,  type => 'varchar(255)', default => ''},
+		{read => 1, write => 1, sql_type => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'longtext', default => ''},
         Repeated => 
 		{read => 1, write => 1, sql_type => 5, length => 6,  is_blob => 0,  is_numeric => 1,  type => 'smallint(6)', default => '0'},
         Description => 
diff --git a/rt/lib/RT/CustomFieldValue.pm b/rt/lib/RT/CustomFieldValue.pm
index 3a081769b..59edee3d7 100644
--- a/rt/lib/RT/CustomFieldValue.pm
+++ b/rt/lib/RT/CustomFieldValue.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,6 +45,7 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by )
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
diff --git a/rt/lib/RT/CustomFieldValue_Overlay.pm b/rt/lib/RT/CustomFieldValue_Overlay.pm
index be1070d6a..5511e520e 100644
--- a/rt/lib/RT/CustomFieldValue_Overlay.pm
+++ b/rt/lib/RT/CustomFieldValue_Overlay.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,6 +45,7 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 use warnings;
 use strict;
 
@@ -62,36 +63,114 @@ from being integers.
 
 sub Create {
     my $self = shift;
-    my %args = @_;
-    (defined $args{$_} or delete $args{$_}) for keys %args;
-    %args = ((CustomField => '0',
-              Name => '',
-              Description => '',
-              SortOrder => '0',
-              Category => ''), %args);
+    my %args = (
+        CustomField => 0,
+        Name        => '',
+        Description => '',
+        SortOrder   => 0,
+        Category    => '',
+        @_,
+    );
+
+    my $cf_id = ref $args{'CustomField'}? $args{'CustomField'}->id: $args{'CustomField'};
+
+    my $cf = RT::CustomField->new( $self->CurrentUser );
+    $cf->Load( $cf_id );
+    unless ( $cf->id ) {
+        return (0, $self->loc("Couldn't load Custom Field #[_1]", $cf_id));
+    }
+    unless ( $cf->CurrentUserHasRight('AdminCustomField') ) {
+        return (0, $self->loc('Permission Denied'));
+    }
 
     my ($id, $msg) = $self->SUPER::Create(
-        map {$_ => $args{$_}} qw(CustomField Name Description SortOrder)
+        CustomField => $cf_id,
+        map { $_ => $args{$_} } qw(Name Description SortOrder)
     );
-    if ($id and length $args{Category}) {
+    return ($id, $msg) unless $id;
+
+    if ( defined $args{'Category'} && length $args{'Category'} ) {
         # $self would be loaded at this stage
-        $self->SetCategory($args{Category});
+        my ($status, $msg) = $self->SetCategory( $args{'Category'} );
+        unless ( $status ) {
+            $RT::Logger->error("Couldn't set category: $msg");
+        }
     }
+
     return ($id, $msg);
 }
 
+=head2 Category
+
+Returns the Category assigned to this Value
+Returns udef if there is no Category
+
+=cut
+
 sub Category {
     my $self = shift;
     my $attr = $self->FirstAttribute('Category') or return undef;
     return $attr->Content;
 }
 
+=head2 SetCategory Category
+
+Takes a string Category and stores it as an attribute of this CustomFieldValue
+
+=cut
+
 sub SetCategory {
     my $self = shift;
     my $category = shift;
-    $self->SetAttribute(Name => 'Category', Content => $category);
+    if ( defined $category && length $category ) {
+        return $self->SetAttribute(
+            Name    => 'Category',
+            Content => $category,
+        );
+    }
+    else {
+        my ($status, $msg) = $self->DeleteAttribute( 'Category' );
+        unless ( $status ) {
+            $RT::Logger->warning("Couldn't delete atribute: $msg");
+        }
+        # return true even if there was no category
+        return (1, $self->loc('Category unset'));
+    }
 }
 
-sub ValidateName { 1 };
+sub ValidateName {
+    return defined $_[1] && length $_[1];
+};
+
+=head2 DeleteCategory
+
+Deletes the category associated with this value
+Returns -1 if there is no Category
+
+=cut
+
+sub DeleteCategory {
+    my $self = shift;
+    my $attr = $self->FirstAttribute('Category') or return (-1,'No Category Set');
+    return $attr->Delete;
+}
+
+=head2 Delete
+
+Make sure we delete our Category when we're deleted
+
+=cut
+
+sub Delete {
+    my $self = shift;
+
+    my ($result, $msg) = $self->DeleteCategory;
+
+    unless ($result) {
+        return ($result, $msg);
+    }
+
+    return $self->SUPER::Delete(@_);
+}
 
 1;
diff --git a/rt/lib/RT/CustomFieldValues.pm b/rt/lib/RT/CustomFieldValues.pm
index 32ab860e8..3539a804a 100644
--- a/rt/lib/RT/CustomFieldValues.pm
+++ b/rt/lib/RT/CustomFieldValues.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,6 +45,7 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by )
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
diff --git a/rt/lib/RT/CustomFieldValues/External.pm b/rt/lib/RT/CustomFieldValues/External.pm
new file mode 100644
index 000000000..645f13678
--- /dev/null
+++ b/rt/lib/RT/CustomFieldValues/External.pm
@@ -0,0 +1,235 @@
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
+package RT::CustomFieldValues::External;
+
+use strict;
+use warnings;
+
+use base qw(RT::CustomFieldValues);
+
+=head1 NAME
+
+RT::CustomFieldValues::External - Pull possible values for a custom
+field from an arbitrary external data source.
+
+=head1 SYNOPSIS
+
+Custom field value lists can be produced by creating a class that
+inherits from C, and overloading
+C and C.  See
+L for a simple example.
+
+=head1 DESCRIPTION
+
+Subclasses should implement the following methods:
+
+=head2 SourceDescription
+
+This method should return a string describing the data source; this is
+the identifier by which the user will see the dropdown.
+
+=head2 ExternalValues
+
+This method should return an array reference of hash references.  The
+hash references should contain keys for C, C, and
+C.
+
+=head1 SEE ALSO
+
+L
+
+=cut
+
+sub _Init {
+    my $self = shift;
+    $self->Table( '' );
+    return ( $self->SUPER::_Init(@_) );
+}
+
+sub CleanSlate {
+    my $self = shift;
+    delete $self->{ $_ } foreach qw(
+        __external_cf
+        __external_cf_limits
+    );
+    return $self->SUPER::CleanSlate(@_);
+}
+
+sub _ClonedAttributes {
+    my $self = shift;
+    return qw(
+        __external_cf
+        __external_cf_limits
+    ), $self->SUPER::_ClonedAttributes;
+}
+
+sub Limit {
+    my $self = shift;
+    my %args = (@_);
+    push @{ $self->{'__external_cf_limits'} ||= [] }, {
+        %args,
+        CALLBACK => $self->__BuildLimitCheck( %args ),
+    };
+    return $self->SUPER::Limit( %args );
+}
+
+sub __BuildLimitCheck {
+    my ($self, %args) = (@_);
+    return undef unless $args{'FIELD'} =~ /^(?:Name|Description)$/;
+
+    $args{'OPERATOR'} ||= '=';
+    my $quoted_value = $args{'VALUE'};
+    if ( $quoted_value ) {
+        $quoted_value =~ s/'/\\'/g;
+        $quoted_value = "'$quoted_value'";
+    }
+
+    my $code = <$args{'FIELD'};
+my \$condition = $quoted_value;
+END
+
+    if ( $args{'OPERATOR'} =~ /^(?:=|!=|<>)$/ ) {
+        $code .= 'return 0 unless defined $value;';
+        my %h = ( '=' => ' eq ', '!=' => ' ne ', '<>' => ' ne ' );
+        $code .= 'return 0 unless $value'. $h{ $args{'OPERATOR'} } .'$condition;';
+        $code .= 'return 1;'
+    }
+    elsif ( $args{'OPERATOR'} =~ /^(?:LIKE|NOT LIKE)$/i ) {
+        $code .= 'return 0 unless defined $value;';
+        my %h = ( 'LIKE' => ' =~ ', 'NOT LIKE' => ' !~ ' );
+        $code .= 'return 0 unless $value'. $h{ uc $args{'OPERATOR'} } .'/\Q$condition/i;';
+        $code .= 'return 1;'
+    }
+    else {
+        $code .= 'return 0;'
+    }
+    $code = "sub {$code}";
+    my $cb = eval "$code";
+    $RT::Logger->error( "Couldn't build callback '$code': $@" ) if $@;
+    return $cb;
+}
+
+sub __BuildAggregatorsCheck {
+    my $self = shift;
+
+    my %h = ( OR => ' || ', AND => ' && ' );
+    
+    my $code = '';
+    for( my $i = 0; $i < @{ $self->{'__external_cf_limits'} }; $i++ ) {
+        next unless $self->{'__external_cf_limits'}->[$i]->{'CALLBACK'};
+        $code .= $h{ uc($self->{'__external_cf_limits'}->[$i]->{'ENTRYAGGREGATOR'} || 'OR') } if $code;
+        $code .= '$sb->{\'__external_cf_limits\'}->['. $i .']->{\'CALLBACK\'}->($record)';
+    }
+    return unless $code;
+
+    $code = "sub { my (\$sb,\$record) = (\@_); return $code }";
+    my $cb = eval "$code";
+    $RT::Logger->error( "Couldn't build callback '$code': $@" ) if $@;
+    return $cb;
+}
+
+sub _DoSearch {
+    my $self = shift;
+
+    delete $self->{'items'};
+
+    my %defaults = (
+            id => 1,
+            name => '',
+            customfield => $self->{'__external_cf'},
+            sortorder => 0,
+            description => '',
+            creator => $RT::SystemUser->id,
+            created => undef,
+            lastupdatedby => $RT::SystemUser->id,
+            lastupdated => undef,
+    );
+
+    my $i = 0;
+
+    my $check = $self->__BuildAggregatorsCheck;
+    foreach( @{ $self->ExternalValues } ) {
+        my $value = $self->NewItem;
+        $value->LoadFromHash( { %defaults, %$_ } );
+        next if $check && !$check->( $self, $value );
+        $self->AddRecord( $value );
+    }
+    $self->{'must_redo_search'} = 0;
+    return $self->_RecordCount;
+}
+
+sub _DoCount {
+    my $self = shift;
+
+    my $count;
+    $count = $self->_DoSearch if $self->{'must_redo_search'};
+    $count = $self->_RecordCount unless defined $count;
+
+    return $self->{'count_all'} = $self->{'raw_rows'} = $count;
+}
+
+sub LimitToCustomField {
+    my $self = shift;
+    $self->{'__external_cf'} = $_[0];
+    return $self->SUPER::LimitToCustomField( @_ );
+}
+
+eval "require RT::CustomFieldValues::External_Vendor";
+if ($@ && $@ !~ qr{^Can't locate RT/CustomFieldValues/External_Vendor.pm}) {
+    die $@;
+};
+
+eval "require RT::CustomFieldValues::External_Local";
+if ($@ && $@ !~ qr{^Can't locate RT/CustomFieldValues/External_Local.pm}) {
+    die $@;
+};
+
+1;
diff --git a/rt/lib/RT/CustomFieldValues/Groups.pm b/rt/lib/RT/CustomFieldValues/Groups.pm
new file mode 100644
index 000000000..5d40c7775
--- /dev/null
+++ b/rt/lib/RT/CustomFieldValues/Groups.pm
@@ -0,0 +1,88 @@
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
+package RT::CustomFieldValues::Groups;
+
+use strict;
+use warnings;
+
+use base qw(RT::CustomFieldValues::External);
+
+sub SourceDescription {
+    return 'RT user defined groups';
+}
+
+sub ExternalValues {
+    my $self = shift;
+
+    my @res;
+    my $i = 0;
+    my $groups = RT::Groups->new( $self->CurrentUser );
+    $groups->LimitToUserDefinedGroups;
+    $groups->OrderByCols( { FIELD => 'Name' } );
+    while( my $group = $groups->Next ) {
+        push @res, {
+            name        => $group->Name,
+            description => $group->Description,
+            sortorder   => $i++,
+        };
+    }
+    return \@res;
+}
+
+eval "require RT::CustomFieldValues::Groups_Vendor";
+if ($@ && $@ !~ qr{^Can't locate RT/CustomFieldValues/Groups_Vendor.pm}) {
+    die $@;
+};
+
+eval "require RT::CustomFieldValues::Groups_Local";
+if ($@ && $@ !~ qr{^Can't locate RT/CustomFieldValues/Groups_Local.pm}) {
+    die $@;
+};
+
+1;
diff --git a/rt/lib/RT/CustomFieldValues_Overlay.pm b/rt/lib/RT/CustomFieldValues_Overlay.pm
index 543a9860b..4c6d7f84d 100644
--- a/rt/lib/RT/CustomFieldValues_Overlay.pm
+++ b/rt/lib/RT/CustomFieldValues_Overlay.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,9 +45,11 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 package RT::CustomFieldValues;
 
 use strict;
+use warnings;
 no warnings qw(redefine);
 
 # {{{ sub LimitToCustomField
@@ -61,13 +63,13 @@ Limits the returned set to values for the custom field with Id FIELD
 sub LimitToCustomField {
     my $self = shift;
     my $cf = shift;
-    return ($self->Limit( FIELD => 'CustomField',
-			  VALUE => $cf,
-			  OPERATOR => '='));
-
+    return $self->Limit(
+        FIELD    => 'CustomField',
+        VALUE    => $cf,
+        OPERATOR => '=',
+    );
 }
 
 # }}}
 
 1;
-
diff --git a/rt/lib/RT/CustomField_Overlay.pm b/rt/lib/RT/CustomField_Overlay.pm
index 2bb42edbf..9286d7a1d 100644
--- a/rt/lib/RT/CustomField_Overlay.pm
+++ b/rt/lib/RT/CustomField_Overlay.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,69 +45,73 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 package RT::CustomField;
 
 use strict;
 no warnings qw(redefine);
 
-use vars qw(%FieldTypes $RIGHTS %FRIENDLY_OBJECT_TYPES);
-
 use RT::CustomFieldValues;
 use RT::ObjectCustomFieldValues;
 
 
-%FieldTypes = (
+our %FieldTypes = (
     Select => [
-        'Select multiple values',	# loc
-        'Select one value',		# loc
-        'Select up to [_1] values',	# loc
+        'Select multiple values',    # loc
+        'Select one value',        # loc
+        'Select up to [_1] values',    # loc
     ],
     Freeform => [
-        'Enter multiple values',	# loc
-        'Enter one value',		# loc
-        'Enter up to [_1] values',	# loc
+        'Enter multiple values',    # loc
+        'Enter one value',        # loc
+        'Enter up to [_1] values',    # loc
     ],
     Text => [
-        'Fill in multiple text areas',	# loc
-        'Fill in one text area',	# loc
+        'Fill in multiple text areas',    # loc
+        'Fill in one text area',    # loc
         'Fill in up to [_1] text areas',# loc
     ],
     Wikitext => [
-        'Fill in multiple wikitext areas',	# loc
-        'Fill in one wikitext area',	# loc
+        'Fill in multiple wikitext areas',    # loc
+        'Fill in one wikitext area',    # loc
         'Fill in up to [_1] wikitext areas',# loc
     ],
     Image => [
-        'Upload multiple images',	# loc
-        'Upload one image',		# loc
-        'Upload up to [_1] images',	# loc
+        'Upload multiple images',    # loc
+        'Upload one image',        # loc
+        'Upload up to [_1] images',    # loc
     ],
     Binary => [
-        'Upload multiple files',	# loc
-        'Upload one file',		# loc
-        'Upload up to [_1] files',	# loc
+        'Upload multiple files',    # loc
+        'Upload one file',        # loc
+        'Upload up to [_1] files',    # loc
     ],
     Combobox => [
-        'Combobox: Select or enter multiple values',	# loc
-        'Combobox: Select or enter one value',		# loc
-        'Combobox: Select or enter up to [_1] values',	# loc
+        'Combobox: Select or enter multiple values',    # loc
+        'Combobox: Select or enter one value',        # loc
+        'Combobox: Select or enter up to [_1] values',    # loc
+    ],
+    Autocomplete => [
+        'Enter multiple values with autocompletion',    # loc
+        'Enter one value with autocompletion',            # loc
+        'Enter up to [_1] values with autocompletion',    # loc
     ],
 );
 
 
-%FRIENDLY_OBJECT_TYPES =  ();
+our %FRIENDLY_OBJECT_TYPES =  ();
 
 RT::CustomField->_ForObjectType( 'RT::Queue-RT::Ticket' => "Tickets", );    #loc
 RT::CustomField->_ForObjectType(
     'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions", );    #loc
 RT::CustomField->_ForObjectType( 'RT::User'  => "Users", );                           #loc
+RT::CustomField->_ForObjectType( 'RT::Queue'  => "Queues", );                         #loc
 RT::CustomField->_ForObjectType( 'RT::Group' => "Groups", );                          #loc
 
-$RIGHTS = {
+our $RIGHTS = {
     SeeCustomField            => 'See custom fields',       # loc_pair
     AdminCustomField          => 'Create, delete and modify custom fields',        # loc_pair
     ModifyCustomField         => 'Add, delete and modify custom field values for objects' #loc_pair
-
 };
 
 # Tell RT::ACE that this sort of object can get acls granted
@@ -117,23 +121,34 @@ foreach my $right ( keys %{$RIGHTS} ) {
     $RT::ACE::LOWERCASERIGHTNAMES{ lc $right } = $right;
 }
 
+=head2 AddRights C, C [, ...]
+
+Adds the given rights to the list of possible rights.  This method
+should be called during server startup, not at runtime.
+
+=cut
+
+sub AddRights {
+    my $self = shift;
+    my %new = @_;
+    $RIGHTS = { %$RIGHTS, %new };
+    %RT::ACE::LOWERCASERIGHTNAMES = ( %RT::ACE::LOWERCASERIGHTNAMES,
+                                      map { lc($_) => $_ } keys %new);
+}
+
 sub AvailableRights {
     my $self = shift;
-    return($RIGHTS);
+    return $RIGHTS;
 }
 
 =head1 NAME
 
-  RT::CustomField_Overlay 
+  RT::CustomField_Overlay - overlay for RT::CustomField
 
 =head1 DESCRIPTION
 
 =head1 'CORE' METHODS
 
-=cut
-
-
-
 =head2 Create PARAMHASH
 
 Create takes a hash of values and creates a row in the database:
@@ -148,49 +163,48 @@ Create takes a hash of values and creates a row in the database:
   varchar(255) 'LookupType'.
   smallint(6) 'Disabled'.
 
-  'LookupType' is generally the result of either 
-  RT::Ticket->CustomFieldLookupType or RT::Transaction->CustomFieldLookupType
+C is generally the result of either
+CCustomFieldLookupType> or CCustomFieldLookupType>.
 
 =cut
 
-
-
-
 sub Create {
     my $self = shift;
-    my %args = ( 
-                Name => '',
-                Type => '',
-		MaxValues => '0',
-		Pattern  => '',
-                Description => '',
-                Disabled => '0',
-		LookupType  => '',
-		Repeated  => '0',
-
-		  @_);
-
-    unless ($self->CurrentUser->HasRight(Object => $RT::System, Right => 'AdminCustomField')) {
+    my %args = (
+        Name        => '',
+        Type        => '',
+        MaxValues   => 0,
+        Pattern     => '',
+        Description => '',
+        Disabled    => 0,
+        LookupType  => '',
+        Repeated    => 0,
+        LinkValueTo => '',
+        IncludeContentForValue => '',
+        @_,
+    );
+
+    unless ( $self->CurrentUser->HasRight(Object => $RT::System, Right => 'AdminCustomField') ) {
         return (0, $self->loc('Permission Denied'));
     }
 
-
-    if ($args{TypeComposite}) {
-	@args{'Type', 'MaxValues'} = split(/-/, $args{TypeComposite}, 2);
+    if ( $args{TypeComposite} ) {
+        @args{'Type', 'MaxValues'} = split(/-/, $args{TypeComposite}, 2);
     }
-    elsif ($args{Type} =~ s/(?:(Single)|Multiple)$//) {
-	# old style Type string
-	$args{'MaxValues'} = $1 ? 1 : 0;
+    elsif ( $args{Type} =~ s/(?:(Single)|Multiple)$// ) {
+        # old style Type string
+        $args{'MaxValues'} = $1 ? 1 : 0;
     }
-    
+    $args{'MaxValues'} = int $args{'MaxValues'};
+
     if ( !exists $args{'Queue'}) {
-	# do nothing -- things below are strictly backward compat
+    # do nothing -- things below are strictly backward compat
     }
     elsif (  ! $args{'Queue'} ) {
         unless ( $self->CurrentUser->HasRight( Object => $RT::System, Right => 'AssignCustomFields') ) {
             return ( 0, $self->loc('Permission Denied') );
         }
-	$args{'LookupType'} = 'RT::Queue-RT::Ticket';
+        $args{'LookupType'} = 'RT::Queue-RT::Ticket';
     }
     else {
         my $queue = RT::Queue->new($self->CurrentUser);
@@ -205,32 +219,51 @@ sub Create {
         $args{'Queue'} = $queue->Id;
     }
 
-    my ($ok, $msg) = $self->_IsValidRegex($args{'Pattern'});
-    if (!$ok) {
-        return (0, $self->loc("Invalid pattern: [_1]", $msg));
+    my ($ok, $msg) = $self->_IsValidRegex( $args{'Pattern'} );
+    return (0, $self->loc("Invalid pattern: [_1]", $msg)) unless $ok;
+
+    if ( $args{'MaxValues'} != 1 && $args{'Type'} =~ /(text|combobox)$/i ) {
+        $RT::Logger->warning("Support for 'multiple' Texts or Comboboxes is not implemented");
+        $args{'MaxValues'} = 1;
     }
 
-    my $rv = $self->SUPER::Create(
-                         Name => $args{'Name'},
-                         Type => $args{'Type'},
-                         MaxValues => $args{'MaxValues'},
-                         Pattern  => $args{'Pattern'},
-                         Description => $args{'Description'},
-                         Disabled => $args{'Disabled'},
-			 LookupType => $args{'LookupType'},
-			 Repeated => $args{'Repeated'},
-);
+    (my $rv, $msg) = $self->SUPER::Create(
+        Name        => $args{'Name'},
+        Type        => $args{'Type'},
+        MaxValues   => $args{'MaxValues'},
+        Pattern     => $args{'Pattern'},
+        Description => $args{'Description'},
+        Disabled    => $args{'Disabled'},
+        LookupType  => $args{'LookupType'},
+        Repeated    => $args{'Repeated'},
+    );
+
+    if ( exists $args{'LinkValueTo'}) {
+	$self->SetLinkValueTo($args{'LinkValueTo'});
+    }
+
+    if ( exists $args{'IncludeContentForValue'}) {
+	$self->SetIncludeContentForValue($args{'IncludeContentForValue'});
+    }
+
+    if ( exists $args{'ValuesClass'} ) {
+        $self->SetValuesClass( $args{'ValuesClass'} );
+    }
 
-    return $rv unless exists $args{'Queue'};
+    if ( exists $args{'BasedOn'} ) {
+        $self->SetBasedOn( $args{'BasedOn'} );
+    }
+
+    return ($rv, $msg) unless exists $args{'Queue'};
 
     # Compat code -- create a new ObjectCustomField mapping
-    my $OCF = RT::ObjectCustomField->new($self->CurrentUser);
+    my $OCF = RT::ObjectCustomField->new( $self->CurrentUser );
     $OCF->Create(
-	CustomField => $self->Id,
-	ObjectId => $args{'Queue'},
+        CustomField => $self->Id,
+        ObjectId => $args{'Queue'},
     );
 
-    return $rv;
+    return ($rv, $msg);
 }
 
 =head2 Load ID/NAME
@@ -239,22 +272,21 @@ Load a custom field.  If the value handed in is an integer, load by custom field
 
 =cut
 
-
 sub Load {
     my $self = shift;
-    my $id = shift;
+    my $id = shift || '';
 
-    if ($id =~ /^\d+$/) {
-        return ($self->SUPER::Load($id));
+    if ( $id =~ /^\d+$/ ) {
+        return $self->SUPER::Load( $id );
     } else {
-        return($self->LoadByName(Name => $id));
+        return $self->LoadByName( Name => $id );
     }
 }
 
 
 # {{{ sub LoadByName
 
-=head2  LoadByName (Queue => QUEUEID, Name => NAME)
+=head2 LoadByName (Queue => QUEUEID, Name => NAME)
 
 Loads the Custom field named NAME.
 
@@ -284,91 +316,82 @@ sub LoadByName {
         @_,
     );
 
+    unless ( defined $args{'Name'} && length $args{'Name'} ) {
+        $RT::Logger->error("Couldn't load Custom Field without Name");
+        return wantarray ? (0, $self->loc("No name provided")) : 0;
+    }
+
     # if we're looking for a queue by name, make it a number
-    if  (defined $args{'Queue'}  &&  $args{'Queue'} !~ /^\d+$/) {
-	my $QueueObj = RT::Queue->new($self->CurrentUser);
-	$QueueObj->Load($args{'Queue'});
-	$args{'Queue'} = $QueueObj->Id;
+    if ( defined $args{'Queue'} && $args{'Queue'} =~ /\D/ ) {
+        my $QueueObj = RT::Queue->new( $self->CurrentUser );
+        $QueueObj->Load( $args{'Queue'} );
+        $args{'Queue'} = $QueueObj->Id;
     }
 
     # XXX - really naive implementation.  Slow. - not really. still just one query
 
-    my $CFs = RT::CustomFields->new($self->CurrentUser);
-
-    $CFs->Limit( FIELD => 'Name', VALUE => $args{'Name'}, CASESENSITIVE => 0);
+    my $CFs = RT::CustomFields->new( $self->CurrentUser );
+    $CFs->SetContextObject( $self->ContextObject );
+    my $field = $args{'Name'} =~ /\D/? 'Name' : 'id';
+    $CFs->Limit( FIELD => $field, VALUE => $args{'Name'}, CASESENSITIVE => 0);
     # Don't limit to queue if queue is 0.  Trying to do so breaks
     # RT::Group type CFs.
-    if (defined $args{'Queue'}) {
-	$CFs->LimitToQueue( $args{'Queue'} );
+    if ( defined $args{'Queue'} ) {
+        $CFs->LimitToQueue( $args{'Queue'} );
     }
 
-    # When loading by name, it's ok if they're disabled. That's not a big deal.
+    # When loading by name, we _can_ load disabled fields, but prefer
+    # non-disabled fields.
     $CFs->{'find_disabled_rows'}=1;
+    $CFs->OrderByCols(
+        { FIELD => "Disabled", ORDER => 'ASC' },
+    );
 
     # We only want one entry.
     $CFs->RowsPerPage(1);
-    unless ($CFs->First) {
-        return(0);
-    }
-    return($self->Load($CFs->First->id));
 
+    # version before 3.8 just returns 0, so we need to test if wantarray to be
+    # backward compatible.
+    return wantarray ? (0, $self->loc("Not found")) : 0 unless my $first = $CFs->First;
+
+    return $self->LoadById( $first->id );
 }
 
 # }}}
 
 # {{{ Dealing with custom field values 
 
-=begin testing
-
-use_ok(RT::CustomField);
-ok(my $cf = RT::CustomField->new($RT::SystemUser));
-ok(my ($id, $msg)=  $cf->Create( Name => 'TestingCF',
-                                 Queue => '0',
-                                 SortOrder => '1',
-                                 Description => 'A Testing custom field',
-                                 Type=> 'SelectSingle'), 'Created a global CustomField');
-ok($id != 0, 'Global custom field correctly created');
-ok ($cf->SingleValue);
-is($cf->Type, 'Select');
-is($cf->MaxValues, 1);
-
-my ($val, $msg) = $cf->SetMaxValues('0');
-ok($val, $msg);
-is($cf->Type, 'Select');
-is($cf->MaxValues, 0);
-ok(!$cf->SingleValue );
-ok(my ($bogus_val, $bogus_msg) = $cf->SetType('BogusType') , "Trying to set a custom field's type to a bogus type");
-ok($bogus_val == 0, "Unable to set a custom field's type to a bogus type");
-
-ok(my $bad_cf = RT::CustomField->new($RT::SystemUser));
-ok(my ($bad_id, $bad_msg)=  $cf->Create( Name => 'TestingCF-bad',
-                                 Queue => '0',
-                                 SortOrder => '1',
-                                 Description => 'A Testing custom field with a bogus Type',
-                                 Type=> 'SelectSingleton'), 'Created a global CustomField with a bogus type');
-ok($bad_id == 0, 'Global custom field correctly decided to not create a cf with a bogus type ');
-
-=end testing
+
+=head2 Custom field values
+
+=head3 Values FIELD
+
+Return a object (collection) of all acceptable values for this Custom Field.
+Class of the object can vary and depends on the return value
+of the C method.
 
 =cut
 
-# {{{ AddValue
+*ValuesObj = \&Values;
 
-=head2 AddValue HASH
+sub Values {
+    my $self = shift;
 
-Create a new value for this CustomField.  Takes a paramhash containing the elements Name, Description and SortOrder
+    my $class = $self->ValuesClass || 'RT::CustomFieldValues';
+    eval "require $class" or die "$@";
+    my $cf_values = $class->new( $self->CurrentUser );
+    # if the user has no rights, return an empty object
+    if ( $self->id && $self->CurrentUserHasRight( 'SeeCustomField') ) {
+        $cf_values->LimitToCustomField( $self->Id );
+    }
+    return ($cf_values);
+}
 
-=begin testing
+# {{{ AddValue
 
-ok(my $cf = RT::CustomField->new($RT::SystemUser));
-$cf->Load(1);
-ok($cf->Id == 1);
-ok(my ($val,$msg)  = $cf->AddValue(Name => 'foo' , Description => 'TestCFValue', SortOrder => '6'));
-ok($val != 0);
-ok (my ($delval, $delmsg) = $cf->DeleteValue($val));
-ok ($delval,"Deleting a cf value: $delmsg");
+=head3 AddValue HASH
 
-=end testing
+Create a new value for this CustomField.  Takes a paramhash containing the elements Name, Description and SortOrder
 
 =cut
 
@@ -382,11 +405,11 @@ sub AddValue {
 
     # allow zero value
     if ( !defined $args{'Name'} || $args{'Name'} eq '' ) {
-        return(0, $self->loc("Can't add a custom field value without a name"));
+        return (0, $self->loc("Can't add a custom field value without a name"));
     }
 
-    my $newval = RT::CustomFieldValue->new($self->CurrentUser);
-    return($newval->Create(%args, CustomField => $self->Id));
+    my $newval = RT::CustomFieldValue->new( $self->CurrentUser );
+    return $newval->Create( %args, CustomField => $self->Id );
 }
 
 
@@ -394,147 +417,38 @@ sub AddValue {
 
 # {{{ DeleteValue
 
-=head2 DeleteValue ID
+=head3 DeleteValue ID
 
-Deletes a value from this custom field by id. 
+Deletes a value from this custom field by id.
 
-Does not remove this value for any article which has had it selected	
+Does not remove this value for any article which has had it selected
 
 =cut
 
 sub DeleteValue {
-	my $self = shift;
+    my $self = shift;
     my $id = shift;
-    unless ($self->CurrentUserHasRight('AdminCustomField')) {
+    unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
         return (0, $self->loc('Permission Denied'));
     }
 
-	my $val_to_del = RT::CustomFieldValue->new($self->CurrentUser);
-	$val_to_del->Load($id);
-	unless ($val_to_del->Id) {
-		return (0, $self->loc("Couldn't find that value"));
-	}
-	unless ($val_to_del->CustomField == $self->Id) {
-		return (0, $self->loc("That is not a value for this custom field"));
-	}
-
-	my $retval = $val_to_del->Delete();
-    if ($retval) {
-        return ($retval, $self->loc("Custom field value deleted"));
-    } else {
-        return(0, $self->loc("Custom field value could not be deleted"));
+    my $val_to_del = RT::CustomFieldValue->new( $self->CurrentUser );
+    $val_to_del->Load( $id );
+    unless ( $val_to_del->Id ) {
+        return (0, $self->loc("Couldn't find that value"));
     }
-}
-
-# }}}
-
-# {{{ Values
-
-=head2 Values FIELD
-
-Return a CustomFieldeValues object of all acceptable values for this Custom Field.
-
-
-=cut
-
-*ValuesObj = \&Values;
-
-sub Values {
-    my $self = shift;
-
-    my $cf_values = RT::CustomFieldValues->new($self->CurrentUser);
-    # if the user has no rights, return an empty object
-    if ($self->id && $self->CurrentUserHasRight( 'SeeCustomField') ) {
-        $cf_values->LimitToCustomField($self->Id);
+    unless ( $val_to_del->CustomField == $self->Id ) {
+        return (0, $self->loc("That is not a value for this custom field"));
     }
-    return ($cf_values);
-}
-
-# }}}
-
-# }}}
-
-# {{{ Ticket related routines
-
-# {{{ ValuesForTicket
-
-=head2 ValuesForTicket TICKET
-
-Returns a RT::ObjectCustomFieldValues object of this Field's values for TICKET.
-TICKET is a ticket id.
-
-This is deprecated -- use ValuesForObject instead.
-
-
-=cut
-
-sub ValuesForTicket {
-	my $self = shift;
-    my $ticket_id = shift;
-    
-    $RT::Logger->debug( ref($self) . " -> ValuesForTicket deprecated in favor of ValuesForObject at (". join(":",caller).")"); 
-    my $ticket = RT::Ticket->new($self->CurrentUser);
-    $ticket->Load($ticket_id);
-
-    return $self->ValuesForObject($ticket);
-}
-
-# }}}
-
-# {{{ AddValueForTicket
-
-=head2 AddValueForTicket HASH
-
-Adds a custom field value for a ticket. Takes a param hash of Ticket and Content
-
-This is deprecated -- use AddValueForObject instead.
-
-=cut
-
-sub AddValueForTicket {
-	my $self = shift;
-	my %args = ( Ticket => undef,
-                 Content => undef,
-		     @_ );
-    $RT::Logger->debug( ref($self) . " -> AddValueForTicket deprecated in favor of AddValueForObject at (". join(":",caller).")");
-
-
-    my $ticket = RT::Ticket->new($self->CurrentUser);
-    $ticket->Load($args{'Ticket'});
-    return($self->AddValueForObject(Content => $args{'Content'}, Object => $ticket,@_));
-
-}
-
-
-# }}}
-
-# {{{ DeleteValueForTicket
-
-=head2 DeleteValueForTicket HASH
-
-Adds a custom field value for a ticket. Takes a param hash of Ticket and Content
-
-This is deprecated -- use DeleteValueForObject instead.
-
-=cut
-
-sub DeleteValueForTicket {
-	my $self = shift;
-	my %args = ( Ticket => undef,
-                 Content => undef,
-		     @_ );
-
-    $RT::Logger->debug( ref($self) . " -> DeleteValueForTicket deprecated in favor of DeleteValueForObject at (". join(":",caller).")"); 
-
-
-    my $ticket = RT::Ticket->new($self->CurrentUser);
-    $ticket->load($args{'Ticket'});
-    return ($self->DeleteValueForObject(Object => $ticket, Content => $args{'Content'}, @_));
 
+    my $retval = $val_to_del->Delete;
+    unless ( $retval ) {
+        return (0, $self->loc("Custom field value could not be deleted"));
+    }
+    return ($retval, $self->loc("Custom field value deleted"));
 }
 
 # }}}
-# }}}
 
 
 =head2 ValidateQueue Queue
@@ -547,18 +461,14 @@ sub ValidateQueue {
     my $self = shift;
     my $id = shift;
 
-    if ($id eq '0') { # 0 means "Global" null would _not_ be ok.
-        return (1); 
-    }
-
-    my $q = RT::Queue->new($RT::SystemUser);
-    $q->Load($id);
-    unless ($q->id) {
-        return undef;
-    }
-    return (1);
-
+    return undef unless defined $id;
+    # 0 means "Global" null would _not_ be ok.
+    return 1 if $id eq '0';
 
+    my $q = RT::Queue->new( $RT::SystemUser );
+    $q->Load( $id );
+    return undef unless $q->id;
+    return 1;
 }
 
 
@@ -571,13 +481,13 @@ Retuns an array of the types of CustomField that are supported
 =cut
 
 sub Types {
-	return (keys %FieldTypes);
+    return (keys %FieldTypes);
 }
 
 # }}}
 
 # {{{ IsSelectionType
- 
+
 =head2 IsSelectionType 
 
 Retuns a boolean value indicating whether the C method makes sense
@@ -587,12 +497,49 @@ to this Custom Field.
 
 sub IsSelectionType {
     my $self = shift;
-    $self->Type =~ /(?:Select|Combobox)/;
+    my $type = @_? shift : $self->Type;
+    return undef unless $type;
+
+    $type =~ /(?:Select|Combobox|Autocomplete)/;
 }
 
 # }}}
 
 
+=head2 IsExternalValues
+
+=cut
+
+sub IsExternalValues {
+    my $self = shift;
+    my $selectable = $self->IsSelectionType( @_ );
+    return $selectable unless $selectable;
+
+    my $class = $self->ValuesClass;
+    return 0 if $class eq 'RT::CustomFieldValues';
+    return 1;
+}
+
+sub ValuesClass {
+    my $self = shift;
+    return '' unless $self->IsSelectionType;
+
+    my $class = $self->FirstAttribute( 'ValuesClass' );
+    $class = $class->Content if $class;
+    return $class || 'RT::CustomFieldValues';
+}
+
+sub SetValuesClass {
+    my $self = shift;
+    my $class = shift || 'RT::CustomFieldValues';
+
+    if( $class eq 'RT::CustomFieldValues' ) {
+        return $self->DeleteAttribute( 'ValuesClass' );
+    }
+    return $self->SetAttribute( Name => 'ValuesClass', Content => $class );
+}
+
+
 =head2 FriendlyType [TYPE, MAX_VALUES]
 
 Returns a localized human-readable version of the custom field type.
@@ -605,9 +552,10 @@ sub FriendlyType {
 
     my $type = @_ ? shift : $self->Type;
     my $max  = @_ ? shift : $self->MaxValues;
+    $max = 0 unless $max;
 
     if (my $friendly_type = $FieldTypes{$type}[$max>2 ? 2 : $max]) {
-	return ( $self->loc( $friendly_type, $max ) );
+        return ( $self->loc( $friendly_type, $max ) );
     }
     else {
         return ( $self->loc( $type ) );
@@ -626,14 +574,6 @@ sub FriendlyTypeComposite {
 Takes a single string. returns true if that string is a value
 type of custom field
 
-=begin testing
-
-ok(my $cf = RT::CustomField->new($RT::SystemUser));
-ok($cf->ValidateType('SelectSingle'));
-ok($cf->ValidateType('SelectMultiple'));
-ok(!$cf->ValidateType('SelectFooMultiple'));
-
-=end testing
 
 =cut
 
@@ -641,12 +581,12 @@ sub ValidateType {
     my $self = shift;
     my $type = shift;
 
-    if ($type =~ s/(?:Single|Multiple)$//) {
-	$RT::Logger->warning( "Prefix 'Single' and 'Multiple' to Type deprecated, use MaxValues instead at (". join(":",caller).")");
+    if ( $type =~ s/(?:Single|Multiple)$// ) {
+        $RT::Logger->warning( "Prefix 'Single' and 'Multiple' to Type deprecated, use MaxValues instead at (". join(":",caller).")");
     }
 
-    if( $FieldTypes{$type}) {
-        return(1);
+    if ( $FieldTypes{$type} ) {
+        return 1;
     }
     else {
         return undef;
@@ -658,8 +598,8 @@ sub SetType {
     my $self = shift;
     my $type = shift;
     if ($type =~ s/(?:(Single)|Multiple)$//) {
-	$RT::Logger->warning("'Single' and 'Multiple' on SetType deprecated, use SetMaxValues instead at (". join(":",caller).")");
-	$self->SetMaxValues($1 ? 1 : 0);
+        $RT::Logger->warning("'Single' and 'Multiple' on SetType deprecated, use SetMaxValues instead at (". join(":",caller).")");
+        $self->SetMaxValues($1 ? 1 : 0);
     }
     $self->SUPER::SetType($type);
 }
@@ -696,8 +636,8 @@ sub _IsValidRegex {
     my $regex = shift or return (1, 'valid');
 
     local $^W; local $@;
-    $SIG{__DIE__} = sub { 1 };
-    $SIG{__WARN__} = sub { 1 };
+    local $SIG{__DIE__} = sub { 1 };
+    local $SIG{__WARN__} = sub { 1 };
 
     if (eval { qr/$regex/; 1 }) {
         return (1, 'valid');
@@ -720,7 +660,7 @@ Returns false if it accepts multiple values
 
 sub SingleValue {
     my $self = shift;
-    if ($self->MaxValues == 1) {
+    if (($self->MaxValues||0) == 1) {
         return 1;
     } 
     else {
@@ -730,7 +670,7 @@ sub SingleValue {
 
 sub UnlimitedValues {
     my $self = shift;
-    if ($self->MaxValues == 0) {
+    if (($self->MaxValues||0) == 0) {
         return 1;
     } 
     else {
@@ -740,8 +680,6 @@ sub UnlimitedValues {
 
 # }}}
 
-# {{{ sub CurrentUserHasRight
-
 =head2 CurrentUserHasRight RIGHT
 
 Helper function to call the custom field's queue's CurrentUserHasRight with the passed in args.
@@ -753,13 +691,44 @@ sub CurrentUserHasRight {
     my $right = shift;
 
     return $self->CurrentUser->HasRight(
-	Object => $self,
-	Right  => $right,
+        Object => $self,
+        Right  => $right,
     );
 }
 
-# }}}
+=head2 ACLEquivalenceObjects
+
+Returns list of objects via which users can get rights on this custom field. For custom fields
+these objects can be set using L.
 
+=cut
+
+sub ACLEquivalenceObjects {
+    my $self = shift;
+
+    my $ctx = $self->ContextObject
+        or return;
+    return ($ctx, $ctx->ACLEquivalenceObjects);
+}
+
+=head2 ContextObject and SetContextObject
+
+Set or get a context for this object. It can be ticket, queue or another object
+this CF applies to. Used for ACL control, for example SeeCustomField can be granted on
+queue level to allow people to see all fields applied to the queue.
+
+=cut
+
+sub SetContextObject {
+    my $self = shift;
+    return $self->{'context_object'} = shift;
+}
+  
+sub ContextObject {
+    my $self = shift;
+    return $self->{'context_object'};
+}
+  
 # {{{ sub _Set
 
 sub _Set {
@@ -768,7 +737,7 @@ sub _Set {
     unless ( $self->CurrentUserHasRight('AdminCustomField') ) {
         return ( 0, $self->loc('Permission Denied') );
     }
-    return ( $self->SUPER::_Set(@_) );
+    return $self->SUPER::_Set( @_ );
 
 }
 
@@ -784,16 +753,18 @@ Returns its value as a string, if the user passes an ACL check
 =cut
 
 sub _Value {
-
     my $self  = shift;
-    my $field = shift;
+    return undef unless $self->id;
 
     # we need to do the rights check
-    unless ( $self->id && $self->CurrentUserHasRight( 'SeeCustomField') ) {
-	    return (undef);
+    unless ( $self->CurrentUserHasRight('SeeCustomField') ) {
+        $RT::Logger->debug(
+            "Permission denied. User #". $self->CurrentUser->id
+            ." has no SeeCustomField right on CF #". $self->id
+        );
+        return (undef);
     }
-    return ( $self->__Value($field) );
-
+    return $self->__Value( @_ );
 }
 
 # }}}
@@ -802,44 +773,39 @@ sub _Value {
 =head2 SetDisabled
 
 Takes a boolean.
-1 will cause this custom field to no longer be avaialble for tickets.
-0 will re-enable this queue
+1 will cause this custom field to no longer be avaialble for objects.
+0 will re-enable this field.
 
 =cut
 
 # }}}
 
-sub Queue {
-    $RT::Logger->debug( ref($_[0]) . " -> Queue deprecated at (". join(":",caller).")");
-    
-    return 0;
-}
-
-sub SetQueue {
-    $RT::Logger->debug( ref($_[0]) . " -> SetQueue deprecated at (". join(":",caller).")");
-
-    return 0;
-}
-
-sub QueueObj {
-    $RT::Logger->debug( ref($_[0]) . " -> QueueObj deprecated at (". join(":",caller).")");
-
-    return undef;
-}
-
 =head2 SetTypeComposite
 
 Set this custom field's type and maximum values as a composite value
 
-
 =cut
 
 sub SetTypeComposite {
     my $self = shift;
     my $composite = shift;
+
+    my $old = $self->TypeComposite;
+
     my ($type, $max_values) = split(/-/, $composite, 2);
-    $self->SetType($type);
-    $self->SetMaxValues($max_values);
+    if ( $type ne $self->Type ) {
+        my ($status, $msg) = $self->SetType( $type );
+        return ($status, $msg) unless $status;
+    }
+    if ( ($max_values || 0) != ($self->MaxValues || 0) ) {
+        my ($status, $msg) = $self->SetMaxValues( $max_values );
+        return ($status, $msg) unless $status;
+    }
+    return 1, $self->loc(
+        "Type changed from '[_1]' to '[_2]'",
+        $self->FriendlyTypeComposite( $old ),
+        $self->FriendlyTypeComposite( $composite ),
+    );
 }
 
 =head2 SetLookupType
@@ -851,13 +817,13 @@ Autrijus: care to doc how LookupTypes work?
 sub SetLookupType {
     my $self = shift;
     my $lookup = shift;
-    if ($lookup ne $self->LookupType) {
-	# Okay... We need to invalidate our existing relationships
-	my $ObjectCustomFields = RT::ObjectCustomFields->new($self->CurrentUser);
-	$ObjectCustomFields->LimitToCustomField($self->Id);
-	$_->Delete foreach @{$ObjectCustomFields->ItemsArrayRef};
+    if ( $lookup ne $self->LookupType ) {
+        # Okay... We need to invalidate our existing relationships
+        my $ObjectCustomFields = RT::ObjectCustomFields->new($self->CurrentUser);
+        $ObjectCustomFields->LimitToCustomField($self->Id);
+        $_->Delete foreach @{$ObjectCustomFields->ItemsArrayRef};
     }
-    $self->SUPER::SetLookupType($lookup);
+    return $self->SUPER::SetLookupType($lookup);
 }
 
 =head2 TypeComposite
@@ -869,7 +835,7 @@ Returns a composite value composed of this object's type and maximum values
 
 sub TypeComposite {
     my $self = shift;
-    join('-', $self->Type, $self->MaxValues);
+    return join '-', ($self->Type || ''), ($self->MaxValues || 0);
 }
 
 =head2 TypeComposites
@@ -896,13 +862,15 @@ sub LookupTypes {
 }
 
 my @FriendlyObjectTypes = (
-    "[_1] objects",		    # loc
-    "[_1]'s [_2] objects",	    # loc
+    "[_1] objects",            # loc
+    "[_1]'s [_2] objects",        # loc
     "[_1]'s [_2]'s [_3] objects",   # loc
 );
 
 =head2 FriendlyTypeLookup
 
+Returns a localized description of the type of this custom field
+
 =cut
 
 sub FriendlyLookupType {
@@ -910,7 +878,7 @@ sub FriendlyLookupType {
     my $lookup = shift || $self->LookupType;
    
     return ($self->loc( $FRIENDLY_OBJECT_TYPES{$lookup} ))
-      	           if (defined  $FRIENDLY_OBJECT_TYPES{$lookup} );
+                     if (defined  $FRIENDLY_OBJECT_TYPES{$lookup} );
 
     my @types = map { s/^RT::// ? $self->loc($_) : $_ }
       grep { defined and length }
@@ -935,7 +903,7 @@ sub AddToObject {
     my $id = $object->Id || 0;
 
     unless (index($self->LookupType, ref($object)) == 0) {
-    	return ( 0, $self->loc('Lookup type mismatch') );
+        return ( 0, $self->loc('Lookup type mismatch') );
     }
 
     unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
@@ -943,7 +911,6 @@ sub AddToObject {
     }
 
     my $ObjectCF = RT::ObjectCustomField->new( $self->CurrentUser );
-
     $ObjectCF->LoadByCols( ObjectId => $id, CustomField => $self->Id );
     if ( $ObjectCF->Id ) {
         return ( 0, $self->loc("That is already the current value") );
@@ -970,7 +937,7 @@ sub RemoveFromObject {
     my $id = $object->Id || 0;
 
     unless (index($self->LookupType, ref($object)) == 0) {
-	return ( 0, $self->loc('Object type mismatch') );
+        return ( 0, $self->loc('Object type mismatch') );
     }
 
     unless ( $object->CurrentUserHasRight('AssignCustomFields') ) {
@@ -978,7 +945,6 @@ sub RemoveFromObject {
     }
 
     my $ObjectCF = RT::ObjectCustomField->new( $self->CurrentUser );
-
     $ObjectCF->LoadByCols( ObjectId => $id, CustomField => $self->Id );
     unless ( $ObjectCF->Id ) {
         return ( 0, $self->loc("This custom field does not apply to that object") );
@@ -1017,21 +983,20 @@ sub AddValueForObject {
         ContentType  => undef,
         @_
     );
-    my $obj = $args{'Object'} or return;
+    my $obj = $args{'Object'} or return ( 0, $self->loc('Invalid object') );
 
     unless ( $self->CurrentUserHasRight('ModifyCustomField') ) {
         return ( 0, $self->loc('Permission Denied') );
     }
 
-    unless ( $self->MatchPattern($args{Content}) ) {
+    unless ( $self->MatchPattern($args{'Content'}) ) {
         return ( 0, $self->loc('Input must match [_1]', $self->FriendlyPattern) );
     }
 
     $RT::Handle->BeginTransaction;
 
-    my $current_values = $self->ValuesForObject($obj);
-
     if ( $self->MaxValues ) {
+        my $current_values = $self->ValuesForObject($obj);
         my $extra_values = ( $current_values->Count + 1 ) - $self->MaxValues;
 
         # (The +1 is for the new value we're adding)
@@ -1042,17 +1007,14 @@ sub AddValueForObject {
 
         while ($extra_values) {
             my $extra_item = $current_values->Next;
-
             unless ( $extra_item->id ) {
-                $RT::Logger->crit(
-"We were just asked to delete a custom fieldvalue that doesn't exist!"
-                );
+                $RT::Logger->crit( "We were just asked to delete "
+                    ."a custom field value that doesn't exist!" );
                 $RT::Handle->Rollback();
                 return (undef);
             }
             $extra_item->Delete;
             $extra_values--;
-
         }
     }
     my $newval = RT::ObjectCustomFieldValue->new( $self->CurrentUser );
@@ -1067,7 +1029,7 @@ sub AddValueForObject {
 
     unless ($val) {
         $RT::Handle->Rollback();
-        return ($val);
+        return ($val, $self->loc("Couldn't create record"));
     }
 
     $RT::Handle->Commit();
@@ -1088,10 +1050,9 @@ and returns a boolean; returns true if the Pattern is empty.
 
 sub MatchPattern {
     my $self = shift;
-    my $regex = $self->Pattern;
+    my $regex = $self->Pattern or return 1;
 
-    return 1 if !length($regex);
-    return ($_[0] =~ $regex);
+    return (( defined $_[0] ? $_[0] : '') =~ $regex);
 }
 
 
@@ -1110,8 +1071,8 @@ sub FriendlyPattern {
     my $self = shift;
     my $regex = $self->Pattern;
 
-    return '' if !length($regex);
-    if ($regex =~ /\(\?#([^)]*)\)/) {
+    return '' unless length $regex;
+    if ( $regex =~ /\(\?#([^)]*)\)/ ) {
         return '[' . $self->loc($1) . ']';
     }
     else {
@@ -1137,7 +1098,7 @@ sub DeleteValueForObject {
     my %args = ( Object => undef,
                  Content => undef,
                  Id => undef,
-		     @_ );
+             @_ );
 
 
     unless ($self->CurrentUserHasRight('ModifyCustomField')) {
@@ -1147,14 +1108,14 @@ sub DeleteValueForObject {
     my $oldval = RT::ObjectCustomFieldValue->new($self->CurrentUser);
 
     if (my $id = $args{'Id'}) {
-	$oldval->Load($id);
+        $oldval->Load($id);
     }
     unless ($oldval->id) { 
-	$oldval->LoadByObjectContentAndCustomField(
-	    Object => $args{'Object'}, 
-	    Content =>  $args{'Content'}, 
-	    CustomField => $self->Id,
-	);
+        $oldval->LoadByObjectContentAndCustomField(
+            Object => $args{'Object'}, 
+            Content =>  $args{'Content'}, 
+            CustomField => $self->Id,
+        );
     }
 
 
@@ -1180,26 +1141,26 @@ sub DeleteValueForObject {
 
 =head2 ValuesForObject OBJECT
 
-Return an RT::ObjectCustomFieldValues object containing all of this custom field's values for OBJECT 
+Return an L object containing all of this custom field's values for OBJECT 
 
 =cut
 
 sub ValuesForObject {
-	my $self = shift;
+    my $self = shift;
     my $object = shift;
 
-	my $values = new RT::ObjectCustomFieldValues($self->CurrentUser);
-	unless ($self->CurrentUserHasRight('SeeCustomField')) {
+    my $values = new RT::ObjectCustomFieldValues($self->CurrentUser);
+    unless ($self->CurrentUserHasRight('SeeCustomField')) {
         # Return an empty object if they have no rights to see
         return ($values);
     }
-	
-	
-	$values->LimitToCustomField($self->Id);
-	$values->LimitToEnabled();
+    
+    
+    $values->LimitToCustomField($self->Id);
+    $values->LimitToEnabled();
     $values->LimitToObject($object);
 
-	return ($values);
+    return ($values);
 }
 
 
@@ -1209,10 +1170,10 @@ Tell RT that a certain object accepts custom fields
 
 Examples:
 
-    'RT::Queue-RT::Ticket'                 => "Tickets",		# loc
-    'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions",	# loc
-    'RT::User'                             => "Users",			# loc
-    'RT::Group'                            => "Groups",			# loc
+    'RT::Queue-RT::Ticket'                 => "Tickets",                # loc
+    'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions",    # loc
+    'RT::User'                             => "Users",                  # loc
+    'RT::Group'                            => "Groups",                 # loc
 
 This is a class method. 
 
@@ -1275,8 +1236,6 @@ With two arguments, attemptes to set the relevant template value.
 
 =cut
 
-
-
 sub _URLTemplate {
     my $self          = shift;
     my $template_name = shift;
@@ -1300,4 +1259,34 @@ sub _URLTemplate {
 
     }
 }
+
+sub SetBasedOn {
+    my $self = shift;
+    my $value = shift;
+
+    return $self->DeleteAttribute( "BasedOn" )
+        unless defined $value and length $value;
+
+    my $cf = RT::CustomField->new( $self->CurrentUser );
+    $cf->Load( ref $value ? $value->Id : $value );
+
+    return (0, "Permission denied")
+        unless $cf->Id && $cf->CurrentUserHasRight('SeeCustomField');
+
+    return $self->AddAttribute(
+        Name => "BasedOn",
+        Description => "Custom field whose CF we depend on",
+        Content => $cf->Id,
+    );
+}
+
+sub BasedOnObj {
+    my $self = shift;
+    my $obj = RT::CustomField->new( $self->CurrentUser );
+
+    my $attribute = $self->FirstAttribute("BasedOn");
+    $obj->Load($attribute->Content) if defined $attribute;
+    return $obj;
+}
+
 1;
diff --git a/rt/lib/RT/CustomFields.pm b/rt/lib/RT/CustomFields.pm
index 7ac18fb4e..27867e8e0 100644
--- a/rt/lib/RT/CustomFields.pm
+++ b/rt/lib/RT/CustomFields.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,6 +45,7 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 # Autogenerated by DBIx::SearchBuilder factory (by )
 # WARNING: THIS FILE IS AUTOGENERATED. ALL CHANGES TO THIS FILE WILL BE LOST.  
 # 
diff --git a/rt/lib/RT/CustomFields_Overlay.pm b/rt/lib/RT/CustomFields_Overlay.pm
index b9f3787f4..0f117c64c 100644
--- a/rt/lib/RT/CustomFields_Overlay.pm
+++ b/rt/lib/RT/CustomFields_Overlay.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,6 +45,7 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 =head1 NAME
 
   RT::CustomFields - a collection of RT CustomField objects
@@ -58,11 +59,6 @@
 =head1 METHODS
 
 
-=begin testing
-
-ok (require RT::CustomFields);
-
-=end testing
 
 =cut
 
@@ -186,25 +182,32 @@ Returns the next custom field that this user can see.
 sub Next {
     my $self = shift;
     
-    
     my $CF = $self->SUPER::Next();
-    if ((defined($CF)) and (ref($CF))) {
-
-	if ($CF->CurrentUserHasRight('SeeCustomField')) {
-	    return($CF);
-	}
-	
-	#If the user doesn't have the right to show this queue
-	else {	
-	    return($self->Next());
-	}
-    }
-    #if there never was any queue
-    else {
-	return(undef);
-    }	
-    
+    return $CF unless $CF;
+
+    $CF->SetContextOject( $self->ContextObject );
+
+    return $self->Next unless $CF->CurrentUserHasRight('SeeCustomField');
+    return $CF;
 }
+
+sub SetContextObject {
+    my $self = shift;
+    return $self->{'context_object'} = shift;
+}
+  
+sub ContextObject {
+    my $self = shift;
+    return $self->{'context_object'};
+}
+
+sub NewItem {
+    my $self = shift;
+    my $res = RT::CustomField->new($self->CurrentUser);
+    $res->SetContextObject($self->ContextObject);
+    return $res;
+}
+
 # }}}
 
 sub LimitToLookupType  {
@@ -251,7 +254,7 @@ sub LimitToGlobalOrObjectId {
                  ENTRYAGGREGATOR => 'OR' ) unless $global_only;
 
     $self->OrderByCols(
-	{ ALIAS => $self->_OCFAlias, FIELD => 'ObjectId' },
+	{ ALIAS => $self->_OCFAlias, FIELD => 'ObjectId', ORDER => 'DESC' },
 	{ ALIAS => $self->_OCFAlias, FIELD => 'SortOrder' },
     );
     
@@ -259,6 +262,6 @@ sub LimitToGlobalOrObjectId {
     #$self->OrderBy( ALIAS => $class_cfs , FIELD => "SortOrder", ORDER => 'ASC');
 
 }
-  
+
 1;
 
diff --git a/rt/lib/RT/Dashboard.pm b/rt/lib/RT/Dashboard.pm
new file mode 100644
index 000000000..c0531c464
--- /dev/null
+++ b/rt/lib/RT/Dashboard.pm
@@ -0,0 +1,358 @@
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
+=head1 NAME
+
+  RT::Dashboard - an API for saving and retrieving dashboards
+
+=head1 SYNOPSIS
+
+  use RT::Dashboard
+
+=head1 DESCRIPTION
+
+  Dashboard is an object that can belong to either an RT::User or an
+  RT::Group.  It consists of an ID, a name, and a number of
+  saved searches and portlets.
+
+=head1 METHODS
+
+
+=cut
+
+package RT::Dashboard;
+
+use RT::SavedSearch;
+
+use strict;
+use warnings;
+use base qw/RT::SharedSetting/;
+
+use RT::System;
+RT::System::AddRights(
+    SubscribeDashboard => 'Subscribe to dashboards', #loc_pair
+
+    SeeDashboard       => 'View system dashboards', #loc_pair
+    CreateDashboard    => 'Create system dashboards', #loc_pair
+    ModifyDashboard    => 'Modify system dashboards', #loc_pair
+    DeleteDashboard    => 'Delete system dashboards', #loc_pair
+
+    SeeOwnDashboard    => 'View personal dashboards', #loc_pair
+    CreateOwnDashboard => 'Create personal dashboards', #loc_pair
+    ModifyOwnDashboard => 'Modify personal dashboards', #loc_pair
+    DeleteOwnDashboard => 'Delete personal dashboards', #loc_pair
+);
+
+
+=head2 ObjectName
+
+An object of this class is called "dashboard"
+
+=cut
+
+sub ObjectName { "dashboard" }
+
+sub SaveAttribute {
+    my $self   = shift;
+    my $object = shift;
+    my $args   = shift;
+
+    return $object->AddAttribute(
+        'Name'        => 'Dashboard',
+        'Description' => $args->{'Name'},
+        'Content'     => {Panes => $args->{'Panes'}},
+    );
+}
+
+sub UpdateAttribute {
+    my $self = shift;
+    my $args = shift;
+
+    my ($status, $msg) = (1, undef);
+    if (defined $args->{'Panes'}) {
+        ($status, $msg) = $self->{'Attribute'}->SetSubValues(
+            Panes => $args->{'Panes'},
+        );
+    }
+
+    if ($status && $args->{'Name'}) {
+        ($status, $msg) = $self->{'Attribute'}->SetDescription($args->{'Name'})
+            unless $self->Name eq $args->{'Name'};
+    }
+
+    if ($status && $args->{'Privacy'}) {
+        my ($new_obj_type, $new_obj_id) = split /-/, $args->{'Privacy'};
+        my ($obj_type, $obj_id) = split /-/, $self->Privacy;
+
+        my $attr = $self->{'Attribute'};
+        if ($new_obj_type ne $obj_type) {
+            ($status, $msg) = $attr->SetObjectType($new_obj_type);
+        }
+        if ($status && $new_obj_id != $obj_id ) {
+            ($status, $msg) = $attr->SetObjectId($new_obj_id);
+        }
+        $self->{'Privacy'} = $args->{'Privacy'} if $status;
+    }
+
+    return ($status, $msg);
+}
+
+=head2 Panes
+
+Returns a hashref of pane name to portlets
+
+=cut
+
+sub Panes {
+    my $self = shift;
+    return unless ref($self->{'Attribute'}) eq 'RT::Attribute';
+    return $self->{'Attribute'}->SubValue('Panes') || {};
+}
+
+=head2 Portlets
+
+Returns the list of this dashboard's portlets, each a hashref with key
+C being C or C.
+
+=cut
+
+sub Portlets {
+    my $self = shift;
+    return map { @$_ } values %{ $self->Panes };
+}
+
+=head2 Dashboards
+
+Returns a list of loaded sub-dashboards
+
+=cut
+
+sub Dashboards {
+    my $self = shift;
+    return map {
+        my $search = RT::Dashboard->new($self->CurrentUser);
+        $search->LoadById($_->{id});
+        $search
+    } grep { $_->{portlet_type} eq 'dashboard' } $self->Portlets;
+}
+
+=head2 Searches
+
+Returns a list of loaded saved searches
+
+=cut
+
+sub Searches {
+    my $self = shift;
+    return map {
+        my $search = RT::SavedSearch->new($self->CurrentUser);
+        $search->Load($_->{privacy}, $_->{id});
+        $search
+    } grep { $_->{portlet_type} eq 'search' } $self->Portlets;
+}
+
+=head2 ShowSearchName Portlet
+
+Returns an array for one saved search, suitable for passing to
+/Elements/ShowSearch.
+
+=cut
+
+sub ShowSearchName {
+    my $self = shift;
+    my $portlet = shift;
+
+    if ($portlet->{privacy} eq 'RT::System') {
+        return Name => $portlet->{description};
+    }
+
+    return SavedSearch => join('-', $portlet->{privacy}, 'SavedSearch', $portlet->{id});
+}
+
+=head2 PossibleHiddenSearches
+
+This will return a list of saved searches that are potentially not visible by
+all users for whom the dashboard is visible. You may pass in a privacy to
+use instead of the dashboard's privacy.
+
+=cut
+
+sub PossibleHiddenSearches {
+    my $self = shift;
+    my $privacy = shift || $self->Privacy;
+
+    return grep { !$_->IsVisibleTo($privacy) } $self->Searches, $self->Dashboards;
+}
+
+# _PrivacyObjects: returns a list of objects that can be used to load
+# dashboards from. If the Modify parameter is true, then check modify rights.
+# If the Create parameter is true, then check create rights. Otherwise, check
+# read rights.
+
+sub _PrivacyObjects {
+    my $self = shift;
+    my %args = @_;
+
+    my $CurrentUser = $self->CurrentUser;
+    my @objects;
+
+    my $prefix = $args{Modify} ? "Modify"
+               : $args{Create} ? "Create"
+                               : "See";
+
+    push @objects, $CurrentUser->UserObj
+        if $self->CurrentUser->HasRight(
+            Right  => "${prefix}OwnDashboard",
+            Object => $RT::System,
+        );
+
+    my $groups = RT::Groups->new($CurrentUser);
+    $groups->LimitToUserDefinedGroups;
+    $groups->WithMember( PrincipalId => $CurrentUser->Id,
+                         Recursively => 1 );
+
+    push @objects, grep {
+        $self->CurrentUser->HasRight(
+            Right  => "${prefix}GroupDashboard",
+            Object => $_,
+        )
+    } @{ $groups->ItemsArrayRef };
+
+    push @objects, RT::System->new($CurrentUser)
+        if $CurrentUser->HasRight(
+            Right  => "${prefix}Dashboard",
+            Object => $RT::System,
+        );
+
+    return @objects;
+}
+
+# ACLs
+
+sub _CurrentUserCan {
+    my $self    = shift;
+    my $privacy = shift || $self->Privacy;
+    my %args    = @_;
+
+    if (!defined($privacy)) {
+        $RT::Logger->debug("No privacy provided to $self->_CurrentUserCan");
+        return 0;
+    }
+
+    my $object = $self->_GetObject($privacy);
+    return 0 unless $object;
+
+    my $level;
+
+       if ($object->isa('RT::User'))   { $level = 'Own' }
+    elsif ($object->isa('RT::Group'))  { $level = 'Group' }
+    elsif ($object->isa('RT::System')) { $level = '' }
+    else {
+        $RT::Logger->error("Unknown object $object from privacy $privacy");
+        return 0;
+    }
+
+    # users are mildly special-cased, since we actually have to check that
+    # the user is operating on himself
+    if ($object->isa('RT::User')) {
+        return 0 unless $object->Id == $self->CurrentUser->Id;
+    }
+
+    my $right = $args{FullRight}
+             || join('', $args{Right}, $level, 'Dashboard');
+
+    # all rights, except group rights, are global
+    $object = $RT::System unless $object->isa('RT::Group');
+
+    return $self->CurrentUser->HasRight(
+        Right  => $right,
+        Object => $object,
+    );
+}
+
+sub CurrentUserCanSee {
+    my $self    = shift;
+    my $privacy = shift;
+
+    $self->_CurrentUserCan($privacy, Right => 'See');
+}
+
+sub CurrentUserCanCreate {
+    my $self    = shift;
+    my $privacy = shift;
+
+    $self->_CurrentUserCan($privacy, Right => 'Create');
+}
+
+sub CurrentUserCanModify {
+    my $self    = shift;
+    my $privacy = shift;
+
+    $self->_CurrentUserCan($privacy, Right => 'Modify');
+}
+
+sub CurrentUserCanDelete {
+    my $self    = shift;
+    my $privacy = shift;
+
+    $self->_CurrentUserCan($privacy, Right => 'Delete');
+}
+
+sub CurrentUserCanSubscribe {
+    my $self    = shift;
+    my $privacy = shift;
+
+    $self->_CurrentUserCan($privacy, FullRight => 'SubscribeDashboard');
+}
+
+eval "require RT::Dashboard_Vendor";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Dashboard_Vendor.pm});
+eval "require RT::Dashboard_Local";
+die $@ if ($@ && $@ !~ qr{^Can't locate RT/Dashboard_Local.pm});
+
+1;
diff --git a/rt/lib/RT/Date.pm b/rt/lib/RT/Date.pm
index 8e9383fd7..fc4c43ce4 100644
--- a/rt/lib/RT/Date.pm
+++ b/rt/lib/RT/Date.pm
@@ -1,8 +1,8 @@
 # BEGIN BPS TAGGED BLOCK {{{
 # 
 # COPYRIGHT:
-#  
-# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC 
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
 #                                          
 # 
 # (Except where explicitly superseded by other copyright notices)
@@ -45,6 +45,7 @@
 # those contributions and any derivatives thereof.
 # 
 # END BPS TAGGED BLOCK }}}
+
 =head1 NAME
 
   RT::Date - a simple Object Oriented date.
@@ -59,11 +60,6 @@ RT Date is a simple Date Object designed to be speedy and easy for RT to use
 
 The fact that it assumes that a time of 0 means "never" is probably a bug.
 
-=begin testing
-
-ok (require RT::Date);
-
-=end testing
 
 =head1 METHODS
 
@@ -73,12 +69,11 @@ ok (require RT::Date);
 package RT::Date;
 
 use Time::Local;
-
-use RT::Base;
+use POSIX qw(tzset);
 
 use strict;
-use vars qw/@ISA/;
-@ISA = qw/RT::Base/;
+use warnings;
+use base qw/RT::Base/;
 
 use vars qw($MINUTE $HOUR $DAY $WEEK $MONTH $YEAR);
 
@@ -86,30 +81,68 @@ $MINUTE = 60;
 $HOUR   = 60 * $MINUTE;
 $DAY    = 24 * $HOUR;
 $WEEK   = 7 * $DAY;
-$MONTH  = 4 * $WEEK;
-$YEAR   = 365 * $DAY;
-
-# {{{ sub new 
-
-sub new  {
-  my $proto = shift;
-  my $class = ref($proto) || $proto;
-  my $self  = {};
-  bless ($self, $class);
-  $self->CurrentUser(@_);
-  $self->Unix(0);
-  return $self;
+$MONTH  = 30.4375 * $DAY;
+$YEAR   = 365.25 * $DAY;
+
+our @MONTHS = (
+    'Jan', # loc
+    'Feb', # loc
+    'Mar', # loc
+    'Apr', # loc
+    'May', # loc
+    'Jun', # loc
+    'Jul', # loc
+    'Aug', # loc
+    'Sep', # loc
+    'Oct', # loc
+    'Nov', # loc
+    'Dec', # loc
+);
+
+our @DAYS_OF_WEEK = (
+    'Sun', # loc
+    'Mon', # loc
+    'Tue', # loc
+    'Wed', # loc
+    'Thu', # loc
+    'Fri', # loc
+    'Sat', # loc
+);
+
+our @FORMATTERS = (
+    'DefaultFormat', # loc
+    'ISO',           # loc
+    'W3CDTF',        # loc
+    'RFC2822',       # loc
+    'RFC2616',       # loc
+    'iCal',          # loc
+);
+if ( eval 'use DateTime qw(); 1;' && eval 'use DateTime::Locale qw(); 1;' && 
+     DateTime->can('format_cldr') && DateTime::Locale::root->can('date_format_full') ) {
+    push @FORMATTERS, 'LocalizedDateTime'; # loc
 }
 
-# }}}
+=head2 new
+
+Object constructor takes one argument C object.
 
-# {{{ sub Set
+=cut
 
-=head2 sub Set
+sub new {
+    my $proto = shift;
+    my $class = ref($proto) || $proto;
+    my $self  = {};
+    bless ($self, $class);
+    $self->CurrentUser(@_);
+    $self->Unix(0);
+    return $self;
+}
 
-takes a param hash with the fields 'Format' and 'Value'
+=head2 Set
 
-if $args->{'Format'} is 'unix', takes the number of seconds since the epoch 
+Takes a param hash with the fields C, C and C.
+
+If $args->{'Format'} is 'unix', takes the number of seconds since the epoch.
 
 If $args->{'Format'} is ISO, tries to parse an ISO date.
 
@@ -118,114 +151,100 @@ things out. This is a heavyweight operation that should never be called from
 within RT's core. But it's really useful for something like the textbox date
 entry where we let the user do whatever they want.
 
-If $args->{'Value'}  is 0, assumes you mean never.
-
-=begin testing
-
-use_ok(RT::Date);
-my $date = RT::Date->new($RT::SystemUser);
-$date->Set(Format => 'unix', Value => '0');
-ok ($date->ISO eq '1970-01-01 00:00:00', "Set a date to midnight 1/1/1970 GMT");
-
-=end testing
+If $args->{'Value'} is 0, assumes you mean never.
 
 =cut
 
 sub Set {
     my $self = shift;
-    my %args = ( Format => 'unix',
-                 Value  => time,
-                 @_ );
-    if ( !$args{'Value'}
-         || ( ( $args{'Value'} =~ /^\d*$/ ) and ( $args{'Value'} == 0 ) ) ) {
-        $self->Unix(-1);
-        return ( $self->Unix() );
-    }
+    my %args = (
+        Format   => 'unix',
+        Value    => time,
+        Timezone => 'user',
+        @_
+    );
+
+    return $self->Unix(0) unless $args{'Value'};
 
     if ( $args{'Format'} =~ /^unix$/i ) {
-        $self->Unix( $args{'Value'} );
+        return $self->Unix( $args{'Value'} );
     }
-
     elsif ( $args{'Format'} =~ /^(sql|datemanip|iso)$/i ) {
-	$args{'Value'} =~ s!/!-!g;
-
-        if (( $args{'Value'} =~ /^(\d{4}?)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/ )
-            || ( $args{'Value'} =~
-                 /^(\d{4}?)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$/ )
-            || ( $args{'Value'} =~
-                 /^(\d{4}?)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)\+00$/ )
-            || ($args{'Value'} =~ /^(\d{4}?)(\d\d)(\d\d)(\d\d):(\d\d):(\d\d)$/ )
+        $args{'Value'} =~ s!/!-!g;
+
+        if (   ( $args{'Value'} =~ /^(\d{4})?(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/ )
+            || ( $args{'Value'} =~ /^(\d{4})?(\d\d)(\d\d)(\d\d):(\d\d):(\d\d)$/ )
+            || ( $args{'Value'} =~ /^(?:(\d{4})-)?(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$/ )
+            || ( $args{'Value'} =~ /^(?:(\d{4})-)?(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)\+00$/ )
           ) {
 
-            my $year  = $1;
-            my $mon   = $2;
-            my $mday  = $3;
-            my $hours = $4;
-            my $min   = $5;
-            my $sec   = $6;
+            my ($year, $mon, $mday, $hours, $min, $sec)  = ($1, $2, $3, $4, $5, $6);
+
+            # use current year if string has no value
+            $year ||= (localtime time)[5] + 1900;
 
             #timegm expects month as 0->11
             $mon--;
 
-            #now that we've parsed it, deal with the case where everything
-            #was 0
-            if ( $mon == -1 ) {
-                $self->Unix(-1);
-            }
-            else {
-
-                #Dateamnip strings aren't in GMT.
-                if ( $args{'Format'} =~ /^datemanip$/i ) {
-                    $self->Unix(
-                          timelocal( $sec, $min, $hours, $mday, $mon, $year ) );
-                }
-
-                #ISO and SQL dates are in GMT
-                else {
-                    $self->Unix(
-                             timegm( $sec, $min, $hours, $mday, $mon, $year ) );
-                }
-
-                $self->Unix(-1) unless $self->Unix;
-            }
+            #now that we've parsed it, deal with the case where everything was 0
+            return $self->Unix(0) if $mon < 0 || $mon > 11;
+
+            my $tz = lc $args{'Format'} eq 'datemanip'? 'user': 'utc';
+            $self->Unix( $self->Timelocal( $tz, $sec, $min, $hours, $mday, $mon, $year ) );
+
+            $self->Unix(0) unless $self->Unix > 0;
         }
         else {
-            use Carp;
-            Carp::cluck;
-            $RT::Logger->debug(
-                     "Couldn't parse date $args{'Value'} as a $args{'Format'}");
-
+            $RT::Logger->warning(
+                "Couldn't parse date '$args{'Value'}' as a $args{'Format'} format"
+            );
+            return $self->Unix(0);
         }
     }
     elsif ( $args{'Format'} =~ /^unknown$/i ) {
         require Time::ParseDate;
-
-        #Convert it to an ISO format string
-
-	my $date = Time::ParseDate::parsedate($args{'Value'},
-			UK => $RT::DateDayBeforeMonth,
-			PREFER_PAST => $RT::AmbiguousDayInPast,
-			PREFER_FUTURE => !($RT::AmbiguousDayInPast));
-
-        #This date has now been set to a date in the _local_ timezone.
-        #since ISO dates are known to be in GMT (for RT's purposes);
-
-        $RT::Logger->debug( "RT::Date used date::parse to make "
-                            . $args{'Value'}
-                            . " $date\n" );
-
-        return ( $self->Set( Format => 'unix', Value => "$date" ) );
+        # the module supports only legacy timezones like PDT or EST...
+        # so we parse date as GMT and later apply offset, this only
+        # should be applied to absolute times, so compensate shift in NOW
+        my $now = time;
+        $now += ($self->Localtime( $args{Timezone}, $now ))[9];
+        my $date = Time::ParseDate::parsedate(
+            $args{'Value'},
+            GMT           => 1,
+            NOW           => $now,
+            UK            => RT->Config->Get('DateDayBeforeMonth'),
+            PREFER_PAST   => RT->Config->Get('AmbiguousDayInPast'),
+            PREFER_FUTURE => RT->Config->Get('AmbiguousDayInFuture'),
+        );
+        # apply timezone offset
+        $date -= ($self->Localtime( $args{Timezone}, $date ))[9];
+
+        $RT::Logger->debug(
+            "RT::Date used Time::ParseDate to make '$args{'Value'}' $date\n"
+        );
+
+        return $self->Set( Format => 'unix', Value => $date);
     }
     else {
-        die "Unknown Date format: " . $args{'Format'} . "\n";
+        $RT::Logger->error(
+            "Unknown Date format: $args{'Format'}\n"
+        );
+        return $self->Unix(0);
     }
 
-    return ( $self->Unix() );
+    return $self->Unix;
 }
 
-# }}}
+=head2 SetToNow
 
-# {{{ sub SetToMidnight 
+Set the object's time to the current time. Takes no arguments
+and returns unix time.
+
+=cut
+
+sub SetToNow {
+    return $_[0]->Unix(time);
+}
 
 =head2 SetToMidnight [Timezone => 'utc']
 
@@ -236,131 +255,114 @@ Arguments:
 
 =over 4
 
-=item Timezone - Timezone context C or C
+=item Timezone
+
+Timezone context C, C or C. See also L.
+
+=back
 
 =cut
 
 sub SetToMidnight {
     my $self = shift;
-    my %args = ( Timezone => 'UTC', @_ );
-    if ( lc $args{'Timezone'} eq 'server' ) {
-        $self->Unix( Time::Local::timelocal( 0,0,0,(localtime $self->Unix)[3..7] ) );
-    } else {
-        $self->Unix( Time::Local::timegm( 0,0,0,(gmtime $self->Unix)[3..7] ) );
-    }
-    return ($self->Unix);
-}
-
-
-# }}}
-
-# {{{ sub SetToNow
-sub SetToNow {
-	my $self = shift;
-	return($self->Set(Format => 'unix', Value => time))
+    my %args = ( Timezone => '', @_ );
+    my $new = $self->Timelocal(
+        $args{'Timezone'},
+        0,0,0,($self->Localtime( $args{'Timezone'} ))[3..9]
+    );
+    return $self->Unix( $new );
 }
-# }}}
-
-# {{{ sub Diff
 
 =head2 Diff
 
-Takes either an RT::Date object or the date in unixtime format as a string
+Takes either an C object or the date in unixtime format as a string,
+if nothing is specified uses the current time.
 
-Returns the differnce between $self and that time as a number of seconds
+Returns the differnce between the time in the current object and that time
+as a number of seconds. Returns C if any of two compared values is
+incorrect or not set.
 
 =cut
 
 sub Diff {
     my $self = shift;
     my $other = shift;
-
-    if (ref($other) eq 'RT::Date') {
-	$other=$other->Unix;
+    $other = time unless defined $other;
+    if ( UNIVERSAL::isa( $other, 'RT::Date' ) ) {
+        $other = $other->Unix;
     }
-    return ($self->Unix - $other);
-}
-# }}}
+    return undef unless $other=~ /^\d+$/ && $other > 0;
 
-# {{{ sub DiffAsString
+    my $unix = $self->Unix;
+    return undef unless $unix > 0;
 
-=head2 sub DiffAsString
+    return $unix - $other;
+}
+
+=head2 DiffAsString
 
-Takes either an RT::Date object or the date in unixtime format as a string
+Takes either an C object or the date in unixtime format as a string,
+if nothing is specified uses the current time.
 
-Returns the differnce between $self and that time as a number of seconds as
-as string fit for human consumption
+Returns the differnce between C<$self> and that time as a number of seconds as
+a localized string fit for human consumption. Returns empty string if any of
+two compared values is incorrect or not set.
 
 =cut
 
 sub DiffAsString {
     my $self = shift;
-    my $other = shift;
-
-
-    if ($other < 1) {
-	return ("");
-    }
-    if ($self->Unix < 1) {
-	return("");
-    }
-    my $diff = $self->Diff($other);
+    my $diff = $self->Diff( @_ );
+    return '' unless defined $diff;
 
-    return ($self->DurationAsString($diff));
+    return $self->DurationAsString( $diff );
 }
-# }}}
-
-# {{{ sub DurationAsString
-
 
 =head2 DurationAsString
 
-Takes a number of seconds. returns a string describing that duration
+Takes a number of seconds. Returns a localized string describing
+that duration.
 
 =cut
 
 sub DurationAsString {
-
     my $self     = shift;
-    my $duration = shift;
-
-    my ( $negative, $s );
+    my $duration = int shift;
 
-    $negative = 1 if ( $duration < 0 );
+    my ( $negative, $s, $time_unit );
+    $negative = 1 if $duration < 0;
+    $duration = abs $duration;
 
-    $duration = abs($duration);
-
-    my $time_unit;
     if ( $duration < $MINUTE ) {
         $s         = $duration;
         $time_unit = $self->loc("sec");
     }
     elsif ( $duration < ( 2 * $HOUR ) ) {
-        $s         = int( $duration / $MINUTE );
+        $s         = int( $duration / $MINUTE + 0.5 );
         $time_unit = $self->loc("min");
     }
     elsif ( $duration < ( 2 * $DAY ) ) {
-        $s         = int( $duration / $HOUR );
+        $s         = int( $duration / $HOUR + 0.5 );
         $time_unit = $self->loc("hours");
     }
     elsif ( $duration < ( 2 * $WEEK ) ) {
-        $s         = int( $duration / $DAY );
+        $s         = int( $duration / $DAY + 0.5 );
         $time_unit = $self->loc("days");
     }
     elsif ( $duration < ( 2 * $MONTH ) ) {
-        $s         = int( $duration / $WEEK );
+        $s         = int( $duration / $WEEK + 0.5 );
         $time_unit = $self->loc("weeks");
     }
     elsif ( $duration < $YEAR ) {
-        $s         = int( $duration / $MONTH );
+        $s         = int( $duration / $MONTH + 0.5 );
         $time_unit = $self->loc("months");
     }
     else {
-        $s         = int( $duration / $YEAR );
+        $s         = int( $duration / $YEAR + 0.5 );
         $time_unit = $self->loc("years");
     }
 
-    if ($negative) {
+    if ( $negative ) {
         return $self->loc( "[_1] [_2] ago", $s, $time_unit );
     }
     else {
@@ -368,47 +370,46 @@ sub DurationAsString {
     }
 }
 
-# }}}
+=head2 AgeAsString
 
-# {{{ sub AgeAsString
+Takes nothing. Returns a string that's the differnce between the
+time in the object and now.
 
-=head2 sub AgeAsString
+=cut
 
-Takes nothing
+sub AgeAsString { return $_[0]->DiffAsString }
 
-Returns a string that's the differnce between the time in the object and now
 
-=cut
 
-sub AgeAsString {
-    my $self = shift;
-    return ($self->DiffAsString(time));
-    }
-# }}}
+=head2 AsString
 
-# {{{ sub AsString
+Returns the object's time as a localized string with curent user's prefered
+format and timezone.
 
-=head2 sub AsString
-
-Returns the object\'s time as a string with the current timezone.
+If the current user didn't choose prefered format then system wide setting is
+used or L if the latter is not specified. See config option
+C.
 
 =cut
 
 sub AsString {
     my $self = shift;
-    return ($self->loc("Not set")) if ($self->Unix <= 0);
+    my %args = (@_);
 
-    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($self->Unix);
+    return $self->loc("Not set") unless $self->Unix > 0;
 
-    return $self->loc("[_1] [_2] [_3] [_4]:[_5]:[_6] [_7]", $self->GetWeekday($wday), $self->GetMonth($mon), map {sprintf "%02d", $_} ($mday, $hour, $min, $sec), ($year+1900));
-}
-# }}}
+    my $format = RT->Config->Get( 'DateTimeFormat', $self->CurrentUser ) || 'DefaultFormat';
+    $format = { Format => $format } unless ref $format;
+    %args = (%$format, %args);
 
-# {{{ GetWeekday
+    return $self->Get( Timezone => 'user', %args );
+}
 
 =head2 GetWeekday DAY
 
-Takes an integer day of week and returns a localized string for that day of week
+Takes an integer day of week and returns a localized string for
+that day of week. Valid values are from range 0-6, Note that B<0
+is sunday>.
 
 =cut
 
@@ -416,227 +417,628 @@ sub GetWeekday {
     my $self = shift;
     my $dow = shift;
     
-    return $self->loc('Mon.') if ($dow == 1);
-    return $self->loc('Tue.') if ($dow == 2);
-    return $self->loc('Wed.') if ($dow == 3);
-    return $self->loc('Thu.') if ($dow == 4);
-    return $self->loc('Fri.') if ($dow == 5);
-    return $self->loc('Sat.') if ($dow == 6);
-    return $self->loc('Sun.') if ($dow == 0);
+    return $self->loc($DAYS_OF_WEEK[$dow])
+        if $DAYS_OF_WEEK[$dow];
+    return '';
 }
 
-# }}}
-
-# {{{ GetMonth
-
-=head2 GetMonth DAY
+=head2 GetMonth MONTH
 
-Takes an integer month and returns a localized string for that month 
+Takes an integer month and returns a localized string for that month.
+Valid values are from from range 0-11.
 
 =cut
 
 sub GetMonth {
     my $self = shift;
-   my $mon = shift;
-
-    # We do this rather than an array so that we don't call localize 12x what we need to
-    return $self->loc('Jan.') if ($mon == 0);
-    return $self->loc('Feb.') if ($mon == 1);
-    return $self->loc('Mar.') if ($mon == 2);
-    return $self->loc('Apr.') if ($mon == 3);
-    return $self->loc('May.') if ($mon == 4);
-    return $self->loc('Jun.') if ($mon == 5);
-    return $self->loc('Jul.') if ($mon == 6);
-    return $self->loc('Aug.') if ($mon == 7);
-    return $self->loc('Sep.') if ($mon == 8);
-    return $self->loc('Oct.') if ($mon == 9);
-    return $self->loc('Nov.') if ($mon == 10);
-    return $self->loc('Dec.') if ($mon == 11);
-}
-
-# }}}
+    my $mon = shift;
 
-# {{{ sub AddSeconds
+    return $self->loc($MONTHS[$mon])
+        if $MONTHS[$mon];
+    return '';
+}
 
-=head2 sub AddSeconds
+=head2 AddSeconds SECONDS
 
-Takes a number of seconds as a string
+Takes a number of seconds and returns the new unix time.
 
-Returns the new time
+Negative value can be used to substract seconds.
 
 =cut
 
 sub AddSeconds {
     my $self = shift;
-    my $delta = shift;
+    my $delta = shift or return $self->Unix;
     
     $self->Set(Format => 'unix', Value => ($self->Unix + $delta));
-    
+ 
     return ($self->Unix);
-    
+}
+
+=head2 AddDays [DAYS]
 
+Adds C<24 hours * DAYS> to the current time. Adds one day when
+no argument is specified. Negative value can be used to substract
+days.
+
+Returns new unix time.
+
+=cut
+
+sub AddDays {
+    my $self = shift;
+    my $days = shift || 1;
+    return $self->AddSeconds( $days * $DAY );
 }
 
-# }}}
+=head2 AddDay
+
+Adds 24 hours to the current time. Returns new unix time.
 
-# {{{ sub AddDays
+=cut
 
-=head2 AddDays $DAYS
+sub AddDay { return $_[0]->AddSeconds($DAY) }
 
-Adds 24 hours * $DAYS to the current time
+=head2 Unix [unixtime]
+
+Optionally takes a date in unix seconds since the epoch format.
+Returns the number of seconds since the epoch
 
 =cut
 
-sub AddDays {
+sub Unix {
+    my $self = shift; 
+    $self->{'time'} = int(shift || 0) if @_;
+    return $self->{'time'};
+}
+
+=head2 DateTime
+
+Alias for L method. Arguments C and