From af59614dbbf895bf0b0ab155fe158b6152edf32b Mon Sep 17 00:00:00 2001 From: Mikal Kolbein Gule Date: Tue, 15 Oct 2013 15:20:51 +0200 Subject: [PATCH] Putting 4.2.0 on top of 4.0.17 --- bin/rt | 4 +- bin/rt-crontool | 54 +- bin/rt-mailgate | 30 +- docs/README | 25 +- docs/UPGRADING-4.0 | 9 +- docs/UPGRADING-4.2 | 305 ++ docs/authentication.pod | 139 + docs/charts.pod | 246 + docs/customizing/approvals.pod | 6 +- docs/customizing/articles_introduction.pod | 21 +- docs/customizing/styling_rt.pod | 46 +- docs/customizing/templates.pod | 52 +- docs/dashboards.pod | 206 + docs/extending/clickable_links.pod | 1 + docs/hacking.pod | 2 +- docs/images/customize-dashboards-menu.png | Bin 0 -> 37547 bytes docs/images/dashboard-chart.png | Bin 0 -> 48074 bytes docs/images/dashboard-content-invoices.png | Bin 0 -> 45939 bytes docs/images/dashboard-search-sorting.png | Bin 0 -> 21780 bytes docs/images/dashboard-subscription.png | Bin 0 -> 63498 bytes docs/images/general-owner-chart.png | Bin 0 -> 43333 bytes .../general-owner-lastupdated-chart.png | Bin 0 -> 66473 bytes docs/images/general-status-chart.png | Bin 0 -> 39175 bytes docs/images/queue-created-started-chart.png | Bin 0 -> 74489 bytes docs/incremental-export/README | 29 + docs/incremental-export/Record_Local.pm | 74 + docs/incremental-export/schema.mysql | 10 + docs/initialdata.pod | 23 +- docs/reminders.pod | 67 + docs/reporting/feeds.pod | 145 + docs/rt_perl.pod | 163 + docs/security.pod | 6 +- docs/web_deployment.pod | 30 +- etc/RT_Config.pm | 756 ++- etc/acl.Pg | 4 +- etc/initialdata | 333 +- etc/schema.Oracle | 568 +- etc/schema.Pg | 52 +- etc/schema.SQLite | 257 +- etc/schema.mysql | 51 +- etc/upgrade/3.1.0/schema.Oracle | 20 +- etc/upgrade/3.1.15/content | 5 +- etc/upgrade/3.1.17/content | 7 +- etc/upgrade/3.5.1/content | 21 +- etc/upgrade/3.7.1/content | 5 +- etc/upgrade/3.7.10/content | 5 +- etc/upgrade/3.7.15/content | 5 +- etc/upgrade/3.7.19/content | 41 +- etc/upgrade/3.7.82/content | 5 +- etc/upgrade/3.7.85/content | 15 +- etc/upgrade/3.7.86/content | 13 +- etc/upgrade/3.7.87/content | 5 +- etc/upgrade/3.8.0/content | 13 +- etc/upgrade/3.8.1/content | 13 +- etc/upgrade/3.8.2/content | 65 +- etc/upgrade/3.8.3/content | 55 +- etc/upgrade/3.8.4/content | 10 +- etc/upgrade/3.8.6/content | 5 +- etc/upgrade/3.8.8/content | 9 +- etc/upgrade/3.8.9/content | 8 +- etc/upgrade/3.9.1/content | 15 +- etc/upgrade/3.9.2/content | 15 +- etc/upgrade/3.9.5/backcompat | 16 +- etc/upgrade/3.9.7/content | 29 +- etc/upgrade/3.9.8/content | 11 +- etc/upgrade/4.0.0rc7/content | 13 +- etc/upgrade/4.0.1/content | 42 +- etc/upgrade/4.0.3/content | 5 +- etc/upgrade/4.0.4/content | 7 +- etc/upgrade/4.0.6/content | 7 +- etc/upgrade/4.0.9/content | 12 +- etc/upgrade/4.1.0/content | 43 + etc/upgrade/4.1.1/acl.Pg | 31 + etc/upgrade/4.1.1/content | 36 + etc/upgrade/4.1.1/schema.Oracle | 30 + etc/upgrade/4.1.1/schema.Pg | 33 + etc/upgrade/4.1.1/schema.SQLite | 31 + etc/upgrade/4.1.1/schema.mysql | 30 + etc/upgrade/4.1.10/schema.Oracle | 1 + etc/upgrade/4.1.10/schema.Pg | 1 + etc/upgrade/4.1.10/schema.mysql | 1 + etc/upgrade/4.1.11/schema.Oracle | 1 + etc/upgrade/4.1.11/schema.Pg | 1 + etc/upgrade/4.1.11/schema.mysql | 1 + etc/upgrade/4.1.12/content | 10 + etc/upgrade/4.1.13/backcompat | 31 + etc/upgrade/4.1.13/schema.Oracle | 3 + etc/upgrade/4.1.13/schema.Pg | 3 + etc/upgrade/4.1.13/schema.SQLite | 3 + etc/upgrade/4.1.13/schema.mysql | 2 + etc/upgrade/4.1.14/schema.Oracle | 2 + etc/upgrade/4.1.14/schema.Pg | 2 + etc/upgrade/4.1.14/schema.mysql | 2 + etc/upgrade/4.1.15/content | 22 + etc/upgrade/4.1.16/content | 16 + etc/upgrade/4.1.17/content | 26 + etc/upgrade/4.1.18/content | 16 + etc/upgrade/4.1.19/schema.Oracle | 2 + etc/upgrade/4.1.19/schema.Pg | 2 + etc/upgrade/4.1.19/schema.mysql | 2 + etc/upgrade/4.1.20/content | 56 + etc/upgrade/4.1.21/content | 64 + etc/upgrade/4.1.22/content | 85 + etc/upgrade/4.1.22/schema.Oracle | 1 + etc/upgrade/4.1.22/schema.Pg | 1 + etc/upgrade/4.1.22/schema.SQLite | 1 + etc/upgrade/4.1.22/schema.mysql | 1 + etc/upgrade/4.1.23/indexes | 169 + etc/upgrade/4.1.4/content | 49 + etc/upgrade/4.1.4/schema.Oracle | 1 + etc/upgrade/4.1.4/schema.Pg | 1 + etc/upgrade/4.1.4/schema.SQLite | 1 + etc/upgrade/4.1.4/schema.mysql | 1 + etc/upgrade/4.1.5/content | 34 + etc/upgrade/4.1.5/schema.Oracle | 5 + etc/upgrade/4.1.5/schema.Pg | 2 + etc/upgrade/4.1.5/schema.mysql | 2 + etc/upgrade/4.1.6/content | 43 + etc/upgrade/4.1.7/schema.Oracle | 5 + etc/upgrade/4.1.7/schema.Pg | 5 + etc/upgrade/4.1.7/schema.SQLite | 2 + etc/upgrade/4.1.7/schema.mysql | 5 + etc/upgrade/4.1.8/schema.Oracle | 2 + etc/upgrade/4.1.8/schema.Pg | 2 + etc/upgrade/4.1.8/schema.SQLite | 3 + etc/upgrade/4.1.8/schema.mysql | 2 + etc/upgrade/4.1.9/content | 190 + etc/upgrade/generate-rtaddressregexp | 2 +- etc/upgrade/sanity-check-stylesheets.pl | 18 +- etc/upgrade/shrink_cgm_table.pl | 4 +- etc/upgrade/shrink_transactions_table.pl | 5 +- etc/upgrade/split-out-cf-categories | 2 +- etc/upgrade/switch-templates-to | 148 + etc/upgrade/time-worked-history.pl | 110 + etc/upgrade/upgrade-articles | 2 +- lib/RT.pm | 207 +- lib/RT/ACE.pm | 191 +- lib/RT/ACL.pm | 119 +- lib/RT/Action.pm | 20 - lib/RT/Action/AutoOpen.pm | 3 +- lib/RT/Action/AutoOpenInactive.pm | 105 + lib/RT/Action/Autoreply.pm | 14 +- lib/RT/Action/CreateTickets.pm | 95 +- lib/RT/Action/EscalatePriority.pm | 86 +- lib/RT/Action/LinearEscalate.pm | 6 +- lib/RT/Action/Notify.pm | 53 +- lib/RT/Action/NotifyGroupAsComment.pm | 8 +- lib/RT/Action/OpenOnStarted.pm | 87 + lib/RT/Action/RecordComment.pm | 23 +- lib/RT/Action/RecordCorrespondence.pm | 24 +- lib/RT/Action/SendEmail.pm | 188 +- lib/RT/Action/SendForward.pm | 137 + lib/RT/Action/SetStatus.pm | 2 +- lib/RT/Approval/Rule/NewPending.pm | 2 +- lib/RT/Approval/Rule/Passed.pm | 2 +- lib/RT/Approval/Rule/Rejected.pm | 2 +- lib/RT/Article.pm | 130 +- lib/RT/Articles.pm | 25 +- lib/RT/Attachment.pm | 265 +- lib/RT/Attachments.pm | 45 +- lib/RT/Attribute.pm | 66 +- lib/RT/Attributes.pm | 32 +- lib/RT/CachedGroupMember.pm | 16 +- lib/RT/CachedGroupMembers.pm | 30 +- lib/RT/Class.pm | 217 +- lib/RT/Classes.pm | 16 +- lib/RT/Condition.pm | 26 +- lib/RT/Condition/BeforeDue.pm | 21 +- lib/RT/Condition/Overdue.pm | 8 +- lib/RT/Condition/OwnerChange.pm | 16 +- lib/RT/Condition/PriorityChange.pm | 6 +- lib/RT/Condition/PriorityExceeds.pm | 6 +- lib/RT/Condition/QueueChange.pm | 6 +- lib/RT/Condition/StatusChange.pm | 4 +- lib/RT/Config.pm | 490 +- lib/RT/Crypt.pm | 835 +++ lib/RT/Crypt/GnuPG.pm | 1928 +++---- lib/RT/Crypt/GnuPG/CRLFHandle.pm | 70 + lib/RT/Crypt/Role.pm | 254 + lib/RT/Crypt/SMIME.pm | 941 ++++ lib/RT/CurrentUser.pm | 45 +- lib/RT/CustomField.pm | 499 +- lib/RT/CustomFieldValue.pm | 41 +- lib/RT/CustomFieldValues.pm | 35 +- lib/RT/CustomFieldValues/External.pm | 4 + lib/RT/CustomFields.pm | 191 +- lib/RT/Dashboard.pm | 43 +- lib/RT/Dashboard/Mailer.pm | 64 +- lib/RT/Dashboards.pm | 4 +- lib/RT/Date.pm | 176 +- lib/RT/DependencyWalker.pm | 305 ++ lib/RT/DependencyWalker/Dependencies.pm | 65 + lib/RT/EmailParser.pm | 38 +- lib/RT/Generated.pm | 5 +- lib/RT/Generated.pm.in | 3 + lib/RT/Group.pm | 830 ++- lib/RT/GroupMember.pm | 150 +- lib/RT/GroupMembers.pm | 32 +- lib/RT/Groups.pm | 107 +- lib/RT/Handle.pm | 568 +- lib/RT/I18N.pm | 318 +- lib/RT/I18N/cs.pm | 5 +- lib/RT/I18N/fr.pm | 10 +- lib/RT/I18N/ru.pm | 2 +- lib/RT/Interface/CLI.pm | 72 +- lib/RT/Interface/Email.pm | 382 +- lib/RT/Interface/Email/Auth/Crypt.pm | 291 + lib/RT/Interface/Web.pm | 1104 ++-- lib/RT/Interface/Web/Handler.pm | 66 +- lib/RT/Interface/Web/Menu.pm | 14 +- lib/RT/Interface/Web/QueryBuilder/Tree.pm | 7 +- lib/RT/Interface/Web/Request.pm | 3 - lib/RT/Interface/Web/Session.pm | 25 +- lib/RT/Lifecycle.pm | 207 +- lib/RT/Lifecycle/Ticket.pm | 125 + lib/RT/Link.pm | 147 +- lib/RT/Links.pm | 33 +- lib/RT/Migrate.pm | 192 + lib/RT/Migrate/Importer.pm | 467 ++ lib/RT/Migrate/Importer/File.pm | 208 + lib/RT/Migrate/Incremental.pm | 622 +++ lib/RT/Migrate/Serializer.pm | 484 ++ lib/RT/Migrate/Serializer/File.pm | 171 + .../Migrate/Serializer/IncrementalRecord.pm | 80 + .../Migrate/Serializer/IncrementalRecords.pm | 69 + lib/RT/ObjectClass.pm | 37 +- lib/RT/ObjectClasses.pm | 12 +- lib/RT/ObjectCustomField.pm | 269 +- lib/RT/ObjectCustomFieldValue.pm | 44 +- lib/RT/ObjectCustomFieldValues.pm | 26 +- lib/RT/ObjectCustomFields.pm | 53 +- lib/RT/ObjectScrip.pm | 252 + lib/RT/ObjectScrips.pm | 92 + lib/RT/ObjectTopic.pm | 44 +- lib/RT/ObjectTopics.pm | 18 +- lib/RT/PlackRunner.pm | 163 + lib/RT/Plugin.pm | 10 +- lib/RT/Pod/HTML.pm | 4 + lib/RT/Pod/HTMLBatch.pm | 10 +- lib/RT/Principal.pm | 113 +- lib/RT/Principals.pm | 15 +- lib/RT/Queue.pm | 811 +-- lib/RT/Queues.pm | 22 +- lib/RT/Record.pm | 825 ++- lib/RT/Record/AddAndSort.pm | 621 +++ lib/RT/Record/Role.pm | 78 + lib/RT/Record/Role/Lifecycle.pm | 219 + lib/RT/Record/Role/Links.pm | 174 + lib/RT/Record/Role/Rights.pm | 133 + lib/RT/Record/Role/Roles.pm | 626 +++ lib/RT/Record/Role/Status.pm | 314 ++ lib/RT/Reminders.pm | 23 +- lib/RT/Report/Tickets.pm | 1052 +++- lib/RT/Report/Tickets/Entry.pm | 81 +- lib/RT/Rule.pm | 3 +- lib/RT/SQL.pm | 79 - lib/RT/SavedSearches.pm | 13 +- lib/RT/Scrip.pm | 448 +- lib/RT/ScripAction.pm | 219 +- lib/RT/ScripActions.pm | 24 +- lib/RT/ScripCondition.pm | 104 +- lib/RT/ScripConditions.pm | 28 +- lib/RT/Scrips.pm | 231 +- lib/RT/Search/ActiveTicketsInQueue.pm | 5 +- lib/RT/Search/Simple.pm | 271 + lib/RT/SearchBuilder.pm | 779 ++- lib/RT/SearchBuilder/AddAndSort.pm | 219 + lib/RT/SearchBuilder/Role.pm | 77 + lib/RT/SearchBuilder/Role/Roles.pm | 399 ++ lib/RT/SharedSetting.pm | 30 +- lib/RT/SharedSettings.pm | 4 +- lib/RT/Shredder.pm | 14 +- lib/RT/Shredder/ACE.pm | 20 - lib/RT/Shredder/Attachment.pm | 42 - lib/RT/Shredder/CachedGroupMember.pm | 42 - lib/RT/Shredder/Constants.pm | 19 - lib/RT/Shredder/CustomField.pm | 36 - lib/RT/Shredder/CustomFieldValue.pm | 30 - lib/RT/Shredder/Dependency.pm | 1 - lib/RT/Shredder/Group.pm | 45 - lib/RT/Shredder/GroupMember.pm | 56 +- lib/RT/Shredder/Link.pm | 20 +- lib/RT/Shredder/ObjectCustomFieldValue.pm | 56 - lib/RT/Shredder/Plugin.pm | 10 +- lib/RT/Shredder/Plugin/Summary.pm | 4 +- lib/RT/Shredder/Plugin/Users.pm | 1 + lib/RT/Shredder/Principal.pm | 38 +- lib/RT/Shredder/Queue.pm | 2 +- lib/RT/Shredder/Record.pm | 103 +- lib/RT/Shredder/Scrip.pm | 63 +- lib/RT/Shredder/ScripAction.pm | 16 - lib/RT/Shredder/ScripCondition.pm | 17 - lib/RT/Shredder/Template.pm | 38 +- lib/RT/Shredder/Ticket.pm | 33 +- lib/RT/Shredder/Transaction.pm | 33 - lib/RT/Shredder/User.pm | 46 +- lib/RT/Squish/CSS.pm | 23 +- lib/RT/Squish/JS.pm | 14 +- lib/RT/StyleGuide.pod | 395 +- lib/RT/System.pm | 264 +- lib/RT/Template.pm | 243 +- lib/RT/Templates.pm | 42 +- lib/RT/Test.pm | 423 +- lib/RT/Test/Apache.pm | 30 +- lib/RT/Test/GnuPG.pm | 11 +- lib/RT/Test/SMIME.pm | 164 + lib/RT/Test/Shredder.pm | 340 ++ lib/RT/Test/Web.pm | 67 +- lib/RT/Ticket.pm | 2186 +++----- lib/RT/Tickets.pm | 1674 ++---- lib/RT/Topic.pm | 66 +- lib/RT/Topics.pm | 12 - lib/RT/Transaction.pm | 754 ++- lib/RT/Transactions.pm | 54 +- lib/RT/URI.pm | 25 +- lib/RT/URI/a.pm | 10 +- lib/RT/URI/fsck_com_article.pm | 82 +- lib/RT/URI/fsck_com_rt.pm | 18 +- lib/RT/User.pm | 492 +- lib/RT/Users.pm | 119 +- lib/RT/Util.pm | 10 +- .../UiOCallbacks/Elements/Tabs/Privileged | 12 +- sbin/rt-attributes-viewer | 15 +- sbin/rt-clean-sessions | 19 +- sbin/rt-dump-metadata | 23 +- sbin/rt-email-dashboards | 20 +- sbin/rt-email-digest | 34 +- sbin/rt-email-group-admin | 49 +- sbin/rt-fulltext-indexer | 20 +- sbin/rt-preferences-viewer | 25 +- sbin/rt-server | 147 +- sbin/rt-server.fcgi | 147 +- sbin/rt-session-viewer | 15 +- sbin/rt-setup-database | 264 +- sbin/rt-setup-fulltext-index | 17 +- sbin/rt-shredder | 179 +- sbin/rt-test-dependencies | 288 +- sbin/rt-validate-aliases | 1 + sbin/rt-validator | 402 +- sbin/standalone_httpd | 147 +- share/html/Admin/Articles/Classes/Modify.html | 62 +- .../html/Admin/Articles/Classes/Objects.html | 8 +- share/html/Admin/Articles/Classes/index.html | 3 +- share/html/Admin/Articles/index.html | 2 +- .../html/Admin/CustomFields/GroupRights.html | 2 +- share/html/Admin/CustomFields/Modify.html | 35 +- share/html/Admin/CustomFields/Objects.html | 24 +- share/html/Admin/CustomFields/index.html | 10 +- .../Admin/Elements/ConfigureDashboardsInMenu | 76 + share/html/Admin/Elements/EditCustomField | 15 +- share/html/Admin/Elements/EditCustomFields | 20 +- share/html/Admin/Elements/EditQueueWatchers | 6 +- share/html/Admin/Elements/EditRights | 20 +- .../Admin/Elements/EditRightsCategoryTabs | 7 +- share/html/Admin/Elements/EditScrips | 152 +- share/html/Admin/Elements/LoggingSummary | 92 + share/html/Admin/Elements/MembershipsPage | 151 + share/html/Admin/Elements/ModifyTemplate | 9 +- share/html/Admin/Elements/SelectGroups | 2 +- .../html/Admin/Elements/SelectNewGroupMembers | 28 +- share/html/Admin/Elements/SelectStage | 10 +- share/html/Admin/Elements/SelectStageForAdded | 54 + share/html/Admin/Elements/ShowKeyInfo | 49 +- share/html/Admin/Elements/UpgradeHistory | 71 + share/html/Admin/Elements/UpgradeHistoryRow | 98 + share/html/Admin/Global/DashboardsInMenu.html | 113 + share/html/Admin/Global/MyRT.html | 10 +- share/html/Admin/Global/Template.html | 4 +- share/html/Admin/Global/index.html | 2 +- share/html/Admin/Groups/History.html | 5 +- share/html/Admin/Groups/Memberships.html | 48 + share/html/Admin/Groups/Modify.html | 23 +- share/html/Admin/Groups/index.html | 37 +- share/html/Admin/Queues/History.html | 5 +- share/html/Admin/Queues/Modify.html | 34 +- share/html/Admin/Queues/People.html | 5 +- share/html/Admin/Queues/Scrips.html | 15 +- share/html/Admin/Queues/index.html | 5 +- share/html/Admin/Scrips/Create.html | 138 + share/html/Admin/Scrips/Elements/EditBasics | 74 + .../html/Admin/Scrips/Elements/EditCustomCode | 77 + .../html/Admin/Scrips/Elements/SelectTemplate | 105 + share/html/Admin/Scrips/Modify.html | 136 + share/html/Admin/Scrips/Objects.html | 166 + share/html/Admin/Scrips/index.html | 71 + share/html/Admin/Tools/Configuration.html | 84 +- share/html/Admin/Tools/Queries.html | 2 +- .../Admin/Tools/Shredder/Elements/PluginHelp | 4 +- share/html/Admin/Tools/Shredder/autohandler | 4 +- share/html/Admin/Tools/Theme.html | 49 +- share/html/Admin/Tools/index.html | 2 +- share/html/Admin/Users/DashboardsInMenu.html | 118 + share/html/Admin/Users/History.html | 5 +- share/html/Admin/Users/Keys.html | 131 + share/html/Admin/Users/Memberships.html | 90 +- share/html/Admin/Users/Modify.html | 149 +- share/html/Admin/Users/MyRT.html | 8 +- share/html/Admin/Users/index.html | 22 +- share/html/Admin/index.html | 2 +- share/html/Approvals/Display.html | 2 +- share/html/Approvals/Elements/Approve | 4 +- .../html/Approvals/Elements/PendingMyApproval | 41 +- share/html/Approvals/Elements/ShowDependency | 26 +- share/html/Approvals/index.html | 13 +- share/html/Articles/Article/Edit.html | 67 +- .../Article/Elements/EditCustomFields | 10 +- .../html/Articles/Article/Elements/EditLinks | 12 +- .../Articles/Article/Elements/Preformatted | 15 +- .../html/Articles/Article/Elements/ShowLinks | 8 +- .../Article/Elements/ShowSavedSearches | 7 +- .../Article/Elements/ShowSearchCriteria | 10 +- share/html/Articles/Article/History.html | 20 +- share/html/Articles/Article/PreCreate.html | 3 +- share/html/Articles/Article/Search.html | 131 +- share/html/Articles/Elements/CreateArticle | 4 +- share/html/Articles/Elements/MaybeNeedsSetup | 54 + share/html/Articles/Elements/NeedsSetup | 52 + share/html/Articles/Elements/NewestArticles | 4 +- share/html/Articles/Elements/QuickSearch | 4 +- share/html/Articles/Elements/SubjectOverride | 92 + share/html/Articles/Elements/UpdatedArticles | 4 +- share/html/Articles/index.html | 1 + .../Dashboards/Elements/DashboardsForObject | 6 +- .../html/Dashboards/Elements/ListOfDashboards | 7 +- share/html/Dashboards/Elements/SelectPrivacy | 6 +- .../Dashboards/Elements/ShowPortlet/component | 12 +- .../Dashboards/Elements/ShowPortlet/dashboard | 1 - share/html/Dashboards/Modify.html | 8 +- share/html/Dashboards/Queries.html | 6 +- share/html/Dashboards/Render.html | 11 +- share/html/Dashboards/Subscription.html | 31 +- share/html/Download/CustomFieldValue/dhandler | 4 +- share/html/Elements/AddLinks | 103 + share/html/Elements/BulkCustomFields | 91 + share/html/Elements/BulkLinks | 198 + share/html/Elements/Callback | 5 +- share/html/Elements/CollectionAsTable/Row | 1 - share/html/Elements/CollectionList | 31 +- share/html/Elements/CollectionListPaging | 20 +- share/html/Elements/ColumnMap | 90 +- share/html/Elements/Crypt/KeyIssues | 94 + .../Elements/Crypt/SelectKeyForEncryption | 80 + share/html/Elements/Crypt/SelectKeyForSigning | 67 + share/html/Elements/Crypt/SignEncryptWidget | 188 + share/html/Elements/CryptStatus | 195 + share/html/Elements/EditCustomField | 28 +- .../html/Elements/EditCustomFieldAutocomplete | 4 +- share/html/Elements/EditCustomFieldBinary | 7 +- share/html/Elements/EditCustomFieldCombobox | 3 +- .../Elements/EditCustomFieldCustomGroupings | 71 + share/html/Elements/EditCustomFieldImage | 7 +- share/html/Elements/EditCustomFieldSelect | 10 +- share/html/Elements/EditCustomFields | 119 + share/html/Elements/EditLinks | 94 +- share/html/Elements/EmailInput | 4 +- share/html/Elements/Error | 13 +- share/html/Elements/FindUser | 50 + share/html/Elements/FoldStanzaJS | 50 + share/html/Elements/GotoUser | 62 + share/html/Elements/Header | 14 +- share/html/Elements/HeaderJavascript | 13 +- share/html/Elements/JavascriptConfig | 84 + share/html/Elements/ListActions | 5 +- share/html/Elements/ListMenu | 4 + share/html/Elements/Login | 5 +- share/html/Elements/LoginHelp | 56 + share/html/Elements/Logo | 1 - share/html/Elements/MakeClicky | 10 +- share/html/Elements/Menu | 36 +- share/html/Elements/MessageBox | 5 +- share/html/Elements/MyRT | 23 +- share/html/Elements/MyReminders | 1 - share/html/Elements/PageLayout | 11 + share/html/Elements/QueryString | 3 +- share/html/Elements/QueueSummaryByLifecycle | 6 +- share/html/Elements/QueueSummaryByStatus | 6 +- share/html/Elements/QuickCreate | 2 +- share/html/Elements/RT__Article/ColumnMap | 13 +- share/html/Elements/RT__Class/ColumnMap | 8 +- share/html/Elements/RT__CustomField/ColumnMap | 37 +- share/html/Elements/RT__Dashboard/ColumnMap | 9 +- share/html/Elements/RT__Group/ColumnMap | 17 +- share/html/Elements/RT__Queue/ColumnMap | 20 +- share/html/Elements/RT__SavedSearch/ColumnMap | 9 +- share/html/Elements/RT__Scrip/ColumnMap | 79 +- share/html/Elements/RT__Template/ColumnMap | 35 +- share/html/Elements/RT__Ticket/ColumnMap | 90 +- share/html/Elements/RT__User/ColumnMap | 9 +- share/html/Elements/SelectBoolean | 4 +- share/html/Elements/SelectCustomFieldValue | 15 +- share/html/Elements/SelectDate | 17 +- share/html/Elements/SelectDateRelation | 2 +- share/html/Elements/SelectLang | 2 - share/html/Elements/SelectMatch | 10 +- share/html/Elements/SelectObject | 124 + share/html/Elements/SelectOwnerAutocomplete | 2 +- share/html/Elements/SelectOwnerDropdown | 27 +- share/html/Elements/SelectQueue | 70 +- share/html/Elements/SelectStatus | 58 +- .../Elements/ShowCustomFieldCustomGroupings | 78 + share/html/Elements/ShowCustomFieldImage | 2 +- share/html/Elements/ShowCustomFields | 10 +- share/html/Elements/ShowHistory | 184 + share/html/Elements/ShowLink | 18 +- share/html/Elements/ShowLinks | 154 +- share/html/Elements/ShowLinksOfType | 127 + share/html/Elements/ShowMemberships | 4 +- share/html/Elements/ShowMessageHeaders | 100 + share/html/Elements/ShowMessageStanza | 176 + share/html/Elements/ShowPrincipal | 71 + share/html/Elements/ShowRecord | 100 + share/html/Elements/ShowRelationLabel | 30 +- share/html/Elements/ShowReminders | 2 +- share/html/Elements/ShowSearch | 21 +- share/html/Elements/ShowTransaction | 267 + .../html/Elements/ShowTransactionAttachments | 293 + share/html/Elements/ShowUser | 53 +- share/html/Elements/SimpleSearch | 5 +- share/html/Elements/Submit | 4 +- share/html/Elements/Tabs | 244 +- share/html/Elements/TicketList | 12 +- share/html/Elements/TitleBox | 1 + share/html/Elements/TitleBoxEnd | 1 + share/html/Elements/TitleBoxStart | 1 + share/html/Elements/ValidateCustomFields | 60 +- share/html/Errors/WebRemoteUser/Deauthorized | 50 + .../html/Errors/WebRemoteUser/NoInternalUser | 50 + share/html/Errors/WebRemoteUser/NoRemoteUser | 50 + .../UserAutocreateDefaultsOnLogin | 50 + share/html/Errors/WebRemoteUser/Wrapper | 80 + .../Helpers/Autocomplete/CustomFieldValues | 6 +- share/html/Helpers/Autocomplete/Groups | 7 +- share/html/Helpers/Autocomplete/Owners | 28 +- share/html/Helpers/Autocomplete/Tickets | 112 + share/html/Helpers/Autocomplete/Users | 70 +- share/html/Helpers/TicketHistory | 18 +- share/html/Helpers/UserInfo | 77 + share/html/Install/DatabaseDetails.html | 1 - share/html/Install/Finish.html | 4 +- share/html/Install/Global.html | 1 - share/html/NoAuth/Logout.html | 2 +- share/html/NoAuth/css/aileron/AfterMenus | 71 + share/html/NoAuth/css/aileron/InHeader | 15 +- share/html/NoAuth/css/autohandler | 21 +- share/html/NoAuth/css/ballard/InHeader | 4 +- share/html/NoAuth/css/dhandler | 2 +- share/html/NoAuth/css/rudder/AfterMenus | 71 + share/html/NoAuth/css/rudder/InHeader | 50 + share/html/NoAuth/css/web2/AfterMenus | 74 + share/html/NoAuth/css/web2/InHeader | 25 +- share/html/NoAuth/iCal/dhandler | 64 +- share/html/NoAuth/js/autohandler | 17 +- share/html/NoAuth/js/dhandler | 4 - share/html/Prefs/DashboardsInMenu.html | 113 + share/html/Prefs/MyRT.html | 9 +- share/html/Prefs/Other.html | 6 +- share/html/Prefs/Quicksearch.html | 13 +- share/html/Prefs/Search.html | 4 +- share/html/REST/1.0/Forms/group/ns | 2 +- share/html/REST/1.0/Forms/queue/ns | 2 +- share/html/REST/1.0/Forms/ticket/attachments | 32 +- share/html/REST/1.0/Forms/ticket/default | 31 +- share/html/REST/1.0/Forms/ticket/history | 96 +- share/html/REST/1.0/Forms/transaction/default | 109 +- share/html/REST/1.0/Forms/user/ns | 2 +- share/html/REST/1.0/NoAuth/mail-gateway | 18 +- share/html/REST/1.0/dhandler | 32 +- share/html/REST/1.0/search/ticket | 36 +- share/html/Search/Build.html | 20 +- share/html/Search/Bulk.html | 188 +- share/html/Search/Chart | 461 +- share/html/Search/Chart.html | 90 +- share/html/Search/Elements/BuildFormatString | 2 +- share/html/Search/Elements/Chart | 99 +- share/html/Search/Elements/ChartTable | 119 + share/html/Search/Elements/ConditionRow | 8 +- share/html/Search/Elements/EditSearches | 13 +- share/html/Search/Elements/PickBasics | 25 +- share/html/Search/Elements/PickCFs | 21 +- share/html/Search/Elements/PickCriteria | 2 + share/html/Search/Elements/PickObjectCFs | 74 + share/html/Search/Elements/PickTicketCFs | 1 + share/html/Search/Elements/ResultsRSSView | 17 +- share/html/Search/Elements/SearchPrivacy | 6 +- share/html/Search/Elements/SearchesForObject | 4 +- share/html/Search/Elements/SelectAndOr | 4 +- .../html/Search/Elements/SelectChartFunction | 79 + share/html/Search/Elements/SelectGroup | 2 +- share/html/Search/Elements/SelectGroupBy | 26 +- share/html/Search/Elements/SelectLinks | 19 +- share/html/Search/Elements/SelectPersonType | 4 +- share/html/Search/Results.html | 10 +- share/html/Search/Results.tsv | 6 + share/html/Search/Simple.html | 4 +- share/html/Search/index.html | 50 + share/html/SelfService/Closed.html | 4 +- share/html/SelfService/Create.html | 43 +- share/html/SelfService/Display.html | 120 +- share/html/SelfService/Elements/MyRequests | 18 +- .../Helpers/Autocomplete/CustomFieldValues | 48 + .../SelfService/Helpers/Autocomplete/Users | 48 + share/html/SelfService/Prefs.html | 6 +- share/html/SelfService/Update.html | 10 +- share/html/Ticket/Attachment/dhandler | 84 +- share/html/Ticket/Create.html | 117 +- share/html/Ticket/Crypt.html | 100 + share/html/Ticket/Display.html | 43 +- share/html/Ticket/Elements/AddAttachments | 17 +- share/html/Ticket/Elements/AddWatchers | 4 +- share/html/Ticket/Elements/Bookmark | 44 +- share/html/Ticket/Elements/ClickToShowHistory | 9 +- share/html/Ticket/Elements/DelayShowHistory | 78 + share/html/Ticket/Elements/EditBasics | 9 +- share/html/Ticket/Elements/EditCustomFields | 62 +- share/html/Ticket/Elements/EditDates | 1 + share/html/Ticket/Elements/EditMerge | 79 + share/html/Ticket/Elements/EditPeople | 30 +- .../Elements/EditTransactionCustomFields | 7 +- share/html/Ticket/Elements/EditWatchers | 2 +- .../html/Ticket/Elements/LoadTextAttachments | 45 +- share/html/Ticket/Elements/PreviewScrips | 2 +- share/html/Ticket/Elements/Reminders | 70 +- share/html/Ticket/Elements/SelectStatus | 81 + share/html/Ticket/Elements/ShowAttachments | 32 +- share/html/Ticket/Elements/ShowBasics | 5 + share/html/Ticket/Elements/ShowCustomFields | 2 +- share/html/Ticket/Elements/ShowDates | 19 +- .../html/Ticket/Elements/ShowDependencyStatus | 78 + share/html/Ticket/Elements/ShowGroupMembers | 23 +- share/html/Ticket/Elements/ShowPeople | 1 + share/html/Ticket/Elements/ShowRequestor | 25 +- .../Ticket/Elements/ShowRequestorExtraInfo | 40 +- .../html/Ticket/Elements/ShowRequestorTickets | 36 +- .../Ticket/Elements/ShowSimplifiedRecipients | 7 +- share/html/Ticket/Elements/ShowSummary | 20 +- share/html/Ticket/Elements/UpdateCc | 11 +- share/html/Ticket/Forward.html | 29 +- .../Graphs/Elements/EditGraphProperties | 10 +- share/html/Ticket/History.html | 13 +- share/html/Ticket/Modify.html | 47 +- share/html/Ticket/ModifyAll.html | 86 +- share/html/Ticket/ModifyDates.html | 2 +- share/html/Ticket/ModifyLinks.html | 15 +- share/html/Ticket/ModifyPeople.html | 4 +- share/html/Ticket/Reminders.html | 7 +- share/html/Ticket/ShowEmailRecord.html | 23 +- share/html/Ticket/Update.html | 45 +- share/html/Tools/MyDay.html | 5 +- .../html/User/Elements/Portlets/ActiveTickets | 70 + .../html/User/Elements/Portlets/CreateTicket | 58 + share/html/User/Elements/Portlets/ExtraInfo | 56 + .../User/Elements/Portlets/InactiveTickets | 70 + share/html/User/Elements/TicketList | 115 + share/html/User/Elements/UserInfo | 64 + share/html/User/History.html | 67 + share/html/User/Search.html | 100 + share/html/User/Summary.html | 87 + share/html/Widgets/ComboBox | 2 +- share/html/Widgets/SavedSearch | 7 +- share/html/Widgets/SelectionBox | 115 +- share/html/Widgets/TitleBoxStart | 4 +- share/html/autohandler | 5 - share/html/index.html | 27 +- share/html/m/_elements/header | 6 +- share/html/m/_elements/login | 9 +- share/html/m/ticket/create | 73 +- share/html/m/ticket/reply | 18 +- share/html/m/ticket/show | 43 +- share/html/m/tickets/search | 19 +- share/po/ar.po | 4753 +++++++++------- share/po/bg.po | 4713 ++++++++++------ share/po/ca.po | 4703 ++++++++++------ share/po/cs.po | 4705 ++++++++++------ share/po/da.po | 4699 ++++++++++------ share/po/de.po | 4705 ++++++++++------ share/po/el.po | 4711 ++++++++++------ share/po/en.po | 4 +- share/po/en_GB.po | 2498 +++++---- share/po/es.po | 4697 ++++++++++------ share/po/et.po | 4733 +++++++++------- share/po/fi.po | 4719 +++++++++------- share/po/fr.po | 4699 ++++++++++------ share/po/he.po | 4755 +++++++++------- share/po/hr.po | 4689 ++++++++++------ share/po/hu.po | 4739 +++++++++------- share/po/id.po | 4725 +++++++++------- share/po/is.po | 4753 +++++++++------- share/po/it.po | 4711 ++++++++++------ share/po/ja.po | 4743 +++++++++------- share/po/lt.po | 4699 ++++++++++------ share/po/lv.po | 4709 ++++++++++------ share/po/mk.po | 4771 +++++++++------- share/po/nb.po | 4705 ++++++++++------ share/po/nl.po | 4753 +++++++++------- share/po/nn.po | 4709 ++++++++++------ share/po/oc.po | 4753 +++++++++------- share/po/pl.po | 4725 +++++++++------- share/po/pt.po | 4739 +++++++++------- share/po/pt_BR.po | 4695 ++++++++++------ share/po/pt_PT.po | 4773 +++++++++------- share/po/rt.pot | 4793 ++++++++++------- share/po/ru.po | 4711 ++++++++++------ share/po/sk.po | 4723 +++++++++------- share/po/sl.po | 4701 ++++++++++------ share/po/sv.po | 4707 ++++++++++------ share/po/tr.po | 4717 +++++++++------- share/po/zh_CN.po | 4691 ++++++++++------ share/po/zh_TW.po | 4691 ++++++++++------ share/static/RichText/LICENSE.md | 1264 +++++ share/static/RichText/ckeditor.js | 871 +++ share/static/RichText/config.js | 47 + share/static/RichText/contents.css | 99 + share/static/RichText/lang/af.js | 5 + share/static/RichText/lang/ar.js | 5 + share/static/RichText/lang/bg.js | 5 + share/static/RichText/lang/bn.js | 5 + share/static/RichText/lang/bs.js | 5 + share/static/RichText/lang/ca.js | 5 + share/static/RichText/lang/cs.js | 5 + share/static/RichText/lang/cy.js | 5 + share/static/RichText/lang/da.js | 5 + share/static/RichText/lang/de.js | 5 + share/static/RichText/lang/el.js | 5 + share/static/RichText/lang/en-au.js | 5 + share/static/RichText/lang/en-ca.js | 5 + share/static/RichText/lang/en-gb.js | 5 + share/static/RichText/lang/en.js | 5 + share/static/RichText/lang/eo.js | 5 + share/static/RichText/lang/es.js | 5 + share/static/RichText/lang/et.js | 5 + share/static/RichText/lang/eu.js | 5 + share/static/RichText/lang/fa.js | 5 + share/static/RichText/lang/fi.js | 5 + share/static/RichText/lang/fo.js | 5 + share/static/RichText/lang/fr-ca.js | 5 + share/static/RichText/lang/fr.js | 5 + share/static/RichText/lang/gl.js | 5 + share/static/RichText/lang/gu.js | 5 + share/static/RichText/lang/he.js | 5 + share/static/RichText/lang/hi.js | 5 + share/static/RichText/lang/hr.js | 5 + share/static/RichText/lang/hu.js | 5 + share/static/RichText/lang/is.js | 5 + share/static/RichText/lang/it.js | 5 + share/static/RichText/lang/ja.js | 5 + share/static/RichText/lang/ka.js | 5 + share/static/RichText/lang/km.js | 5 + share/static/RichText/lang/ko.js | 5 + share/static/RichText/lang/ku.js | 5 + share/static/RichText/lang/lt.js | 5 + share/static/RichText/lang/lv.js | 5 + share/static/RichText/lang/mk.js | 5 + share/static/RichText/lang/mn.js | 5 + share/static/RichText/lang/ms.js | 5 + share/static/RichText/lang/nb.js | 5 + share/static/RichText/lang/nl.js | 5 + share/static/RichText/lang/no.js | 5 + share/static/RichText/lang/pl.js | 5 + share/static/RichText/lang/pt-br.js | 5 + share/static/RichText/lang/pt.js | 5 + share/static/RichText/lang/ro.js | 5 + share/static/RichText/lang/ru.js | 5 + share/static/RichText/lang/sk.js | 5 + share/static/RichText/lang/sl.js | 5 + share/static/RichText/lang/sr-latn.js | 5 + share/static/RichText/lang/sr.js | 5 + share/static/RichText/lang/sv.js | 5 + share/static/RichText/lang/th.js | 5 + share/static/RichText/lang/tr.js | 5 + share/static/RichText/lang/ug.js | 5 + share/static/RichText/lang/uk.js | 5 + share/static/RichText/lang/vi.js | 5 + share/static/RichText/lang/zh-cn.js | 5 + share/static/RichText/lang/zh.js | 5 + .../plugins/a11yhelp/dialogs/a11yhelp.js | 10 + .../dialogs/lang/_translationstatus.txt | 25 + .../plugins/a11yhelp/dialogs/lang/ar.js | 9 + .../plugins/a11yhelp/dialogs/lang/bg.js | 9 + .../plugins/a11yhelp/dialogs/lang/ca.js | 9 + .../plugins/a11yhelp/dialogs/lang/cs.js | 10 + .../plugins/a11yhelp/dialogs/lang/cy.js | 9 + .../plugins/a11yhelp/dialogs/lang/da.js | 9 + .../plugins/a11yhelp/dialogs/lang/de.js | 10 + .../plugins/a11yhelp/dialogs/lang/el.js | 10 + .../plugins/a11yhelp/dialogs/lang/en.js | 9 + .../plugins/a11yhelp/dialogs/lang/eo.js | 10 + .../plugins/a11yhelp/dialogs/lang/es.js | 10 + .../plugins/a11yhelp/dialogs/lang/et.js | 9 + .../plugins/a11yhelp/dialogs/lang/fa.js | 9 + .../plugins/a11yhelp/dialogs/lang/fi.js | 10 + .../plugins/a11yhelp/dialogs/lang/fr.js | 10 + .../plugins/a11yhelp/dialogs/lang/gu.js | 9 + .../plugins/a11yhelp/dialogs/lang/he.js | 9 + .../plugins/a11yhelp/dialogs/lang/hi.js | 9 + .../plugins/a11yhelp/dialogs/lang/hr.js | 9 + .../plugins/a11yhelp/dialogs/lang/hu.js | 9 + .../plugins/a11yhelp/dialogs/lang/it.js | 10 + .../plugins/a11yhelp/dialogs/lang/ja.js | 9 + .../plugins/a11yhelp/dialogs/lang/ku.js | 10 + .../plugins/a11yhelp/dialogs/lang/lt.js | 9 + .../plugins/a11yhelp/dialogs/lang/lv.js | 10 + .../plugins/a11yhelp/dialogs/lang/mk.js | 9 + .../plugins/a11yhelp/dialogs/lang/mn.js | 9 + .../plugins/a11yhelp/dialogs/lang/nb.js | 9 + .../plugins/a11yhelp/dialogs/lang/nl.js | 10 + .../plugins/a11yhelp/dialogs/lang/no.js | 9 + .../plugins/a11yhelp/dialogs/lang/pl.js | 9 + .../plugins/a11yhelp/dialogs/lang/pt-br.js | 9 + .../plugins/a11yhelp/dialogs/lang/pt.js | 9 + .../plugins/a11yhelp/dialogs/lang/ro.js | 9 + .../plugins/a11yhelp/dialogs/lang/ru.js | 9 + .../plugins/a11yhelp/dialogs/lang/sk.js | 10 + .../plugins/a11yhelp/dialogs/lang/sl.js | 9 + .../plugins/a11yhelp/dialogs/lang/sv.js | 10 + .../plugins/a11yhelp/dialogs/lang/tr.js | 10 + .../plugins/a11yhelp/dialogs/lang/ug.js | 9 + .../plugins/a11yhelp/dialogs/lang/uk.js | 9 + .../plugins/a11yhelp/dialogs/lang/vi.js | 9 + .../plugins/a11yhelp/dialogs/lang/zh-cn.js | 7 + .../RichText/plugins/about/dialogs/about.js | 6 + .../plugins/about/dialogs/logo_ckeditor.png | Bin 0 -> 2759 bytes .../plugins/clipboard/dialogs/paste.js | 11 + .../colordialog/dialogs/colordialog.js | 13 + .../plugins/dialog/dialogDefinition.js | 4 + .../RichText/plugins/div/dialogs/div.js | 9 + .../plugins/fakeobjects/images/spacer.gif | Bin 0 -> 43 bytes .../RichText/plugins/find/dialogs/find.js | 24 + .../RichText/plugins/flash/dialogs/flash.js | 23 + .../plugins/flash/images/placeholder.png | Bin 0 -> 256 bytes .../RichText/plugins/forms/dialogs/button.js | 8 + .../plugins/forms/dialogs/checkbox.js | 8 + .../RichText/plugins/forms/dialogs/form.js | 8 + .../plugins/forms/dialogs/hiddenfield.js | 8 + .../RichText/plugins/forms/dialogs/radio.js | 8 + .../RichText/plugins/forms/dialogs/select.js | 20 + .../plugins/forms/dialogs/textarea.js | 8 + .../plugins/forms/dialogs/textfield.js | 10 + .../plugins/forms/images/hiddenfield.gif | Bin 0 -> 105 bytes share/static/RichText/plugins/icons.png | Bin 0 -> 20881 bytes .../RichText/plugins/iframe/dialogs/iframe.js | 10 + .../plugins/iframe/images/placeholder.png | Bin 0 -> 449 bytes .../RichText/plugins/image/dialogs/image.js | 41 + .../RichText/plugins/image/images/noimage.png | Bin 0 -> 2115 bytes .../RichText/plugins/link/dialogs/anchor.js | 8 + .../RichText/plugins/link/dialogs/link.js | 36 + .../RichText/plugins/link/images/anchor.png | Bin 0 -> 566 bytes .../plugins/liststyle/dialogs/liststyle.js | 10 + .../plugins/magicline/images/icon.png | Bin 0 -> 172 bytes .../plugins/pagebreak/images/pagebreak.gif | Bin 0 -> 54 bytes .../plugins/pastefromword/filter/default.js | 31 + .../RichText/plugins/preview/preview.html | 10 + .../static/RichText/plugins/scayt/LICENSE.md | 28 + share/static/RichText/plugins/scayt/README.md | 25 + .../RichText/plugins/scayt/dialogs/options.js | 19 + .../plugins/scayt/dialogs/toolbar.css | 71 + .../showblocks/images/block_address.png | Bin 0 -> 171 bytes .../showblocks/images/block_blockquote.png | Bin 0 -> 181 bytes .../plugins/showblocks/images/block_div.png | Bin 0 -> 136 bytes .../plugins/showblocks/images/block_h1.png | Bin 0 -> 127 bytes .../plugins/showblocks/images/block_h2.png | Bin 0 -> 134 bytes .../plugins/showblocks/images/block_h3.png | Bin 0 -> 131 bytes .../plugins/showblocks/images/block_h4.png | Bin 0 -> 133 bytes .../plugins/showblocks/images/block_h5.png | Bin 0 -> 133 bytes .../plugins/showblocks/images/block_h6.png | Bin 0 -> 129 bytes .../plugins/showblocks/images/block_p.png | Bin 0 -> 119 bytes .../plugins/showblocks/images/block_pre.png | Bin 0 -> 136 bytes .../RichText/plugins/smiley/dialogs/smiley.js | 10 + .../plugins/smiley/images/angel_smile.gif | Bin 0 -> 465 bytes .../plugins/smiley/images/angry_smile.gif | Bin 0 -> 443 bytes .../plugins/smiley/images/broken_heart.gif | Bin 0 -> 192 bytes .../plugins/smiley/images/confused_smile.gif | Bin 0 -> 464 bytes .../plugins/smiley/images/cry_smile.gif | Bin 0 -> 468 bytes .../plugins/smiley/images/devil_smile.gif | Bin 0 -> 436 bytes .../smiley/images/embaressed_smile.gif | Bin 0 -> 442 bytes .../smiley/images/embarrassed_smile.gif | Bin 0 -> 442 bytes .../plugins/smiley/images/envelope.gif | Bin 0 -> 426 bytes .../RichText/plugins/smiley/images/heart.gif | Bin 0 -> 183 bytes .../RichText/plugins/smiley/images/kiss.gif | Bin 0 -> 241 bytes .../plugins/smiley/images/lightbulb.gif | Bin 0 -> 368 bytes .../plugins/smiley/images/omg_smile.gif | Bin 0 -> 451 bytes .../plugins/smiley/images/regular_smile.gif | Bin 0 -> 450 bytes .../plugins/smiley/images/sad_smile.gif | Bin 0 -> 460 bytes .../plugins/smiley/images/shades_smile.gif | Bin 0 -> 449 bytes .../plugins/smiley/images/teeth_smile.gif | Bin 0 -> 442 bytes .../plugins/smiley/images/thumbs_down.gif | Bin 0 -> 408 bytes .../plugins/smiley/images/thumbs_up.gif | Bin 0 -> 396 bytes .../plugins/smiley/images/tongue_smile.gif | Bin 0 -> 446 bytes .../plugins/smiley/images/tounge_smile.gif | Bin 0 -> 446 bytes .../images/whatchutalkingabout_smile.gif | Bin 0 -> 452 bytes .../plugins/smiley/images/wink_smile.gif | Bin 0 -> 458 bytes .../dialogs/lang/_translationstatus.txt | 20 + .../plugins/specialchar/dialogs/lang/ca.js | 13 + .../plugins/specialchar/dialogs/lang/cs.js | 13 + .../plugins/specialchar/dialogs/lang/cy.js | 14 + .../plugins/specialchar/dialogs/lang/de.js | 13 + .../plugins/specialchar/dialogs/lang/el.js | 13 + .../plugins/specialchar/dialogs/lang/en.js | 13 + .../plugins/specialchar/dialogs/lang/eo.js | 12 + .../plugins/specialchar/dialogs/lang/et.js | 13 + .../plugins/specialchar/dialogs/lang/fa.js | 13 + .../plugins/specialchar/dialogs/lang/fi.js | 13 + .../plugins/specialchar/dialogs/lang/fr.js | 11 + .../plugins/specialchar/dialogs/lang/he.js | 13 + .../plugins/specialchar/dialogs/lang/hr.js | 13 + .../plugins/specialchar/dialogs/lang/it.js | 14 + .../plugins/specialchar/dialogs/lang/ku.js | 14 + .../plugins/specialchar/dialogs/lang/lv.js | 13 + .../plugins/specialchar/dialogs/lang/nb.js | 11 + .../plugins/specialchar/dialogs/lang/nl.js | 13 + .../plugins/specialchar/dialogs/lang/no.js | 11 + .../plugins/specialchar/dialogs/lang/pt-br.js | 11 + .../plugins/specialchar/dialogs/lang/sk.js | 13 + .../plugins/specialchar/dialogs/lang/sv.js | 11 + .../plugins/specialchar/dialogs/lang/tr.js | 12 + .../plugins/specialchar/dialogs/lang/ug.js | 13 + .../plugins/specialchar/dialogs/lang/zh-cn.js | 9 + .../specialchar/dialogs/specialchar.js | 14 + .../RichText/plugins/table/dialogs/table.js | 20 + .../plugins/tabletools/dialogs/tableCell.js | 16 + .../plugins/templates/dialogs/templates.css | 84 + .../plugins/templates/dialogs/templates.js | 10 + .../plugins/templates/templates/default.js | 6 + .../templates/templates/images/template1.gif | Bin 0 -> 375 bytes .../templates/templates/images/template2.gif | Bin 0 -> 333 bytes .../templates/templates/images/template3.gif | Bin 0 -> 422 bytes share/static/RichText/plugins/wsc/LICENSE.md | 28 + share/static/RichText/plugins/wsc/README.md | 25 + .../RichText/plugins/wsc/dialogs/ciframe.html | 49 + .../plugins/wsc/dialogs/tmpFrameset.html | 52 + .../RichText/plugins/wsc/dialogs/wsc.css | 82 + .../RichText/plugins/wsc/dialogs/wsc.js | 11 + share/static/RichText/skins/kama/dialog.css | 5 + .../static/RichText/skins/kama/dialog_ie.css | 5 + .../static/RichText/skins/kama/dialog_ie7.css | 5 + .../static/RichText/skins/kama/dialog_ie8.css | 5 + .../RichText/skins/kama/dialog_iequirks.css | 5 + .../RichText/skins/kama/dialog_opera.css | 5 + share/static/RichText/skins/kama/editor.css | 5 + .../static/RichText/skins/kama/editor_ie.css | 5 + .../static/RichText/skins/kama/editor_ie7.css | 5 + .../static/RichText/skins/kama/editor_ie8.css | 5 + .../RichText/skins/kama/editor_iequirks.css | 5 + share/static/RichText/skins/kama/icons.png | Bin 0 -> 13244 bytes .../skins/kama/images/dialog_sides.gif | Bin 0 -> 48 bytes .../skins/kama/images/dialog_sides.png | Bin 0 -> 178 bytes .../skins/kama/images/dialog_sides_rtl.png | Bin 0 -> 181 bytes .../RichText/skins/kama/images/mini.gif | Bin 0 -> 183 bytes .../RichText/skins/kama/images/sprites.png | Bin 0 -> 7086 bytes .../skins/kama/images/sprites_ie6.png | Bin 0 -> 2724 bytes .../skins/kama/images/toolbar_start.gif | Bin 0 -> 105 bytes share/static/RichText/skins/kama/readme.md | 40 + share/static/RichText/styles.js | 112 + share/static/css/aileron/base.css | 15 + share/static/css/aileron/boxes.css | 133 + share/static/css/aileron/forms.css | 33 + share/static/css/aileron/layout.css | 127 + share/static/css/aileron/login.css | 3 + share/static/css/aileron/main.css | 12 + share/static/css/aileron/misc.css | 11 + share/static/css/aileron/msie.css | 133 + share/static/css/aileron/msie6.css | 39 + share/static/css/aileron/nav.css | 170 + share/static/css/aileron/ticket-lists.css | 189 + share/static/css/aileron/ticket-search.css | 206 + share/static/css/aileron/ticket.css | 9 + share/static/css/ballard/base.css | 15 + share/static/css/ballard/boxes.css | 142 + share/static/css/ballard/layout.css | 130 + share/static/css/ballard/main.css | 9 + share/static/css/ballard/misc.css | 11 + share/static/css/ballard/msie.css | 187 + share/static/css/ballard/msie6.css | 41 + share/static/css/ballard/nav.css | 145 + share/static/css/ballard/ticket-lists.css | 189 + share/static/css/ballard/ticket-search.css | 197 + share/static/css/base/accordion.css | 53 + share/static/css/base/admin.css | 84 + share/static/css/base/articles.css | 11 + share/static/css/base/charts.css | 28 + share/static/css/base/collection.css | 16 + share/static/css/base/farbtastic.css | 51 + share/static/css/base/forms.css | 244 + share/static/css/base/history-folding.css | 44 + share/static/css/base/history.css | 160 + .../css/base/jquery-ui-timepicker-addon.css | 22 + share/static/css/base/jquery-ui.css | 1 + .../css/base/jquery-ui.custom.modified.css | 854 +++ share/static/css/base/jquery.modal.css | 66 + share/static/css/base/login.css | 71 + share/static/css/base/main.css | 28 + share/static/css/base/misc.css | 69 + share/static/css/base/nav.css | 17 + share/static/css/base/portlets.css | 17 + share/static/css/base/print.css | 189 + share/static/css/base/record.css | 10 + share/static/css/base/rights-editor.css | 130 + share/static/css/base/superfish-navbar.css | 95 + share/static/css/base/superfish-vertical.css | 23 + share/static/css/base/superfish.css | 138 + share/static/css/base/tablesorter.css | 52 + share/static/css/base/theme-editor.css | 69 + share/static/css/base/ticket-form.css | 75 + share/static/css/base/ticket.css | 138 + share/static/css/base/tools.css | 7 + share/static/css/base/yui-fonts.css | 7 + share/static/css/images/arrows-ffffff.gif | Bin 0 -> 114 bytes share/static/css/images/arrows-ffffff.png | Bin 0 -> 244 bytes share/static/css/images/arrows-grey.gif | Bin 0 -> 114 bytes share/static/css/images/arrows-grey.png | Bin 0 -> 256 bytes .../static/css/images/background-gradient.png | Bin 0 -> 394 bytes .../static/css/images/jquery-modal-close.png | Bin 0 -> 1910 bytes share/static/css/images/shadow.gif | Bin 0 -> 807 bytes share/static/css/images/shadow.png | Bin 0 -> 1698 bytes share/static/css/mobile.css | 459 ++ share/static/css/rudder/admin.css | 16 + share/static/css/rudder/base.css | 17 + share/static/css/rudder/boxes.css | 150 + share/static/css/rudder/dashboards.css | 7 + share/static/css/rudder/forms.css | 94 + share/static/css/rudder/history.css | 80 + share/static/css/rudder/layout.css | 116 + share/static/css/rudder/login.css | 7 + share/static/css/rudder/main.css | 16 + share/static/css/rudder/misc.css | 36 + share/static/css/rudder/msie.css | 109 + share/static/css/rudder/nav.css | 224 + share/static/css/rudder/ticket-forms.css | 10 + share/static/css/rudder/ticket-lists.css | 223 + share/static/css/rudder/ticket-search.css | 188 + share/static/css/rudder/ticket.css | 66 + share/static/css/web2/base.css | 15 + share/static/css/web2/boxes.css | 144 + share/static/css/web2/layout.css | 163 + share/static/css/web2/main.css | 9 + share/static/css/web2/misc.css | 11 + share/static/css/web2/msie.css | 195 + share/static/css/web2/msie6.css | 49 + share/static/css/web2/nav.css | 245 + share/static/css/web2/ticket-lists.css | 189 + share/static/css/web2/ticket-search.css | 197 + share/static/images/bpslogo.png | Bin 0 -> 3929 bytes share/static/images/css/cb-light.gif | Bin 0 -> 186 bytes share/static/images/css/cb.gif | Bin 0 -> 163 bytes share/static/images/css/cbr-b2g.gif | Bin 0 -> 135 bytes share/static/images/css/cbr-b2lb.gif | Bin 0 -> 137 bytes share/static/images/css/cbr-gray.gif | Bin 0 -> 137 bytes share/static/images/css/cbr-trans.gif | Bin 0 -> 183 bytes share/static/images/css/cbr.gif | Bin 0 -> 188 bytes share/static/images/css/ct-light.gif | Bin 0 -> 162 bytes share/static/images/css/ct.gif | Bin 0 -> 162 bytes share/static/images/css/ctr-b2g.gif | Bin 0 -> 136 bytes share/static/images/css/ctr-b2lb.gif | Bin 0 -> 114 bytes share/static/images/css/ctr-gray.gif | Bin 0 -> 138 bytes share/static/images/css/ctr-trans.gif | Bin 0 -> 182 bytes share/static/images/css/ctr.gif | Bin 0 -> 188 bytes share/static/images/css/dark-arrow-up.png | Bin 0 -> 346 bytes share/static/images/css/dark-arrow.png | Bin 0 -> 337 bytes .../images/css/fieldbg-autocomplete.gif | Bin 0 -> 1164 bytes share/static/images/css/light-arrow-up.png | Bin 0 -> 348 bytes share/static/images/css/light-arrow.png | Bin 0 -> 340 bytes share/static/images/css/rolldown-arrow.gif | Bin 0 -> 98 bytes share/static/images/css/rolldown-arrow.png | Bin 0 -> 259 bytes share/static/images/css/rollup-arrow.gif | Bin 0 -> 97 bytes share/static/images/empty_star.gif | Bin 0 -> 914 bytes share/static/images/eyedropper.png | Bin 0 -> 376 bytes share/static/images/farbtastic/marker.png | Bin 0 -> 652 bytes share/static/images/farbtastic/mask.png | Bin 0 -> 2020 bytes share/static/images/farbtastic/wheel.png | Bin 0 -> 11733 bytes share/static/images/favicon.png | Bin 0 -> 335 bytes .../images/jquery_ui/animated-overlay.gif | Bin 0 -> 1738 bytes .../jquery_ui/ui-bg_flat_0_aaaaaa_40x100.png | Bin 0 -> 274 bytes .../jquery_ui/ui-bg_flat_75_ffffff_40x100.png | Bin 0 -> 271 bytes .../jquery_ui/ui-bg_glass_55_fbf9ee_1x400.png | Bin 0 -> 387 bytes .../jquery_ui/ui-bg_glass_65_ffffff_1x400.png | Bin 0 -> 272 bytes .../jquery_ui/ui-bg_glass_75_dadada_1x400.png | Bin 0 -> 375 bytes .../jquery_ui/ui-bg_glass_75_e6e6e6_1x400.png | Bin 0 -> 368 bytes .../jquery_ui/ui-bg_glass_95_fef1ec_1x400.png | Bin 0 -> 384 bytes .../ui-bg_highlight-soft_75_cccccc_1x100.png | Bin 0 -> 360 bytes .../jquery_ui/ui-icons_222222_256x240.png | Bin 0 -> 6781 bytes .../jquery_ui/ui-icons_2e83ff_256x240.png | Bin 0 -> 4353 bytes .../jquery_ui/ui-icons_454545_256x240.png | Bin 0 -> 6854 bytes .../jquery_ui/ui-icons_888888_256x240.png | Bin 0 -> 6897 bytes .../jquery_ui/ui-icons_cd0a0a_256x240.png | Bin 0 -> 4353 bytes share/static/images/star.gif | Bin 0 -> 161 bytes share/static/images/tablesorter/asc.gif | Bin 0 -> 54 bytes share/static/images/tablesorter/bg.gif | Bin 0 -> 64 bytes share/static/images/tablesorter/desc.gif | Bin 0 -> 54 bytes share/static/images/test.png | 2 + share/static/js/autocomplete.js | 89 + share/static/js/cascaded.js | 91 + share/static/js/combobox.js | 215 + share/static/js/event-registration.js | 102 + share/static/js/farbtastic.js | 347 ++ share/static/js/forms.js | 13 + share/static/js/history-folding.js | 26 + share/static/js/i18n.js | 15 + share/static/js/jquery-1.9.1.min.js | 5 + .../static/js/jquery-ui-1.10.0.custom.min.js | 6 + share/static/js/jquery-ui-patch-datepicker.js | 45 + share/static/js/jquery-ui-timepicker-addon.js | 1919 +++++++ share/static/js/jquery.cookie.js | 92 + share/static/js/jquery.event.hover-1.0.js | 85 + share/static/js/jquery.modal-defaults.js | 2 + share/static/js/jquery.modal.min.js | 26 + share/static/js/jquery.supposition.js | 83 + share/static/js/jquery.tablesorter.min.js | 16 + share/static/js/jquery_noconflict.js | 4 + share/static/js/late.js | 38 + share/static/js/superfish.js | 126 + share/static/js/supersubs.js | 90 + share/static/js/titlebox-state.js | 36 + share/static/js/util.js | 355 ++ 1112 files changed, 163477 insertions(+), 83638 deletions(-) create mode 100644 docs/UPGRADING-4.2 create mode 100644 docs/authentication.pod create mode 100644 docs/charts.pod create mode 100644 docs/dashboards.pod create mode 100644 docs/images/customize-dashboards-menu.png create mode 100644 docs/images/dashboard-chart.png create mode 100644 docs/images/dashboard-content-invoices.png create mode 100644 docs/images/dashboard-search-sorting.png create mode 100644 docs/images/dashboard-subscription.png create mode 100644 docs/images/general-owner-chart.png create mode 100644 docs/images/general-owner-lastupdated-chart.png create mode 100644 docs/images/general-status-chart.png create mode 100644 docs/images/queue-created-started-chart.png create mode 100644 docs/incremental-export/README create mode 100644 docs/incremental-export/Record_Local.pm create mode 100644 docs/incremental-export/schema.mysql create mode 100644 docs/reminders.pod create mode 100644 docs/reporting/feeds.pod create mode 100644 docs/rt_perl.pod create mode 100644 etc/upgrade/4.1.0/content create mode 100644 etc/upgrade/4.1.1/acl.Pg create mode 100644 etc/upgrade/4.1.1/content create mode 100644 etc/upgrade/4.1.1/schema.Oracle create mode 100644 etc/upgrade/4.1.1/schema.Pg create mode 100644 etc/upgrade/4.1.1/schema.SQLite create mode 100644 etc/upgrade/4.1.1/schema.mysql create mode 100644 etc/upgrade/4.1.10/schema.Oracle create mode 100644 etc/upgrade/4.1.10/schema.Pg create mode 100644 etc/upgrade/4.1.10/schema.mysql create mode 100644 etc/upgrade/4.1.11/schema.Oracle create mode 100644 etc/upgrade/4.1.11/schema.Pg create mode 100644 etc/upgrade/4.1.11/schema.mysql create mode 100644 etc/upgrade/4.1.12/content create mode 100644 etc/upgrade/4.1.13/backcompat create mode 100644 etc/upgrade/4.1.13/schema.Oracle create mode 100644 etc/upgrade/4.1.13/schema.Pg create mode 100644 etc/upgrade/4.1.13/schema.SQLite create mode 100644 etc/upgrade/4.1.13/schema.mysql create mode 100644 etc/upgrade/4.1.14/schema.Oracle create mode 100644 etc/upgrade/4.1.14/schema.Pg create mode 100644 etc/upgrade/4.1.14/schema.mysql create mode 100644 etc/upgrade/4.1.15/content create mode 100644 etc/upgrade/4.1.16/content create mode 100644 etc/upgrade/4.1.17/content create mode 100644 etc/upgrade/4.1.18/content create mode 100644 etc/upgrade/4.1.19/schema.Oracle create mode 100644 etc/upgrade/4.1.19/schema.Pg create mode 100644 etc/upgrade/4.1.19/schema.mysql create mode 100644 etc/upgrade/4.1.20/content create mode 100644 etc/upgrade/4.1.21/content create mode 100644 etc/upgrade/4.1.22/content create mode 100644 etc/upgrade/4.1.22/schema.Oracle create mode 100644 etc/upgrade/4.1.22/schema.Pg create mode 100644 etc/upgrade/4.1.22/schema.SQLite create mode 100644 etc/upgrade/4.1.22/schema.mysql create mode 100644 etc/upgrade/4.1.23/indexes create mode 100644 etc/upgrade/4.1.4/content create mode 100644 etc/upgrade/4.1.4/schema.Oracle create mode 100644 etc/upgrade/4.1.4/schema.Pg create mode 100644 etc/upgrade/4.1.4/schema.SQLite create mode 100644 etc/upgrade/4.1.4/schema.mysql create mode 100644 etc/upgrade/4.1.5/content create mode 100644 etc/upgrade/4.1.5/schema.Oracle create mode 100644 etc/upgrade/4.1.5/schema.Pg create mode 100644 etc/upgrade/4.1.5/schema.mysql create mode 100644 etc/upgrade/4.1.6/content create mode 100644 etc/upgrade/4.1.7/schema.Oracle create mode 100644 etc/upgrade/4.1.7/schema.Pg create mode 100644 etc/upgrade/4.1.7/schema.SQLite create mode 100644 etc/upgrade/4.1.7/schema.mysql create mode 100644 etc/upgrade/4.1.8/schema.Oracle create mode 100644 etc/upgrade/4.1.8/schema.Pg create mode 100644 etc/upgrade/4.1.8/schema.SQLite create mode 100644 etc/upgrade/4.1.8/schema.mysql create mode 100644 etc/upgrade/4.1.9/content create mode 100644 etc/upgrade/switch-templates-to create mode 100644 etc/upgrade/time-worked-history.pl create mode 100644 lib/RT/Action/AutoOpenInactive.pm create mode 100644 lib/RT/Action/OpenOnStarted.pm create mode 100644 lib/RT/Action/SendForward.pm create mode 100644 lib/RT/Crypt.pm create mode 100644 lib/RT/Crypt/GnuPG/CRLFHandle.pm create mode 100644 lib/RT/Crypt/Role.pm create mode 100644 lib/RT/Crypt/SMIME.pm create mode 100644 lib/RT/DependencyWalker.pm create mode 100644 lib/RT/DependencyWalker/Dependencies.pm create mode 100644 lib/RT/Interface/Email/Auth/Crypt.pm create mode 100644 lib/RT/Lifecycle/Ticket.pm create mode 100644 lib/RT/Migrate.pm create mode 100644 lib/RT/Migrate/Importer.pm create mode 100644 lib/RT/Migrate/Importer/File.pm create mode 100644 lib/RT/Migrate/Incremental.pm create mode 100644 lib/RT/Migrate/Serializer.pm create mode 100644 lib/RT/Migrate/Serializer/File.pm create mode 100644 lib/RT/Migrate/Serializer/IncrementalRecord.pm create mode 100644 lib/RT/Migrate/Serializer/IncrementalRecords.pm create mode 100644 lib/RT/ObjectScrip.pm create mode 100644 lib/RT/ObjectScrips.pm create mode 100644 lib/RT/PlackRunner.pm create mode 100644 lib/RT/Record/AddAndSort.pm create mode 100644 lib/RT/Record/Role.pm create mode 100644 lib/RT/Record/Role/Lifecycle.pm create mode 100644 lib/RT/Record/Role/Links.pm create mode 100644 lib/RT/Record/Role/Rights.pm create mode 100644 lib/RT/Record/Role/Roles.pm create mode 100644 lib/RT/Record/Role/Status.pm create mode 100644 lib/RT/Search/Simple.pm create mode 100644 lib/RT/SearchBuilder/AddAndSort.pm create mode 100644 lib/RT/SearchBuilder/Role.pm create mode 100644 lib/RT/SearchBuilder/Role/Roles.pm create mode 100644 lib/RT/Test/SMIME.pm create mode 100644 lib/RT/Test/Shredder.pm create mode 100644 share/html/Admin/Elements/ConfigureDashboardsInMenu create mode 100644 share/html/Admin/Elements/LoggingSummary create mode 100644 share/html/Admin/Elements/MembershipsPage create mode 100644 share/html/Admin/Elements/SelectStageForAdded create mode 100644 share/html/Admin/Elements/UpgradeHistory create mode 100644 share/html/Admin/Elements/UpgradeHistoryRow create mode 100644 share/html/Admin/Global/DashboardsInMenu.html create mode 100644 share/html/Admin/Groups/Memberships.html create mode 100644 share/html/Admin/Scrips/Create.html create mode 100644 share/html/Admin/Scrips/Elements/EditBasics create mode 100644 share/html/Admin/Scrips/Elements/EditCustomCode create mode 100644 share/html/Admin/Scrips/Elements/SelectTemplate create mode 100644 share/html/Admin/Scrips/Modify.html create mode 100644 share/html/Admin/Scrips/Objects.html create mode 100644 share/html/Admin/Scrips/index.html create mode 100644 share/html/Admin/Users/DashboardsInMenu.html create mode 100644 share/html/Admin/Users/Keys.html create mode 100644 share/html/Articles/Elements/MaybeNeedsSetup create mode 100644 share/html/Articles/Elements/NeedsSetup create mode 100644 share/html/Articles/Elements/SubjectOverride create mode 100644 share/html/Elements/AddLinks create mode 100644 share/html/Elements/BulkCustomFields create mode 100644 share/html/Elements/BulkLinks create mode 100644 share/html/Elements/Crypt/KeyIssues create mode 100644 share/html/Elements/Crypt/SelectKeyForEncryption create mode 100644 share/html/Elements/Crypt/SelectKeyForSigning create mode 100644 share/html/Elements/Crypt/SignEncryptWidget create mode 100644 share/html/Elements/CryptStatus create mode 100644 share/html/Elements/EditCustomFieldCustomGroupings create mode 100644 share/html/Elements/EditCustomFields create mode 100644 share/html/Elements/FindUser create mode 100644 share/html/Elements/FoldStanzaJS create mode 100644 share/html/Elements/GotoUser create mode 100644 share/html/Elements/JavascriptConfig create mode 100644 share/html/Elements/LoginHelp create mode 100644 share/html/Elements/SelectObject create mode 100644 share/html/Elements/ShowCustomFieldCustomGroupings create mode 100644 share/html/Elements/ShowHistory create mode 100644 share/html/Elements/ShowLinksOfType create mode 100644 share/html/Elements/ShowMessageHeaders create mode 100644 share/html/Elements/ShowMessageStanza create mode 100644 share/html/Elements/ShowPrincipal create mode 100644 share/html/Elements/ShowRecord create mode 100644 share/html/Elements/ShowTransaction create mode 100644 share/html/Elements/ShowTransactionAttachments create mode 100644 share/html/Errors/WebRemoteUser/Deauthorized create mode 100644 share/html/Errors/WebRemoteUser/NoInternalUser create mode 100644 share/html/Errors/WebRemoteUser/NoRemoteUser create mode 100644 share/html/Errors/WebRemoteUser/UserAutocreateDefaultsOnLogin create mode 100644 share/html/Errors/WebRemoteUser/Wrapper create mode 100644 share/html/Helpers/Autocomplete/Tickets create mode 100644 share/html/Helpers/UserInfo create mode 100644 share/html/NoAuth/css/aileron/AfterMenus create mode 100644 share/html/NoAuth/css/rudder/AfterMenus create mode 100644 share/html/NoAuth/css/rudder/InHeader create mode 100644 share/html/NoAuth/css/web2/AfterMenus create mode 100644 share/html/Prefs/DashboardsInMenu.html create mode 100644 share/html/Search/Elements/ChartTable create mode 100644 share/html/Search/Elements/PickObjectCFs create mode 100644 share/html/Search/Elements/SelectChartFunction create mode 100644 share/html/Search/index.html create mode 100644 share/html/SelfService/Helpers/Autocomplete/CustomFieldValues create mode 100644 share/html/SelfService/Helpers/Autocomplete/Users create mode 100644 share/html/Ticket/Crypt.html create mode 100644 share/html/Ticket/Elements/DelayShowHistory create mode 100644 share/html/Ticket/Elements/EditMerge create mode 100644 share/html/Ticket/Elements/SelectStatus create mode 100644 share/html/Ticket/Elements/ShowDependencyStatus create mode 100644 share/html/User/Elements/Portlets/ActiveTickets create mode 100644 share/html/User/Elements/Portlets/CreateTicket create mode 100644 share/html/User/Elements/Portlets/ExtraInfo create mode 100644 share/html/User/Elements/Portlets/InactiveTickets create mode 100644 share/html/User/Elements/TicketList create mode 100644 share/html/User/Elements/UserInfo create mode 100644 share/html/User/History.html create mode 100644 share/html/User/Search.html create mode 100644 share/html/User/Summary.html create mode 100644 share/static/RichText/LICENSE.md create mode 100644 share/static/RichText/ckeditor.js create mode 100644 share/static/RichText/config.js create mode 100644 share/static/RichText/contents.css create mode 100644 share/static/RichText/lang/af.js create mode 100644 share/static/RichText/lang/ar.js create mode 100644 share/static/RichText/lang/bg.js create mode 100644 share/static/RichText/lang/bn.js create mode 100644 share/static/RichText/lang/bs.js create mode 100644 share/static/RichText/lang/ca.js create mode 100644 share/static/RichText/lang/cs.js create mode 100644 share/static/RichText/lang/cy.js create mode 100644 share/static/RichText/lang/da.js create mode 100644 share/static/RichText/lang/de.js create mode 100644 share/static/RichText/lang/el.js create mode 100644 share/static/RichText/lang/en-au.js create mode 100644 share/static/RichText/lang/en-ca.js create mode 100644 share/static/RichText/lang/en-gb.js create mode 100644 share/static/RichText/lang/en.js create mode 100644 share/static/RichText/lang/eo.js create mode 100644 share/static/RichText/lang/es.js create mode 100644 share/static/RichText/lang/et.js create mode 100644 share/static/RichText/lang/eu.js create mode 100644 share/static/RichText/lang/fa.js create mode 100644 share/static/RichText/lang/fi.js create mode 100644 share/static/RichText/lang/fo.js create mode 100644 share/static/RichText/lang/fr-ca.js create mode 100644 share/static/RichText/lang/fr.js create mode 100644 share/static/RichText/lang/gl.js create mode 100644 share/static/RichText/lang/gu.js create mode 100644 share/static/RichText/lang/he.js create mode 100644 share/static/RichText/lang/hi.js create mode 100644 share/static/RichText/lang/hr.js create mode 100644 share/static/RichText/lang/hu.js create mode 100644 share/static/RichText/lang/is.js create mode 100644 share/static/RichText/lang/it.js create mode 100644 share/static/RichText/lang/ja.js create mode 100644 share/static/RichText/lang/ka.js create mode 100644 share/static/RichText/lang/km.js create mode 100644 share/static/RichText/lang/ko.js create mode 100644 share/static/RichText/lang/ku.js create mode 100644 share/static/RichText/lang/lt.js create mode 100644 share/static/RichText/lang/lv.js create mode 100644 share/static/RichText/lang/mk.js create mode 100644 share/static/RichText/lang/mn.js create mode 100644 share/static/RichText/lang/ms.js create mode 100644 share/static/RichText/lang/nb.js create mode 100644 share/static/RichText/lang/nl.js create mode 100644 share/static/RichText/lang/no.js create mode 100644 share/static/RichText/lang/pl.js create mode 100644 share/static/RichText/lang/pt-br.js create mode 100644 share/static/RichText/lang/pt.js create mode 100644 share/static/RichText/lang/ro.js create mode 100644 share/static/RichText/lang/ru.js create mode 100644 share/static/RichText/lang/sk.js create mode 100644 share/static/RichText/lang/sl.js create mode 100644 share/static/RichText/lang/sr-latn.js create mode 100644 share/static/RichText/lang/sr.js create mode 100644 share/static/RichText/lang/sv.js create mode 100644 share/static/RichText/lang/th.js create mode 100644 share/static/RichText/lang/tr.js create mode 100644 share/static/RichText/lang/ug.js create mode 100644 share/static/RichText/lang/uk.js create mode 100644 share/static/RichText/lang/vi.js create mode 100644 share/static/RichText/lang/zh-cn.js create mode 100644 share/static/RichText/lang/zh.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/a11yhelp.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/_translationstatus.txt create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/ar.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/bg.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/ca.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/cs.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/cy.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/da.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/de.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/el.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/en.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/eo.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/es.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/et.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/fa.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/fi.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/fr.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/gu.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/he.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/hi.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/hr.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/hu.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/it.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/ja.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/ku.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/lt.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/lv.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/mk.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/mn.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/nb.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/nl.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/no.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/pl.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/pt-br.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/pt.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/ro.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/ru.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/sk.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/sl.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/sv.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/tr.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/ug.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/uk.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/vi.js create mode 100644 share/static/RichText/plugins/a11yhelp/dialogs/lang/zh-cn.js create mode 100644 share/static/RichText/plugins/about/dialogs/about.js create mode 100644 share/static/RichText/plugins/about/dialogs/logo_ckeditor.png create mode 100644 share/static/RichText/plugins/clipboard/dialogs/paste.js create mode 100644 share/static/RichText/plugins/colordialog/dialogs/colordialog.js create mode 100644 share/static/RichText/plugins/dialog/dialogDefinition.js create mode 100644 share/static/RichText/plugins/div/dialogs/div.js create mode 100644 share/static/RichText/plugins/fakeobjects/images/spacer.gif create mode 100644 share/static/RichText/plugins/find/dialogs/find.js create mode 100644 share/static/RichText/plugins/flash/dialogs/flash.js create mode 100644 share/static/RichText/plugins/flash/images/placeholder.png create mode 100644 share/static/RichText/plugins/forms/dialogs/button.js create mode 100644 share/static/RichText/plugins/forms/dialogs/checkbox.js create mode 100644 share/static/RichText/plugins/forms/dialogs/form.js create mode 100644 share/static/RichText/plugins/forms/dialogs/hiddenfield.js create mode 100644 share/static/RichText/plugins/forms/dialogs/radio.js create mode 100644 share/static/RichText/plugins/forms/dialogs/select.js create mode 100644 share/static/RichText/plugins/forms/dialogs/textarea.js create mode 100644 share/static/RichText/plugins/forms/dialogs/textfield.js create mode 100644 share/static/RichText/plugins/forms/images/hiddenfield.gif create mode 100644 share/static/RichText/plugins/icons.png create mode 100644 share/static/RichText/plugins/iframe/dialogs/iframe.js create mode 100644 share/static/RichText/plugins/iframe/images/placeholder.png create mode 100644 share/static/RichText/plugins/image/dialogs/image.js create mode 100644 share/static/RichText/plugins/image/images/noimage.png create mode 100644 share/static/RichText/plugins/link/dialogs/anchor.js create mode 100644 share/static/RichText/plugins/link/dialogs/link.js create mode 100644 share/static/RichText/plugins/link/images/anchor.png create mode 100644 share/static/RichText/plugins/liststyle/dialogs/liststyle.js create mode 100644 share/static/RichText/plugins/magicline/images/icon.png create mode 100644 share/static/RichText/plugins/pagebreak/images/pagebreak.gif create mode 100644 share/static/RichText/plugins/pastefromword/filter/default.js create mode 100644 share/static/RichText/plugins/preview/preview.html create mode 100644 share/static/RichText/plugins/scayt/LICENSE.md create mode 100644 share/static/RichText/plugins/scayt/README.md create mode 100644 share/static/RichText/plugins/scayt/dialogs/options.js create mode 100644 share/static/RichText/plugins/scayt/dialogs/toolbar.css create mode 100644 share/static/RichText/plugins/showblocks/images/block_address.png create mode 100644 share/static/RichText/plugins/showblocks/images/block_blockquote.png create mode 100644 share/static/RichText/plugins/showblocks/images/block_div.png create mode 100644 share/static/RichText/plugins/showblocks/images/block_h1.png create mode 100644 share/static/RichText/plugins/showblocks/images/block_h2.png create mode 100644 share/static/RichText/plugins/showblocks/images/block_h3.png create mode 100644 share/static/RichText/plugins/showblocks/images/block_h4.png create mode 100644 share/static/RichText/plugins/showblocks/images/block_h5.png create mode 100644 share/static/RichText/plugins/showblocks/images/block_h6.png create mode 100644 share/static/RichText/plugins/showblocks/images/block_p.png create mode 100644 share/static/RichText/plugins/showblocks/images/block_pre.png create mode 100644 share/static/RichText/plugins/smiley/dialogs/smiley.js create mode 100644 share/static/RichText/plugins/smiley/images/angel_smile.gif create mode 100644 share/static/RichText/plugins/smiley/images/angry_smile.gif create mode 100644 share/static/RichText/plugins/smiley/images/broken_heart.gif create mode 100644 share/static/RichText/plugins/smiley/images/confused_smile.gif create mode 100644 share/static/RichText/plugins/smiley/images/cry_smile.gif create mode 100644 share/static/RichText/plugins/smiley/images/devil_smile.gif create mode 100644 share/static/RichText/plugins/smiley/images/embaressed_smile.gif create mode 100644 share/static/RichText/plugins/smiley/images/embarrassed_smile.gif create mode 100644 share/static/RichText/plugins/smiley/images/envelope.gif create mode 100644 share/static/RichText/plugins/smiley/images/heart.gif create mode 100644 share/static/RichText/plugins/smiley/images/kiss.gif create mode 100644 share/static/RichText/plugins/smiley/images/lightbulb.gif create mode 100644 share/static/RichText/plugins/smiley/images/omg_smile.gif create mode 100644 share/static/RichText/plugins/smiley/images/regular_smile.gif create mode 100644 share/static/RichText/plugins/smiley/images/sad_smile.gif create mode 100644 share/static/RichText/plugins/smiley/images/shades_smile.gif create mode 100644 share/static/RichText/plugins/smiley/images/teeth_smile.gif create mode 100644 share/static/RichText/plugins/smiley/images/thumbs_down.gif create mode 100644 share/static/RichText/plugins/smiley/images/thumbs_up.gif create mode 100644 share/static/RichText/plugins/smiley/images/tongue_smile.gif create mode 100644 share/static/RichText/plugins/smiley/images/tounge_smile.gif create mode 100644 share/static/RichText/plugins/smiley/images/whatchutalkingabout_smile.gif create mode 100644 share/static/RichText/plugins/smiley/images/wink_smile.gif create mode 100644 share/static/RichText/plugins/specialchar/dialogs/lang/_translationstatus.txt create mode 100644 share/static/RichText/plugins/specialchar/dialogs/lang/ca.js create mode 100644 share/static/RichText/plugins/specialchar/dialogs/lang/cs.js create mode 100644 share/static/RichText/plugins/specialchar/dialogs/lang/cy.js create mode 100644 share/static/RichText/plugins/specialchar/dialogs/lang/de.js create mode 100644 share/static/RichText/plugins/specialchar/dialogs/lang/el.js create mode 100644 share/static/RichText/plugins/specialchar/dialogs/lang/en.js create mode 100644 share/static/RichText/plugins/specialchar/dialogs/lang/eo.js create mode 100644 share/static/RichText/plugins/specialchar/dialogs/lang/et.js create mode 100644 share/static/RichText/plugins/specialchar/dialogs/lang/fa.js create mode 100644 share/static/RichText/plugins/specialchar/dialogs/lang/fi.js create mode 100644 share/static/RichText/plugins/specialchar/dialogs/lang/fr.js create mode 100644 share/static/RichText/plugins/specialchar/dialogs/lang/he.js create mode 100644 share/static/RichText/plugins/specialchar/dialogs/lang/hr.js create mode 100644 share/static/RichText/plugins/specialchar/dialogs/lang/it.js create mode 100644 share/static/RichText/plugins/specialchar/dialogs/lang/ku.js create mode 100644 share/static/RichText/plugins/specialchar/dialogs/lang/lv.js create mode 100644 share/static/RichText/plugins/specialchar/dialogs/lang/nb.js create mode 100644 share/static/RichText/plugins/specialchar/dialogs/lang/nl.js create mode 100644 share/static/RichText/plugins/specialchar/dialogs/lang/no.js create mode 100644 share/static/RichText/plugins/specialchar/dialogs/lang/pt-br.js create mode 100644 share/static/RichText/plugins/specialchar/dialogs/lang/sk.js create mode 100644 share/static/RichText/plugins/specialchar/dialogs/lang/sv.js create mode 100644 share/static/RichText/plugins/specialchar/dialogs/lang/tr.js create mode 100644 share/static/RichText/plugins/specialchar/dialogs/lang/ug.js create mode 100644 share/static/RichText/plugins/specialchar/dialogs/lang/zh-cn.js create mode 100644 share/static/RichText/plugins/specialchar/dialogs/specialchar.js create mode 100644 share/static/RichText/plugins/table/dialogs/table.js create mode 100644 share/static/RichText/plugins/tabletools/dialogs/tableCell.js create mode 100644 share/static/RichText/plugins/templates/dialogs/templates.css create mode 100644 share/static/RichText/plugins/templates/dialogs/templates.js create mode 100644 share/static/RichText/plugins/templates/templates/default.js create mode 100644 share/static/RichText/plugins/templates/templates/images/template1.gif create mode 100644 share/static/RichText/plugins/templates/templates/images/template2.gif create mode 100644 share/static/RichText/plugins/templates/templates/images/template3.gif create mode 100644 share/static/RichText/plugins/wsc/LICENSE.md create mode 100644 share/static/RichText/plugins/wsc/README.md create mode 100644 share/static/RichText/plugins/wsc/dialogs/ciframe.html create mode 100644 share/static/RichText/plugins/wsc/dialogs/tmpFrameset.html create mode 100644 share/static/RichText/plugins/wsc/dialogs/wsc.css create mode 100644 share/static/RichText/plugins/wsc/dialogs/wsc.js create mode 100644 share/static/RichText/skins/kama/dialog.css create mode 100644 share/static/RichText/skins/kama/dialog_ie.css create mode 100644 share/static/RichText/skins/kama/dialog_ie7.css create mode 100644 share/static/RichText/skins/kama/dialog_ie8.css create mode 100644 share/static/RichText/skins/kama/dialog_iequirks.css create mode 100644 share/static/RichText/skins/kama/dialog_opera.css create mode 100644 share/static/RichText/skins/kama/editor.css create mode 100644 share/static/RichText/skins/kama/editor_ie.css create mode 100644 share/static/RichText/skins/kama/editor_ie7.css create mode 100644 share/static/RichText/skins/kama/editor_ie8.css create mode 100644 share/static/RichText/skins/kama/editor_iequirks.css create mode 100644 share/static/RichText/skins/kama/icons.png create mode 100644 share/static/RichText/skins/kama/images/dialog_sides.gif create mode 100644 share/static/RichText/skins/kama/images/dialog_sides.png create mode 100644 share/static/RichText/skins/kama/images/dialog_sides_rtl.png create mode 100644 share/static/RichText/skins/kama/images/mini.gif create mode 100644 share/static/RichText/skins/kama/images/sprites.png create mode 100644 share/static/RichText/skins/kama/images/sprites_ie6.png create mode 100644 share/static/RichText/skins/kama/images/toolbar_start.gif create mode 100644 share/static/RichText/skins/kama/readme.md create mode 100644 share/static/RichText/styles.js create mode 100644 share/static/css/aileron/base.css create mode 100644 share/static/css/aileron/boxes.css create mode 100644 share/static/css/aileron/forms.css create mode 100644 share/static/css/aileron/layout.css create mode 100644 share/static/css/aileron/login.css create mode 100644 share/static/css/aileron/main.css create mode 100644 share/static/css/aileron/misc.css create mode 100644 share/static/css/aileron/msie.css create mode 100644 share/static/css/aileron/msie6.css create mode 100644 share/static/css/aileron/nav.css create mode 100644 share/static/css/aileron/ticket-lists.css create mode 100644 share/static/css/aileron/ticket-search.css create mode 100644 share/static/css/aileron/ticket.css create mode 100644 share/static/css/ballard/base.css create mode 100644 share/static/css/ballard/boxes.css create mode 100644 share/static/css/ballard/layout.css create mode 100644 share/static/css/ballard/main.css create mode 100644 share/static/css/ballard/misc.css create mode 100644 share/static/css/ballard/msie.css create mode 100644 share/static/css/ballard/msie6.css create mode 100644 share/static/css/ballard/nav.css create mode 100644 share/static/css/ballard/ticket-lists.css create mode 100644 share/static/css/ballard/ticket-search.css create mode 100644 share/static/css/base/accordion.css create mode 100644 share/static/css/base/admin.css create mode 100644 share/static/css/base/articles.css create mode 100644 share/static/css/base/charts.css create mode 100644 share/static/css/base/collection.css create mode 100644 share/static/css/base/farbtastic.css create mode 100644 share/static/css/base/forms.css create mode 100644 share/static/css/base/history-folding.css create mode 100644 share/static/css/base/history.css create mode 100644 share/static/css/base/jquery-ui-timepicker-addon.css create mode 100644 share/static/css/base/jquery-ui.css create mode 100644 share/static/css/base/jquery-ui.custom.modified.css create mode 100644 share/static/css/base/jquery.modal.css create mode 100644 share/static/css/base/login.css create mode 100644 share/static/css/base/main.css create mode 100644 share/static/css/base/misc.css create mode 100644 share/static/css/base/nav.css create mode 100644 share/static/css/base/portlets.css create mode 100644 share/static/css/base/print.css create mode 100644 share/static/css/base/record.css create mode 100644 share/static/css/base/rights-editor.css create mode 100644 share/static/css/base/superfish-navbar.css create mode 100644 share/static/css/base/superfish-vertical.css create mode 100644 share/static/css/base/superfish.css create mode 100644 share/static/css/base/tablesorter.css create mode 100644 share/static/css/base/theme-editor.css create mode 100644 share/static/css/base/ticket-form.css create mode 100644 share/static/css/base/ticket.css create mode 100644 share/static/css/base/tools.css create mode 100644 share/static/css/base/yui-fonts.css create mode 100644 share/static/css/images/arrows-ffffff.gif create mode 100644 share/static/css/images/arrows-ffffff.png create mode 100644 share/static/css/images/arrows-grey.gif create mode 100644 share/static/css/images/arrows-grey.png create mode 100644 share/static/css/images/background-gradient.png create mode 100644 share/static/css/images/jquery-modal-close.png create mode 100644 share/static/css/images/shadow.gif create mode 100644 share/static/css/images/shadow.png create mode 100644 share/static/css/mobile.css create mode 100644 share/static/css/rudder/admin.css create mode 100644 share/static/css/rudder/base.css create mode 100644 share/static/css/rudder/boxes.css create mode 100644 share/static/css/rudder/dashboards.css create mode 100644 share/static/css/rudder/forms.css create mode 100644 share/static/css/rudder/history.css create mode 100644 share/static/css/rudder/layout.css create mode 100644 share/static/css/rudder/login.css create mode 100644 share/static/css/rudder/main.css create mode 100644 share/static/css/rudder/misc.css create mode 100644 share/static/css/rudder/msie.css create mode 100644 share/static/css/rudder/nav.css create mode 100644 share/static/css/rudder/ticket-forms.css create mode 100644 share/static/css/rudder/ticket-lists.css create mode 100644 share/static/css/rudder/ticket-search.css create mode 100644 share/static/css/rudder/ticket.css create mode 100644 share/static/css/web2/base.css create mode 100644 share/static/css/web2/boxes.css create mode 100644 share/static/css/web2/layout.css create mode 100644 share/static/css/web2/main.css create mode 100644 share/static/css/web2/misc.css create mode 100644 share/static/css/web2/msie.css create mode 100644 share/static/css/web2/msie6.css create mode 100644 share/static/css/web2/nav.css create mode 100644 share/static/css/web2/ticket-lists.css create mode 100644 share/static/css/web2/ticket-search.css create mode 100644 share/static/images/bpslogo.png create mode 100644 share/static/images/css/cb-light.gif create mode 100644 share/static/images/css/cb.gif create mode 100644 share/static/images/css/cbr-b2g.gif create mode 100644 share/static/images/css/cbr-b2lb.gif create mode 100644 share/static/images/css/cbr-gray.gif create mode 100644 share/static/images/css/cbr-trans.gif create mode 100644 share/static/images/css/cbr.gif create mode 100644 share/static/images/css/ct-light.gif create mode 100644 share/static/images/css/ct.gif create mode 100644 share/static/images/css/ctr-b2g.gif create mode 100644 share/static/images/css/ctr-b2lb.gif create mode 100644 share/static/images/css/ctr-gray.gif create mode 100644 share/static/images/css/ctr-trans.gif create mode 100644 share/static/images/css/ctr.gif create mode 100644 share/static/images/css/dark-arrow-up.png create mode 100644 share/static/images/css/dark-arrow.png create mode 100644 share/static/images/css/fieldbg-autocomplete.gif create mode 100644 share/static/images/css/light-arrow-up.png create mode 100644 share/static/images/css/light-arrow.png create mode 100644 share/static/images/css/rolldown-arrow.gif create mode 100644 share/static/images/css/rolldown-arrow.png create mode 100644 share/static/images/css/rollup-arrow.gif create mode 100644 share/static/images/empty_star.gif create mode 100644 share/static/images/eyedropper.png create mode 100644 share/static/images/farbtastic/marker.png create mode 100644 share/static/images/farbtastic/mask.png create mode 100644 share/static/images/farbtastic/wheel.png create mode 100644 share/static/images/favicon.png create mode 100644 share/static/images/jquery_ui/animated-overlay.gif create mode 100644 share/static/images/jquery_ui/ui-bg_flat_0_aaaaaa_40x100.png create mode 100644 share/static/images/jquery_ui/ui-bg_flat_75_ffffff_40x100.png create mode 100644 share/static/images/jquery_ui/ui-bg_glass_55_fbf9ee_1x400.png create mode 100644 share/static/images/jquery_ui/ui-bg_glass_65_ffffff_1x400.png create mode 100644 share/static/images/jquery_ui/ui-bg_glass_75_dadada_1x400.png create mode 100644 share/static/images/jquery_ui/ui-bg_glass_75_e6e6e6_1x400.png create mode 100644 share/static/images/jquery_ui/ui-bg_glass_95_fef1ec_1x400.png create mode 100644 share/static/images/jquery_ui/ui-bg_highlight-soft_75_cccccc_1x100.png create mode 100644 share/static/images/jquery_ui/ui-icons_222222_256x240.png create mode 100644 share/static/images/jquery_ui/ui-icons_2e83ff_256x240.png create mode 100644 share/static/images/jquery_ui/ui-icons_454545_256x240.png create mode 100644 share/static/images/jquery_ui/ui-icons_888888_256x240.png create mode 100644 share/static/images/jquery_ui/ui-icons_cd0a0a_256x240.png create mode 100644 share/static/images/star.gif create mode 100644 share/static/images/tablesorter/asc.gif create mode 100644 share/static/images/tablesorter/bg.gif create mode 100644 share/static/images/tablesorter/desc.gif create mode 100644 share/static/images/test.png create mode 100644 share/static/js/autocomplete.js create mode 100644 share/static/js/cascaded.js create mode 100644 share/static/js/combobox.js create mode 100644 share/static/js/event-registration.js create mode 100644 share/static/js/farbtastic.js create mode 100644 share/static/js/forms.js create mode 100644 share/static/js/history-folding.js create mode 100644 share/static/js/i18n.js create mode 100644 share/static/js/jquery-1.9.1.min.js create mode 100644 share/static/js/jquery-ui-1.10.0.custom.min.js create mode 100644 share/static/js/jquery-ui-patch-datepicker.js create mode 100644 share/static/js/jquery-ui-timepicker-addon.js create mode 100644 share/static/js/jquery.cookie.js create mode 100644 share/static/js/jquery.event.hover-1.0.js create mode 100644 share/static/js/jquery.modal-defaults.js create mode 100644 share/static/js/jquery.modal.min.js create mode 100644 share/static/js/jquery.supposition.js create mode 100644 share/static/js/jquery.tablesorter.min.js create mode 100644 share/static/js/jquery_noconflict.js create mode 100644 share/static/js/late.js create mode 100644 share/static/js/superfish.js create mode 100644 share/static/js/supersubs.js create mode 100644 share/static/js/titlebox-state.js create mode 100644 share/static/js/util.js diff --git a/bin/rt b/bin/rt index a6df962..d5a1161 100755 --- a/bin/rt +++ b/bin/rt @@ -701,7 +701,9 @@ EDIT: # If we submitted a bad form, we'll give the user a chance # to correct it and resubmit. if ($edit || (!$input && !$cl)) { - $text = $r->content; + my $content = $r->content . "\n"; + $content =~ s/^(?!#)/# /mg; + $text = $content . $text; $synerr = 1; goto EDIT; } diff --git a/bin/rt-crontool b/bin/rt-crontool index 7786b7c..c8e0798 100755 --- a/bin/rt-crontool +++ b/bin/rt-crontool @@ -50,30 +50,16 @@ use strict; use warnings; use Carp; -use lib '/www/data/rt/rt-perl/current-perl10/share/perl5'; -use lib '/www/data/rt/rt-perl/current-perl10/lib/perl5'; -use lib '/www/data/rt/rt-perl/current-perl10/lib64/perl5'; - -use lib ("/www/var/rt/local/lib", "/www/var/rt/lib"); - # fix lib paths, some may be relative -BEGIN { +BEGIN { # BEGIN RT CMD BOILERPLATE require File::Spec; + require Cwd; 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; - } - } + $bin_path ||= ( File::Spec->splitpath(Cwd::abs_path(__FILE__)) )[1]; $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib ); } unshift @INC, $lib; @@ -87,9 +73,6 @@ use Getopt::Long; use RT::Interface::CLI qw(CleanEnv GetCurrentUser GetMessageContent loc); -#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( @@ -112,11 +95,14 @@ GetOptions( RT::LoadConfig(); # adjust logging to the screen according to options -RT->Config->Set( LogToScreen => $log ) if $log; +RT->Config->Set( LogToSTDERR => $log ) if $log; #Connect to the database and get RT::SystemUser and RT::Nobody loaded RT::Init(); +# Clean out all the nasties from the environment +CleanEnv(); + require RT::Tickets; require RT::Template; @@ -319,37 +305,37 @@ sub help { . "\n"; print loc("It takes several arguments:") . "\n\n"; - print " " + print " " . loc( "[_1] - Specify the search module you want to use", "--search" ) . "\n"; - print " " + print " " . loc( "[_1] - An argument to pass to [_2]", "--search-arg", "--search" ) . "\n"; - print " " + print " " . loc( "[_1] - Specify the condition module you want to use", "--condition" ) . "\n"; - print " " + print " " . loc( "[_1] - An argument to pass to [_2]", "--condition-arg", "--condition" ) . "\n"; - print " " + print " " . loc( "[_1] - Specify the action module you want to use", "--action" ) . "\n"; - print " " + print " " . loc( "[_1] - An argument to pass to [_2]", "--action-arg", "--action" ) . "\n"; - print " " + print " " . loc( "[_1] - Specify name or id of template(s) you want to use", "--template" ) . "\n"; - print " " + print " " . loc( "[_1] - Specify if you want to use either 'first', 'last' or 'all' transactions", "--transaction" ) . "\n"; - print " " + print " " . 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 " " + print " " + . loc( "[_1] - Adjust LogToSTDERR config option", "--log" ) . "\n"; + print " " . loc( "[_1] - Output status updates to STDOUT", "--verbose" ) . "\n"; print "\n"; print "\n"; @@ -464,7 +450,7 @@ Specify the comma separated list of transactions' types you want to use =item log -Adjust LogToScreen config option +Adjust LogToSTDERR config option =item verbose diff --git a/bin/rt-mailgate b/bin/rt-mailgate index 105affd..deccd70 100755 --- a/bin/rt-mailgate +++ b/bin/rt-mailgate @@ -144,12 +144,6 @@ sub validate_cli_flags { return $self->permfail(); } - if (($opts->{'ca-file'} or $opts->{"verify-ssl"}) - and not LWP::UserAgent->can("ssl_opts")) { - print STDERR "Verifying SSL certificates requires LWP::UserAgent 6.0 or higher.\n"; - return $self->tempfail(); - } - $opts->{"verify-ssl"} = 1 unless defined $opts->{"verify-ssl"}; } @@ -159,11 +153,9 @@ sub get_useragent { my $ua = LWP::UserAgent->new(); $ua->cookie_jar( { file => $opts->{'jar'} } ) if $opts->{'jar'}; - if ( $ua->can("ssl_opts") ) { - $ua->ssl_opts( verify_hostname => $opts->{'verify-ssl'} ); - $ua->ssl_opts( SSL_ca_file => $opts->{'ca-file'} ) - if $opts->{'ca-file'}; - } + $ua->ssl_opts( verify_hostname => $opts->{'verify-ssl'} ); + $ua->ssl_opts( SSL_ca_file => $opts->{'ca-file'} ) + if $opts->{'ca-file'}; return $ua; } @@ -252,13 +244,8 @@ sub check_failure { my $r = shift; return if $r->is_success; - # XXX TODO 4.2: Remove the multi-line error strings in favor of something more concise - print STDERR <<" ERROR"; -An Error Occurred -================= - -@{[ $r->status_line ]} - ERROR + print STDERR "HTTP request failed: @{[ $r->status_line ]}. " + ."Your webserver logs may have more information or there may be a network problem.\n"; print STDERR "\n$0: undefined server error\n" if $opts->{'debug'}; return $self->tempfail(); } @@ -358,10 +345,6 @@ is found. This flag tells the mail gateway where it can find your RT server. You should probably use the same URL that users use to log into RT. -If your RT server uses SSL, you will need to install additional Perl -libraries. RT will detect and install these dependencies if you pass the -C<--enable-ssl-mailgate> flag to configure as documented in RT's README. - If you have a self-signed SSL certificate, you may also need to pass C<--ca-file> or C<--no-verify-ssl>, below. @@ -382,9 +365,6 @@ of CA. This is required if you have a self-signed certificate, or some other certificate which is not traceable back to an certificate your system ultimitely trusts. -Verifying SSL certificates requires L version 6.0 or -higher; explicitly passing C<--verify-ssl> on prior versions will error. - =item C<--extension> OPTIONAL Some MTAs will route mail sent to user-foo@host or user+foo@host to user@host diff --git a/docs/README b/docs/README index 4cb3918..ec7fdcc 100644 --- a/docs/README +++ b/docs/README @@ -14,17 +14,17 @@ us at . REQUIRED PACKAGES ----------------- -o Perl 5.8.3 or later (http://www.perl.org). +o Perl 5.10.1 or later (http://www.perl.org). - Perl versions prior to 5.8.3 contain bugs that could result in - data corruption. RT won't start on older versions. + RT won't start on versions of Perl older than 5.10.1. o A supported SQL database - Currently supported: MySQL 4.1 or later with InnoDB support. - Postgres 8.1 or later. + Currently supported: MySQL 5.1 or later with InnoDB support. + Postgres 8.4 or later; 9.0 or later suggested Oracle 9iR2 or later. - SQLite 3.0. (Not recommended for production) + SQLite 3.0 or later; for testing only, no + upgrade path guaranteed o Apache version 1.3.x or 2.x (http://httpd.apache.org) with mod_perl -- (http://perl.apache.org) @@ -101,9 +101,9 @@ GENERAL INSTALLATION If you are unsure of your CPAN version, it will be printed when you run the shell. - If you are having trouble installing GD or Graphviz, you should - install gd-devel and the graphviz libraries using your - distribution's package manager. + If you are having trouble installing GD, refer to "Installing GD libraries" + in docs/charts.pod. Ticket relationship graphing requires the graphviz + library which you should install using your distribution's package manager. 5) Check to make sure everything was installed properly. @@ -226,13 +226,6 @@ GENERAL INSTALLATION perldoc /opt/rt4/bin/rt-mailgate - If your webserver uses SSL, rt-mailgate will require several new - Perl libraries. RT can detect and install these for you automatically - if you include --enable-ssl-mailgate when running configure and then - run make fixdeps as described in step 4. It is safe to rerun configure - and make fixdeps after you have installed RT, you should be sure to include - all the arguments you used in step 2 plus --enable-ssl-mailgate. - GETTING HELP ------------ diff --git a/docs/UPGRADING-4.0 b/docs/UPGRADING-4.0 index 3e5b74a..63dd2ee 100644 --- a/docs/UPGRADING-4.0 +++ b/docs/UPGRADING-4.0 @@ -126,6 +126,14 @@ database level. =back +=head2 Ticket content searches (full text search) + +Since 4.0.0, RT's ticket content search is disabled by default because of +performance issues when used without full text indexing. For details on how to +re-enable it with (or without) full text indexing, see +F. + + =head1 UPGRADING FROM 4.0.5 AND EARLIER @@ -238,4 +246,3 @@ upgrade by running: Followed by re-running make upgrade-database and answering 3.9.8 when prompted for which RT version you're upgrading from. ->>>>>>> 4.0/pg-9.2-compatibility diff --git a/docs/UPGRADING-4.2 b/docs/UPGRADING-4.2 new file mode 100644 index 0000000..0e458fa --- /dev/null +++ b/docs/UPGRADING-4.2 @@ -0,0 +1,305 @@ +=head1 UPGRADING FROM RT 4.0.0 and greater + +The 4.2 release is a major upgrade and as such there are more changes +than in a minor bugfix release (e.g., 4.0.13 to 4.0.14) and some of these +changes are backward-incompatible. The following lists some of the notable +changes, especially those that might require you to change a configuration +option or other setting due to a change in RT. Read this section carefully +before you upgrade and look for changes to features you currently use. + +=over + +=item * + +The L option defaults to on. This provides +a number of improvements, most notably no longer showing pages of empty results +if the user doesn't have permissions to view the tickets in question. +It may, in some cases, have performance impacts, but these have been +found to be minimal in existing 4.0 installs. + +=item * + +The C<$LogToScreen> config setting is now named +L<< "C<$LogToSTDERR>"|RT_Config/"$LogToSyslog, $LogToSTDERR" >> which +better describes what the log level controls. Setting C<$LogToScreen> will +still work, but an informational notice will be issued on server start telling +you about the rename. To avoid this you should set C<$LogToSTDERR> instead. + +=item * + +The link direction and type maps are consolidated into RT::Link. If you +wrote local customizations or extensions utilizing C<%RT::Ticket::LINKDIRMAP>, +C<%RT::Ticket::LINKTYPEMAP>, CLINKDIRMAP>, +CLINKTYPEMAP>, or C<%RT::Record::LINKDIRMAP>, you will need to +switch to C<%RT::Link::DIRMAP> and C<%RT::Link::TYPEMAP>. + +=item * + +C<$LinkTransactionsRun1Scrip> is removed. If you were relying on this behavior +(by setting it to 1), you should adjust your scrips to ignore one of the link +transactions. + +=item * + +The C<$AttachmentUnits> option was removed in preference of always displaying in +megabytes, kilobytes, or bytes as appropriate. The option was incompletely +implemented and controlled display in the attachments list but not history. + +=item * + +MakeClicky handlers added via a callback are now passed an "object" key in +the parameter hash instead of "ticket". The object may be any L +subclass. + +=item * + +C<$MessageBoxWrap> was removed. Wrapping is now always C. If you want hard +line breaks, enter them manually. + +=item * + +C handlers (C) have moved out of Mason components +and into C methods. Any custom username formats will need to be +reimplemented as C methods. Renaming should follow that of the core +components: + + /Elements/ShowUserConcise => RT::User->_FormatUserConcise + /Elements/ShowUserVerbose => RT::User->_FormatUserVerbose + +The C<_FormatUser*> methods are passed a hash containing the keys C and +C
, which have the same properties as before. + +=item * + +Rich text (HTML) messages are now preferred for display. If you prefer plain +text messages, set L to 0. + +=item * + +User email addresses are now validated by default and multiple, +comma-separated addresses for a single user are no longer allowed. Existing +users with invalid addresses will continue to work until the next time they +are updated by an administrator on the modify user page. If you prefer no +address validation, set L to 0. + +=item * + +The C option for L, along with the associated +C<$SMTPServer>, C<$SMTPFrom>, and C<$SMTPDebug> options, has been removed +because it did not guarantee delivery. Instead, use a local MTA for +outgoing mail, via the 'sendmailpipe' setting to C<$MailCommand>. + +=item * + +The L config now only keeps additional JavaScript filenames; if +you had copied C<@JSFiles> to add extra entries in your C, +remove the core JS from the list, or RT will serve those files +multiple times. + +=item * + +The C<$DeferTransactionLoading> option was combined into the new option +L. If you had enabled C<$DeferTransactionLoading>, +you may want to set C<$ShowHistory> to C. However, C<$ShowHistory> +provides a new mode, C, which is the default and may be a more +appealing alternative to C. + +=item * + +A C transaction is now recorded when a ticket status changes as a +result of a queue change. Scrips with conditions relying on Status changes +may start to trigger on these transitions; previously these Status changes +never triggered scrips. + +=item * + +The C search has been renamed to C. If you were +using this in an L<< C >> cronjob or had used a +C to add features, you will need to convert to +using L instead. + +=item * + +On merge, RT retains transactions from both tickets. Previously, RT +also recorded explicit time change transactions during a +merge to adjust the total time spent. This caused the total time +spent, as summed from transactions, to be different from the ticket's +overall time spent. This has been fixed: time is adjusted during the +merge commit itself, removing the need for the confusing +extra transactions, and keeping the summed time spent consistent. + +In order to fix the history records of old ticket you can run the following +command: + + perl -I /opt/rt4/local/lib/ -I /opt/rt4/lib/ etc/upgrade/time-worked-history.pl + +This command deletes records from the Transactions table. This script can only fix +TimeWorked mismatches, but not TimeLeft or TimeEstimated. + +=item * + +A new action, "Open Inactive Tickets", has been added, and on new +installs the default scrip "On Correspond Open Tickets" has been +replaced by "On Correspond Open Inactive Tickets". The key difference +between "Open Tickets" and "Open Inactive Tickets" is that the latter +will not adjust the status of a ticket if it is already active. This +is particularly useful when creating complex workflows using +Lifecycles. + +=item * + +CSS is no longer processed through Mason; it's served by a proper static file +handler. If you used the C or C callbacks of C in the +aileron, web2, or ballard themes, you should transition to the +L config option. If you need to target specific themes, +you can use the class set on the ECE element (for example: +body.aileron). See F for more information +on custom styles. + +=item * + +There are now HTML versions of the standard plain text templates. Running +make upgrade as described in the F will insert the new templates into +existing installs. While new installs use the HTML templates by default, +upgrades from older versions don't automatically switch to the HTML versions. +To switch existing scrips, run: + + /opt/rt4/etc/upgrade/switch-templates-to html + +To switch from HTML back to text, run: + + /opt/rt4/etc/upgrade/switch-templates-to text + +=item * + +The Articles menu is now a top-level menu item and display is controlled by +the right C. This right is only grantable globally to groups +or users. During the upgrade, the new right will be automatically granted to +Privileged users so that the menu doesn't disappear for anyone previously +using it. You may wish to revoke the right from Privileged and grant it +more selectively. + +=item * + +The Owner drop-down now only includes privileged users (no matter if +unprivileged users have been granted the OwnTicket right) because +configurations which have unprivileged Owners are exceedingly rare, +and granting Everyone the OwnTicket right is a common cause of +performance problems. Unprivileged Owners (if they exist) may still +be set using the Autocompleter. + +=item * + +The functionality that changed the ticket status to Open when the Started +date is set has been moved to a Scrip called 'On transaction and SetStarted +Open Ticket'. If you do not depend on this functionality, the Scrip can +be deleted. + +=item * + +New installs will notify Ccs and one-time Ccs/Bccs on create and Owners on +create and correspond. Upgraded installations will not. If you'd like to +adjust your scrips to match the new install behavior, create and edit the +following scrips from the admin scrip page: + +To notify Ccs on create, on the 'Create a global scrip' page: + + Description: On Create Notify Ccs + Condition: On Create + Action: Notify Ccs + Template: Correspondence in HTML + +To notify one-time Ccs/Bccs on create, on the 'Create a global scrip' page: + + Description: On Create Notify Other Recipients + Condition: On Create + Action: Notify Other Recipients + Template: Correspondence in HTML + +To notify Owners on create, click 'On Create Notify AdminCcs'. Change the +fields listed below to their corresponding values: + + Description: On Create Notify Owner and AdminCcs + Action: Notify Owner and AdminCcs + +To notify Owners on correspond, click 'On Correspond Notify AdminCcs'. Change +the fields listed below to their corresponding values: + + Description: On Correspond Notify Owner and AdminCcs + Action: Notify Owner and AdminCcs + +=item * + +Notifications to AdminCcs on approvals are now handled via the New Pending +Approval template in the hidden ___Approvals queue. If you customized the +Transaction template, you should port your changes to New Pending Approval. + +=item * + +On Oracle, sessions are now stored in the database by default instead of +on-disk. If you wish to preserve the original behavior, ensure that +L is set in your C: + + Set($WebSessionClass, "Apache::Session::File"); + +=item * + +Configuration options dealing with "external authentication" have been +renamed to reduce confusion with the common extension +L. The old names will work, but produce +deprecation warnings. The old names, and their new counterparts, are: + + WebExternalAuth => WebRemoteUserAuth + WebExternalAuthContinuous => WebRemoteUserContinuous + WebFallbackToInternalAuth => WebFallbackToRTLogin + WebExternalGecos => WebRemoteUserGecos + WebExternalAuto => WebRemoteUserAutocreate + AutoCreate => UserAutocreateDefaultsOnLogin + +=item * + +Due to many long-standing bugs and limitations, the "Offline Tool" was +removed. + +=item * + +To increase security against offline brute-force attacks, RT's default +password encryption has been switched to the popular bcrypt() key +derivation function. Passwords cannot be automatically bulk upgraded to +the new format, but will be replaced with bcrypt versions upon the first +successful login. + +=item * + +We updated default "Forward" and "Forward Ticket" templates to support +customizing messages on forward. They will be updated automatically if you +didn't change them before. + +But in case you have changed them already, you need to update them manually. +You can use $ForwardTransaction to refer to the customized message in the +templates, e.g. "Forward" template could be updated to: + +{ $ForwardTransaction->Content =~ /\S/ ? $ForwardTransaction->Content : "This is a forward of transaction #".$Transaction->id." of ticket #". $Ticket->id } + +=item * + +RT has generated RT-Ticket: RT-Originator: and Managed-By: headers in +compliance with RFC2822/6648 but we've discovered that some smarthost +providers are requiring strict adherence to RFC822 which mandates X- +prefixes on these headers. We've made this change in 4.2 for users +relying on those providers. + +Any external scripts which were parsing on these RT mail headers will +need to be updated. + +=item * + +GnuPG and S/MIME are no longer enabled in F merely by the +presence of the C or C binaries. Systems which depended +on C enabling these in F implicitly will need +to pass C<--enable-gpg> to C, or alter their +C to enable the functionality explicitly. + +=back + +=cut diff --git a/docs/authentication.pod b/docs/authentication.pod new file mode 100644 index 0000000..a24b422 --- /dev/null +++ b/docs/authentication.pod @@ -0,0 +1,139 @@ +=encoding utf-8 + +=head1 RT Authentication + +RT allows for several different ways to authenticate users including an +internal user management system and a number of ways to integrate with existing +authentication systems. + +=head1 Internal Authentication + +RT's native internal authentication system provides administration tools to +manage usernames and passwords. If you plan to run your RT as a stand-alone +system and don't need to use accounts associated with any other system, this +may be all you need. The administration pages under Admin → Users +provide new user creation as well as password setting and control of RT's +privileged flag for existing users. + +=head1 External Authentication + +There are two primary types of external authentication: in one you type your +username and password into RT's login form, and in the other your web server +(such as Apache) handles authentication, often seamlessly, and tells RT the +user logged in. + +The second is supported by RT out of the box under the configuration option +C<$WebRemoteUserAuth> and other related options. The first is supported by an RT +extension named L. These two types may be used +independently or together, and both can fallback to RT's internal +authentication. + +No matter what type of external authentication you use, RT still maintains user +records in its database that correspond to your external source. This is +necessary so RT can link tickets, groups, rights, dashboards, etc. to users. + +All that is necessary for integration with external authentication systems is a +shared username or email address. However, in RT you may want to leverage +additional information from your external source. Synchronization of users, +user data, and groups is provided by an extension named +L. It uses an external LDAP source, such an +OpenLDAP or Active Directory server, as the authoritative repository and keeps +RT up to date accordingly. This can be used in tandem with any of the external +authentication options as it does not provide any authentication itself. + +=head2 Via your web server, aka C<$WebRemoteUserAuth>, aka C + +This type of external authentication is built-in to RT and bypasses the RT +login form. Instead, RT defers authentication to the web server which is +expected to set a C environment variable. Upon a request, RT +checks the value of C against its internal database and logs in +the matched user. + +It is often used to provide single sign-on (SSO) support via Apache modules +such as C (to talk to Active Directory). C<$WebRemoteUserAuth> is +widely used by organizations with existing authentication standards for web +services that leverge web server modules for central authentication services. +The flexibility of RT's C<$WebRemoteUserAuth> support means that it can be setup +with almost any authentication system. + +In order to keep user data in sync, this type of external auth is almost always +used in combination with one or both of L and +L. + +=head3 Configuration options + +All of the following options control the behaviour of RT's built-in external +authentication which relies on the web server. They are documented in detail +under the "Authorization and user configuration" section of C +and you can read the documentation by running C. + +The list below is meant to make you aware of what's available. You should read +the full documentation as described above. + +=head4 C<$WebRemoteUserAuth> + +Enables or disables RT's expectation that the web server will provide +authentication using the C environment variable. + +=head4 C<$WebRemoteUserContinuous> + +Check C on every request rather than the initial request. + +When this is off, users will remain logged into RT even after C is +no longer provided. This provides a separation of sessions, but it may not be +desirable in all cases. For example, if a user logs out of the external +authentication system their RT session will remain active unless +C<$WebRemoteUserContinuous> is on. + +=head4 C<$WebFallbackToRTLogin> + +If true, allows internal logins as well as C by providing a login +form if external authentication fails. This is useful to provide local admin +access (usually as root) or self service access for people without external +user accounts. + +=head4 C<$WebRemoteUserAutocreate> + +Enables or disables auto-creation of RT users when a new C is +encountered. + +=head4 C<$UserAutocreateDefaultsOnLogin> + +Specifies the default properties of auto-created users. + +=head4 C<$WebRemoteUserGecos> + +Tells RT to compare C to the C field of RT users instead of +the C field. + +=head2 Via RT's login form, aka RT::Authen::ExternalAuth + +L is an RT extension which provides authentication +B RT's login form. It can be configured to talk to an LDAP source (such +as Active Directory), an external database, or an SSO cookie. + +The key difference between C<$WebRemoteUserAuth> and L +is the use of the RT login form and what part of the system talks to your +authentication source (your web server vs. RT itself). + +=head3 Info mode and Authentication mode + +There are two modes of operation in L: info and auth. +Usually you want to configure both so that successfully authenticated users +also get their information pulled and updated from your external source. + +Auth-only configurations are rare, and generally not as useful. + +Info-only configurations are commonly setup in tandem with C<$WebRemoteUserAuth>. +This lets your web server handle authentication (usually for SSO) and +C ensures user data is updated every time someone +logs in. + +=head2 RT::Extension::LDAPImport + +L provides no authentication, but is worth +mentioning because it provides user data and group member synchronization from +any LDAP source into RT. It provides a similar but more complete sync solution +than L (which only updates upon login and doesn't +handle groups). It may be used with either of RT's external authentication +sources, or on it's own. diff --git a/docs/charts.pod b/docs/charts.pod new file mode 100644 index 0000000..52db66f --- /dev/null +++ b/docs/charts.pod @@ -0,0 +1,246 @@ +=head1 RT Charts + +RT has a built-in charting feature to allow you to create charts +and graphs to visualize ticket data. Charts can be useful for +anything from one-off reports (how many tickets did we process +last year?) to regular status reports that you then include in +shared dashboards that everyone can see. + +RT has had charts for a long time, but many significant improvements +came in RT 4.2. If you're running a version of RT earlier than +4.2 some options and features described here may not be available. + +=head1 Enabling Charts + +=head2 Installing GD + +While charts is a core part of RT, you do need to enable it using +the C<--enable-gd> option and install the required dependencies when +you install RT. If you didn't originally install with this flag, you can +enable it by re-running the L<< C >> script from the RT +distribution (including all previous options passed to it originally) +or doing the following in your current install: + +=over + +=item 1 + +In your C set C<$DisableGD> to 0: + + Set($DisableGD, 0); + +=item 2 + +Run the RT dependency checker: + + $ /opt/rt4/sbin/rt-test-dependencies --with-gd --verbose + +=item 3 + +Install GD libraries + +GD is an open source graphics library and it is available as a package +for most Linux systems. The package might be called C, C, C, +or something similar. On some systems you will also need additional package +required to compile code using the library. The package might be called +C, C, or something similar. + +=item 4 + +Install Perl GD modules: + + $ /opt/rt4/sbin/rt-test-dependencies --with-gd --install --verbose + + +=back + +=head2 Chart Configuration Options + +By default, RT is configured to use the "Droid Sans" font for Unicode support +across English, many western european languages, Chinese (Traditional and +Simplified), and Japanese. If you prefer to use a different font, you can +change the L<< "C<%ChartFont>"|RT_Config/ChartFont >> option. + +There is also an option to use database timezone conversion for PostgreSQL +and MySQL to enable timezone conversion for time-based reports. See +F for details. + +Search for "Chart" entries in L<< C >> for more information on these +options. + +=head1 Basic Charting + +Charts are based on the set of tickets returned by a search, so every chart starts +with a search of your RT tickets. When constructing your search, think about +the report you need to generate and try to narrow the results to the set +of tickets that will have the information you want. + +=head2 Basic Ticket Search + +As a basic example, assume you want to look at activity in July 2012 for +the General queue. First use the Query Builder to build a query with +something like: + + Queue = 'General' + AND Created >= '2012-07-01' + AND Created <= '2012-07-31' + +This search will give you tickets for July because the criteria uses before +and after for the dates. + +This search shows one of the initial things you'll want to consider, which is +the element of ticket metadata you want to use as the basis for time. In +the example we're using Created, but depending on what you are reporting +on you might want Started, Resolved, or any of the other ticket time values. + +When selecting the criteria for the time search, make sure it +is appropriate to the report you want to see and be consistent so you +are looking at the right set of tickets in the search and the resulting +charts and reports. + +For this example, we'll say the activity we want to look at is new +tickets coming into the queue, and Created works well for that. + +=head2 Ticket Charts + +If we run the search and look at the results, we get the standard +RT search results page. From this page we can click on Chart in the submenu +on the upper right of the page. This brings us to the Charts page with a +default bar chart showing tickets by status in the General queue. + +=for html General Queue Status Chart + +=for :text [General Queue Status Chart F] + +=for :man [General Queue Status Chart F] + +This chart gives us a nice view of tickets by status and the good news is +most of the tickets in this time period have been resolved. Under the graph +is a "Group by" section and we can see "Status" is selected as the criteria +in the first dropdown in the first section. The second dropdown is also Status +since status only has one representation. + +Now let's assume we want to see who was working on those tickets. You can +select Owner from the first 'Group tickets by' dropdown and you'll see +the second dropdown now has options to display labels based on RT user +entries. Select an option and click 'Update Chart' and now you'll see +the tickets displayed by Owner. + +=for html General Owner Chart + +=for :text [General Owner Chart F] + +=for :man [General Owner Chart F] + +In this case, we can see that although people are resolving tickets, they +aren't Taking the tickets and Owner is not getting set. We may want to +remind people to take tickets or even create a scrip to set Owner +automatically on reply or resolve. + +Before we do that, we can use more chart features to find out more about +what's going on. The "Group by" portlet allows us to set multiple criteria, +so in the second set of dropdowns we'll select LastUpdatedBy and Name +and click 'Update Chart'. + +=for html General Owner LastUpdatedBy Chart + +=for :text [General Owner LastUpdatedBy Chart F] + +=for :man [General Owner LastUpdatedBy Chart F] + +Now we can see that our culprit seems to primarily be the root user, who +is getting a bunch of work done but isn't taking tickets. Maybe we just +need to remind root to take tickets. + +=head2 Using Multiple Group Bys + +As you can see in the previous example, RT's charts allow you to define +multiple criteria for grouping data from your search results. In many cases, +grouping multiple levels of criteria can reveal interesting and useful graphs. +To give you the greatest flexibility possible, the RT interface allows you +to select from nearly all ticket values, but not all combinations of group +by criteria will make sense or create a helpful chart. If you select some +options and produce a chart that looks jumbled, consider again what +you're trying to visualize from the data. + + +=head1 Calculated Values + +The Calculate section of the RT charts interface allows you to generate +charts with calculated time values. You can select time values used in time +tracking (e.g., TimeWorked) and calculated values from the various timestamps +on tickets like Created, Resolved, etc. Once you have selected the values or +ranges you want to view, you can choose to see an Average, Total, Maximum, +Minimum or a summary presenting them all. + +=head2 Viewing Ticket Response Times + +As described above, the Calculate section allows you to pull out durations +like how long it took for tickets to be opened, which is the difference +between Created and Started. To create a chart with this information, +we first create a new search to return all resolved tickets for a select +group of queues we're interested in. You could also add some date criteria +to narrow the search to a range of time as in the previous example. + +After getting our result set and clicking on Charts, we select +Queue from the "Group by" section so we see data grouped by the queues we +selected. In the Calculate section we select Created-Started from the +first dropdown and the Summary option from the second dropdown and click +"Update Chart". + +This generates a detailed chart with a bunch of time data for all of the +queues we selected in our search. It's a little busy, so we might look at +some of the other display options available in the second dropdown. +What we're really interested in is the average time from Created to Started, +since this will give us a general idea how long it's taking people to +initially respond to requests. + +The second dropdown in the Calculate section has an option for +"Average Created-Started". If we select that and update the +chart, we see a nice graph of average time for tickets to be opened across +all of the queues we selected. + +=for html Queue Created Started Chart + +=for :text [Queue Created Started Chart F] + +=for :man [Queue Created Started Chart F] + +Now perhaps we also want to see how long tickets stay active. In the Calculate +section you can add Started-Resolved to the first "and then" and select +"Average Started-Resolved" from the second dropdown. Click "Update Chart" +and you've now got a graphical view of how long, on average, tickets are +waiting to be opened and how long people are working on them. + +If you use RT for time tracking, you can create similar useful charts +using TimeEstimated, TimeWorked, and TimeLeft. + +=head1 Chart Style and Size + +Charts default to a bar style, but you can display data as a pie chart +by selecting pie in the "Picture" portlet. You can also adjust the width +height of the generated chart by entering a size in pixels. These width +and height values are saved if you save the chart and are used if +you include the chart on a Dashboard as well. + +=head1 Saving Charts + +Much like searches, you can save charts once you get them configured the +way you want. The Privacy setting determines who else on the RT system +will be able to see your saved charts. Note that this setting applies only +to the chart itself and not necessarily the data included which may still +be blocked from other users. + +To save a chart, select a Privacy setting, give it a Description and click +Save. Once saved, you can retrieve the chart later by coming to the chart +page and selecting it from the "Load saved search" dropdown and clicking +Load. + +When you save a chart, it also becomes available to the Dashboard interface. +This allows you to go to Home > New Dashboard and create a Dashboard that +shows the chart you have created. This can be very useful for charts you +want to monitor frequently or create for others. + +If you need to change a chart, load it, make your changes, then click +Update. Delete deletes the saved chart and will also remove it from all +Dashboards that are using it. diff --git a/docs/customizing/approvals.pod b/docs/customizing/approvals.pod index 4f768f7..3d16252 100644 --- a/docs/customizing/approvals.pod +++ b/docs/customizing/approvals.pod @@ -24,8 +24,8 @@ and process the approval or rejection. Since this example will use a change management queue as the queue where tickets need approval, first we'll set up the queue. -Login into UI as the 'root' user. Go to Tools -> Configuration -> -Queues and create a new 'Change requests' queue. +Login into UI as the 'root' user. Go to Admin -> Queues and create a new +'Change requests' queue. When you set up this queue, do not select the "approvals" Lifecycle. That selection is for the ___Approvals queue itself, not for queues that @@ -94,7 +94,7 @@ queue. ___Approvals is a special queue where all approvals are created. The queue is disabled and is not shown in until you search for it. -Go to Tools -> Configuration -> Queues, leave "Name is" in the search +Go to Admin -> Queues, leave "Name is" in the search area and enter ___Approvals into the search field. Check 'Include disabled queues in listing.' and click Go! You should now see the ___Approvals queue configuration page. diff --git a/docs/customizing/articles_introduction.pod b/docs/customizing/articles_introduction.pod index 73b5c33..363a385 100644 --- a/docs/customizing/articles_introduction.pod +++ b/docs/customizing/articles_introduction.pod @@ -8,11 +8,16 @@ RT. They are organized into classes and topics. =head2 UI -The user interface to Articles is available from the Tools -> Articles -menu. Admin functionality can be found under Tools -> Configuration -> -Articles. Once configured, articles will become available for searching -on the Reply/Comment page on tickets. There are L -to make Articles available on ticket creation. +The user interface to Articles is available from the Articles menu. Admin +functionality can be found under Admin -> Articles. Once configured, articles +will become available for searching on the Reply/Comment page on tickets. +There are L to make Articles available on ticket +creation. + +For the Articles menu to be visible to your Privileged users, you must grant +Privileged the ShowArticlesMenu right globally (Admin -> Global -> Group +Rights). You may grant the right as selectively as you wish if, for example, +you only want a certain group of your users to use articles. =head2 Basics @@ -27,7 +32,7 @@ Classes can be made available globally or on a per-Queue basis. =head3 Classes Classes are equivalent to RT's queues. They can be created by going -to Tools -> Configuration -> Articles -> Classes -> New Class. Articles +to Admin -> Articles -> Classes -> New Class. Articles are assigned to one Class. When you create Custom Fields for use with Articles, they will be applied Globally or to a Class, like Custom Fields are applied to a Queue in RT. @@ -40,7 +45,7 @@ when inserting the Article in a reply. You can control this behavior on the Class configuration page. Classes need to be Applied, just like a Custom Field, by using the -Applies To link on the Modify Class page (Tools -> Configuration -> +Applies To link on the Modify Class page (Admin -> Articles -> Classes, select the class to modify). You can apply them globally or on a queue-by-queue basis. @@ -48,7 +53,7 @@ them globally or on a queue-by-queue basis. You can also use Topics to organize your Articles. While editing a Class, there is a Topics tab for Class-specific Topics. You can create -global Topics from the Global tab under Tools -> Configuration. +global Topics from the Global tab under Admin. When editing Topics, type the name (and optionally description) of the Topic, and then click the button at the appropriate location in the diff --git a/docs/customizing/styling_rt.pod b/docs/customizing/styling_rt.pod index c5802a8..7e37d00 100644 --- a/docs/customizing/styling_rt.pod +++ b/docs/customizing/styling_rt.pod @@ -12,18 +12,22 @@ RT versions have a default, and the RT admin can set the system-wide theme with the C<$WebDefaultStylesheet> configuration value in the F file. -RT 4.0 comes with the following themes: +RT comes with the following themes: =over -=item web2 +=item rudder -An approximation of the 3.8 style +The default layout for RT 4.2 =item aileron The default layout for RT 4.0 +=item web2 + +An approximation of the 3.8 style + =item ballard Theme which doesn't rely on JavaScript for menuing @@ -32,14 +36,14 @@ Theme which doesn't rely on JavaScript for menuing If you have granted the ModifySelf right to users on your system, they can pick a different theme for themselves by going to -Logged in as -> Settings -> Options and selecting a different theme. +Logged in as -> Settings -> Preferences and selecting a different theme. =head1 RT Theme Editor RT has some built-in controls to manage the look of the theme you select. To use the Theme Editor, log in as a SuperUser (like root), and navigate -to Tools -> Configuration -> Tools -> Theme. +to Admin -> Tools -> Theme. =for html RT theme editor, defaults @@ -89,38 +93,18 @@ default CSS styles, via the C<@CSSFiles> configuration option. To add an extra CSS file, for example F, create the local overlay directory: - $ mkdir -p local/html/NoAuth/css/ + $ mkdir -p local/static/css/ And place your F file in it. Finally, adjust your C<@CSSFiles> in your F: Set( @CSSFiles, ('my-site.css') ); -This technique is preferred to callbacks (below) because CSS included -via this way will be minified. It is also included across all styles, -unlike the callback technique. +CSS added this way is included across all themes. If you are writing an extension, see L for how to simply and programmatically add values to C<@CSSFiles>. -=head2 Callbacks - -RT's CSS files are also Mason templates and the main CSS file, -conveniently called C, has a C and C callback -allowing you to inject custom CSS. - -To create an End callback, create the callback directory and an -End file in that directory: - - $ mkdir -p local/html/Callbacks/MyRT/NoAuth/css/aileron/main.css - $ touch local/html/Callbacks/MyRT/NoAuth/css/aileron/main.css/End - -You can use any name you want for the C directory and the theme -directory should correspond with the theme you want to change. - -RT will now evaluate the contents of that file after it processes all -of the C<@import> statements in C. - =head1 Designing Your Own Theme @@ -134,11 +118,11 @@ local modifications to RT. Run the following commands in your C directory (or wherever your RT is installed) to get started: - $ mkdir -p local/html/NoAuth/css/localstyle - $ cp -R share/html/NoAuth/css/aileron/* local/html/NoAuth/css/localstyle/ + $ mkdir -p local/static/css/localstyle + $ cp -R share/static/css/rudder/* local/static/css/localstyle/ You can call your "localstyle" directory whatever you want and you don't -have to copy the aileron theme to start from, but it's a good place to +have to copy the rudder theme to start from, but it's a good place to start off for RT4. Now set C<$WebDefaultStylesheet> in RT_SiteConfig.pm to the new directory @@ -147,7 +131,7 @@ name you selected, for example: Set( $WebDefaultStylesheet, 'localstyle' ); If you restart your RT it should look just the same (assuming you copied -your current default theme), but if you go to your Options page you'll +your current default theme), but if you go to your Preferences page you'll see that the system default theme is now your new "localtheme." If you look at the CSS being loaded, you'll also see that the main css diff --git a/docs/customizing/templates.pod b/docs/customizing/templates.pod index 5733f60..d61542d 100644 --- a/docs/customizing/templates.pod +++ b/docs/customizing/templates.pod @@ -1,7 +1,14 @@ =head1 Templates -Each template is split into two sections. A block of headers and a body. These -sections are separated by a blank line. +Templates are used in RT to send notifications, typically email. You have +access to RT data via variables available to you in the scope of the template. +Templates can also be used for some special actions like creating a new ticket +as part of the execution of a scrip. + +Each template is split into two sections: a block of headers and a body. These +sections are separated by a blank line. Blank lines are not allowed before +the headers, but can be included in the body as needed after the headers +section. Templates are processed by the L module. This module allows you to embed arbitrary Perl code into your templates. Text wrapped @@ -28,17 +35,17 @@ readable, while users with clients which can display HTML will receive the full experience. Please be aware that HTML support in mail clients varies greatly, much more so than different web browsers. -We welcome contributions of HTML-ization of builtin templates. +Starting in RT 4.2, HTML templates are included along with plain text templates +for the standard RT notifications. =back =head2 Template Types -Templates have a Type which dictates which level of code execution is -allowed. +Templates have a Type which dictates the level of code execution allowed. Templates of type C are evaluated using L -which allows arbitrary code execution. Only users which have the global +which allows arbitrary code execution. Only users with the global C privilege may write templates of type C. Prior to RT 4.0, this was the only type of Template available. @@ -82,6 +89,24 @@ A localization function. See L. =back +The C<$Transaction> and C<$Ticket> objects are particularly useful. For +example, here are some values you can get from each: + + $Ticket->Status # Current status + $Ticket->Owner # Current owner + $Ticket->FirstCustomFieldValue('CustomFieldName') # CF value + $Ticket->DueAsString # Current due date as a string + $Ticket->DueObj # Due as an RT::Date object + $Ticket->QueueObj # Queue object for this ticket + + $Transaction->Type # Type of transaction + $Transaction->OldValue # Previous value, if type is Set + $Transaction->NewValue # New value, if type is Set + $Transaction->CreatorObj->EmailAddress # Email address of trans creator + +You can see the methods available in the L and L +documentation. + =head3 Selected Simple template variables Since method calls are not allowed in simple templates, many common @@ -128,5 +153,20 @@ For example, C<$TransactionCFLocation>. =back +=head2 Templates Provided with RT + +RT comes with a set of templates for the default notifications. As you start to +customize your templates, these templates are a good place to look for +examples. As you customize, it can be helpful to create new templates and +update your scrips to reference your new templates. This leaves the original RT +templates in place for easy reference. + +Starting in RT 4.2, each template has a plain text version and an HTML +version. For example, the "Correspondence" template is the plain text version +of the default template for correspondence (replies) and the "Correspondence in +HTML" template is the same template formatted in HTML. The 4.2 upgrade provides +a C script to switch all default templates from plain text +to HTML or the reverse. See the L notes for details. + =cut diff --git a/docs/dashboards.pod b/docs/dashboards.pod new file mode 100644 index 0000000..0a96521 --- /dev/null +++ b/docs/dashboards.pod @@ -0,0 +1,206 @@ +=head1 Dashboards + +RT's dashboard feature provides a convenient way to create your own pages +focused on the tickets and charts you need. Dashboards are available right from +the Home menu, can be set up individually or shared, and can even be sent out +via email on a schedule. To show some of the dashboard features, we'll set up a +dashboard and notifications to track outstanding invoice tickets. + +There are several different rights you can grant to allow users access to the +features described here. These rights are described in L. + +=head2 Creating a Personal Dashboard + +Saved searches and charts are the building blocks of dashboards, so to set up a +new dashboard you first need to create and save a search that displays the +ticket data you want. We want to view new and open invoice tickets and, for our +example, assume we have an Accounts Receivable queue. On the ticket search +page, we create a new search with this query: + + Queue = 'Accounts Receivable' + AND ( + Status = 'new' + OR Status = 'open' ) + +We also want to modify the sort order of the search to use Due rather than the +default id. In the Sorting section, we select Due for the initial sort, then +add Created as the second sort value. Finally, we set Rows per page to +Unlimited so we don't miss any invoices. + +=for html Dashboard search sorting + +=for :text [Search sorting F] + +=for :man [Search sorting F] + +Once you have those set, you can click "Add these terms and Search" or "Update +format and Search" to see the results. If it's still not quite right, you can +click "Edit Search" in the submenu and continue to refine things. + +When you're finished tweaking the search, return to Query Builder page again so +you can save it. Under the "Saved Searches" box, type "Outstanding Invoices" +in the Description box. For now, leave Privacy set to "My saved searches" and +click Save. You now have a saved search you can use for your dashboard. + +To create the dashboard, select Home > New Dashboard. Type "Outstanding +Invoices" for the name and leave the privacy set to "My Dashboards". Click +Create and the new dashboard is created. + +Now we want to populate the new dashboard with the saved search we created. +Click Content in the submenu to go to the content selection page. Dashboards +allow you to put content in the main body or the sidebar, much like the default +RT homepage, so you'll see a Body section and a Sidebar section to set the +content. Find your saved "Outstanding Invoices" search, select it, and click +the arrow to move it to the righthand box and add it to the dashboard. + +=for html Adding dashboard content + +=for :text [Adding dashboard content +F] + +=for :man [Adding dashboard content +F] + +Click Show in the submenu and you'll see your new dashboard. Click Home to +return to the "RT at a glance" page and you'll see your new dashboard is in the +Dashboards portlet on the right side of the page. + +On dashboard pages, you can click on the title of any section and go to the +search results page for the saved search. This makes it easy to find the saved +search and update it, or modify it ad-hoc for a one-off search based on the +saved dashboard search. + +In this example we're only adding one search, but you can add multiple searches +to each individual dashboard to track different types of interrelated +information and see it at a glance. For instance, two queries, "outstanding +invoices" and "overdue invoices," could form a dashboard called "all +outstanding invoices." Software engineers using RT might combine three queries, +"bug fixes," "feature requests," and "documentation," into a dashboard +called "our new release." + +=head2 Charts in Dashboards + +You can also display saved charts in dashboards, creating a powerful visual of +ticket data in a convenient page. To add a chart, start with a search, refine +your query, then click Chart in the submenu in the Query Builder or Search +Results page. Configure your chart as described in L, select a Privacy +setting, name it "Outstanding Invoices", and click Save. + +Return to the dashboard, click Content, and you'll see a new "Chart: +Outstanding Invoices" option in the Available column. Select it and click the +arrow to add it to the dashboard. Now when you load the dashboard, the chart +will be rendered right below the saved search. + +=for html Dashboard chart + +=for :text [Dashboard chart F] + +=for :man [Dashboard chart F] + +=head2 Dashboard Menu Entries + +In addition to having dashboards available on the "RT at a glance" page, you +can also add them to the Home menu. To modify the Home menu, select Home > +"Update This Menu" or "Logged in as" > Settings > "Dashboards in menu". You'll +see the Customize dashboard page which is similar to the Dashboard Content page. + +=for html Customize dashboard menu + +=for :text [Customize dashboard menu +F] + +=for :man [Customize dashboard menu F] + +Select the dashboard you want, click the arrow to move it to the righthand +column, then check your Home menu. You'll see your dashboard is now available +from the menu. + +As an RT administrator, you can populate the dashboard menu for other users on +the system. Find a user using Search > Users or Admin > Users > Select, then +click on the user to open the modify user page. In the submenu, you'll see a +"Dashboards in menu" option, and it works the same as the personal setting. + +=head2 Group Dashboards + +You're enjoying your new dashboard but it's time for some vacation and it would +be nice for someone else in the accounting department to be able to use your +dashboard while you're gone. RT makes this easy with group-level dashboards. + +As we've seen, the dashboard is based on a saved search, so you first need to +make that available. Go to the ticket search page (Query Builder), find your +saved search in the "Load saved search" dropdown, and click Load. If you are in +a group, like the Accounting group, there will be an option in the Privacy +dropdown called "Accounting's saved searches". Select that option and click +Update to make the search available to the Accounting group. + +To update your dashboard, select it from the menu to view it, then click Basics +in the submenu. Like on Query Builder page, you'll see your group listed in the +Privacy dropdown. Assuming your group is Accounting, select "Accounting's +Dashboards" and click Save Changes. + +Click on Content and you'll see a message that a query has been deleted and +removed from the dashboard. This is because RT has detected that you have moved +the saved search from personal to group privacy. Select "Outstanding Invoices" +from the Available column and click the arrow to add the group-based search to +the dashboard. + +All members of the Accounting group should now have access to your dashboard. +They can now add it to their Home menu if they want. If other members can't see +it, make sure you have granted sufficent rights to the group (see L). + +=head2 System-wide Dashboards + +You can also set up dashboards for all users on your RT system. Follow the +steps above for group dashboards, but for Privacy, select "RT System" for the +saved search and dashboard. + +If you want to make sure everyone has the dashboard in their Home menu, you can +set this globally as well if you are the RT administrator. The Admin > Global > +"Dashboards in menu" opens a page similar to the personal dashboard menu page, +but it puts the selected dashboards into everyone's dashboard menu. + +=head2 Dashboard Subscriptions + +RT's dashboard subscription feature allows you to email dashboards based on a +schedule you set. These scheduled dashboards can be particularly useful for +time-based reports that you want to see on a regular basis. + +To set up a subscription, go to the dashboard you'd like to have emailed and +click on Subscription in the submenu. This will take you to the subscription +page. + +=for html Dashboard subscription + +=for :text [Dashboard subscription F] + +=for :man [Dashboard subscription F] + +Select the frequency and timing you want and enter the email address the +dashboard should go to. You can leave it blank to send mail to your RT email +address. Click Subscribe and that's it, you'll start getting dashboards via +email. + +This feature requires the F script to be scheduled in +C as described in RT's F file. + +=head2 Dashboard Rights + +There are several rights you can selectively grant to allow users access to +dashboard features. As with any RT rights, you can grant these to individual +users (usually difficult to maintain over time), to system roles like +Privileged, or to groups you define. + +Since dashboards rely on saved searches, you need to grant "Allow loading of +saved searches" (LoadSavedSearch) for users to see the searches. You may want +to also grant "Allow creation of saved searches" (CreateSavedSearch) to allow +users to create their own and "View saved searches" (ShowSavedSearches) + +For dashboards themselves, there are See, Create, Modify, and Delete rights for +each of personal, group, and system dashboards. This allows you to select the +right combination of rights for users and groups on your system. For +subscriptions, there is a "Subscribe to dashboards" (SubscribeDashboard) right. \ No newline at end of file diff --git a/docs/extending/clickable_links.pod b/docs/extending/clickable_links.pod index 91e9eec..b7a525d 100644 --- a/docs/extending/clickable_links.pod +++ b/docs/extending/clickable_links.pod @@ -23,6 +23,7 @@ after the URL. =item C Detects URLs as C format, but replaces the URL with a link. +This action is enabled by default. =back diff --git a/docs/hacking.pod b/docs/hacking.pod index 7c50ee9..a3280c9 100644 --- a/docs/hacking.pod +++ b/docs/hacking.pod @@ -172,7 +172,7 @@ can create and drop databases: You'll need to configure RT and make sure you have all the dependencies before running tests. To do this in place without installing: - ./configure.ac --with-my-user-group --enable-layout=inplace --with-devel-mode + ./configure.ac --with-my-user-group --enable-layout=inplace --enable-developer make testdeps make fixdeps diff --git a/docs/images/customize-dashboards-menu.png b/docs/images/customize-dashboards-menu.png new file mode 100644 index 0000000000000000000000000000000000000000..b78dbbaecc6eff71668be37de4d9409f53be31ee GIT binary patch literal 37547 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%0AZm|kKgAO2?XO6&) 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 MEG<+n@cqaC0(UDdu>b%7 literal 0 HcmV?d00001 diff --git a/docs/images/dashboard-chart.png b/docs/images/dashboard-chart.png new file mode 100644 index 0000000000000000000000000000000000000000..51013e7c9080d73cbef9fdd2a2c898c6a8343f45 GIT binary patch literal 48074 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%0DC~qY_q4 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 E2lC*fv;Y7A literal 0 HcmV?d00001 diff --git a/docs/images/dashboard-content-invoices.png b/docs/images/dashboard-content-invoices.png new file mode 100644 index 0000000000000000000000000000000000000000..ed6a67e4d5312e34ff1ede153b47024e05d4ee07 GIT binary patch literal 45939 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%0AV1ps)?gLJp2X&ZZ2XYWwJE=f%y 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
  • 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
    .
    +By default, RT doesn't show remote images attached to incoming (and outgoing)
    +ticket updates inline.  Set this variable to 1 if you'd like to enable remote
    +image display.  Showing remote images may allow spammers and other senders to
    +track when messages are viewed and see referer information.
     
    -=cut
    +Note that this setting is independent of L above.
     
    -Set($PlainTextPre, 0);
    +=cut
     
    +Set($ShowRemoteImages, 0);
     
     =item C<$PlainTextMono>
     
    -Set C<$PlainTextMono> to 1 to use monospaced font and preserve
    -formatting; unlike C<$PlainTextPre>, the text will wrap to fit width
    -of the browser window; this option overrides 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.  Set C<$PlainTextMono> to 1 to use a monospaced
    +font and preserve formatting.
     
     =cut
     
    @@ -1695,14 +1834,23 @@ provides two formats:
       link after the URL.
     
     * 'httpurl_overwrite': also detects URLs as 'httpurl' format, but
    -  replaces the URL with a link.
    +  replaces the URL with a link.  Enabled by default.
     
     See F for documentation on how to add
     your own styles of link detection.
     
     =cut
     
    -Set(@Active_MakeClicky, qw());
    +Set(@Active_MakeClicky, qw(httpurl_overwrite));
    +
    +=item C<$QuoteFolding>
    +
    +Quote folding is the hiding of old replies in transaction history.
    +It defaults to on.  Set this to 0 to disable it.
    +
    +=cut
    +
    +Set($QuoteFolding, 1);
     
     =back
     
    @@ -1716,9 +1864,11 @@ Set(@Active_MakeClicky, qw());
     
     If C<$ParseNewMessageForTicketCcs> is set to 1, RT will attempt to
     divine Ticket 'Cc' watchers from the To and Cc lines of incoming
    -messages.  Be forewarned that if you have I 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.
    +messages that create new Tickets. This option does not apply to replies
    +or comments on existing Tickets. Be forewarned that if you have I
    +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
     
    @@ -1769,8 +1919,9 @@ Set($ApprovalRejectionNotes, 1);
     =item C<$ForceApprovalsView>
     
     Should approval tickets only be viewed and modified through the standard
    -approval interface?  Changing this setting to 1 will redirect any attempt to
    -use the normal ticket display and modify page for approval tickets.
    +approval interface?  With this setting enabled (by default), any attempt to use
    +the normal ticket display and modify page for approval tickets will be
    +redirected.
     
     For example, with this option set to 1 and an approval ticket #123:
     
    @@ -1780,11 +1931,13 @@ is redirected to
     
         /Approval/Display.html?id=123
     
    +With this option set to 0, the redirect won't happen.
    +
     =back
     
     =cut
     
    -Set($ForceApprovalsView, 0);
    +Set($ForceApprovalsView, 1);
     
     =head1 Extra security
     
    @@ -1865,6 +2018,17 @@ Simple wildcards, similar to SSL certificates, are allowed.  For example:
     
     Set(@ReferrerWhitelist, qw());
     
    +
    +=item C<$BcryptCost>
    +
    +This sets the default cost parameter used for the C key
    +derivation function.  Valid values range from 4 to 31, inclusive, with
    +higher numbers denoting greater effort.
    +
    +=cut
    +
    +Set($BcryptCost, 10);
    +
     =back
     
     
    @@ -1873,74 +2037,90 @@ Set(@ReferrerWhitelist, qw());
     
     =over 4
     
    -=item C<$WebExternalAuth>
    +=item C<$WebRemoteUserAuth>
     
    -If C<$WebExternalAuth> is defined, RT will defer to the environment's
    -REMOTE_USER variable.
    +If C<$WebRemoteUserAuth> is defined, RT will defer to the environment's
    +REMOTE_USER variable, which should be set by the webserver's
    +authentication layer.
     
     =cut
     
    -Set($WebExternalAuth, undef);
    +Set($WebRemoteUserAuth, undef);
     
    -=item C<$WebExternalAuthContinuous>
    +=item C<$WebRemoteUserContinuous>
     
    -If C<$WebExternalAuthContinuous> is defined, RT will check for the
    +If C<$WebRemoteUserContinuous> 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 authentication system
    +setting will help ensure that if your webserver's authentication layer
     deauthenticates a user, RT notices as soon as possible.
     
     =cut
     
    -Set($WebExternalAuthContinuous, 1);
    +Set($WebRemoteUserContinuous, 1);
     
    -=item C<$WebFallbackToInternalAuth>
    +=item C<$WebFallbackToRTLogin>
     
    -If C<$WebFallbackToInternalAuth> is defined, the user is allowed a
    +If C<$WebFallbackToRTLogin> is defined, the user is allowed a
     chance of fallback to the login screen, even if REMOTE_USER failed.
     
     =cut
     
    -Set($WebFallbackToInternalAuth, undef);
    +Set($WebFallbackToRTLogin, undef);
     
    -=item C<$WebExternalGecos>
    +=item C<$WebRemoteUserGecos>
     
    -C<$WebExternalGecos> means to match 'gecos' field as the user
    -identity); useful with mod_auth_pwcheck and IIS Integrated Windows
    +C<$WebRemoteUserGecos> means to match 'gecos' field as the user
    +identity; useful with C and IIS Integrated Windows
     logon.
     
     =cut
     
    -Set($WebExternalGecos, undef);
    +Set($WebRemoteUserGecos, undef);
     
    -=item C<$WebExternalAuto>
    +=item C<$WebRemoteUserAutocreate>
     
    -C<$WebExternalAuto> will create users under the same name as
    -REMOTE_USER upon login, if it's missing in the Users table.
    +C<$WebRemoteUserAutocreate> will create users under the same name as
    +REMOTE_USER upon login, if they are missing from the Users table.
     
     =cut
     
    -Set($WebExternalAuto, undef);
    +Set($WebRemoteUserAutocreate, undef);
     
    -=item C<$AutoCreate>
    +=item C<$UserAutocreateDefaultsOnLogin>
     
    -If C<$WebExternalAuto> is set to 1, C<$AutoCreate> will be passed to
    -User's Create method.  Use it to set defaults, such as creating
    -Unprivileged users with C<{ Privileged => 0 }> This must be a hashref.
    +If C<$WebRemoteUserAutocreate> is set to 1, C<$UserAutocreateDefaultsOnLogin>
    +will be passed to L.  Use it to set defaults, such as
    +creating unprivileged users with C<<{ Privileged => 0 }>>.  This must be
    +a hashref.
     
     =cut
     
    -Set($AutoCreate, undef);
    +Set($UserAutocreateDefaultsOnLogin, undef);
     
     =item C<$WebSessionClass>
     
    -C<$WebSessionClass> is the class you wish to use for managing sessions.
    -It defaults to use your SQL database, except on Oracle, where it
    -defaults to files on disk.
    +C<$WebSessionClass> is the class you wish to use for storing sessions.  On
    +MySQL, Pg, and Oracle it defaults to using your database, in other cases
    +sessions are stored in files using L. Other installed
    +Apache:Session::* modules can be used to store sessions.
    +
    +    Set($WebSessionClass, "Apache::Session::File");
     
     =cut
     
    -# Set($WebSessionClass, "Apache::Session::File");
    +Set($WebSessionClass, undef);
    +
    +=item C<%WebSessionProperties>
    +
    +C<%WebSessionProperties> is the hash to configure class L
    +in case custom class is used. By default it's empty and values are picked
    +depending on the class. Make sure that it's empty if you're using DB as session
    +backend.
    +
    +=cut
    +
    +Set( %WebSessionProperties );
     
     =item C<$AutoLogoff>
     
    @@ -2103,12 +2283,139 @@ minutes.  Note that this only effects entry, not display.
     
     Set($DefaultTimeUnitsToHours, 0);
     
    +=item C<$TimeInICal>
    +
    +By default, events in the iCal feed on the ticket search page
    +contain only dates, making them all day calendar events. Set
    +C<$TimeInICal> if you have start or due dates on tickets that
    +have significant time values and you want those times to be
    +included in the events in the iCal feed.
    +
    +This option can also be set as an individual user preference.
    +
    +=cut
    +
    +Set($TimeInICal, 0);
    +
    +=back
    +
    +
    +
    +=head1 Cryptography
    +
    +A complete description of RT's cryptography capabilities can be found in
    +L. At this moment, GnuPG (PGP) and SMIME security protocols are
    +supported.
    +
    +=over 4
    +
    +=item C<%Crypt>
    +
    +The following options apply to all cryptography protocols.
    +
    +By default, all enabled security protocols will analyze each incoming
    +email. You may set C to a subset of this list, if some enabled
    +protocols do not apply to incoming mail; however, this is usually
    +unnecessary. Note that for any verification or decryption to occur for
    +incoming mail, the C mail plugin must be added to
    +L as specified in L.
    +
    +For outgoing emails, the first security protocol from the above list is
    +used. Use the C option to set a security protocol that should
    +be used in outgoing emails.  At this moment, only one protocol can be
    +used to protect outgoing emails.
    +
    +Set C to true if all incoming email must be
    +properly encrypted.  All unencrypted emails will be rejected by RT.
    +
    +Set C to false if you don't want to reject
    +emails encrypted for key RT doesn't have and can not decrypt.
    +
    +Set C to false if you don't want to reject letters
    +with incorrect data.
    +
    +If you want to allow people to encrypt attachments inside the DB then
    +set C to 1.
    +
    +Set C to a hash with Encrypt and Sign keys to control
    +whether dashboards should be encrypted and/or signed correspondingly.
    +By default they are not encrypted or signed.
    +
     =back
     
    +=cut
    +
    +Set( %Crypt,
    +    Incoming                  => undef, # ['GnuPG', 'SMIME']
    +    Outgoing                  => undef, # 'SMIME'
    +
    +    RejectOnUnencrypted       => 0,
    +    RejectOnMissingPrivateKey => 1,
    +    RejectOnBadData           => 1,
    +
    +    AllowEncryptDataInDB      => 0,
    +
    +    Dashboards => {
    +        Encrypt => 0,
    +        Sign    => 0,
    +    },
    +);
    +
    +=head2 SMIME configuration
    +
    +A full description of the SMIME integration can be found in
    +L.
    +
    +=over 4
    +
    +=item C<%SMIME>
    +
    +Set C to false or true value to disable or enable SMIME for
    +encrypting and signing messages.
    +
    +Set C to path to F executable.
    +
    +Set C to directory with key files.  Key and certificates should
    +be stored in a PEM file in this directory named named, e.g.,
    +F.
     
    +Set C to either a PEM-formatted certificate of a single signing
    +certificate authority, or a directory of such (including hash symlinks
    +as created by the openssl tool C).  Only SMIME certificates
    +signed by these certificate authorities will be treated as valid
    +signatures.  If left unset (and C is unset, as it is
    +by default), no signatures will be marked as valid!
     
    +Set C to allow arbitrary SMIME certificates, no
    +matter their signing entities.  Such mails will be marked as untrusted,
    +but signed; C will be used to mark which mails are signed by
    +trusted certificate authorities.  This configuration is generally
    +insecure, as it allows the possibility of accepting forged mail signed
    +by an untrusted certificate authority.
     
    -=head1 GnuPG integration
    +Setting C also allows encryption to users with
    +certificates created by untrusted CAs.
    +
    +Set C to a scalar (to use for all keys), an anonymous
    +function, or a hash (to look up by address).  If the hash is used, the
    +'' key is used as a default.
    +
    +See L for details.
    +
    +=back
    +
    +=cut
    +
    +Set( %SMIME,
    +    Enable => 0,
    +    OpenSSL => 'openssl',
    +    Keyring => q{var/data/smime},
    +    CAPath => undef,
    +    AcceptUntrustedCAs => undef,
    +    Passphrase => undef,
    +);
    +
    +=head2 GnuPG configuration
     
     A full description of the (somewhat extensive) GnuPG integration can
     be found by running the command `perldoc L` (or
    @@ -2118,27 +2425,25 @@ be found by running the command `perldoc L` (or
     
     =item C<%GnuPG>
     
    -Set C to 'inline' to use inline encryption and
    -signatures instead of 'RFC' (GPG/MIME: RFC3156 and RFC1847) format.
    +Set C to false or true value to disable or enable GnuPG interfaces
    +for encrypting and signing outgoing messages.
     
    -If you want to allow people to encrypt attachments inside the DB then
    -set C to 1.
    +Set C to the name or path of the gpg binary to use.
     
    -Set C to false if you don't want to reject
    -emails encrypted for key RT doesn't have and can not decrypt.
    +Set C to a scalar (to use for all keys), an anonymous
    +function, or a hash (to look up by address).  If the hash is used, the
    +'' key is used as a default.
     
    -Set C to false if you don't want to reject letters
    -with incorrect GnuPG data.
    +Set C to 'inline' to use inline encryption and
    +signatures instead of 'RFC' (GPG/MIME: RFC3156 and RFC1847) format.
     
     =cut
     
     Set(%GnuPG,
    -    Enable => 0,
    +    Enable                 => 0,
    +    GnuPG                  => 'gpg',
    +    Passphrase             => undef,
         OutgoingMessagesFormat => "RFC", # Inline
    -    AllowEncryptDataInDB   => 0,
    -
    -    RejectOnMissingPrivateKey => 1,
    -    RejectOnBadData           => 1,
     );
     
     =item C<%GnuPGOptions>
    @@ -2583,7 +2888,7 @@ Set(%Lifecycles,
     
     RT can show administrators a feed of recent RT releases and other
     related announcements and information from Best Practical on the top
    -level Configuration page.  This feature helps you stay up to date on
    +level Admin page.  This feature helps you stay up to date on
     RT security announcements and version updates.
     
     RT provides this feature using an "iframe" on C
    @@ -2624,22 +2929,17 @@ Set(%AdminSearchResultFormat,
         CustomFields =>
             q{'__id__/TITLE:#'}
             .q{,'__Name__/TITLE:Name'}
    -        .q{,__AppliedTo__, __FriendlyType__, __FriendlyPattern__},
    +        .q{,__AddedTo__, __FriendlyType__, __FriendlyPattern__},
     
         Scrips =>
    -        q{'__id__/TITLE:#'}
    -        .q{,'__Description__/TITLE:Description'}
    -        .q{,__Stage__, __Condition__, __Action__, __Template__},
    -
    -    GlobalScrips =>
    -        q{'__id__/TITLE:#'}
    -        .q{,'__Description__/TITLE:Description'}
    -        .q{,__Stage__, __Condition__, __Action__, __Template__},
    +        q{'__id__/TITLE:#'}
    +        .q{,'__Description__/TITLE:Description'}
    +        .q{,__Condition__, __Action__, __Template__, __Disabled__},
     
         Templates =>
             q{'__id__/TITLE:#'}
             .q{,'__Name__/TITLE:Name'}
    -        .q{,'__Description__'},
    +        .q{,'__Description__','__UsedBy__','__IsEmpty__'},
         Classes =>
             q{ '__id__/TITLE:#'}
             .q{,'__Name__/TITLE:Name'}
    @@ -2695,7 +2995,7 @@ be added while the server is running.
     
     =cut
     
    -Set($DevelMode, "0");
    +Set($DevelMode, 0);
     
     
     =item C<$RecordBaseClass>
    @@ -2731,7 +3031,7 @@ C<$StatementLog> to be the level that you wish SQL statements to be
     logged at.
     
     Enabling this option will also expose the SQL Queries page in the
    -Configuration -> Tools menu for SuperUsers.
    +Admin -> Tools menu for SuperUsers.
     
     =cut
     
    @@ -2739,30 +3039,6 @@ Set($StatementLog, undef);
     
     =back
     
    -
    -
    -
    -=head1 Deprecated options
    -
    -=over 4
    -
    -=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<$ResolveDefaultUpdateType>
    -
    -This option has been deprecated.  You can configure this site-wide
    -with L (see L).
    -
    -=back
    -
     =cut
     
     1;
    diff --git a/etc/acl.Pg b/etc/acl.Pg
    index 9da28db..9cd06ec 100644
    --- a/etc/acl.Pg
    +++ b/etc/acl.Pg
    @@ -23,6 +23,8 @@ sub acl {
             Transactions 
             scrips_id_seq
             Scrips 
    +        objectscrips_id_seq
    +        ObjectScrips
             acl_id_seq
             ACL 
             groupmembers_id_seq
    @@ -64,7 +66,7 @@ sub acl {
         # 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 \"$db_user\" WITH PASSWORD '$db_pass' NOCREATEDB NOCREATEUSER;";
         }
     
         my $sequence_right
    diff --git a/etc/initialdata b/etc/initialdata
    index e688c19..2a4a6e9 100644
    --- a/etc/initialdata
    +++ b/etc/initialdata
    @@ -58,7 +58,10 @@
           Description => 'Sends mail to the administrative Ccs',              # loc
           ExecModule  => 'Notify',
           Argument    => 'AdminCc' },
    -
    +    { Name        => 'Notify Owner and AdminCcs',                         # loc
    +      Description => 'Sends mail to the Owner and administrative Ccs',    # loc
    +      ExecModule  => 'Notify',
    +      Argument    => 'Owner,AdminCc' },
         { Name        => 'Notify Requestors and Ccs as Comment',              # loc
           Description => 'Send mail to requestors and Ccs as a comment',      # loc
           ExecModule  => 'NotifyAsComment',
    @@ -95,9 +98,15 @@
         { Name        => 'Open Tickets',                                      # loc
           Description => 'Open tickets on correspondence',                    # loc
           ExecModule  => 'AutoOpen' },
    +    { Name        => 'Open Inactive Tickets',                             # loc
    +      Description => 'Open inactive tickets',                             # loc
    +      ExecModule  => 'AutoOpenInactive' },
         { Name        => 'Extract Subject Tag',                               # loc
           Description => 'Extract tags from a Transaction\'s subject and add them to the Ticket\'s subject.', # loc
           ExecModule  => 'ExtractSubjectTag' },
    +    { Name        => 'Send Forward',                 # loc
    +      Description => 'Send forwarded message',       # loc
    +      ExecModule  => 'SendForward', },
     );
     
     @ScripConditions = (
    @@ -216,7 +225,7 @@
           Content     => '', },
         {  Queue       => '0',
            Name        => 'Autoreply',                                         # loc
    -       Description => 'Default Autoresponse template',                     # loc
    +       Description => 'Plain text Autoresponse template',                     # loc
            Content     => 'Subject: AutoReply: {$Ticket->Subject}
     
     
    @@ -224,7 +233,7 @@ Greetings,
     
     This message has been automatically generated in response to the
     creation of a trouble ticket regarding:
    -	"{$Ticket->Subject()}", 
    +        "{$Ticket->Subject()}", 
     a summary of which appears below.
     
     There is no need to reply to this message right now.  Your ticket has been
    @@ -244,10 +253,35 @@ you may reply to this message.
     {$Transaction->Content()}
     '
         },
    +    {  Queue       => '0',
    +       Name        => 'Autoreply in HTML',                              # loc
    +       Description => 'HTML Autoresponse template',                     # loc
    +       Content     => q[Subject: AutoReply: {$Ticket->Subject}
    +Content-Type: text/html
    +
    +

    Greetings,

    + +

    This message has been automatically generated in response to the +creation of a trouble ticket regarding {$Ticket->Subject()}, +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 {$Ticket->SubjectTag}.

    + +

    Please include the string {$Ticket->SubjectTag} +in the subject line of all future correspondence about this issue. To do so, +you may reply to this message.

    + +

    Thank you,
    +{$Ticket->QueueObj->CorrespondAddress()}

    + +
    +{$Transaction->Content(Type => 'text/html')} +], + }, { Queue => '0', Name => 'Transaction', # loc - Description => 'Default transaction template', # loc + Description => 'Plain text transaction template', # loc Content => 'RT-Attach-Message: yes @@ -264,12 +298,43 @@ you may reply to this message. {$Transaction->Content()} ' }, - + { Queue => '0', + Name => 'Transaction in HTML', # loc + Description => 'HTML transaction template', # loc + Content => 'RT-Attach-Message: yes +Content-Type: text/html + +{$Transaction->CreatedAsString}: Request id}">{$Ticket->id} was acted upon by {$Transaction->CreatorObj->Name}. +
    + + + + + + + + +
    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 URL:id}">{RT->Config->Get("WebURL")}Ticket/Display.html?id={$Ticket->id}
    +
    +
    +{$Transaction->Content( Type => "text/html")} +' + }, + # Shadow the global templates of the same name to suppress duplicate + # notifications until rules is ripped out. + { Queue => "___Approvals", + Name => "Transaction in HTML", + Content => "", + }, + { Queue => "___Approvals", + Name => "Transaction", + Content => "", + }, { Queue => '0', Name => 'Admin Correspondence', # loc - Description => 'Default admin correspondence template', # loc + Description => 'Plain text admin correspondence template', # loc Content => 'RT-Attach-Message: yes @@ -278,19 +343,38 @@ you may reply to this message. {$Transaction->Content()} ' }, + { Queue => '0', + Name => 'Admin Correspondence in HTML', # loc + Description => 'HTML admin correspondence template', # loc + Content => 'RT-Attach-Message: yes +Content-Type: text/html +Ticket URL: id}">{RT->Config->Get("WebURL")}Ticket/Display.html?id={$Ticket->id} +
    +
    +{$Transaction->Content(Type => "text/html");} +' + }, { Queue => '0', Name => 'Correspondence', # loc - Description => 'Default correspondence template', # loc + Description => 'Plain text correspondence template', # loc Content => 'RT-Attach-Message: yes {$Transaction->Content()} ' }, + { Queue => '0', + Name => 'Correspondence in HTML', # loc + Description => 'HTML correspondence template', # loc + Content => 'RT-Attach-Message: yes +Content-Type: text/html +{$Transaction->Content( Type => "text/html")} +' + }, { Queue => '0', Name => 'Admin Comment', # loc - Description => 'Default admin comment template', # loc + Description => 'Plain text admin comment template', # loc Content => 'Subject: [Comment] {my $s=($Transaction->Subject||$Ticket->Subject||""); $s =~ s/\\[Comment\\]\\s*//g; $s =~ s/^Re:\\s*//i; $s;} RT-Attach-Message: yes @@ -300,6 +384,30 @@ RT-Attach-Message: yes This is a comment. It is not sent to the Requestor(s): {$Transaction->Content()} +' + }, + { Queue => '0', + Name => 'Admin Comment in HTML', # loc + Description => 'HTML admin comment template', # loc + Content => +'Subject: [Comment] {my $s=($Transaction->Subject||$Ticket->Subject||""); $s =~ s/\\[Comment\\]\\s*//g; $s =~ s/^Re:\\s*//i; $s;} +RT-Attach-Message: yes +Content-Type: text/html + +

    This is a comment about id}">ticket {$Ticket->id}. It is not sent to the Requestor(s):

    + +{$Transaction->Content(Type => "text/html")} +' + }, + { Queue => '0', + Name => 'Reminder', # loc + Description => 'Default reminder template', # loc + Content => +'Subject:{$Ticket->Subject} is due {$Ticket->DueObj->AsString} + +This reminder is for ticket #{$Target = $Ticket->RefersTo->First->TargetObj;$Target->Id}. + +{RT->Config->Get(\'WebURL\')}Ticket/Display.html?id={$Target->Id} ' }, @@ -314,7 +422,18 @@ This is a comment. It is not sent to the Requestor(s): {$Transaction->Content()} ' }, + { Queue => '0', + Name => 'Status Change in HTML', # loc + Description => 'HTML Ticket status changed', # loc + Content => 'Subject: Status Changed to: {$Transaction->NewValue} +Content-Type: text/html +id}">{RT->Config->Get("WebURL")}Ticket/Display.html?id={$Ticket->id} +
    +
    +{$Transaction->Content(Type => "text/html")} +' + }, { Queue => '0', @@ -324,6 +443,15 @@ This is a comment. It is not sent to the Requestor(s): According to our records, your request has been resolved. If you have any further questions or concerns, please respond to this message. +' + }, + { Queue => '0', + Name => 'Resolved in HTML', # loc + Description => 'HTML Ticket Resolved', # loc + Content => 'Subject: Resolved: {$Ticket->Subject} +Content-Type: text/html + +

    According to our records, your request has been resolved. If you have any further questions or concerns, please respond to this message.

    ' }, { Queue => '___Approvals', @@ -343,6 +471,25 @@ batch-process all your pending approvals. ------------------------------------------------------------------------- {$Transaction->Content()} +' + }, + { Queue => '___Approvals', + Name => "New Pending Approval in HTML", # loc + Description => "Notify Owners and AdminCcs of new items pending their approval", # loc + Content => 'Subject: New Pending Approval: {$Ticket->Subject} +Content-Type: text/html + +

    Greetings,

    + +

    There is a new item pending your approval: {$Ticket->Subject()}, +a summary of which appears below.

    + +

    Please approve +or reject this ticket, or visit the approvals +overview to batch-process all your pending approvals.

    + +
    +{$Transaction->Content()} ' }, { Queue => '___Approvals', @@ -357,6 +504,22 @@ Your ticket has been approved by { eval { $Approver->Name } }. Other approvals may be pending. Approver\'s notes: { $Notes } +' + }, + { Queue => '___Approvals', + Name => "Approval Passed in HTML", # loc + Description => + "Notify Requestor of their ticket has been approved by some approver", # loc + Content => 'Subject: Ticket Approved: {$Ticket->Subject} +Content-Type: text/html + +

    Greetings,

    + +

    Your ticket has been approved by { eval { $Approver->Name } }. +Other approvals may be pending.

    + +

    Approver\'s notes:

    +
    { $Notes }
    ' }, { Queue => '___Approvals', @@ -371,6 +534,22 @@ Your ticket has been approved by { eval { $Approver->Name } }. Its Owner may now start to act on it. Approver\'s notes: { $Notes } +' + }, + { Queue => '___Approvals', + Name => "All Approvals Passed in HTML", # loc + Description => + "Notify Requestor of their ticket has been approved by all approvers", # loc + Content => 'Subject: Ticket Approved: {$Ticket->Subject} +Content-Type: text/html + +

    Greetings,

    + +

    Your ticket has been approved by { eval { $Approver->Name } }. +Its Owner may now start to act on it.

    + +

    Approver\'s notes:

    +
    { $Notes }
    ' }, { Queue => '___Approvals', @@ -384,6 +563,21 @@ Greetings, Your ticket has been rejected by { eval { $Approver->Name } }. Approver\'s notes: { $Notes } +' + }, + { Queue => '___Approvals', + Name => "Approval Rejected in HTML", # loc + Description => + "Notify Owner of their rejected ticket", # loc + Content => 'Subject: Ticket Rejected: {$Ticket->Subject} +Content-Type: text/html + +

    Greetings,

    + +

    Your ticket has been rejected by { eval { $Approver->Name } }.

    + +

    Approver\'s notes:

    +
    { $Notes }
    ' }, { Queue => '___Approvals', @@ -396,21 +590,44 @@ Greetings, The ticket has been approved, you may now start to act on it. +' + }, + { Queue => '___Approvals', + Name => "Approval Ready for Owner in HTML", # loc + Description => + "Notify Owner of their ticket has been approved and is ready to be acted on", # loc + Content => 'Subject: Ticket Approved: {$Ticket->Subject} +Content-Type: text/html + +

    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 + Description => "Forwarded message", # loc Content => q{ -This is a forward of transaction #{$Transaction->id} of ticket #{ $Ticket->id } + +{ $ForwardTransaction->Content =~ /\S/ ? $ForwardTransaction->Content : "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 + Description => "Forwarded ticket message", # loc Content => q{ -This is a forward of ticket #{ $Ticket->id } +{ $ForwardTransaction->Content =~ /\S/ ? $ForwardTransaction->Content : "This is a forward of ticket #". $Ticket->id } +} + }, + { Queue => 0, + Name => "Error: unencrypted message", # loc + Description => + "Inform user that their unencrypted mail has been rejected", # loc + Content => q{Subject: RT requires that all incoming mail be encrypted + +You received this message because RT received mail from you that was not encrypted. As such, it has been rejected. } }, { Queue => 0, @@ -448,12 +665,12 @@ Please, check that you encrypt messages with correct keys or contact the system administrator.} }, { Queue => 0, - Name => "Error: bad GnuPG data", # loc + Name => "Error: bad encrypted data", # loc Description => - "Inform user that a message he sent has invalid GnuPG data", # loc + "Inform user that a message he sent has invalid encryption 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): +You sent us a message that we cannot handle due to corrupted signature or encrypted block. we get the following error(s): { foreach my $msg ( @Messages ) { $OUT .= "* $msg\n"; } @@ -474,10 +691,10 @@ Your new password is: } }, - { Queue => '0', - Name => 'Email Digest', # loc - Description => 'Email template for periodic notification digests', # loc - Content => q[Subject: RT Email Digest + { Queue => '0', + Name => 'Email Digest', # loc + Description => 'Email template for periodic notification digests', # loc + Content => q[Subject: RT Email Digest { $Argument } ], @@ -511,50 +728,66 @@ Hour: { $SubscriptionObj->SubValue('Hour') } ); @Scrips = ( - { Description => 'On Correspond Open Tickets', + { Description => 'On Comment Notify AdminCcs as Comment', + ScripCondition => 'On Comment', + ScripAction => 'Notify AdminCcs As Comment', + Template => 'Admin Comment in HTML' }, + { Description => 'On Comment Notify Other Recipients as Comment', + ScripCondition => 'On Comment', + ScripAction => 'Notify Other Recipients As Comment', + Template => 'Correspondence in HTML' }, + { Description => 'On Correspond Notify Owner and AdminCcs', ScripCondition => 'On Correspond', - ScripAction => 'Open Tickets', - Template => 'Blank' }, - { Description => 'On Owner Change Notify Owner', - ScripCondition => 'On Owner Change', - ScripAction => 'Notify Owner', - Template => 'Transaction' }, - { Description => 'On Create Autoreply To Requestors', - ScripCondition => 'On Create', - ScripAction => 'AutoReply To Requestors', - Template => 'AutoReply' }, - { Description => 'On Create Notify AdminCcs', - ScripCondition => 'On Create', - ScripAction => 'Notify AdminCcs', - Template => 'Transaction' }, - { Description => 'On Correspond Notify AdminCcs', + ScripAction => 'Notify Owner and AdminCcs', + Template => 'Admin Correspondence in HTML' }, + { Description => 'On Correspond Notify Other Recipients', ScripCondition => 'On Correspond', - ScripAction => 'Notify AdminCcs', - Template => 'Admin Correspondence' }, + ScripAction => 'Notify Other Recipients', + Template => 'Correspondence in HTML' }, { Description => 'On Correspond Notify Requestors and Ccs', ScripCondition => 'On Correspond', ScripAction => 'Notify Requestors And Ccs', - Template => 'Correspondence' }, - { Description => 'On Correspond Notify Other Recipients', + Template => 'Correspondence in HTML' }, + { Description => 'On Correspond Open Inactive Tickets', ScripCondition => 'On Correspond', + ScripAction => 'Open Inactive Tickets', + Template => 'Blank' }, + { Description => 'On Create Autoreply To Requestors', + ScripCondition => 'On Create', + ScripAction => 'AutoReply To Requestors', + Template => 'AutoReply in HTML' }, + { Description => 'On Create Notify Owner and AdminCcs', + ScripCondition => 'On Create', + ScripAction => 'Notify Owner and AdminCcs', + Template => 'Transaction in HTML' }, + { Description => 'On Create Notify Ccs', + ScripCondition => 'On Create', + ScripAction => 'Notify Ccs', + Template => 'Correspondence in HTML' }, + { Description => 'On Create Notify Other Recipients', + ScripCondition => 'On Create', ScripAction => 'Notify Other Recipients', - Template => 'Correspondence' }, - { Description => 'On Comment Notify AdminCcs as Comment', - ScripCondition => 'On Comment', - ScripAction => 'Notify AdminCcs As Comment', - Template => 'Admin Comment' }, - { Description => 'On Comment Notify Other Recipients as Comment', - ScripCondition => 'On Comment', - ScripAction => 'Notify Other Recipients As Comment', - Template => 'Correspondence' }, + Template => 'Correspondence in HTML' }, + { Description => 'On Owner Change Notify Owner', + ScripCondition => 'On Owner Change', + ScripAction => 'Notify Owner', + Template => 'Transaction in HTML' }, { Description => 'On Resolve Notify Requestors', ScripCondition => 'On Resolve', ScripAction => 'Notify Requestors', - Template => 'Resolved' }, + Template => 'Resolved in HTML' }, { 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' }, + { Description => 'On Forward Transaction Send forwarded message', + ScripCondition => 'On Forward Transaction', + ScripAction => 'Send Forward', + Template => 'Forward' }, + { Description => 'On Forward Ticket Send forwarded message', + ScripCondition => 'On Forward Ticket', + ScripAction => 'Send Forward', + Template => 'Forward Ticket' }, ); @ACL = ( @@ -627,7 +860,7 @@ Hour: { $SubscriptionObj->SubValue('Hour') } name => 'QuickCreate' # loc }, ], - 'summary' => # loc + 'sidebar' => # loc [ { type => 'component', diff --git a/etc/schema.Oracle b/etc/schema.Oracle index 4bcae6c..effefc5 100644 --- a/etc/schema.Oracle +++ b/etc/schema.Oracle @@ -1,19 +1,19 @@ CREATE SEQUENCE ATTACHMENTS_seq; CREATE TABLE Attachments ( - id NUMBER(11,0) - CONSTRAINT Attachments_Key PRIMARY KEY, - TransactionId NUMBER(11,0) NOT NULL, - Parent NUMBER(11,0) DEFAULT 0 NOT NULL, - MessageId VARCHAR2(160), - Subject VARCHAR2(255), - Filename VARCHAR2(255), - ContentType VARCHAR2(80), - ContentEncoding VARCHAR2(80), - Content CLOB, - Headers CLOB, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE + id NUMBER(11,0) + CONSTRAINT Attachments_Key PRIMARY KEY, + TransactionId NUMBER(11,0) NOT NULL, + Parent NUMBER(11,0) DEFAULT 0 NOT NULL, + MessageId VARCHAR2(160), + Subject VARCHAR2(255), + Filename VARCHAR2(255), + ContentType VARCHAR2(80), + ContentEncoding VARCHAR2(80), + Content CLOB, + Headers CLOB, + Creator NUMBER(11,0) DEFAULT 0 NOT NULL, + Created DATE ); CREATE INDEX Attachments2 ON Attachments (TransactionId); CREATE INDEX Attachments3 ON Attachments (Parent, TransactionId); @@ -21,40 +21,40 @@ CREATE INDEX Attachments3 ON Attachments (Parent, TransactionId); CREATE SEQUENCE QUEUES_seq; CREATE TABLE Queues ( - id NUMBER(11,0) - CONSTRAINT Queues_Key PRIMARY KEY, - Name VARCHAR2(200) CONSTRAINT Queues_Name_Unique UNIQUE NOT NULL, - Description VARCHAR2(255), - CorrespondAddress VARCHAR2(120), - CommentAddress VARCHAR2(120), - Lifecycle VARCHAR2(32), - SubjectTag VARCHAR2(120), - InitialPriority NUMBER(11,0) DEFAULT 0 NOT NULL, - FinalPriority NUMBER(11,0) DEFAULT 0 NOT NULL, - DefaultDueIn NUMBER(11,0) DEFAULT 0 NOT NULL, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE, - Disabled NUMBER(11,0) DEFAULT 0 NOT NULL + id NUMBER(11,0) + CONSTRAINT Queues_Key PRIMARY KEY, + Name VARCHAR2(200) CONSTRAINT Queues_Name_Unique UNIQUE NOT NULL, + Description VARCHAR2(255), + CorrespondAddress VARCHAR2(120), + CommentAddress VARCHAR2(120), + Lifecycle VARCHAR2(32), + SubjectTag VARCHAR2(120), + InitialPriority NUMBER(11,0) DEFAULT 0 NOT NULL, + FinalPriority NUMBER(11,0) DEFAULT 0 NOT NULL, + DefaultDueIn NUMBER(11,0) DEFAULT 0 NOT NULL, + Creator NUMBER(11,0) DEFAULT 0 NOT NULL, + Created DATE, + LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, + LastUpdated DATE, + Disabled NUMBER(11,0) DEFAULT 0 NOT NULL ); - CREATE INDEX Queues1 ON Queues (LOWER(Name)); +CREATE UNIQUE INDEX Queues1 ON Queues (LOWER(Name)); CREATE INDEX Queues2 ON Queues (Disabled); CREATE SEQUENCE LINKS_seq; CREATE TABLE Links ( - id NUMBER(11,0) - CONSTRAINT Links_Key PRIMARY KEY, - Base VARCHAR2(240), - Target VARCHAR2(240), - Type VARCHAR2(20) NOT NULL, - LocalTarget NUMBER(11,0) DEFAULT 0 NOT NULL, - LocalBase NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE + id NUMBER(11,0) + CONSTRAINT Links_Key PRIMARY KEY, + Base VARCHAR2(240), + Target VARCHAR2(240), + Type VARCHAR2(20) NOT NULL, + LocalTarget NUMBER(11,0) DEFAULT 0 NOT NULL, + LocalBase NUMBER(11,0) DEFAULT 0 NOT NULL, + LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, + LastUpdated DATE, + Creator NUMBER(11,0) DEFAULT 0 NOT NULL, + Created DATE ); CREATE UNIQUE INDEX Links1 ON Links (Base, Target, Type); CREATE INDEX Links2 ON Links (Base, Type); @@ -64,182 +64,194 @@ CREATE INDEX Links4 ON Links(Type,LocalBase); CREATE SEQUENCE PRINCIPALS_seq; CREATE TABLE Principals ( - id NUMBER(11,0) - CONSTRAINT Principals_Key PRIMARY KEY, - PrincipalType VARCHAR2(16), - ObjectId NUMBER(11,0), - Disabled NUMBER(11,0) DEFAULT 0 NOT NULL + id NUMBER(11,0) + CONSTRAINT Principals_Key PRIMARY KEY, + PrincipalType VARCHAR2(16), + ObjectId NUMBER(11,0), + Disabled NUMBER(11,0) DEFAULT 0 NOT NULL ); CREATE UNIQUE INDEX Principals2 ON Principals (ObjectId); CREATE SEQUENCE GROUPS_seq; CREATE TABLE Groups ( - id NUMBER(11,0) - CONSTRAINT Groups_Key PRIMARY KEY, - Name VARCHAR2(200), - Description VARCHAR2(255), - Domain VARCHAR2(64), - Type VARCHAR2(64), - Instance NUMBER(11,0) DEFAULT 0, -- NOT NULL - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE --- Instance VARCHAR2(64) + id NUMBER(11,0) + CONSTRAINT Groups_Key PRIMARY KEY, + Name VARCHAR2(200), + Description VARCHAR2(255), + Domain VARCHAR2(64), + Type VARCHAR2(64), + Instance NUMBER(11,0) DEFAULT 0, -- NOT NULL + Creator NUMBER(11,0) DEFAULT 0 NOT NULL, + Created DATE, + LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, + LastUpdated DATE +-- Instance VARCHAR2(64) ); -CREATE INDEX Groups1 ON Groups (LOWER(Domain), Instance, LOWER(Type), id); -CREATE INDEX Groups2 ON Groups (LOWER(Type), Instance, LOWER(Domain)); +CREATE INDEX Groups1 ON Groups (LOWER(Domain), LOWER(Type), Instance); +CREATE INDEX Groups2 ON Groups (LOWER(Domain), LOWER(Name), Instance); +CREATE INDEX Groups3 ON Groups (Instance); CREATE SEQUENCE SCRIPCONDITIONS_seq; CREATE TABLE ScripConditions ( - id NUMBER(11, 0) - CONSTRAINT ScripConditions_Key PRIMARY KEY, - Name VARCHAR2(200), - Description VARCHAR2(255), - ExecModule VARCHAR2(60), - Argument VARCHAR2(255), - ApplicableTransTypes VARCHAR2(60), - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE + id NUMBER(11, 0) + CONSTRAINT ScripConditions_Key PRIMARY KEY, + Name VARCHAR2(200), + Description VARCHAR2(255), + ExecModule VARCHAR2(60), + Argument VARCHAR2(255), + ApplicableTransTypes VARCHAR2(60), + Creator NUMBER(11,0) DEFAULT 0 NOT NULL, + Created DATE, + LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, + LastUpdated DATE ); CREATE SEQUENCE TRANSACTIONS_seq; CREATE TABLE Transactions ( - id NUMBER(11,0) - CONSTRAINT Transactions_Key PRIMARY KEY, - ObjectType VARCHAR2(255), - ObjectId NUMBER(11,0) DEFAULT 0 NOT NULL, - TimeTaken NUMBER(11,0) DEFAULT 0 NOT NULL, - Type VARCHAR2(20), - Field VARCHAR2(40), - OldValue VARCHAR2(255), - NewValue VARCHAR2(255), - ReferenceType VARCHAR2(255), - OldReference NUMBER(11,0), - NewReference NUMBER(11,0), - Data VARCHAR2(255), - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE + id NUMBER(11,0) + CONSTRAINT Transactions_Key PRIMARY KEY, + ObjectType VARCHAR2(255), + ObjectId NUMBER(11,0) DEFAULT 0 NOT NULL, + TimeTaken NUMBER(11,0) DEFAULT 0 NOT NULL, + Type VARCHAR2(20), + Field VARCHAR2(40), + OldValue VARCHAR2(255), + NewValue VARCHAR2(255), + ReferenceType VARCHAR2(255), + OldReference NUMBER(11,0), + NewReference NUMBER(11,0), + Data VARCHAR2(255), + Creator NUMBER(11,0) DEFAULT 0 NOT NULL, + Created DATE ); CREATE INDEX Transactions1 ON Transactions (ObjectType, ObjectId); CREATE SEQUENCE SCRIPS_seq; CREATE TABLE Scrips ( - id NUMBER(11,0) - CONSTRAINT Scrips_Key PRIMARY KEY, - Description VARCHAR2(255), - ScripCondition NUMBER(11,0) DEFAULT 0 NOT NULL, - ScripAction NUMBER(11,0) DEFAULT 0 NOT NULL, - ConditionRules CLOB, - ActionRules CLOB, - CustomIsApplicableCode CLOB, - CustomPrepareCode CLOB, - CustomCommitCode CLOB, - Stage VARCHAR2(32), - Queue NUMBER(11,0) DEFAULT 0 NOT NULL, - Template NUMBER(11,0) DEFAULT 0 NOT NULL, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE + id NUMBER(11,0) + CONSTRAINT Scrips_Key PRIMARY KEY, + Description VARCHAR2(255), + ScripCondition NUMBER(11,0) DEFAULT 0 NOT NULL, + ScripAction NUMBER(11,0) DEFAULT 0 NOT NULL, + CustomIsApplicableCode CLOB, + CustomPrepareCode CLOB, + CustomCommitCode CLOB, + Disabled NUMBER(11,0) DEFAULT 0 NOT NULL, + Template VARCHAR2(200) NOT NULL, + Creator NUMBER(11,0) DEFAULT 0 NOT NULL, + Created DATE, + LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, + LastUpdated DATE ); +CREATE SEQUENCE OBJECTSCRIPS_seq; +CREATE TABLE ObjectScrips ( + id NUMBER(11,0) + CONSTRAINT ObjectScrips_Key PRIMARY KEY, + Scrip NUMBER(11,0) NOT NULL, + Stage VARCHAR2(32) DEFAULT 'TransactionCreate' NOT NULL, + ObjectId NUMBER(11,0) NOT NULL, + SortOrder NUMBER(11,0) DEFAULT 0 NOT NULL, + Creator NUMBER(11,0) DEFAULT 0 NOT NULL, + Created DATE, + LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, + LastUpdated DATE +); +CREATE UNIQUE INDEX ObjectScrips1 ON ObjectScrips (ObjectId, Scrip); CREATE SEQUENCE ACL_seq; CREATE TABLE ACL ( - id NUMBER(11,0) - CONSTRAINT ACL_Key PRIMARY KEY, - PrincipalType VARCHAR2(25) NOT NULL, - PrincipalId NUMBER(11,0) NOT NULL, - RightName VARCHAR2(25) NOT NULL, - ObjectType VARCHAR2(25) NOT NULL, - ObjectId NUMBER(11,0) DEFAULT 0 NOT NULL, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE + id NUMBER(11,0) + CONSTRAINT ACL_Key PRIMARY KEY, + PrincipalType VARCHAR2(25) NOT NULL, + PrincipalId NUMBER(11,0) NOT NULL, + RightName VARCHAR2(25) NOT NULL, + ObjectType VARCHAR2(25) NOT NULL, + ObjectId NUMBER(11,0) DEFAULT 0 NOT NULL, + Creator NUMBER(11,0) DEFAULT 0 NOT NULL, + Created DATE, + LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, + LastUpdated DATE ); CREATE INDEX ACL1 ON ACL(RightName, ObjectType, ObjectId, PrincipalType, PrincipalId); CREATE SEQUENCE GROUPMEMBERS_seq; CREATE TABLE GroupMembers ( - id NUMBER(11,0) - CONSTRAINT GroupMembers_Key PRIMARY KEY, - GroupId NUMBER(11,0) DEFAULT 0 NOT NULL, - MemberId NUMBER(11,0) DEFAULT 0 NOT NULL, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE + id NUMBER(11,0) + CONSTRAINT GroupMembers_Key PRIMARY KEY, + GroupId NUMBER(11,0) DEFAULT 0 NOT NULL, + MemberId NUMBER(11,0) DEFAULT 0 NOT NULL, + Creator NUMBER(11,0) DEFAULT 0 NOT NULL, + Created DATE, + LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, + LastUpdated DATE ); CREATE UNIQUE INDEX GroupMembers1 ON GroupMembers (GroupId, MemberId); CREATE SEQUENCE CachedGroupMembers_seq; CREATE TABLE CachedGroupMembers ( - id NUMBER(11,0) - CONSTRAINT CachedGroupMembers_Key PRIMARY KEY, - GroupId NUMBER(11,0), - MemberId NUMBER(11,0), - Via NUMBER(11,0), - ImmediateParentId NUMBER(11,0), - Disabled NUMBER(11,0) DEFAULT 0 NOT NULL + id NUMBER(11,0) + CONSTRAINT CachedGroupMembers_Key PRIMARY KEY, + GroupId NUMBER(11,0), + MemberId NUMBER(11,0), + Via NUMBER(11,0), + ImmediateParentId NUMBER(11,0), + Disabled NUMBER(11,0) DEFAULT 0 NOT NULL ); CREATE INDEX DisGrouMem ON CachedGroupMembers (GroupId, MemberId, Disabled); -CREATE INDEX GrouMem ON CachedGroupMembers (GroupId, MemberId); +CREATE INDEX CachedGroupMembers2 ON CachedGroupMembers (MemberId, GroupId, Disabled); CREATE INDEX CachedGroupMembers3 on CachedGroupMembers (MemberId, ImmediateParentId); CREATE SEQUENCE USERS_seq; CREATE TABLE Users ( - id NUMBER(11,0) - CONSTRAINT Users_Key PRIMARY KEY, - Name VARCHAR2(200) CONSTRAINT Users_Name_Unique - unique NOT NULL, - Password VARCHAR2(256), - AuthToken VARCHAR2(16), - Comments CLOB, - Signature CLOB, - EmailAddress VARCHAR2(120), - FreeFormContactInfo CLOB, - Organization VARCHAR2(200), - RealName VARCHAR2(120), - NickName VARCHAR2(16), - Lang VARCHAR2(16), - EmailEncoding VARCHAR2(16), - WebEncoding VARCHAR2(16), - ExternalContactInfoId VARCHAR2(100), - ContactInfoSystem VARCHAR2(30), - ExternalAuthId VARCHAR2(100), - AuthSystem VARCHAR2(30), - Gecos VARCHAR2(16), - HomePhone VARCHAR2(30), - WorkPhone VARCHAR2(30), - MobilePhone VARCHAR2(30), - PagerPhone VARCHAR2(30), - Address1 VARCHAR2(200), - Address2 VARCHAR2(200), - City VARCHAR2(100), - State VARCHAR2(100), - Zip VARCHAR2(16), - Country VARCHAR2(50), - Timezone VARCHAR2(50), - PGPKey CLOB, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE + id NUMBER(11,0) + CONSTRAINT Users_Key PRIMARY KEY, + Name VARCHAR2(200) CONSTRAINT Users_Name_Unique + unique NOT NULL, + Password VARCHAR2(256), + AuthToken VARCHAR2(16), + Comments CLOB, + Signature CLOB, + EmailAddress VARCHAR2(120), + FreeFormContactInfo CLOB, + Organization VARCHAR2(200), + RealName VARCHAR2(120), + NickName VARCHAR2(16), + Lang VARCHAR2(16), + EmailEncoding VARCHAR2(16), + WebEncoding VARCHAR2(16), + ExternalContactInfoId VARCHAR2(100), + ContactInfoSystem VARCHAR2(30), + ExternalAuthId VARCHAR2(100), + AuthSystem VARCHAR2(30), + Gecos VARCHAR2(16), + HomePhone VARCHAR2(30), + WorkPhone VARCHAR2(30), + MobilePhone VARCHAR2(30), + PagerPhone VARCHAR2(30), + Address1 VARCHAR2(200), + Address2 VARCHAR2(200), + City VARCHAR2(100), + State VARCHAR2(100), + Zip VARCHAR2(16), + Country VARCHAR2(50), + Timezone VARCHAR2(50), + PGPKey CLOB, + SMIMECertificate CLOB, + Creator NUMBER(11,0) DEFAULT 0 NOT NULL, + Created DATE, + LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, + LastUpdated DATE ); --- CREATE UNIQUE INDEX Users1 ON Users (Name); -CREATE INDEX Users2 ON Users( LOWER(Name)); +CREATE UNIQUE INDEX Users1 ON Users (LOWER(Name)); CREATE INDEX Users4 ON Users (LOWER(EmailAddress)); @@ -247,102 +259,99 @@ CREATE SEQUENCE TICKETS_seq; CREATE TABLE Tickets ( id NUMBER(11, 0) CONSTRAINT Tickets_Key PRIMARY KEY, - EffectiveId NUMBER(11,0) DEFAULT 0 NOT NULL, - Queue NUMBER(11,0) DEFAULT 0 NOT NULL, - Type VARCHAR2(16), - IssueStatement NUMBER(11,0) DEFAULT 0 NOT NULL, - Resolution NUMBER(11,0) DEFAULT 0 NOT NULL, - Owner NUMBER(11,0) DEFAULT 0 NOT NULL, - Subject VARCHAR2(200) DEFAULT '[no subject]', - InitialPriority NUMBER(11,0) DEFAULT 0 NOT NULL, - FinalPriority NUMBER(11,0) DEFAULT 0 NOT NULL, - Priority NUMBER(11,0) DEFAULT 0 NOT NULL, - TimeEstimated NUMBER(11,0) DEFAULT 0 NOT NULL, - TimeWorked NUMBER(11,0) DEFAULT 0 NOT NULL, - Status VARCHAR2(64), - TimeLeft NUMBER(11,0) DEFAULT 0 NOT NULL, - Told DATE, - Starts DATE, - Started DATE, - Due DATE, - Resolved DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - Disabled NUMBER(11,0) DEFAULT 0 NOT NULL + EffectiveId NUMBER(11,0) DEFAULT 0 NOT NULL, + IsMerged NUMBER(11,0) DEFAULT NULL NULL, + Queue NUMBER(11,0) DEFAULT 0 NOT NULL, + Type VARCHAR2(16), + IssueStatement NUMBER(11,0) DEFAULT 0 NOT NULL, + Resolution NUMBER(11,0) DEFAULT 0 NOT NULL, + Owner NUMBER(11,0) DEFAULT 0 NOT NULL, + Subject VARCHAR2(200) DEFAULT '[no subject]', + InitialPriority NUMBER(11,0) DEFAULT 0 NOT NULL, + FinalPriority NUMBER(11,0) DEFAULT 0 NOT NULL, + Priority NUMBER(11,0) DEFAULT 0 NOT NULL, + TimeEstimated NUMBER(11,0) DEFAULT 0 NOT NULL, + TimeWorked NUMBER(11,0) DEFAULT 0 NOT NULL, + Status VARCHAR2(64), + TimeLeft NUMBER(11,0) DEFAULT 0 NOT NULL, + Told DATE, + Starts DATE, + Started DATE, + Due DATE, + Resolved DATE, + LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, + LastUpdated DATE, + Creator NUMBER(11,0) DEFAULT 0 NOT NULL, + Created DATE, + Disabled NUMBER(11,0) DEFAULT 0 NOT NULL ); CREATE INDEX Tickets1 ON Tickets (Queue, Status); CREATE INDEX Tickets2 ON Tickets (Owner); -CREATE INDEX Tickets4 ON Tickets (id, Status); -CREATE INDEX Tickets5 ON Tickets (id, EffectiveId); CREATE INDEX Tickets6 ON Tickets (EffectiveId, Type); CREATE SEQUENCE SCRIPACTIONS_seq; CREATE TABLE ScripActions ( - id NUMBER(11,0) - CONSTRAINT ScripActions_Key PRIMARY KEY, - Name VARCHAR2(200), - Description VARCHAR2(255), - ExecModule VARCHAR2(60), - Argument VARCHAR2(255), - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE + id NUMBER(11,0) + CONSTRAINT ScripActions_Key PRIMARY KEY, + Name VARCHAR2(200), + Description VARCHAR2(255), + ExecModule VARCHAR2(60), + Argument VARCHAR2(255), + Creator NUMBER(11,0) DEFAULT 0 NOT NULL, + Created DATE, + LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, + LastUpdated DATE ); CREATE SEQUENCE TEMPLATES_seq; CREATE TABLE Templates ( - id NUMBER(11,0) - CONSTRAINT Templates_Key PRIMARY KEY, - Queue NUMBER(11,0) DEFAULT 0 NOT NULL, - Name VARCHAR2(200) NOT NULL, - Description VARCHAR2(255), - Type VARCHAR2(16), - Language VARCHAR2(16), - TranslationOf NUMBER(11,0) DEFAULT 0 NOT NULL, - Content CLOB, - LastUpdated DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE + id NUMBER(11,0) + CONSTRAINT Templates_Key PRIMARY KEY, + Queue NUMBER(11,0) DEFAULT 0 NOT NULL, + Name VARCHAR2(200) NOT NULL, + Description VARCHAR2(255), + Type VARCHAR2(16), + Content CLOB, + LastUpdated DATE, + LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, + Creator NUMBER(11,0) DEFAULT 0 NOT NULL, + Created DATE ); CREATE SEQUENCE OBJECTCUSTOMFIELDS_seq; CREATE TABLE ObjectCustomFields ( - id NUMBER(11,0) + id NUMBER(11,0) CONSTRAINT ObjectCustomFields_Key PRIMARY KEY, CustomField NUMBER(11,0) NOT NULL, ObjectId NUMBER(11,0) NOT NULL, - SortOrder NUMBER(11,0) DEFAULT 0 NOT NULL, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE + SortOrder NUMBER(11,0) DEFAULT 0 NOT NULL, + Creator NUMBER(11,0) DEFAULT 0 NOT NULL, + Created DATE, + LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, + LastUpdated DATE ); CREATE SEQUENCE OBJECTCUSTOMFIELDVALUES_seq; CREATE TABLE ObjectCustomFieldValues ( - id NUMBER(11,0) - CONSTRAINT ObjectCustomFieldValues_Key PRIMARY KEY, - CustomField NUMBER(11,0) NOT NULL, - ObjectType VARCHAR2(25) NOT NULL, - ObjectId NUMBER(11,0) DEFAULT 0 NOT NULL, - SortOrder NUMBER(11,0) DEFAULT 0 NOT NULL, - Content VARCHAR2(255), - LargeContent CLOB, - ContentType VARCHAR2(80), - ContentEncoding VARCHAR2(80), - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE, - Disabled NUMBER(11,0) DEFAULT 0 NOT NULL + id NUMBER(11,0) + CONSTRAINT ObjectCustomFieldValues_Key PRIMARY KEY, + CustomField NUMBER(11,0) NOT NULL, + ObjectType VARCHAR2(25) NOT NULL, + ObjectId NUMBER(11,0) DEFAULT 0 NOT NULL, + SortOrder NUMBER(11,0) DEFAULT 0 NOT NULL, + Content VARCHAR2(255), + LargeContent CLOB, + ContentType VARCHAR2(80), + ContentEncoding VARCHAR2(80), + Creator NUMBER(11,0) DEFAULT 0 NOT NULL, + Created DATE, + LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, + LastUpdated DATE, + Disabled NUMBER(11,0) DEFAULT 0 NOT NULL ); CREATE INDEX ObjectCustomFieldValues1 ON ObjectCustomFieldValues (Content); @@ -350,57 +359,56 @@ CREATE INDEX ObjectCustomFieldValues2 ON ObjectCustomFieldValues (CustomField,Ob CREATE SEQUENCE CUSTOMFIELDS_seq; CREATE TABLE CustomFields ( - id NUMBER(11,0) - CONSTRAINT CustomFields_Key PRIMARY KEY, - Name VARCHAR2(200), - Type VARCHAR2(200), - RenderType VARCHAR2(64), - MaxValues NUMBER(11,0) DEFAULT 0 NOT NULL, - Pattern CLOB, - Repeated NUMBER(11,0) DEFAULT 0 NOT NULL, - ValuesClass VARCHAR2(64), + id NUMBER(11,0) + CONSTRAINT CustomFields_Key PRIMARY KEY, + Name VARCHAR2(200), + Type VARCHAR2(200), + RenderType VARCHAR2(64), + MaxValues NUMBER(11,0) DEFAULT 0 NOT NULL, + Pattern CLOB, + ValuesClass VARCHAR2(64), BasedOn NUMBER(11,0) NULL, - Description VARCHAR2(255), - SortOrder NUMBER(11,0) DEFAULT 0 NOT NULL, - LookupType VARCHAR2(255), - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE, - Disabled NUMBER(11,0) DEFAULT 0 NOT NULL + Description VARCHAR2(255), + SortOrder NUMBER(11,0) DEFAULT 0 NOT NULL, + LookupType VARCHAR2(255), + Creator NUMBER(11,0) DEFAULT 0 NOT NULL, + Created DATE, + LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, + LastUpdated DATE, + Disabled NUMBER(11,0) DEFAULT 0 NOT NULL ); CREATE SEQUENCE CUSTOMFIELDVALUES_seq; CREATE TABLE CustomFieldValues ( - id NUMBER(11,0) - CONSTRAINT CustomFieldValues_Key PRIMARY KEY, - CustomField NUMBER(11,0), - Name VARCHAR2(200), - Description VARCHAR2(255), - SortOrder NUMBER(11,0) DEFAULT 0 NOT NULL, + id NUMBER(11,0) + CONSTRAINT CustomFieldValues_Key PRIMARY KEY, + CustomField NUMBER(11,0), + Name VARCHAR2(200), + Description VARCHAR2(255), + SortOrder NUMBER(11,0) DEFAULT 0 NOT NULL, Category VARCHAR2(255), - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE + Creator NUMBER(11,0) DEFAULT 0 NOT NULL, + Created DATE, + LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, + LastUpdated DATE ); CREATE INDEX CustomFieldValues1 ON CustomFieldValues (CustomField); CREATE SEQUENCE ATTRIBUTES_seq; CREATE TABLE Attributes ( - id NUMBER(11,0) PRIMARY KEY, - Name VARCHAR2(255) NOT NULL, - Description VARCHAR2(255), - Content CLOB, + id NUMBER(11,0) PRIMARY KEY, + Name VARCHAR2(255) NOT NULL, + Description VARCHAR2(255), + Content CLOB, ContentType VARCHAR(16), - ObjectType VARCHAR2(25) NOT NULL, - ObjectId NUMBER(11,0) DEFAULT 0 NOT NULL, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE + ObjectType VARCHAR2(25) NOT NULL, + ObjectId NUMBER(11,0) DEFAULT 0 NOT NULL, + Creator NUMBER(11,0) DEFAULT 0 NOT NULL, + Created DATE, + LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, + LastUpdated DATE ); CREATE INDEX Attributes1 on Attributes(Name); @@ -408,10 +416,10 @@ CREATE INDEX Attributes2 on Attributes(ObjectType, ObjectId); CREATE TABLE sessions ( - id VARCHAR2(32) - CONSTRAINT Sessions_Key PRIMARY KEY, - a_session CLOB, - LastUpdated DATE + id VARCHAR2(32) + CONSTRAINT Sessions_Key PRIMARY KEY, + a_session CLOB, + LastUpdated DATE ); CREATE SEQUENCE Classes_seq; diff --git a/etc/schema.Pg b/etc/schema.Pg index 565f76b..e5e2a04 100644 --- a/etc/schema.Pg +++ b/etc/schema.Pg @@ -63,7 +63,7 @@ CREATE TABLE Queues ( PRIMARY KEY (id) ); -CREATE UNIQUE INDEX Queues1 ON Queues (Name) ; +CREATE UNIQUE INDEX Queues1 ON Queues (LOWER(Name)) ; @@ -144,8 +144,9 @@ CREATE TABLE Groups ( PRIMARY KEY (id) ); -CREATE UNIQUE INDEX Groups1 ON Groups (Domain,Instance,Type,id, Name); -CREATE INDEX Groups2 On Groups (Type, Instance, Domain); +CREATE INDEX Groups1 ON Groups (LOWER(Domain), LOWER(Type), Instance); +CREATE INDEX Groups2 ON Groups (LOWER(Domain), LOWER(Name), Instance); +CREATE INDEX Groups3 On Groups (Instance); @@ -225,14 +226,11 @@ CREATE TABLE Scrips ( 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 , + Disabled integer NOT NULL DEFAULT 0 , + Template varchar(200) NOT NULL, Creator integer NOT NULL DEFAULT 0 , Created TIMESTAMP NULL , LastUpdatedBy integer NOT NULL DEFAULT 0 , @@ -242,7 +240,24 @@ CREATE TABLE Scrips ( ); +CREATE SEQUENCE objectscrips_id_seq; + +CREATE TABLE ObjectScrips ( + id INTEGER DEFAULT nextval('objectscrips_id_seq'), + Scrip integer NOT NULL, + Stage varchar(32) NOT NULL DEFAULT 'TransactionCreate' , + ObjectId integer NOT NULL, + SortOrder integer NOT NULL DEFAULT 0 , + + Creator integer NOT NULL DEFAULT 0 , + Created TIMESTAMP NULL , + LastUpdatedBy integer NOT NULL DEFAULT 0 , + LastUpdated TIMESTAMP NULL , + PRIMARY KEY (id) + +); +CREATE UNIQUE INDEX ObjectScrips1 ON ObjectScrips (ObjectId, Scrip); @@ -320,9 +335,9 @@ CREATE TABLE CachedGroupMembers ( ); -CREATE INDEX CachedGroupMembers2 on CachedGroupMembers (MemberId); -CREATE INDEX CachedGroupMembers3 on CachedGroupMembers (GroupId); -CREATE INDEX DisGrouMem on CachedGroupMembers (GroupId,MemberId,Disabled); +CREATE INDEX CachedGroupMembers2 on CachedGroupMembers (MemberId, GroupId, Disabled); +CREATE INDEX DisGrouMem on CachedGroupMembers (GroupId,MemberId,Disabled); +CREATE INDEX CachedGroupMembers3 on CachedGroupMembers (MemberId,ImmediateParentId); @@ -368,6 +383,7 @@ CREATE TABLE Users ( Country varchar(50) NULL , Timezone varchar(50) NULL , PGPKey text NULL, + SMIMECertificate text NULL, Creator integer NOT NULL DEFAULT 0 , Created TIMESTAMP NULL , @@ -378,8 +394,7 @@ CREATE TABLE Users ( ); -CREATE UNIQUE INDEX Users1 ON Users (Name) ; -CREATE INDEX Users3 ON Users (id, EmailAddress); +CREATE UNIQUE INDEX Users1 ON Users (LOWER(Name)) ; CREATE INDEX Users4 ON Users (EmailAddress); @@ -398,6 +413,7 @@ CREATE SEQUENCE tickets_id_seq; CREATE TABLE Tickets ( id INTEGER DEFAULT nextval('tickets_id_seq'), EffectiveId integer NOT NULL DEFAULT 0 , + IsMerged smallint NULL DEFAULT NULL , Queue integer NOT NULL DEFAULT 0 , Type varchar(16) NULL , IssueStatement integer NOT NULL DEFAULT 0 , @@ -430,13 +446,6 @@ CREATE TABLE Tickets ( CREATE INDEX Tickets1 ON Tickets (Queue, Status) ; CREATE INDEX Tickets2 ON Tickets (Owner) ; CREATE INDEX Tickets3 ON Tickets (EffectiveId) ; -CREATE INDEX Tickets4 ON Tickets (id, Status) ; -CREATE INDEX Tickets5 ON Tickets (id, EffectiveId) ; - - - - - -- @@ -477,8 +486,6 @@ CREATE TABLE Templates ( Name varchar(200) NOT NULL , Description varchar(255) NULL , Type varchar(16) NULL , - Language varchar(16) NULL , - TranslationOf integer NOT NULL DEFAULT 0 , Content text NULL , LastUpdated TIMESTAMP NULL , LastUpdatedBy integer NOT NULL DEFAULT 0 , @@ -542,7 +549,6 @@ CREATE TABLE CustomFields ( Type varchar(200) NULL , RenderType varchar(64) NULL , MaxValues integer NOT NULL DEFAULT 0 , - Repeated integer NOT NULL DEFAULT 0 , ValuesClass varchar(64) NULL , BasedOn integer NULL, Pattern varchar(65536) NULL , diff --git a/etc/schema.SQLite b/etc/schema.SQLite index 6897be2..c50e5b1 100644 --- a/etc/schema.SQLite +++ b/etc/schema.SQLite @@ -4,13 +4,13 @@ CREATE TABLE Attachments ( id INTEGER PRIMARY KEY , TransactionId INTEGER , Parent integer 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 , + MessageId varchar(160) collate NOCASE NULL , + Subject varchar(255) collate NOCASE NULL , + Filename varchar(255) collate NOCASE NULL , + ContentType varchar(80) collate NOCASE NULL , + ContentEncoding varchar(80) collate NOCASE NULL , + Content LONGTEXT collate NOCASE NULL , + Headers LONGTEXT collate NOCASE NULL , Creator integer NULL DEFAULT 0 , Created DATETIME NULL @@ -24,12 +24,12 @@ CREATE INDEX Attachments3 ON Attachments (Parent, TransactionId) ; --- {{{ Queues CREATE TABLE Queues ( id INTEGER PRIMARY KEY , - Name varchar(200) NOT NULL , - Description varchar(255) NULL , - CorrespondAddress varchar(120) NULL , - CommentAddress varchar(120) NULL , - Lifecycle varchar(32) NULL , - SubjectTag varchar(120) NULL , + Name varchar(200) collate NOCASE NOT NULL , + Description varchar(255) collate NOCASE NULL , + CorrespondAddress varchar(120) collate NOCASE NULL , + CommentAddress varchar(120) collate NOCASE NULL , + Lifecycle varchar(32) collate NOCASE NULL , + SubjectTag varchar(120) collate NOCASE NULL , InitialPriority integer NULL DEFAULT 0 , FinalPriority integer NULL DEFAULT 0 , DefaultDueIn integer NULL DEFAULT 0 , @@ -48,9 +48,9 @@ CREATE UNIQUE INDEX Queues1 ON Queues (Name) ; CREATE TABLE Links ( id INTEGER PRIMARY KEY , - Base varchar(240) NULL , - Target varchar(240) NULL , - Type varchar(20) NOT NULL , + Base varchar(240) collate NOCASE NULL , + Target varchar(240) collate NOCASE NULL , + Type varchar(20) collate NOCASE NOT NULL , LocalTarget integer NULL DEFAULT 0 , LocalBase integer NULL DEFAULT 0 , LastUpdatedBy integer NULL DEFAULT 0 , @@ -68,7 +68,7 @@ CREATE INDEX Links4 ON Links(Type,LocalBase); CREATE TABLE Principals ( id INTEGER PRIMARY KEY, - PrincipalType VARCHAR(16) not null, + PrincipalType VARCHAR(16) collate NOCASE not null, ObjectId integer, Disabled int2 NOT NULL DEFAULT 0 @@ -80,10 +80,10 @@ CREATE TABLE Principals ( CREATE TABLE Groups ( id INTEGER , - Name varchar(200) NULL , - Description varchar(255) NULL , - Domain varchar(64), - Type varchar(64), + Name varchar(200) collate NOCASE NULL , + Description varchar(255) collate NOCASE NULL , + Domain varchar(64) collate NOCASE, + Type varchar(64) collate NOCASE, Instance integer, Creator integer NOT NULL DEFAULT 0 , Created DATETIME NULL , @@ -92,7 +92,9 @@ CREATE TABLE Groups ( ) ; -CREATE UNIQUE INDEX Groups1 ON Groups (Name,Domain,Type,Instance) ; +CREATE INDEX Groups1 ON Groups (Domain,Type,Instance); +CREATE INDEX Groups2 ON Groups (Domain,Name,Instance); +CREATE INDEX Groups3 ON Groups (Instance); --- }}} @@ -100,11 +102,11 @@ CREATE UNIQUE INDEX Groups1 ON Groups (Name,Domain,Type,Instance) ; CREATE TABLE ScripConditions ( id INTEGER PRIMARY KEY , - Name varchar(200) NULL , - Description varchar(255) NULL , - ExecModule varchar(60) NULL , - Argument varchar(255) NULL , - ApplicableTransTypes varchar(60) NULL , + Name varchar(200) collate NOCASE NULL , + Description varchar(255) collate NOCASE NULL , + ExecModule varchar(60) collate NOCASE NULL , + Argument varchar(255) collate NOCASE NULL , + ApplicableTransTypes varchar(60) collate NOCASE NULL , Creator integer NULL DEFAULT 0 , Created DATETIME NULL , @@ -118,17 +120,17 @@ CREATE TABLE ScripConditions ( --- {{{ Transactions CREATE TABLE Transactions ( id INTEGER PRIMARY KEY , - ObjectType varchar(255) NULL , + ObjectType varchar(255) collate NOCASE NULL , ObjectId integer NULL DEFAULT 0 , TimeTaken integer NULL DEFAULT 0 , - Type varchar(20) NULL , - Field varchar(40) NULL , - OldValue varchar(255) NULL , - NewValue varchar(255) NULL , - ReferenceType varchar(255) NULL , + Type varchar(20) collate NOCASE NULL , + Field varchar(40) collate NOCASE NULL , + OldValue varchar(255) collate NOCASE NULL , + NewValue varchar(255) collate NOCASE NULL , + ReferenceType varchar(255) collate NOCASE NULL , OldReference integer NULL , NewReference integer NULL , - Data varchar(255) NULL , + Data varchar(255) collate NOCASE NULL , Creator integer NULL DEFAULT 0 , Created DATETIME NULL @@ -142,17 +144,14 @@ CREATE INDEX Transactions1 ON Transactions (ObjectType, ObjectId); CREATE TABLE Scrips ( id INTEGER PRIMARY KEY , - Description varchar(255), + Description varchar(255) collate NOCASE, ScripCondition integer NULL DEFAULT 0 , ScripAction integer NULL DEFAULT 0 , - ConditionRules text NULL , - ActionRules text NULL , - CustomIsApplicableCode text NULL , - CustomPrepareCode text NULL , - CustomCommitCode text NULL , - Stage varchar(32) NULL , - Queue integer NULL DEFAULT 0 , - Template integer NULL DEFAULT 0 , + CustomIsApplicableCode text collate NOCASE NULL , + CustomPrepareCode text collate NOCASE NULL , + CustomCommitCode text collate NOCASE NULL , + Disabled int2 NOT NULL DEFAULT 0 , + Template varchar(200) collate NOCASE NOT NULL , Creator integer NULL DEFAULT 0 , Created DATETIME NULL , LastUpdatedBy integer NULL DEFAULT 0 , @@ -162,14 +161,29 @@ CREATE TABLE Scrips ( --- }}} +CREATE TABLE ObjectScrips ( + id INTEGER NOT NULL , + Scrip int NOT NULL , + Stage varchar(32) collate NOCASE NOT NULL DEFAULT 'TransactionCreate' , + 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) +); +CREATE UNIQUE INDEX ObjectScrips1 ON ObjectScrips (ObjectId, Scrip); + --- {{{ ACL CREATE TABLE ACL ( id INTEGER PRIMARY KEY , - PrincipalType varchar(25) NOT NULL, + PrincipalType varchar(25) collate NOCASE NOT NULL, PrincipalId INTEGER DEFAULT 0, - RightName varchar(25) NOT NULL , - ObjectType varchar(25) NOT NULL , + RightName varchar(25) collate NOCASE NOT NULL , + ObjectType varchar(25) collate NOCASE NOT NULL , ObjectId INTEGER default 0, Creator integer NOT NULL DEFAULT 0 , Created DATETIME NULL , @@ -194,6 +208,8 @@ CREATE TABLE GroupMembers ( ) ; +CREATE UNIQUE INDEX GroupMembers1 ON GroupMembers(GroupId, MemberId); + --- }}} --- {{{ CachedGroupMembers @@ -213,42 +229,47 @@ create table CachedGroupMembers ( ) ; +CREATE INDEX CachedGroupMembers1 ON CachedGroupMembers (GroupId, MemberId, Disabled); +CREATE INDEX CachedGroupMembers2 ON CachedGroupMembers (MemberId, GroupId, Disabled); +CREATE INDEX CachedGroupMembers3 ON CachedGroupMembers (MemberId, ImmediateParentId); + --- }}} --- {{{ Users CREATE TABLE Users ( id INTEGER , - Name varchar(200) NOT NULL , - Password varchar(256) NULL , - AuthToken varchar(16) NULL , + Name varchar(200) collate NOCASE NOT NULL , + Password varchar(256) collate NOCASE NULL , + AuthToken varchar(16) collate NOCASE NULL , Comments blob NULL , Signature blob NULL , - EmailAddress varchar(120) NULL , + EmailAddress varchar(120) collate NOCASE 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 , + Organization varchar(200) collate NOCASE NULL , + RealName varchar(120) collate NOCASE NULL , + NickName varchar(16) collate NOCASE NULL , + Lang varchar(16) collate NOCASE NULL , + EmailEncoding varchar(16) collate NOCASE NULL , + WebEncoding varchar(16) collate NOCASE NULL , + ExternalContactInfoId varchar(100) collate NOCASE NULL , + ContactInfoSystem varchar(30) collate NOCASE NULL , + ExternalAuthId varchar(100) collate NOCASE NULL , + AuthSystem varchar(30) collate NOCASE NULL , + Gecos varchar(16) collate NOCASE NULL , + HomePhone varchar(30) collate NOCASE NULL , + WorkPhone varchar(30) collate NOCASE NULL , + MobilePhone varchar(30) collate NOCASE NULL , + PagerPhone varchar(30) collate NOCASE NULL , + Address1 varchar(200) collate NOCASE NULL , + Address2 varchar(200) collate NOCASE NULL , + City varchar(100) collate NOCASE NULL , + State varchar(100) collate NOCASE NULL , + Zip varchar(16) collate NOCASE NULL , + Country varchar(50) collate NOCASE NULL , Timezone char(50) NULL , - PGPKey text NULL, + PGPKey text collate NOCASE NULL, + SMIMECertificate text collate NOCASE NULL, Creator integer NULL DEFAULT 0 , Created DATETIME NULL , @@ -259,8 +280,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); @@ -271,18 +290,19 @@ CREATE INDEX Users4 ON Users (EmailAddress); CREATE TABLE Tickets ( id INTEGER PRIMARY KEY , EffectiveId integer NULL DEFAULT 0 , + IsMerged int2 NULL DEFAULT NULL, Queue integer NULL DEFAULT 0 , - Type varchar(16) NULL , + Type varchar(16) collate NOCASE NULL , IssueStatement integer NULL DEFAULT 0 , Resolution integer NULL DEFAULT 0 , Owner integer NULL DEFAULT 0 , - Subject varchar(200) NULL DEFAULT '[no subject]' , + Subject varchar(200) collate NOCASE NULL DEFAULT '[no subject]' , InitialPriority integer NULL DEFAULT 0 , FinalPriority integer NULL DEFAULt 0 , Priority integer NULL DEFAULT 0 , TimeEstimated integer NULL DEFAULT 0 , TimeWorked integer NULL DEFAULT 0 , - Status varchar(64) NULL , + Status varchar(64) collate NOCASE NULL , TimeLeft integer NULL DEFAULT 0 , Told DATETIME NULL , Starts DATETIME NULL , @@ -302,8 +322,6 @@ CREATE TABLE Tickets ( CREATE INDEX Tickets1 ON Tickets (Queue, Status) ; CREATE INDEX Tickets2 ON Tickets (Owner) ; CREATE INDEX Tickets3 ON Tickets (EffectiveId) ; -CREATE INDEX Tickets4 ON Tickets (id, Status) ; -CREATE INDEX Tickets5 ON Tickets (id, EffectiveId) ; --- }}} @@ -311,10 +329,10 @@ CREATE INDEX Tickets5 ON Tickets (id, EffectiveId) ; CREATE TABLE ScripActions ( id INTEGER PRIMARY KEY , - Name varchar(200) NULL , - Description varchar(255) NULL , - ExecModule varchar(60) NULL , - Argument varchar(255) NULL , + Name varchar(200) collate NOCASE NULL , + Description varchar(255) collate NOCASE NULL , + ExecModule varchar(60) collate NOCASE NULL , + Argument varchar(255) collate NOCASE NULL , Creator integer NULL DEFAULT 0 , Created DATETIME NULL , LastUpdatedBy integer NULL DEFAULT 0 , @@ -329,11 +347,9 @@ CREATE TABLE ScripActions ( CREATE TABLE Templates ( id INTEGER PRIMARY KEY , 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 NULL DEFAULT 0 , + Name varchar(200) collate NOCASE NOT NULL , + Description varchar(255) collate NOCASE NULL , + Type varchar(16) collate NOCASE NULL , Content blob NULL , LastUpdated DATETIME NULL , LastUpdatedBy integer NULL DEFAULT 0 , @@ -350,14 +366,14 @@ CREATE TABLE Templates ( CREATE TABLE ObjectCustomFieldValues ( id INTEGER NOT NULL , CustomField int NOT NULL , - ObjectType varchar(255) NOT NULL, # Final target of the Object - ObjectId int NOT NULL , # New -- Replaces Ticket + ObjectType varchar(255) collate NOCASE NOT NULL, # Final target of the Object + ObjectId int NOT NULL , # New -- Replaces Ticket SortOrder integer NOT NULL DEFAULT 0 , - 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 + Content varchar(255) collate NOCASE NULL , + LargeContent LONGTEXT collate NOCASE NULL, # New -- to hold 255+ strings + ContentType varchar(80) collate NOCASE NULL, # New -- only text/* gets searched + ContentEncoding varchar(80) collate NOCASE NULL , # New -- for binary Content Creator integer NOT NULL DEFAULT 0 , Created DATETIME NULL , @@ -374,17 +390,16 @@ CREATE INDEX ObjectCustomFieldValues2 ON ObjectCustomFieldValues (CustomField,Ob CREATE TABLE CustomFields ( id INTEGER NOT NULL , - Name varchar(200) NULL , - Type varchar(200) NULL , # Changed -- 'Single' and 'Multiple' is moved out - RenderType varchar(64) NULL , - MaxValues integer, # New -- was 'Single'(1) and 'Multiple'(0) - Pattern varchar(65536) NULL , # New -- Must validate against this - Repeated int2 NOT NULL DEFAULT 0 , # New -- repeated table entry + Name varchar(200) collate NOCASE NULL , + Type varchar(200) collate NOCASE NULL , # Changed -- 'Single' and 'Multiple' is moved out + RenderType varchar(64) collate NOCASE NULL , + MaxValues integer, # New -- was 'Single'(1) and 'Multiple'(0) + Pattern varchar(65536) collate NOCASE NULL , # New -- Must validate against this BasedOn INTEGER NULL, - ValuesClass varchar(64) NULL , - Description varchar(255) NULL , + ValuesClass varchar(64) collate NOCASE NULL , + Description varchar(255) collate NOCASE NULL , SortOrder integer NOT NULL DEFAULT 0 , - LookupType varchar(255) NOT NULL, + LookupType varchar(255) collate NOCASE NOT NULL, Creator integer NOT NULL DEFAULT 0 , Created DATETIME NULL , @@ -414,10 +429,10 @@ CREATE TABLE ObjectCustomFields ( CREATE TABLE CustomFieldValues ( id INTEGER NOT NULL , CustomField int NOT NULL , - Name varchar(200) NULL , - Description varchar(255) NULL , + Name varchar(200) collate NOCASE NULL , + Description varchar(255) collate NOCASE NULL , SortOrder integer NOT NULL DEFAULT 0 , - Category varchar(255) NULL , + Category varchar(255) collate NOCASE NULL , Creator integer NOT NULL DEFAULT 0 , Created DATETIME NULL , @@ -432,11 +447,11 @@ CREATE INDEX CustomFieldValues1 ON CustomFieldValues (CustomField); --- {{{ Attributes CREATE TABLE Attributes ( id INTEGER PRIMARY KEY , - Name varchar(255) NOT NULL , - Description varchar(255) NULL , - Content LONGTEXT NULL , - ContentType varchar(16), - ObjectType varchar(25) NOT NULL , + Name varchar(255) collate NOCASE NOT NULL , + Description varchar(255) collate NOCASE NULL , + Content LONGTEXT collate NOCASE NULL , + ContentType varchar(16) collate NOCASE, + ObjectType varchar(25) collate NOCASE NOT NULL , ObjectId INTEGER , Creator integer NULL DEFAULT 0 , Created DATETIME NULL , @@ -451,8 +466,8 @@ CREATE INDEX Attributes2 on Attributes(ObjectType, ObjectId); CREATE TABLE Classes ( id INTEGER PRIMARY KEY, -Name varchar(255) NOT NULL DEFAULT '', -Description varchar(255) NOT NULL DEFAULT '', +Name varchar(255) collate NOCASE NOT NULL DEFAULT '', +Description varchar(255) collate NOCASE NOT NULL DEFAULT '', SortOrder integer NOT NULL DEFAULT 0, Disabled smallint NOT NULL DEFAULT 0, Creator integer NOT NULL DEFAULT 0, @@ -464,12 +479,12 @@ HotList smallint NOT NULL DEFAULT 0 CREATE TABLE Articles ( id INTEGER PRIMARY KEY, -Name varchar(255) NOT NULL DEFAULT '', -Summary varchar(255) NOT NULL DEFAULT '', +Name varchar(255) collate NOCASE NOT NULL DEFAULT '', +Summary varchar(255) collate NOCASE NOT NULL DEFAULT '', SortOrder integer NOT NULL DEFAULT 0, Class integer NOT NULL DEFAULT 0, Parent integer NOT NULL DEFAULT 0, -URI varchar(255), +URI varchar(255) collate NOCASE, Creator integer NOT NULL DEFAULT 0, Created TIMESTAMP NULL, LastUpdatedBy integer NOT NULL DEFAULT 0, @@ -480,9 +495,9 @@ LastUpdated TIMESTAMP NULL CREATE TABLE Topics ( id INTEGER PRIMARY KEY, Parent integer NOT NULL DEFAULT 0, -Name varchar(255) NOT NULL DEFAULT '', -Description varchar(255) NOT NULL DEFAULT '', -ObjectType varchar(64) NOT NULL DEFAULT '', +Name varchar(255) collate NOCASE NOT NULL DEFAULT '', +Description varchar(255) collate NOCASE NOT NULL DEFAULT '', +ObjectType varchar(64) collate NOCASE NOT NULL DEFAULT '', ObjectId integer NOT NULL DEFAULT 0 ); @@ -490,14 +505,14 @@ ObjectId integer NOT NULL DEFAULT 0 CREATE TABLE ObjectTopics ( id INTEGER PRIMARY KEY, Topic integer NOT NULL DEFAULT 0, -ObjectType varchar(64) NOT NULL DEFAULT '', +ObjectType varchar(64) collate NOCASE NOT NULL DEFAULT '', ObjectId integer NOT NULL DEFAULT 0 ); CREATE TABLE ObjectClasses ( id INTEGER PRIMARY KEY, Class integer NOT NULL DEFAULT 0, -ObjectType varchar(64) NOT NULL DEFAULT '', +ObjectType varchar(64) collate NOCASE NOT NULL DEFAULT '', ObjectId integer NOT NULL DEFAULT 0, Creator integer NOT NULL DEFAULT 0, Created TIMESTAMP NULL, diff --git a/etc/schema.mysql b/etc/schema.mysql index 9ed0337..3669fb3 100644 --- a/etc/schema.mysql +++ b/etc/schema.mysql @@ -88,10 +88,9 @@ CREATE TABLE Groups ( PRIMARY KEY (id) ) ENGINE=InnoDB CHARACTER SET utf8; -CREATE INDEX Groups1 ON Groups (Domain,Instance,Type,id); -CREATE INDEX Groups2 On Groups (Type, Instance); - - +CREATE INDEX Groups1 ON Groups (Domain, Type, Instance); +CREATE INDEX Groups2 ON Groups (Domain, Name, Instance); +CREATE INDEX Groups3 On Groups (Instance); CREATE TABLE ScripConditions ( id INTEGER NOT NULL AUTO_INCREMENT, @@ -137,14 +136,25 @@ CREATE TABLE Scrips ( 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 , + Disabled int2 NOT NULL DEFAULT 0 , + Template varchar(200) NOT NULL , + Creator integer NOT NULL DEFAULT 0 , + Created DATETIME NULL , + LastUpdatedBy integer NOT NULL DEFAULT 0 , + LastUpdated DATETIME NULL , + PRIMARY KEY (id) +) ENGINE=InnoDB CHARACTER SET utf8; + +CREATE TABLE ObjectScrips ( + id INTEGER NOT NULL AUTO_INCREMENT, + Scrip integer NOT NULL , + Stage varchar(32) CHARACTER SET ascii NOT NULL DEFAULT 'TransactionCreate', + 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 , @@ -152,6 +162,7 @@ CREATE TABLE Scrips ( PRIMARY KEY (id) ) ENGINE=InnoDB CHARACTER SET utf8; +CREATE UNIQUE INDEX ObjectScrips1 ON ObjectScrips (ObjectId, Scrip); CREATE TABLE ACL ( id INTEGER NOT NULL AUTO_INCREMENT, @@ -204,6 +215,7 @@ create table CachedGroupMembers ( ) ENGINE=InnoDB CHARACTER SET utf8; CREATE INDEX DisGrouMem on CachedGroupMembers (GroupId,MemberId,Disabled); +CREATE INDEX CachedGroupMembers2 on CachedGroupMembers (MemberId, GroupId, Disabled); CREATE INDEX CachedGroupMembers3 on CachedGroupMembers (MemberId, ImmediateParentId); @@ -240,6 +252,7 @@ CREATE TABLE Users ( Country varchar(50) NULL , Timezone varchar(50) NULL , PGPKey text NULL, + SMIMECertificate text NULL, Creator integer NOT NULL DEFAULT 0 , Created DATETIME NULL , @@ -258,6 +271,7 @@ CREATE INDEX Users4 ON Users (EmailAddress); CREATE TABLE Tickets ( id INTEGER NOT NULL AUTO_INCREMENT, EffectiveId integer NOT NULL DEFAULT 0 , + IsMerged int2 NULL DEFAULT NULL, Queue integer NOT NULL DEFAULT 0 , Type varchar(16) CHARACTER SET ascii NULL , IssueStatement integer NOT NULL DEFAULT 0 , @@ -313,8 +327,6 @@ CREATE TABLE Templates ( 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 , @@ -328,14 +340,14 @@ CREATE TABLE Templates ( 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 + 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 + 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 , @@ -353,11 +365,10 @@ CREATE INDEX ObjectCustomFieldValues2 ON ObjectCustomFieldValues (CustomField,Ob 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 + Type varchar(200) CHARACTER SET ascii NULL , # Changed -- 'Single' and 'Multiple' is moved out RenderType varchar(64) CHARACTER SET ascii NULL , - 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 + MaxValues integer, # New -- was 'Single'(1) and 'Multiple'(0) + Pattern TEXT NULL , # New -- Must validate against this BasedOn INTEGER NULL, ValuesClass varchar(64) CHARACTER SET ascii NULL , Description varchar(255) NULL , diff --git a/etc/upgrade/3.1.0/schema.Oracle b/etc/upgrade/3.1.0/schema.Oracle index a8aae18..8a3c142 100644 --- a/etc/upgrade/3.1.0/schema.Oracle +++ b/etc/upgrade/3.1.0/schema.Oracle @@ -1,16 +1,16 @@ CREATE SEQUENCE ATTRIBUTES_seq; CREATE TABLE Attributes ( - id NUMBER(11,0) PRIMARY KEY, - Name VARCHAR2(255) NOT NULL, - Description VARCHAR2(255), - Content CLOB, + id NUMBER(11,0) PRIMARY KEY, + Name VARCHAR2(255) NOT NULL, + Description VARCHAR2(255), + Content CLOB, ContentType VARCHAR(16), - ObjectType VARCHAR2(25) NOT NULL, - ObjectId NUMBER(11,0) DEFAULT 0 NOT NULL, - Creator NUMBER(11,0) DEFAULT 0 NOT NULL, - Created DATE, - LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, - LastUpdated DATE + ObjectType VARCHAR2(25) NOT NULL, + ObjectId NUMBER(11,0) DEFAULT 0 NOT NULL, + Creator NUMBER(11,0) DEFAULT 0 NOT NULL, + Created DATE, + LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, + LastUpdated DATE ); CREATE INDEX Attributes1 on Attributes(Name); diff --git a/etc/upgrade/3.1.15/content b/etc/upgrade/3.1.15/content index d23125a..9e4b253 100644 --- a/etc/upgrade/3.1.15/content +++ b/etc/upgrade/3.1.15/content @@ -1,4 +1,7 @@ -@Scrips = ( +use strict; +use warnings; + +our @Scrips = ( { ScripCondition => 'On Owner Change', ScripAction => 'Notify Owner', Template => 'Transaction' }, diff --git a/etc/upgrade/3.1.17/content b/etc/upgrade/3.1.17/content index 1d648d8..a6b5c54 100644 --- a/etc/upgrade/3.1.17/content +++ b/etc/upgrade/3.1.17/content @@ -1,4 +1,7 @@ -@ScripActions = ( +use strict; +use warnings; + +our @ScripActions = ( { Name => 'Notify Ccs as Comment', # loc Description => 'Sends mail to the Ccs as a comment', # loc ExecModule => 'NotifyAsComment', @@ -10,7 +13,7 @@ ); -@ScripConditions = ( +our @ScripConditions = ( { Name => 'On Priority Change', # loc Description => 'Whenever a ticket\'s priority changes', # loc diff --git a/etc/upgrade/3.5.1/content b/etc/upgrade/3.5.1/content index 02d6a0c..59f9dfd 100644 --- a/etc/upgrade/3.5.1/content +++ b/etc/upgrade/3.5.1/content @@ -1,4 +1,7 @@ -@Attributes = ( +use strict; +use warnings; + +our @Attributes = ( { Name => 'Search - My Tickets', Description => '[_1] highest priority tickets I own', Content => @@ -19,16 +22,16 @@ Description => 'HomepageSettings', Content => { 'body' => - [ { 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 => 'component', name => 'QuickCreate'}, + ], 'summary' => - [ - { type => 'component', name => 'MyReminders' }, + [ + { type => 'component', name => 'MyReminders' }, { type => 'component', name => 'Quicksearch' }, - { type => 'component', name => 'RefreshHomepage' }, - ] + { type => 'component', name => 'RefreshHomepage' }, + ] }, } ); diff --git a/etc/upgrade/3.7.1/content b/etc/upgrade/3.7.1/content index fdd5061..e39ef8a 100644 --- a/etc/upgrade/3.7.1/content +++ b/etc/upgrade/3.7.1/content @@ -1,4 +1,7 @@ -@ScripConditions = ( +use strict; +use warnings; + +our @ScripConditions = ( { Name => 'On Close', # loc Description => 'Whenever a ticket is closed', # loc ApplicableTransTypes => 'Status,Set', diff --git a/etc/upgrade/3.7.10/content b/etc/upgrade/3.7.10/content index d19f9e6..fd4628d 100644 --- a/etc/upgrade/3.7.10/content +++ b/etc/upgrade/3.7.10/content @@ -1,5 +1,8 @@ +use strict; +use warnings; -@Templates = ( + +our @Templates = ( { Queue => 0, Name => "Error: public key", # loc Description => diff --git a/etc/upgrade/3.7.15/content b/etc/upgrade/3.7.15/content index 9d97c35..a3a27fd 100644 --- a/etc/upgrade/3.7.15/content +++ b/etc/upgrade/3.7.15/content @@ -1,5 +1,8 @@ +use strict; +use warnings; -@Templates = ( + +our @Templates = ( { Queue => 0, Name => "Forward", # loc Description => "Heading of a forwarded message", # loc diff --git a/etc/upgrade/3.7.19/content b/etc/upgrade/3.7.19/content index 31ab1c8..a694562 100644 --- a/etc/upgrade/3.7.19/content +++ b/etc/upgrade/3.7.19/content @@ -1,26 +1,28 @@ +use strict; +use warnings; -{ use strict; -add_description_to_all_scrips(); +our @Final = ( + sub { + require RT::Scrips; + my $scrips = RT::Scrips->new( RT->SystemUser ); + $scrips->{'with_disabled_column'} = 0; + $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; -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 ); - $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"; + 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; @@ -32,6 +34,5 @@ sub gen_scrip_description { || ('Run Action #'. $scrip->Action); return join ' ', $condition, $action; } -} 1; diff --git a/etc/upgrade/3.7.82/content b/etc/upgrade/3.7.82/content index a1c555f..da406c4 100644 --- a/etc/upgrade/3.7.82/content +++ b/etc/upgrade/3.7.82/content @@ -1,4 +1,7 @@ -@Attributes = ( +use strict; +use warnings; + +our @Attributes = ( { Name => 'Search - Bookmarked Tickets', Description => 'Bookmarked Tickets', #loc Content => diff --git a/etc/upgrade/3.7.85/content b/etc/upgrade/3.7.85/content index 49ab286..e9ec15c 100644 --- a/etc/upgrade/3.7.85/content +++ b/etc/upgrade/3.7.85/content @@ -1,9 +1,12 @@ -@Templates = ( - - { Queue => '0', - Name => 'Email Digest', # loc - Description => 'Email template for periodic notification digests', # loc - Content => q[Subject: RT Email Digest +use strict; +use warnings; + +our @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/etc/upgrade/3.7.86/content b/etc/upgrade/3.7.86/content index 94912d6..029939d 100644 --- a/etc/upgrade/3.7.86/content +++ b/etc/upgrade/3.7.86/content @@ -1,13 +1,16 @@ -@Final = ( +use strict; +use warnings; + +our @Final = ( sub { - $RT::Logger->debug("Adding search for bookmarked tickets to defaults"); + 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"); + RT->Logger->error("You have no global home page settings"); return; } my $content = $attr->Content; @@ -15,9 +18,9 @@ { type => 'system', name => 'Bookmarked Tickets' }; my ($status, $msg) = $attr->SetContent( $content ); - $RT::Logger->error($msg) unless $status; + RT->Logger->error($msg) unless $status; - $RT::Logger->debug("done."); + RT->Logger->debug("done."); return 1; }, ); diff --git a/etc/upgrade/3.7.87/content b/etc/upgrade/3.7.87/content index 0c677c4..c67feb4 100644 --- a/etc/upgrade/3.7.87/content +++ b/etc/upgrade/3.7.87/content @@ -1,4 +1,7 @@ -@Templates = ( +use strict; +use warnings; + +our @Templates = ( { Queue => 0, Name => "Error: Missing dashboard", # loc diff --git a/etc/upgrade/3.8.0/content b/etc/upgrade/3.8.0/content index 4fa5ac7..ae3a9fc 100644 --- a/etc/upgrade/3.8.0/content +++ b/etc/upgrade/3.8.0/content @@ -1,21 +1,24 @@ -@Final = ( +use strict; +use warnings; + +our @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'"); + 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."); + 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->error($msg) and return undef unless $status; - $RT::Logger->debug("Renamed."); + RT->Logger->debug("Renamed."); return 1; }, ); diff --git a/etc/upgrade/3.8.1/content b/etc/upgrade/3.8.1/content index 1667efa..08c6430 100644 --- a/etc/upgrade/3.8.1/content +++ b/etc/upgrade/3.8.1/content @@ -1,22 +1,25 @@ -@Final = ( +use strict; +use warnings; + +our @Final = ( sub { - $RT::Logger->debug("Going to adjust 'Bookmarked Tickets'"); + 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."); + 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->error($msg) and return undef unless $status; - $RT::Logger->debug("Fixed."); + RT->Logger->debug("Fixed."); return 1; }, ); diff --git a/etc/upgrade/3.8.2/content b/etc/upgrade/3.8.2/content index 0eef401..572e343 100644 --- a/etc/upgrade/3.8.2/content +++ b/etc/upgrade/3.8.2/content @@ -1,6 +1,9 @@ -@Initial = ( +use strict; +use warnings; + +our @Initial = ( sub { - $RT::Logger->warning( + RT->Logger->warning( "Going to add [OLD] prefix to all templates in approvals queue." ." If you have never used approvals, you can safely delete all the" ." templates with the [OLD] prefix. Leave the new Approval templates because" @@ -10,7 +13,7 @@ my $approvals_q = RT::Queue->new( RT->SystemUser ); $approvals_q->Load('___Approvals'); unless ( $approvals_q->id ) { - $RT::Logger->error("You have no approvals queue."); + RT->Logger->error("You have no approvals queue."); return 1; } @@ -19,19 +22,32 @@ 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"); + RT->Logger->error("Couldn't rename template #". $tmpl->id .": $msg"); } } return 1; }, -); -@ACL = ( - { GroupDomain => 'SystemInternal', - GroupType => 'privileged', - Right => 'ShowApprovalsTab', }, + + sub { + my $group = RT::Group->new( RT->SystemUser ); + $group->DBIx::SearchBuilder::Record::LoadByCols( + Domain => 'SystemInternal', + Type => 'Privileged', + ); + unless ($group->id) { + RT->Logger->warn("Failed to load Privilged group"); + return; + } + my ( $return, $msg ) = $group->PrincipalObj->GrantRight( + Right => 'ShowApprovalsTab', + Object => RT->System, + ); + RT->Logger->warn("Failed to grant ShowApprovalsTab right: $msg") + unless $return; + }, ); -@Templates = ( +our @Templates = ( { Queue => '___Approvals', Name => "New Pending Approval", # loc Description => @@ -106,9 +122,9 @@ The ticket has been approved, you may now start to act on it. }, ); -@Final = ( +our @Final = ( sub { - $RT::Logger->debug("Going to adjust dashboards"); + RT->Logger->debug("Going to adjust dashboards"); my $sys = RT::System->new(RT->SystemUser); my $attrs = RT::Attributes->new( RT->SystemUser ); @@ -116,7 +132,7 @@ The ticket has been approved, you may now start to act on it. my @dashboards = $attrs->Named('Dashboard'); if (@dashboards == 0) { - $RT::Logger->debug("You have no dashboards. Skipped."); + RT->Logger->debug("You have no dashboards. Skipped."); return 1; } @@ -140,28 +156,28 @@ The ticket has been approved, you may now start to act on it. }; } my ($status, $msg) = $attr->SetContent( $props ); - $RT::Logger->error($msg) unless $status; + RT->Logger->error($msg) unless $status; } - $RT::Logger->debug("Fixed."); + 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."); + 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."); + RT->Logger->error("Couldn't create temporary file."); return 0; } - $RT::Logger->warning( + RT->Logger->warning( "IMPORTANT: We're going to delete all scrips in Approvals queue" ." and save them in '$tmp_fn' file." ); @@ -169,17 +185,20 @@ The ticket has been approved, you may now start to act on it. require Data::Dumper; my $scrips = RT::Scrips->new( RT->SystemUser ); - $scrips->LimitToQueue( $approvals_q->id ); + $scrips->{'with_disabled_column'} = 0; + $scrips->Limit( FIELD => 'Queue', VALUE => $approvals_q->id ); while ( my $scrip = $scrips->Next ) { my %tmp = - map { $tmp->{ $_ } = $scrip->_Value( $_ ) } - $scrip->ReadableAttributes; + map { $_ => $scrip->_Value( $_ ) } + qw/id Description ScripCondition ScripAction + CustomIsApplicableCode CustomPrepareCode CustomCommitCode + Stage Queue Template Creator Created LastUpdatedBy LastUpdated/; print $tmp_fh Data::Dumper::Dumper( \%tmp ); - my ($status, $msg) = $scrip->Delete; + my ($status, $msg) = $scrip->DBIx::SearchBuilder::Record::Delete; unless ( $status ) { - $RT::Logger->error( "Couldn't delete scrip: $msg"); + RT->Logger->error( "Couldn't delete scrip: $msg"); } } }, diff --git a/etc/upgrade/3.8.3/content b/etc/upgrade/3.8.3/content index b8052ac..3147c87 100644 --- a/etc/upgrade/3.8.3/content +++ b/etc/upgrade/3.8.3/content @@ -1,4 +1,7 @@ -@ScripConditions = ( +use strict; +use warnings; + +our @ScripConditions = ( { Name => 'On Reject', # loc Description => 'Whenever a ticket is rejected', # loc ApplicableTransTypes => 'Status', @@ -8,9 +11,9 @@ }, ); -@Final = ( +our @Final = ( sub { - $RT::Logger->debug("Going to correct descriptions of notify actions in the DB"); + RT->Logger->debug("Going to correct descriptions of notify actions in the DB"); my $actions = RT::ScripActions->new( RT->SystemUser ); $actions->Limit( @@ -23,11 +26,11 @@ ); 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" ) + 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" ) + RT->Logger->warning( "Couldn't change action description: $msg" ) unless $status; } @@ -42,24 +45,24 @@ ); 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" ) + 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" ) + RT->Logger->warning( "Couldn't change action description: $msg" ) unless $status; } - $RT::Logger->debug("Corrected descriptions of notify actions in the DB."); + RT->Logger->debug("Corrected descriptions of notify actions in the DB."); return 1; }, ); - +our (@ScripActions, @Scrips); { -$RT::Logger->debug("Going to add in Extract Subject Tag actions if they were missed during a previous upgrade"); +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 ); +my $actions = RT::ScripActions->new( RT->SystemUser ); $actions->Limit( FIELD => 'ExecModule', VALUE => 'ExtractSubjectTag', @@ -67,10 +70,10 @@ $actions->Limit( 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"); + 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"); + 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 @@ -78,14 +81,24 @@ if ( $extract_action && $extract_action->Id ) { }, ); - $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' - }, - ); + RT->Logger->debug("Adding Extract Subject Tag Scrip"); + push @Final, sub { + my $action = RT::ScripAction->new( RT->SystemUser ); + $action->Load( 'Extract Subject Tag' ); + my $condition = RT::ScripCondition->new( RT->SystemUser ); + $condition->Load( 'On Transaction' ); + my $template = RT::Template->new( RT->SystemUser ); + $template->LoadByName( Name => 'Blank', Queue => 0 ); + my $scrip = RT::Scrip->new( RT->SystemUser ); + $scrip->RT::Record::Create( + Description => "On transaction, add any tags in the transaction's subject to the ticket's subject", + ScripCondition => $condition->id, + ScripAction => $action->id, + Template => $template->id, + Stage => 'TransactionCreate', + Queue => 0, + ); + }; } } diff --git a/etc/upgrade/3.8.4/content b/etc/upgrade/3.8.4/content index 14ecba4..ac490d3 100644 --- a/etc/upgrade/3.8.4/content +++ b/etc/upgrade/3.8.4/content @@ -1,8 +1,10 @@ +use strict; +use warnings; -@Final = ( + +our @Final = ( sub { - $RT::Logger->debug("Going to correct arguments of NotifyGroup actions if you have any"); - use strict; + RT->Logger->debug("Going to correct arguments of NotifyGroup actions if you have any"); my $actions = RT::ScripActions->new( RT->SystemUser ); $actions->Limit( @@ -50,7 +52,7 @@ 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" ) + RT->Logger->warning( "Couldn't change argument value of the action: $msg" ) unless $status; } }, diff --git a/etc/upgrade/3.8.6/content b/etc/upgrade/3.8.6/content index a9793c6..3651a66 100644 --- a/etc/upgrade/3.8.6/content +++ b/etc/upgrade/3.8.6/content @@ -1,4 +1,7 @@ -@Templates = ( +use strict; +use warnings; + +our @Templates = ( { Queue => 0, Name => "Forward Ticket", # loc Description => "Heading of a forwarded Ticket", # loc diff --git a/etc/upgrade/3.8.8/content b/etc/upgrade/3.8.8/content index cad77e9..50b3314 100644 --- a/etc/upgrade/3.8.8/content +++ b/etc/upgrade/3.8.8/content @@ -1,4 +1,7 @@ -@Initial = ( +use strict; +use warnings; + +our @Initial = ( sub { # make sure global CFs are not applied to local objects my $ocfs = RT::ObjectCustomFields->new( RT->SystemUser ); @@ -15,7 +18,7 @@ }, sub { # sort SortOrder - my $sth = $RT::Handle->dbh->prepare( + my $sth = RT->DatabaseHandle->dbh->prepare( "SELECT cfs.LookupType, ocfs.id" ." FROM ObjectCustomFields ocfs, CustomFields cfs" ." WHERE cfs.id = ocfs.CustomField" @@ -29,7 +32,7 @@ my $ocf = RT::ObjectCustomField->new( RT->SystemUser ); $ocf->Load( $id ); my ($status, $msg) = $ocf->SetSortOrder( $i++ ); - $RT::Logger->warning("Couldn't set SortOrder: $msg") + RT->Logger->warning("Couldn't set SortOrder: $msg") unless $status; $prev_type = $lt; } diff --git a/etc/upgrade/3.8.9/content b/etc/upgrade/3.8.9/content index 898c19e..cfd41c8 100644 --- a/etc/upgrade/3.8.9/content +++ b/etc/upgrade/3.8.9/content @@ -1,7 +1,9 @@ -@Initial = ( +use strict; +use warnings; + +our @Initial = ( sub { - use strict; - $RT::Logger->debug('Make sure local links are local'); + RT->Logger->debug('Make sure local links are local'); use RT::URI::fsck_com_rt; my $prefix = RT::URI::fsck_com_rt->LocalURIPrefix . '/ticket/'; diff --git a/etc/upgrade/3.9.1/content b/etc/upgrade/3.9.1/content index acdc0ad..3e35f47 100644 --- a/etc/upgrade/3.9.1/content +++ b/etc/upgrade/3.9.1/content @@ -1,7 +1,9 @@ -@Initial = ( +use strict; +use warnings; + +our @Initial = ( sub { - use strict; - $RT::Logger->debug('Make sure templates all have known types'); + RT->Logger->debug('Make sure templates all have known types'); # We update all NULL rows, below. We want to find non-NULL # rows, which weren't created by the current codebase running @@ -26,12 +28,11 @@ ); while (my $template = $templates->Next) { my ($status, $msg) = $template->SetType('Perl'); - $RT::Logger->warning( "Couldn't change Type of Template #" . $template->Id . ": $msg" ) unless $status; + RT->Logger->warning( "Couldn't change Type of Template #" . $template->Id . ": $msg" ) unless $status; } }, sub { - use strict; - $RT::Logger->debug('Adding ExecuteCode right to principals that currently have ModifyTemplate or ModifyScrips'); + RT->Logger->debug('Adding ExecuteCode right to principals that currently have ModifyTemplate or ModifyScrips'); my $acl = RT::ACL->new(RT->SystemUser); $acl->Limit( @@ -60,7 +61,7 @@ ); if (!$ok) { - $RT::Logger->warn("Unable to grant ExecuteCode on principal " . $principal->id . ": $msg"); + RT->Logger->warn("Unable to grant ExecuteCode on principal " . $principal->id . ": $msg"); } } }, diff --git a/etc/upgrade/3.9.2/content b/etc/upgrade/3.9.2/content index d0dbbfd..1851a9e 100644 --- a/etc/upgrade/3.9.2/content +++ b/etc/upgrade/3.9.2/content @@ -1,7 +1,9 @@ -@Initial = ( +use strict; +use warnings; + +our @Initial = ( sub { - use strict; - $RT::Logger->debug('Removing all delegated rights'); + RT->Logger->debug('Removing all delegated rights'); my $acl = RT::ACL->new(RT->SystemUser); $acl->Limit( CLAUSE => 'search', @@ -20,7 +22,7 @@ my ( $ok, $msg ) = $ace->Delete(); if ( !$ok ) { - $RT::Logger->warn( + RT->Logger->warn( "Unable to delete ACE " . $ace->id . ": " . $msg ); } } @@ -28,14 +30,15 @@ my $groups = RT::Groups->new(RT->SystemUser); $groups->Limit( FIELD => 'Domain', OPERATOR => '=', - VALUE => 'Personal' + VALUE => 'Personal', + CASESENSITIVE => 0, ); while ( my $group = $groups->Next ) { my $members = $group->MembersObj(); while ( my $member = $members->Next ) { my ( $ok, $msg ) = $group->DeleteMember( $member->MemberId ); if ( !$ok ) { - $RT::Logger->warn( "Unable to remove group member " + RT->Logger->warn( "Unable to remove group member " . $member->id . ": " . $msg ); } diff --git a/etc/upgrade/3.9.5/backcompat b/etc/upgrade/3.9.5/backcompat index 611ab51..ca0b289 100644 --- a/etc/upgrade/3.9.5/backcompat +++ b/etc/upgrade/3.9.5/backcompat @@ -1 +1,15 @@ -RT::ACE LastUpdated LastUpdatedBy Creator Created +my ($upgrade) = @_; + +my %removed; +my @fields = qw/LastUpdated LastUpdatedBy Creator Created/; + +RT::ACE->_BuildTableAttributes; +$RT::Logger->debug("Temporarily removing @fields from RT::ACE"); +$removed{$_} = delete $RT::Record::_TABLE_ATTR->{"RT::ACE"}{$_} + for @fields; + +$upgrade->(); + +# Put back the fields we chopped off +$RT::Record::_TABLE_ATTR->{"RT::ACE"}{$_} = $removed{$_} + for @fields; diff --git a/etc/upgrade/3.9.7/content b/etc/upgrade/3.9.7/content index 504ddf1..9b48b4b 100644 --- a/etc/upgrade/3.9.7/content +++ b/etc/upgrade/3.9.7/content @@ -1,24 +1,27 @@ +use strict; +use warnings; + my $move_attributes = sub { my ($table, $type, $column) = @_; my $query = "UPDATE $table SET $column = (SELECT Content FROM Attributes WHERE" ." Name = ? AND ObjectType = ? AND $table.id = Attributes.ObjectId)"; - my $res = $RT::Handle->SimpleQuery( $query, $column, $type ); + my $res = RT->DatabaseHandle->SimpleQuery( $query, $column, $type ); unless ( $res ) { - $RT::Logger->error("Failed to move $column on $type from Attributes into $table table"); + RT->Logger->error("Failed to move $column on $type from Attributes into $table table"); return; } $query = 'DELETE FROM Attributes WHERE Name = ? AND ObjectType = ?'; - $res = $RT::Handle->SimpleQuery( $query, $column, $type ); + $res = RT->DatabaseHandle->SimpleQuery( $query, $column, $type ); unless ( $res ) { - $RT::Logger->error("Failed to delete $column on $type from Attributes"); + RT->Logger->error("Failed to delete $column on $type from Attributes"); return; } return 1; }; -@Initial = ( +our @Initial = ( sub { return $move_attributes->( 'Users', 'RT::User', 'AuthToken'); }, @@ -26,7 +29,7 @@ my $move_attributes = sub { return $move_attributes->( 'CustomFields', 'RT::CustomField', 'RenderType'); }, sub { - my $cfs = RT::CustomFields->new($RT::SystemUser); + my $cfs = RT::CustomFields->new( RT->SystemUser ); $cfs->UnLimit; $cfs->FindAllRows; while ( my $cf = $cfs->Next ) { @@ -39,10 +42,10 @@ my $move_attributes = sub { next unless $attr; $cf->SetBasedOn($attr->Content); } - $query = 'DELETE FROM Attributes WHERE Name = ? AND ObjectType = ?'; - $res = $RT::Handle->SimpleQuery( $query, 'BasedOn', 'RT::CustomField' ); + my $query = 'DELETE FROM Attributes WHERE Name = ? AND ObjectType = ?'; + my $res = RT->DatabaseHandle->SimpleQuery( $query, 'BasedOn', 'RT::CustomField' ); unless ( $res ) { - $RT::Logger->error("Failed to delete BasedOn CustomFields from Attributes"); + RT->Logger->error("Failed to delete BasedOn CustomFields from Attributes"); return; } return 1; @@ -52,9 +55,9 @@ my $move_attributes = sub { or return; my $query = "UPDATE CustomFields SET ValuesClass = NULL WHERE ValuesClass = ?"; - my $res = $RT::Handle->SimpleQuery( $query, 'RT::CustomFieldValues' ); + my $res = RT->DatabaseHandle->SimpleQuery( $query, 'RT::CustomFieldValues' ); unless ( $res ) { - $RT::Logger->error("Failed to replace default with NULLs"); + RT->Logger->error("Failed to replace default with NULLs"); return; } return 1; @@ -68,13 +71,13 @@ my $move_attributes = sub { my $queue = RT::Queue->new( RT->SystemUser ); $queue->Load( $qid ); unless ( $queue->id ) { - $RT::Logger->warning("Couldn't load queue #$qid. Skipping..."); + RT->Logger->warning("Couldn't load queue #$qid. Skipping..."); next; } my ($status, $msg) = $queue->SetSubjectTag($tag); unless ( $status ) { - $RT::Logger->error("Couldn't set subject tag for queue #$qid: $msg"); + RT->Logger->error("Couldn't set subject tag for queue #$qid: $msg"); next; } } diff --git a/etc/upgrade/3.9.8/content b/etc/upgrade/3.9.8/content index 24242fd..0305aa9 100644 --- a/etc/upgrade/3.9.8/content +++ b/etc/upgrade/3.9.8/content @@ -1,4 +1,7 @@ -@Initial = sub { +use strict; +use warnings; + +our @Initial = sub { my $found_fm_tables = {}; foreach my $name ( $RT::Handle->_TableNames ) { next unless $name =~ /^fm_/i; @@ -8,15 +11,15 @@ return unless %$found_fm_tables; unless ( $found_fm_tables->{fm_topics} && $found_fm_tables->{fm_objecttopics} ) { - $RT::Logger->error("You appear to be upgrading from RTFM 2.0 - We don't support upgrading this old of an RTFM yet"); + RT->Logger->error("You appear to be upgrading from RTFM 2.0 - We don't support upgrading this old of an RTFM yet"); } - $RT::Logger->error("We found RTFM tables in your database. Checking for content."); + RT->Logger->error("We found RTFM tables in your database. Checking for content."); my $dbh = $RT::Handle->dbh; my $result = $dbh->selectall_arrayref("SELECT count(*) AS articlecount FROM FM_Articles", { Slice => {} } ); if ($result->[0]{articlecount} > 0) { - $RT::Logger->error("You appear to have RTFM Articles. You can upgrade using the etc/upgrade/upgrade-articles script. Read more about it in docs/UPGRADING-4.0"); + RT->Logger->error("You appear to have RTFM Articles. You can upgrade using the etc/upgrade/upgrade-articles script. Read more about it in docs/UPGRADING-4.0"); } }; diff --git a/etc/upgrade/4.0.0rc7/content b/etc/upgrade/4.0.0rc7/content index d0d210b..4fd63e7 100644 --- a/etc/upgrade/4.0.0rc7/content +++ b/etc/upgrade/4.0.0rc7/content @@ -1,19 +1,22 @@ -@Initial = ( +use strict; +use warnings; + +our @Initial = ( sub { - $RT::Logger->debug("Going to set lifecycle for approvals"); + RT->Logger->debug("Going to set lifecycle for approvals"); my $queue = RT::Queue->new( RT->SystemUser ); $queue->Load('___Approvals'); unless ( $queue->id ) { - $RT::Logger->warning("There is no ___Approvals queue in the DB"); + RT->Logger->warning("There is no ___Approvals queue in the DB"); return 1; } - return 1 if $queue->Lifecycle->Name eq 'approvals'; + return 1 if $queue->Lifecycle && $queue->Lifecycle eq 'approvals'; my ($status, $msg) = $queue->SetLifecycle('approvals'); unless ( $status ) { - $RT::Logger->error("Couldn't set lifecycle for '___Approvals' queue: $msg"); + RT->Logger->error("Couldn't set lifecycle for '___Approvals' queue: $msg"); return 0; } return 1; diff --git a/etc/upgrade/4.0.1/content b/etc/upgrade/4.0.1/content index 9b74ff1..cc3b5f1 100644 --- a/etc/upgrade/4.0.1/content +++ b/etc/upgrade/4.0.1/content @@ -1,40 +1,44 @@ -@Initial = ( +use strict; +use warnings; + +our @Initial = ( sub { - use strict; - $RT::Logger->debug('Removing all delegated rights'); + RT->Logger->debug('Removing all delegated rights'); my $acl = RT::ACL->new(RT->SystemUser); - my $groupjoin = $acl->NewAlias('Groups'); - $acl->Join( ALIAS1 => 'main', - FIELD1 => 'PrincipalId', - ALIAS2 => $groupjoin, - FIELD2 => 'id' - ); + my $groupjoin = $acl->Join( + ALIAS1 => 'main', + FIELD1 => 'PrincipalId', + TABLE2 => 'Groups', + FIELD2 => 'id', + ); $acl->Limit( ALIAS => $groupjoin, FIELD => 'Domain', OPERATOR => '=', VALUE => 'Personal', + CASESENSITIVE => 0, ); while ( my $ace = $acl->Next ) { my ( $ok, $msg ) = $ace->Delete(); if ( !$ok ) { - $RT::Logger->warn( "Unable to delete ACE " . $ace->id . ": " . $msg ); + RT->Logger->warn( "Unable to delete ACE " . $ace->id . ": " . $msg ); } } my $groups = RT::Groups->new(RT->SystemUser); $groups->Limit( FIELD => 'Domain', OPERATOR => '=', - VALUE => 'Personal' + VALUE => 'Personal', + CASESENSITIVE => 0, ); while ( my $group = $groups->Next ) { my $members = $group->MembersObj(); while ( my $member = $members->Next ) { my ( $ok, $msg ) = $group->DeleteMember( $member->MemberId ); if ( !$ok ) { - $RT::Logger->warn( "Unable to remove group member " + RT->Logger->warn( "Unable to remove group member " . $member->id . ": " . $msg ); } @@ -44,8 +48,7 @@ } }, sub { - use strict; - $RT::Logger->debug('Removing all Delegate and PersonalGroup rights'); + RT->Logger->debug('Removing all Delegate and PersonalGroup rights'); my $acl = RT::ACL->new(RT->SystemUser); for my $right (qw/AdminOwnPersonalGroups AdminAllPersonalGroups DelegateRights/) { @@ -54,16 +57,15 @@ while ( my $ace = $acl->Next ) { my ( $ok, $msg ) = $ace->Delete(); - $RT::Logger->debug("Removing ACE ".$ace->id." for right ".$ace->__Value('RightName')); + RT->Logger->debug("Removing ACE ".$ace->id." for right ".$ace->__Value('RightName')); if ( !$ok ) { - $RT::Logger->warn( "Unable to delete ACE " . $ace->id . ": " . $msg ); + RT->Logger->warn( "Unable to delete ACE " . $ace->id . ": " . $msg ); } } }, sub { - use strict; - $RT::Logger->debug('Removing unimplemented RejectTicket and ModifyTicketStatus rights'); + RT->Logger->debug('Removing unimplemented RejectTicket and ModifyTicketStatus rights'); my $acl = RT::ACL->new(RT->SystemUser); for my $right (qw/RejectTicket ModifyTicketStatus/) { @@ -72,10 +74,10 @@ while ( my $ace = $acl->Next ) { my ( $ok, $msg ) = $ace->Delete(); - $RT::Logger->debug("Removing ACE ".$ace->id." for right ".$ace->__Value('RightName')); + RT->Logger->debug("Removing ACE ".$ace->id." for right ".$ace->__Value('RightName')); if ( !$ok ) { - $RT::Logger->warn( "Unable to delete ACE " . $ace->id . ": " . $msg ); + RT->Logger->warn( "Unable to delete ACE " . $ace->id . ": " . $msg ); } } }, diff --git a/etc/upgrade/4.0.3/content b/etc/upgrade/4.0.3/content index 3e06c89..74870aa 100644 --- a/etc/upgrade/4.0.3/content +++ b/etc/upgrade/4.0.3/content @@ -1,4 +1,7 @@ -@ScripConditions = ( +use strict; +use warnings; + +our @ScripConditions = ( { Name => 'On Forward', # loc diff --git a/etc/upgrade/4.0.4/content b/etc/upgrade/4.0.4/content index fdfcb3e..4770289 100644 --- a/etc/upgrade/4.0.4/content +++ b/etc/upgrade/4.0.4/content @@ -1,4 +1,7 @@ -@Initial = ( +use strict; +use warnings; + +our @Initial = ( sub { use strict; my $templates = RT::Templates->new(RT->SystemUser); @@ -9,7 +12,7 @@ ); while (my $template = $templates->Next) { my ($status, $msg) = $template->SetType('Perl'); - $RT::Logger->warning( "Couldn't change Type of Template #" . $template->Id . ": $msg" ) unless $status; + RT->Logger->warning( "Couldn't change Type of Template #" . $template->Id . ": $msg" ) unless $status; } }, ); diff --git a/etc/upgrade/4.0.6/content b/etc/upgrade/4.0.6/content index dc1a009..ef01f4e 100644 --- a/etc/upgrade/4.0.6/content +++ b/etc/upgrade/4.0.6/content @@ -1,6 +1,9 @@ -@Initial = ( +use strict; +use warnings; + +our @Initial = ( sub { - my $txns = RT::Transactions->new( $RT::SystemUser ); + my $txns = RT::Transactions->new( RT->SystemUser ); $txns->Limit( FIELD => "ObjectType", VALUE => "RT::User", diff --git a/etc/upgrade/4.0.9/content b/etc/upgrade/4.0.9/content index f2abf62..6f526ce 100644 --- a/etc/upgrade/4.0.9/content +++ b/etc/upgrade/4.0.9/content @@ -1,6 +1,9 @@ -@Initial = ( +use strict; +use warnings; + +our @Initial = ( sub { - $RT::Logger->debug( + RT->Logger->debug( 'Going to update empty Queue Lifecycle column to "default"'); my $queues = RT::Queues->new( RT->SystemUser ); @@ -32,7 +35,8 @@ my $groups = RT::Groups->new(RT->SystemUser); $groups->Limit( FIELD => 'Domain', OPERATOR => '=', - VALUE => 'Personal' + VALUE => 'Personal', + CASESENSITIVE => 0, ); $groups->LimitToDeleted; while ( my $group = $groups->Next ) { @@ -40,7 +44,7 @@ while ( my $member = $members->Next ) { my ( $ok, $msg ) = $group->DeleteMember( $member->MemberId ); if ( !$ok ) { - $RT::Logger->warn( "Unable to remove group member " + RT->Logger->warn( "Unable to remove group member " . $member->id . ": " . $msg ); } diff --git a/etc/upgrade/4.1.0/content b/etc/upgrade/4.1.0/content new file mode 100644 index 0000000..2a02c68 --- /dev/null +++ b/etc/upgrade/4.1.0/content @@ -0,0 +1,43 @@ +use strict; +use warnings; + +our @Initial = ( + sub { + my $users = RT::Users->new(RT->SystemUser); + my $attributes = $users->Join( + ALIAS1 => "main", + FIELD1 => "id", + TABLE2 => "Attributes", + FIELD2 => "ObjectId", + ); + $users->Limit( + ALIAS => $attributes, + FIELD => "ObjectType", + VALUE => "RT::User", + ); + $users->Limit( + ALIAS => $attributes, + FIELD => "Name", + VALUE => RT::User::_PrefName('HomepageSettings'), + ); + + while (my $user = $users->Next) { + my $settings = $user->Preferences('HomepageSettings') + or next; + next if exists $settings->{sidebar}; + + $settings->{sidebar} = delete $settings->{summary}; + $user->SetPreferences('HomepageSettings', $settings); + } + }, + sub { + my ($default_portlets) = RT->System->Attributes->Named('HomepageSettings'); + my $settings = $default_portlets->Content; + return if exists $settings->{sidebar}; + + $settings->{sidebar} = delete $settings->{summary}; + $default_portlets->SetContent($settings); + }, +); + + diff --git a/etc/upgrade/4.1.1/acl.Pg b/etc/upgrade/4.1.1/acl.Pg new file mode 100644 index 0000000..9e8fc0a --- /dev/null +++ b/etc/upgrade/4.1.1/acl.Pg @@ -0,0 +1,31 @@ + +sub acl { + my $dbh = shift; + + my @acls; + + my @tables = qw ( + objectscrips_id_seq + ObjectScrips + ); + + my $db_user = RT->Config->Get('DatabaseUser'); + + my $sequence_right + = ( $dbh->{pg_server_version} >= 80200 ) + ? "USAGE, SELECT, UPDATE" + : "SELECT, UPDATE"; + + foreach my $table (@tables) { + # Tables are upper-case, sequences are lowercase in @tables + if ( $table =~ /^[a-z]/ ) { + 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/etc/upgrade/4.1.1/content b/etc/upgrade/4.1.1/content new file mode 100644 index 0000000..f3580bd --- /dev/null +++ b/etc/upgrade/4.1.1/content @@ -0,0 +1,36 @@ +use strict; +use warnings; + +our @Initial = ( + sub { + require RT::ObjectScrips; + foreach my $stage ('TransactionCreate', 'TransactionBatch') { + my $applications = RT::ObjectScrips->new( RT->SystemUser ); + $applications->Limit( FIELD => 'Stage', VALUE => $stage ); + my $alias = $applications->Join( + FIELD1 => 'Scrip', + TABLE2 => 'Scrips', FIELD2 => 'id' + ); + $applications->OrderByCols( + { ALIAS => $alias, FIELD => 'Description', ORDER => 'ASC' }, + ); + my %h; my $top_so = $h{0} = 0; + while ( my $record = $applications->Next ) { + my $oid = $record->ObjectId || 0; + + my $so; + unless ( $oid ) { + %h = (); $h{0} = $so = ++$top_so; + } + else { + $so = $h{ $oid } = ($h{$oid}||$h{0}) + 1; + } + next if $record->SortOrder == $so; + + my ($status, $msg) = $record->SetSortOrder($so); + RT->Logger->error("Couldn't set sort order: $msg") + unless $status; + } + } + }, +); diff --git a/etc/upgrade/4.1.1/schema.Oracle b/etc/upgrade/4.1.1/schema.Oracle new file mode 100644 index 0000000..180cf0c --- /dev/null +++ b/etc/upgrade/4.1.1/schema.Oracle @@ -0,0 +1,30 @@ +CREATE SEQUENCE OBJECTSCRIPS_seq; +CREATE TABLE ObjectScrips ( + id NUMBER(11,0) + CONSTRAINT ObjectScrips_Key PRIMARY KEY, + Scrip NUMBER(11,0) NOT NULL, + Stage VARCHAR2(32) DEFAULT 'TransactionCreate' NOT NULL, + ObjectId NUMBER(11,0) NOT NULL, + SortOrder NUMBER(11,0) DEFAULT 0 NOT NULL, + Creator NUMBER(11,0) DEFAULT 0 NOT NULL, + Created DATE, + LastUpdatedBy NUMBER(11,0) DEFAULT 0 NOT NULL, + LastUpdated DATE +); +ALTER TABLE Scrips ADD COLUMN Disabled int2 NOT NULL DEFAULT 0; + +INSERT INTO ObjectScrips( + id, Scrip, Stage, ObjectId, + Creator, Created, LastUpdatedBy, LastUpdated +) +(SELECT OBJECTSCRIPS_seq.nextval, id, Stage, Queue, Creator, Created, LastUpdatedBy, LastUpdated +FROM Scrips) +; + +UPDATE Scrips SET Disabled = 1 WHERE Stage = 'Disabled'; +UPDATE ObjectScrips SET Stage = 'TransactionCreate' WHERE Stage = 'Disabled'; + +CREATE UNIQUE INDEX ObjectScrips1 ON ObjectScrips (ObjectId, Scrip); + +ALTER TABLE Scrips DROP COLUMN Stage; +ALTER TABLE Scrips DROP COLUMN Queue; diff --git a/etc/upgrade/4.1.1/schema.Pg b/etc/upgrade/4.1.1/schema.Pg new file mode 100644 index 0000000..25fe3c1 --- /dev/null +++ b/etc/upgrade/4.1.1/schema.Pg @@ -0,0 +1,33 @@ +CREATE SEQUENCE objectscrips_id_seq; + +CREATE TABLE ObjectScrips ( + id INTEGER DEFAULT nextval('objectscrips_id_seq'), + Scrip integer NOT NULL, + Stage varchar(32) NOT NULL DEFAULT 'TransactionCreate' , + ObjectId integer NOT NULL, + SortOrder integer NOT NULL DEFAULT 0 , + + Creator integer NOT NULL DEFAULT 0 , + Created TIMESTAMP NULL , + LastUpdatedBy integer NOT NULL DEFAULT 0 , + LastUpdated TIMESTAMP NULL , + PRIMARY KEY (id) + +); +ALTER TABLE Scrips ADD COLUMN Disabled int2 NOT NULL DEFAULT 0; + +INSERT INTO ObjectScrips( + Scrip, Stage, ObjectId, + Creator, Created, LastUpdatedBy, LastUpdated +) +SELECT id, Stage, Queue, Creator, Created, LastUpdatedBy, LastUpdated +FROM Scrips +; + +UPDATE Scrips SET Disabled = 1 WHERE Stage = 'Disabled'; +UPDATE ObjectScrips SET Stage = 'TransactionCreate' WHERE Stage = 'Disabled'; + +CREATE UNIQUE INDEX ObjectScrips1 ON ObjectScrips (ObjectId, Scrip); + +ALTER TABLE Scrips DROP COLUMN Stage; +ALTER TABLE Scrips DROP COLUMN Queue; diff --git a/etc/upgrade/4.1.1/schema.SQLite b/etc/upgrade/4.1.1/schema.SQLite new file mode 100644 index 0000000..a3a34f0 --- /dev/null +++ b/etc/upgrade/4.1.1/schema.SQLite @@ -0,0 +1,31 @@ + +CREATE TABLE ObjectScrips ( + id INTEGER NOT NULL , + Scrip int NOT NULL , + Stage varchar(32) NOT NULL DEFAULT 'TransactionCreate' , + 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) +); +ALTER TABLE Scrips ADD COLUMN Disabled int2 NOT NULL DEFAULT 0; + +INSERT INTO ObjectScrips( + Scrip, Stage, ObjectId, + Creator, Created, LastUpdatedBy, LastUpdated +) +SELECT id, Stage, Queue, Creator, Created, LastUpdatedBy, LastUpdated +FROM Scrips +; + +UPDATE Scrips SET Disabled = 1 WHERE Stage = 'Disabled'; +UPDATE ObjectScrips SET Stage = 'TransactionCreate' WHERE Stage = 'Disabled'; + +CREATE UNIQUE INDEX ObjectScrips1 ON ObjectScrips (ObjectId, Scrip); + +# TODO: ALTER TABLE Scrips DROP COLUMN Stage; +# TODO: ALTER TABLE Scrips DROP COLUMN Queue; diff --git a/etc/upgrade/4.1.1/schema.mysql b/etc/upgrade/4.1.1/schema.mysql new file mode 100644 index 0000000..d285019 --- /dev/null +++ b/etc/upgrade/4.1.1/schema.mysql @@ -0,0 +1,30 @@ +CREATE TABLE ObjectScrips ( + id INTEGER NOT NULL AUTO_INCREMENT, + Scrip integer NOT NULL , + Stage varchar(32) CHARACTER SET ascii NOT NULL DEFAULT 'TransactionCreate', + 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) +) ENGINE=InnoDB CHARACTER SET utf8; +ALTER TABLE Scrips ADD COLUMN Disabled int2 NOT NULL DEFAULT 0; + +INSERT INTO ObjectScrips( + Scrip, Stage, ObjectId, + Creator, Created, LastUpdatedBy, LastUpdated +) +SELECT id, Stage, Queue, Creator, Created, LastUpdatedBy, LastUpdated +FROM Scrips +; + +UPDATE Scrips SET Disabled = 1 WHERE Stage = 'Disabled'; +UPDATE ObjectScrips SET Stage = 'TransactionCreate' WHERE Stage = 'Disabled'; + +CREATE UNIQUE INDEX ObjectScrips1 ON ObjectScrips (ObjectId, Scrip); + +ALTER TABLE Scrips DROP COLUMN Stage; +ALTER TABLE Scrips DROP COLUMN Queue; diff --git a/etc/upgrade/4.1.10/schema.Oracle b/etc/upgrade/4.1.10/schema.Oracle new file mode 100644 index 0000000..93f036f --- /dev/null +++ b/etc/upgrade/4.1.10/schema.Oracle @@ -0,0 +1 @@ +-- No update is necessary, given that '' == NULL on Oracle diff --git a/etc/upgrade/4.1.10/schema.Pg b/etc/upgrade/4.1.10/schema.Pg new file mode 100644 index 0000000..af862b6 --- /dev/null +++ b/etc/upgrade/4.1.10/schema.Pg @@ -0,0 +1 @@ +UPDATE ObjectCustomFieldValues SET Content = NULL WHERE LargeContent IS NOT NULL AND Content = ''; diff --git a/etc/upgrade/4.1.10/schema.mysql b/etc/upgrade/4.1.10/schema.mysql new file mode 100644 index 0000000..af862b6 --- /dev/null +++ b/etc/upgrade/4.1.10/schema.mysql @@ -0,0 +1 @@ +UPDATE ObjectCustomFieldValues SET Content = NULL WHERE LargeContent IS NOT NULL AND Content = ''; diff --git a/etc/upgrade/4.1.11/schema.Oracle b/etc/upgrade/4.1.11/schema.Oracle new file mode 100644 index 0000000..6ae68bd --- /dev/null +++ b/etc/upgrade/4.1.11/schema.Oracle @@ -0,0 +1 @@ +ALTER TABLE CustomFields DROP COLUMN Repeated; diff --git a/etc/upgrade/4.1.11/schema.Pg b/etc/upgrade/4.1.11/schema.Pg new file mode 100644 index 0000000..6ae68bd --- /dev/null +++ b/etc/upgrade/4.1.11/schema.Pg @@ -0,0 +1 @@ +ALTER TABLE CustomFields DROP COLUMN Repeated; diff --git a/etc/upgrade/4.1.11/schema.mysql b/etc/upgrade/4.1.11/schema.mysql new file mode 100644 index 0000000..6ae68bd --- /dev/null +++ b/etc/upgrade/4.1.11/schema.mysql @@ -0,0 +1 @@ +ALTER TABLE CustomFields DROP COLUMN Repeated; diff --git a/etc/upgrade/4.1.12/content b/etc/upgrade/4.1.12/content new file mode 100644 index 0000000..1f0473d --- /dev/null +++ b/etc/upgrade/4.1.12/content @@ -0,0 +1,10 @@ +use strict; +use warnings; + +our @ACL = ( { + Right => 'ShowArticlesMenu', + GroupDomain => 'SystemInternal', + GroupType => 'Privileged', +} ); + +1; diff --git a/etc/upgrade/4.1.13/backcompat b/etc/upgrade/4.1.13/backcompat new file mode 100644 index 0000000..b4e69a3 --- /dev/null +++ b/etc/upgrade/4.1.13/backcompat @@ -0,0 +1,31 @@ +my $upgrade = shift; + +my $groups = RT::Groups->new( RT->SystemUser ); +$groups->Limit( + FIELD => 'Name', OPERATOR => '!=', VALUE => 'main.Type', QUOTEVALUE => 0 +); +$groups->Limit( + FIELD => 'Domain', + VALUE => 'SystemInternal', + CASESENSITIVE => 0, +); +$groups->RowsPerPage(1); +if ( $groups->Next ) { + my $dbh = $RT::Handle->dbh; + my $db_type = RT->Config->Get('DatabaseType'); + if ( $db_type eq 'Oracle' || $db_type eq 'Pg' ) { + $dbh->do( + "UPDATE Groups SET Name = Type + WHERE LOWER(Domain) IN ('aclequivalence', 'systeminternal') + OR LOWER(Domain) LIKE '%-role'" + ); + } else { + $dbh->do( + "UPDATE Groups SET Name = Type + WHERE Domain IN ('ACLEquivalence', 'SystemInternal') + OR Domain LIKE '%-Role'" + ); + } +} + +$upgrade->(); diff --git a/etc/upgrade/4.1.13/schema.Oracle b/etc/upgrade/4.1.13/schema.Oracle new file mode 100644 index 0000000..96869c6 --- /dev/null +++ b/etc/upgrade/4.1.13/schema.Oracle @@ -0,0 +1,3 @@ +UPDATE Groups SET Name = Type +WHERE LOWER(Domain) IN ('aclequivalence', 'systeminternal') OR LOWER(Domain) LIKE '%-role'; + diff --git a/etc/upgrade/4.1.13/schema.Pg b/etc/upgrade/4.1.13/schema.Pg new file mode 100644 index 0000000..96869c6 --- /dev/null +++ b/etc/upgrade/4.1.13/schema.Pg @@ -0,0 +1,3 @@ +UPDATE Groups SET Name = Type +WHERE LOWER(Domain) IN ('aclequivalence', 'systeminternal') OR LOWER(Domain) LIKE '%-role'; + diff --git a/etc/upgrade/4.1.13/schema.SQLite b/etc/upgrade/4.1.13/schema.SQLite new file mode 100644 index 0000000..9ea6a91 --- /dev/null +++ b/etc/upgrade/4.1.13/schema.SQLite @@ -0,0 +1,3 @@ +UPDATE Groups SET Name = Type +WHERE Domain IN ('ACLEquivalence', 'SystemInternal') OR Domain LIKE '%-Role'; + diff --git a/etc/upgrade/4.1.13/schema.mysql b/etc/upgrade/4.1.13/schema.mysql new file mode 100644 index 0000000..a429007 --- /dev/null +++ b/etc/upgrade/4.1.13/schema.mysql @@ -0,0 +1,2 @@ +UPDATE Groups SET Name = Type +WHERE Domain IN ('ACLEquivalence', 'SystemInternal') OR Domain LIKE '%-Role'; \ No newline at end of file diff --git a/etc/upgrade/4.1.14/schema.Oracle b/etc/upgrade/4.1.14/schema.Oracle new file mode 100644 index 0000000..5c4609c --- /dev/null +++ b/etc/upgrade/4.1.14/schema.Oracle @@ -0,0 +1,2 @@ +ALTER TABLE Scrips DROP COLUMN ConditionRules; +ALTER TABLE Scrips DROP COLUMN ActionRules; diff --git a/etc/upgrade/4.1.14/schema.Pg b/etc/upgrade/4.1.14/schema.Pg new file mode 100644 index 0000000..5c4609c --- /dev/null +++ b/etc/upgrade/4.1.14/schema.Pg @@ -0,0 +1,2 @@ +ALTER TABLE Scrips DROP COLUMN ConditionRules; +ALTER TABLE Scrips DROP COLUMN ActionRules; diff --git a/etc/upgrade/4.1.14/schema.mysql b/etc/upgrade/4.1.14/schema.mysql new file mode 100644 index 0000000..5c4609c --- /dev/null +++ b/etc/upgrade/4.1.14/schema.mysql @@ -0,0 +1,2 @@ +ALTER TABLE Scrips DROP COLUMN ConditionRules; +ALTER TABLE Scrips DROP COLUMN ActionRules; diff --git a/etc/upgrade/4.1.15/content b/etc/upgrade/4.1.15/content new file mode 100644 index 0000000..3e1f1d5 --- /dev/null +++ b/etc/upgrade/4.1.15/content @@ -0,0 +1,22 @@ +use strict; +use warnings; + +our @ScripActions = ( + { Name => 'Notify Owner and AdminCcs', # loc + Description => 'Sends mail to the Owner and administrative Ccs', # loc + ExecModule => 'Notify', + Argument => 'Owner,AdminCc' }, +); + +our @Templates = ( + # Shadow the global templates of the same name to suppress duplicate + # notifications until rules is ripped out. + { Queue => "___Approvals", + Name => "Transaction in HTML", + Content => "", + }, + { Queue => "___Approvals", + Name => "Transaction", + Content => "", + }, +); diff --git a/etc/upgrade/4.1.16/content b/etc/upgrade/4.1.16/content new file mode 100644 index 0000000..44f2129 --- /dev/null +++ b/etc/upgrade/4.1.16/content @@ -0,0 +1,16 @@ +use strict; +use warnings; + +our @Templates = ( + { Queue => '0', + Name => 'Reminder', # loc + Description => 'Default reminder template', # loc + Content => +'Subject:{$Ticket->Subject} is due {$Ticket->DueObj->AsString} + +This reminder is for ticket #{$Target = $Ticket->RefersTo->First->TargetObj;$Target->Id}. + +{RT->Config->Get(\'WebURL\')}Ticket/Display.html?id={$Target->Id} +' + }, +); diff --git a/etc/upgrade/4.1.17/content b/etc/upgrade/4.1.17/content new file mode 100644 index 0000000..3454f78 --- /dev/null +++ b/etc/upgrade/4.1.17/content @@ -0,0 +1,26 @@ +use strict; +use warnings; + +our @Initial = (sub { + my $searches = RT::Attributes->new(RT->SystemUser); + $searches->Limit( FIELD => 'Name', VALUE => 'SavedSearch' ); + $searches->OrderBy( FIELD => 'id' ); + + while (my $search = $searches->Next) { + my $content = $search->Content; + next unless ref $content eq 'HASH'; + next unless ($content->{SearchType} || '') eq 'Chart'; + + # Switch from PrimaryGroupBy to GroupBy name + # Switch from "CreatedMonthly" to "Created.Monthly" + $content->{GroupBy} ||= [delete $content->{PrimaryGroupBy}]; + for (@{$content->{GroupBy}}) { + next if /\./; + s/(?<=[a-z])(?=[A-Z])/./; + } + + my ($ok, $msg) = $search->SetContent($content); + RT->Logger->error("Unable to upgrade saved chart #@{[$search->id]}: $msg") + unless $ok; + } +}); diff --git a/etc/upgrade/4.1.18/content b/etc/upgrade/4.1.18/content new file mode 100644 index 0000000..818351b --- /dev/null +++ b/etc/upgrade/4.1.18/content @@ -0,0 +1,16 @@ +use strict; +use warnings; + +# Ticket-level notifications +our @ScripActions = ({ + Name => 'On SetStarted Open Ticket', + Description => 'When Started is Updated Set Ticket Status to Open', + ExecModule => 'OpenOnStarted', +}); + +our @Scrips = ({ + Description => "On transaction and SetStarted Open Ticket", + ScripCondition => 'On Transaction', + ScripAction => 'On SetStarted Open Ticket', + Template => 'Blank' +}); diff --git a/etc/upgrade/4.1.19/schema.Oracle b/etc/upgrade/4.1.19/schema.Oracle new file mode 100644 index 0000000..4e938e1 --- /dev/null +++ b/etc/upgrade/4.1.19/schema.Oracle @@ -0,0 +1,2 @@ +ALTER TABLE Templates DROP COLUMN Language; +ALTER TABLE Templates DROP COLUMN TranslationOf; diff --git a/etc/upgrade/4.1.19/schema.Pg b/etc/upgrade/4.1.19/schema.Pg new file mode 100644 index 0000000..4e938e1 --- /dev/null +++ b/etc/upgrade/4.1.19/schema.Pg @@ -0,0 +1,2 @@ +ALTER TABLE Templates DROP COLUMN Language; +ALTER TABLE Templates DROP COLUMN TranslationOf; diff --git a/etc/upgrade/4.1.19/schema.mysql b/etc/upgrade/4.1.19/schema.mysql new file mode 100644 index 0000000..4e938e1 --- /dev/null +++ b/etc/upgrade/4.1.19/schema.mysql @@ -0,0 +1,2 @@ +ALTER TABLE Templates DROP COLUMN Language; +ALTER TABLE Templates DROP COLUMN TranslationOf; diff --git a/etc/upgrade/4.1.20/content b/etc/upgrade/4.1.20/content new file mode 100644 index 0000000..afed9c3 --- /dev/null +++ b/etc/upgrade/4.1.20/content @@ -0,0 +1,56 @@ +use strict; +use warnings; + +our @ScripActions = ( + { Name => 'Send Forward', + Description => 'Send forwarded message', + ExecModule => 'SendForward', }, +); + +our @Scrips = ( + { Description => 'On Forward Transaction Send forwarded message', + ScripCondition => 'On Forward Transaction', + ScripAction => 'Send Forward', + Template => 'Forward' }, + { Description => 'On Forward Ticket Send forwarded message', + ScripCondition => 'On Forward Ticket', + ScripAction => 'Send Forward', + Template => 'Forward Ticket' }, +); + +our @Initial = ( + sub { + my $forward_template = RT::Template->new(RT->SystemUser); + $forward_template->Load('Forward'); + $forward_template->SetDescription('Forwarded message'); + + if ( $forward_template->Content =~ + m/^\n*This is (a )?forward of transaction #\{\s*\$Transaction->id\s*\} of (a )?ticket #\{\s*\$Ticket->id\s*\}\n*$/ + ) { + $forward_template->SetContent(q{ + +{ $ForwardTransaction->Content =~ /\S/ ? $ForwardTransaction->Content : "This is a forward of transaction #".$Transaction->id." of ticket #". $Ticket->id } +}); + } + else { + RT->Logger->error('Current "Forward" template is not the default version, please check docs/4.2-UPGRADING'); + } + + my $forward_ticket_template = RT::Template->new(RT->SystemUser); + $forward_ticket_template->Load('Forward Ticket'); + $forward_ticket_template->SetDescription('Forwarded ticket message'); + if ( $forward_ticket_template->Content eq q{ + +This is a forward of ticket #{ $Ticket->id } +} ) { + $forward_ticket_template->SetContent(q{ + +{ $ForwardTransaction->Content =~ /\S/ ? $ForwardTransaction->Content : "This is a forward of ticket #". $Ticket->id } +}); + + } + else { + RT->Logger->error('Current "Forward Ticket" template is not the default version, please check docs/4.2-UPGRADING'); + } + }, +); diff --git a/etc/upgrade/4.1.21/content b/etc/upgrade/4.1.21/content new file mode 100644 index 0000000..dbf75c7 --- /dev/null +++ b/etc/upgrade/4.1.21/content @@ -0,0 +1,64 @@ +use strict; +use warnings; + +sub dashboards_for_object { + my $object = shift; + my $user = shift; + my %dashboards; + my $privacy = RT::Dashboard->_build_privacy($object); + + while ( my $attr = $object->Attributes->Next ) { + if ( $attr->Name =~ /^Dashboard\b/ ) { + my $dashboard = RT::Dashboard->new($user); + my ( $ok, $msg ) = $dashboard->Load( $privacy, $attr->id ); + next unless $ok; + + if ( $object->isa('RT::System') ) { + push @{ $dashboards{system} }, $dashboard; + } + elsif ( $object->isa('RT::User') ) { + push @{ $dashboards{personal} }, $dashboard; + } + elsif ( $object->isa('RT::Group') ) { + push @{ $dashboards{group}{ $object->Name } }, $dashboard; + } + } + } + return \%dashboards; +} + +our @Final = ( + sub { + my $users = RT::Users->new( RT->SystemUser ); + $users->LimitToPrivileged(); + while ( my $user = $users->Next ) { + my @objs = RT::Dashboard->new($user)->ObjectsForLoading( IncludeSuperuserGroups => 0 ); + + my %dashboard_map; + + for my $object (@objs) { + my $dashboards = dashboards_for_object( $object, $user ); + push @{ $dashboard_map{$_} }, @{ $dashboards->{$_} || [] } for qw/personal system/; + + push @{ $dashboard_map{group}{$_} }, @{ $dashboards->{group}{$_} } + for keys %{ $dashboards->{group} || {} }; + } + + my @dashboards = ( + ( sort { $a->Id <=> $b->Id } @{ $dashboard_map{personal} || [] } ), + ( sort { $a->Id <=> $b->Id } @{ $dashboard_map{system} || [] } ), + + map { + sort { $a->Id <=> $b->Id } + @{ $dashboard_map{group}{$_} } + } + keys %{ $dashboard_map{group} || {} }, + ); + + splice @dashboards, 7 if @dashboards > 7; + @dashboards = map { $_->id } @dashboards; + my ( $ret, $msg ) = $user->SetPreferences( 'DashboardsInMenu', { dashboards => \@dashboards } ); + RT->Logger->error( $msg ) unless $ret; + } + }, +); diff --git a/etc/upgrade/4.1.22/content b/etc/upgrade/4.1.22/content new file mode 100644 index 0000000..c9f18ff --- /dev/null +++ b/etc/upgrade/4.1.22/content @@ -0,0 +1,85 @@ +use strict; +use warnings; + +our @Initial = ( + sub { + my $template = RT::Template->new( RT->SystemUser ); + $template->Load("Error: bad GnuPG data"); + unless ($template->id) { + RT->Logger->error( "Couldn't find 'Error: bad GnuPG data' template to rename" ); + return; + } + + my ($ok, $msg) = $template->SetName("Error: bad encrypted data"); + RT->Logger->error( "Couldn't rename 'Error: bad GnuPG data' template: $msg") + unless $ok; + + ($ok, $msg) = $template->SetDescription("Inform user that a message he sent has invalid encryption data"); + RT->Logger->error( "Couldn't update 'Error: bad encrypted data' template description: $msg") + unless $ok; + + my $content = $template->Content; + $content =~ s/GnuPG signature/signature/g; + ($ok, $msg) = $template->SetContent( $content ); + RT->Logger->error( "Couldn't update 'Error: bad encrypted data' template content: $msg") + unless $ok; + }, + sub { + my $type = RT::User->new( $RT::SystemUser )->CustomFieldLookupType; + my $cf = RT::CustomField->new( $RT::SystemUser ); + $cf->LoadByCols( Name => 'SMIME Key', LookupType => $type ); + $cf->LoadByCols( Name => 'PublicKey', LookupType => $type ) unless $cf->id; + unless ( $cf->id ) { + $RT::Logger->debug("You don't have an 'SMIME Key' or 'PublicKey' user CF -- nothing to do."); + return; + } + + my $users = RT::Users->new( RT->SystemUser ); + $users->LimitCustomField( + CUSTOMFIELD => $cf->id, + OPERATOR => "IS NOT", + VALUE => "NULL", + ); + while (my $u = $users->Next) { + $u->SetSMIMECertificate( + $u->FirstCustomFieldValue( $cf->id ), + ); + } + + my $ocfs = $cf->AddedTo; + while (my $ocf = $ocfs->Next) { + my ($ok, $msg) = $ocf->Delete; + RT->Logger->error( "Couldn't delete OCF ".$ocf->id." while deleting ".$cf->Name." CF: $msg") + unless $ok; + } + + my ($ok, $msg) = $cf->Delete; + RT->Logger->error( "Couldn't delete ".$cf->Name." CF: $msg") + unless $ok; + }, + sub { + $RT::Logger->info("Going to delete all SMIMEKeyNotAfter attributes"); + my $attrs = RT::Attributes->new( $RT::SystemUser ); + $attrs->Limit( FIELD => 'ObjectType', VALUE => 'RT::User' ); + $attrs->Limit( FIELD => 'Name', VALUE => 'SMIMEKeyNotAfter' ); + while ( my $attr = $attrs->Next ) { + my ($status, $msg) = $attr->Delete; + unless ( $status ) { + $RT::Logger->error("Couldn't delete attribute: $msg"); + } + } + return 1; + }, +); + +our @Templates = ( + { Queue => 0, + Name => "Error: unencrypted message", # loc + Description => + "Inform user that their unencrypted mail has been rejected", # loc + Content => q{Subject: RT requires that all incoming mail be encrypted + +You received this message because RT received mail from you that was not encrypted. As such, it has been rejected. +} + }, +); diff --git a/etc/upgrade/4.1.22/schema.Oracle b/etc/upgrade/4.1.22/schema.Oracle new file mode 100644 index 0000000..6ce5646 --- /dev/null +++ b/etc/upgrade/4.1.22/schema.Oracle @@ -0,0 +1 @@ +ALTER TABLE Users ADD COLUMN SMIMECertificate CLOB; diff --git a/etc/upgrade/4.1.22/schema.Pg b/etc/upgrade/4.1.22/schema.Pg new file mode 100644 index 0000000..7da9d2c --- /dev/null +++ b/etc/upgrade/4.1.22/schema.Pg @@ -0,0 +1 @@ +ALTER TABLE Users ADD COLUMN SMIMECertificate TEXT NULL; diff --git a/etc/upgrade/4.1.22/schema.SQLite b/etc/upgrade/4.1.22/schema.SQLite new file mode 100644 index 0000000..3b7d6ce --- /dev/null +++ b/etc/upgrade/4.1.22/schema.SQLite @@ -0,0 +1 @@ +ALTER TABLE Users ADD COLUMN SMIMECertificate TEXT COLLATE NOCASE NULL; diff --git a/etc/upgrade/4.1.22/schema.mysql b/etc/upgrade/4.1.22/schema.mysql new file mode 100644 index 0000000..7da9d2c --- /dev/null +++ b/etc/upgrade/4.1.22/schema.mysql @@ -0,0 +1 @@ +ALTER TABLE Users ADD COLUMN SMIMECertificate TEXT NULL; diff --git a/etc/upgrade/4.1.23/indexes b/etc/upgrade/4.1.23/indexes new file mode 100644 index 0000000..d76264f --- /dev/null +++ b/etc/upgrade/4.1.23/indexes @@ -0,0 +1,169 @@ +use strict; +use warnings; + +# groups table +{ + foreach my $name ( qw(Groups1 Groups2 Groups3) ) { + my ($status, $msg) = $RT::Handle->DropIndexIfExists( + Table => 'Groups', Name => $name, + ); + my $method = $status ? 'debug' : 'warning'; + RT->Logger->$method($msg); + } + + my ($name, $msg) = $RT::Handle->CreateIndex( + Table => 'Groups', + Columns => [qw(Domain Type Instance)], + CaseInsensitive => { domain => 1, type => 1 }, + ); + my $method = $name ? 'debug' : 'warning'; + RT->Logger->$method($msg); + + ($name, $msg) = $RT::Handle->CreateIndex( + Table => 'Groups', + Columns => [qw(Domain Name Instance)], + CaseInsensitive => { domain => 1, name => 1 }, + ); + $method = $name ? 'debug' : 'warning'; + RT->Logger->$method($msg); + + ($name, $msg) = $RT::Handle->CreateIndex( + Table => 'Groups', + Columns => [qw(Instance)], + ); + $method = $name ? 'debug' : 'warning'; + RT->Logger->$method($msg); +} + +my $dedup = sub { + my ($table, $column) = (@_); + + my $collection_class = "RT::$table"; + my $record_class = $collection_class; + $record_class =~ s/s$//; + + my $sql; + + my $cs = $RT::Handle->CaseSensitive; + if ($cs) { + $sql = "SELECT DISTINCT LOWER(t1.$column) FROM $table t1, $table t2" + ." WHERE LOWER(t1.$column) = LOWER(t2.$column)" + .' AND t1.id != t2.id'; + } else { + $sql = "SELECT DISTINCT t1.$column FROM $table t1, $table t2" + ." WHERE t1.$column = t2.$column" + .' AND t1.id != t2.id'; + } + + my $dbh = $RT::Handle->dbh; + my $sth = $dbh->prepare($sql); + $sth->execute; + + my $found = 0; + while ( my ($value) = $sth->fetchrow_array ) { + $found = 1; + + my $ids = $dbh->selectcol_arrayref( + "SELECT id FROM $table WHERE ". ($cs? "LOWER($column)" : $column) ." = LOWER(?)", + undef, + $value + ); + + # skip first + shift @$ids; + + foreach my $id ( @$ids ) { + RT->Logger->debug("Changing $column of $record_class #". $id ); + $dbh->do("UPDATE $table SET $column = ? WHERE id = ?", undef, $value . '-dup-'.$id, $id); + } + } + + if ( $found ) { + RT->Logger->warning( + "Records in $table table had non-unique values in $column column." + ." $column has been changed for such records, and now matches '%-dup-%'" + ); + } +}; + +# a few case insensitive and unique indexes +{ + my @list = ( + { Table => 'Queues', Column => 'Name' }, + { Table => 'Users', Column => 'Name' }, + ); + foreach my $e (@list) { + RT->Logger->debug("Checking index on ". $e->{'Column'} ." in ". $e->{'Table'} ); + my ($index) = $RT::Handle->IndexesThatBeginWith( + Table => $e->{'Table'}, Columns => [$e->{'Column'}] + ); + $index = undef if $index && @{$index->{'Columns'}}>1; + if ( + $index && $index->{'Unique'} + && ($RT::Handle->CaseSensitive? $index->{'CaseInsensitive'}{ lc $e->{'Column'} } : 1 ) + ) { + RT->Logger->debug("Required index exists. Skipping."); + next; + } + + $dedup->( $e->{'Table'}, $e->{'Column'} ); + + if ( $index ) { + my ($status, $msg) = $RT::Handle->DropIndex( + Table => $e->{'Table'}, Name => $index->{'Name'}, + ); + my $method = $status ? 'debug' : 'warning'; + RT->Logger->$method($msg); + } + + my ($status, $msg) = $RT::Handle->CreateIndex( + Table => $e->{'Table'}, Columns => [$e->{'Column'}], + Unique => 1, CaseInsensitive => { lc $e->{'Column'} => 1 }, + ); + my $method = $status ? 'debug' : 'warning'; + RT->Logger->$method($msg); + } +} + +# cached group members +{ + $RT::Handle->MakeSureIndexExists( + Table => 'CachedGroupMembers', + Columns => ['MemberId', 'ImmediateParentId'], + ); + $RT::Handle->MakeSureIndexExists( + Table => 'CachedGroupMembers', + Columns => ['MemberId', 'GroupId'], + Optional => ['Disabled'], + ); + $RT::Handle->DropIndexesThatArePrefix( + Table => 'CachedGroupMembers', + Columns => ['MemberId', 'GroupId', 'Disabled'], + ); + $RT::Handle->MakeSureIndexExists( + Table => 'CachedGroupMembers', + Columns => ['GroupId', 'MemberId'], + Optional => ['Disabled'], + ); + $RT::Handle->DropIndexesThatArePrefix( + Table => 'CachedGroupMembers', + Columns => ['GroupId', 'MemberId', 'Disabled'], + ); +} + +# drop indexes that start with 'id' column +foreach my $table ('Users', 'Tickets') { + my @list = $RT::Handle->IndexesThatBeginWith( + Table => $table, Columns => ['id'], + ); + @list = grep @{ $_->{'Columns'} } > 1, @list; + + foreach my $index (@list) { + my ($status, $msg) = $RT::Handle->DropIndex( + Table => $table, Name => $index->{'Name'}, + ); + RT->Logger->info($msg); + } +} + +1; diff --git a/etc/upgrade/4.1.4/content b/etc/upgrade/4.1.4/content new file mode 100644 index 0000000..b320695 --- /dev/null +++ b/etc/upgrade/4.1.4/content @@ -0,0 +1,49 @@ +use strict; +use warnings; + +our (@Final); + +push @Final, sub { + my %global = %{ RT->System->AvailableRights }; + my $handle = RT->DatabaseHandle; + + for my $role (RT::System->Roles) { + my $group = RT::Group->new( RT->SystemUser ); + my ($ok, $msg) = $group->LoadRoleGroup( + Object => RT->System, + Name => $role, + ); + + unless ($group->id) { + RT->Logger->error("Can't load role group $role: $msg"); + next; + } + + my %rights = %{ RT->System->AvailableRights( $group->PrincipalObj ) }; + + # Global rights which aren't available on the role anymore + my @remove = grep { not $rights{$_} } + keys %global; + my $placeholders = join ",", map { "?" } 1 .. scalar @remove; + + my $query = <<" SQL"; + DELETE FROM ACL + WHERE PrincipalType = ? + AND PrincipalId = ? + AND ObjectType = 'RT::System' + AND RightName IN ($placeholders) + SQL + + my $res = $handle->SimpleQuery( + $query, + $role, # Type + $group->PrincipalId, # Id + @remove, # Right names + ); + + unless ($res) { + RT->Logger->error("Failed to delete invalid rights on system role $role!"); + next; + } + } +}; diff --git a/etc/upgrade/4.1.4/schema.Oracle b/etc/upgrade/4.1.4/schema.Oracle new file mode 100644 index 0000000..e530ede --- /dev/null +++ b/etc/upgrade/4.1.4/schema.Oracle @@ -0,0 +1 @@ +UPDATE Groups SET Instance = 1 WHERE Domain = 'RT::System-Role' AND Instance = 0; diff --git a/etc/upgrade/4.1.4/schema.Pg b/etc/upgrade/4.1.4/schema.Pg new file mode 100644 index 0000000..e530ede --- /dev/null +++ b/etc/upgrade/4.1.4/schema.Pg @@ -0,0 +1 @@ +UPDATE Groups SET Instance = 1 WHERE Domain = 'RT::System-Role' AND Instance = 0; diff --git a/etc/upgrade/4.1.4/schema.SQLite b/etc/upgrade/4.1.4/schema.SQLite new file mode 100644 index 0000000..e530ede --- /dev/null +++ b/etc/upgrade/4.1.4/schema.SQLite @@ -0,0 +1 @@ +UPDATE Groups SET Instance = 1 WHERE Domain = 'RT::System-Role' AND Instance = 0; diff --git a/etc/upgrade/4.1.4/schema.mysql b/etc/upgrade/4.1.4/schema.mysql new file mode 100644 index 0000000..e530ede --- /dev/null +++ b/etc/upgrade/4.1.4/schema.mysql @@ -0,0 +1 @@ +UPDATE Groups SET Instance = 1 WHERE Domain = 'RT::System-Role' AND Instance = 0; diff --git a/etc/upgrade/4.1.5/content b/etc/upgrade/4.1.5/content new file mode 100644 index 0000000..0ed1dda --- /dev/null +++ b/etc/upgrade/4.1.5/content @@ -0,0 +1,34 @@ +use strict; +use warnings; + +our @Initial = ( + # upgrade Template from id to name + sub { + require RT::Scrips; + my $scrips = RT::Scrips->new( RT->SystemUser ); + $scrips->UnLimit; + while ( my $scrip = $scrips->Next ) { + my $id = $scrip->Template; + if ( $id =~ /\D/ ) { + $RT::Logger->info('Template column for scrip #'. $scrip->id .' already contains characters'); + next; + } + + my $name; + + my $template = RT::Template->new( RT->SystemUser ); + $template->Load( $id ); + unless ( $template->id ) { + $RT::Logger->error("Scrip #". $scrip->id ." has template set to #$id, but it's not in DB, setting it 'Blank'"); + $name = 'Blank'; + } else { + $name = $template->Name; + } + + my ($status, $msg) = $scrip->_Set( Field => 'Template', Value => $name ); + unless ( $status ) { + $RT::Logger->error("Couldn't set template: $msg"); + } + } + }, +); diff --git a/etc/upgrade/4.1.5/schema.Oracle b/etc/upgrade/4.1.5/schema.Oracle new file mode 100644 index 0000000..b0b4d4c --- /dev/null +++ b/etc/upgrade/4.1.5/schema.Oracle @@ -0,0 +1,5 @@ +# Template column +ALTER TABLE Scrips RENAME COLUMN Template TO TemplateOld; +ALTER TABLE Scrips ADD COLUMN Template VARCHAR2(200) NOT NULL; +UPDATE TABLE Scrips SET Template = CAST(TemplateOld AS varchar2); +ALTER TABLE Scrips DROP COLUMN TemplateOld; \ No newline at end of file diff --git a/etc/upgrade/4.1.5/schema.Pg b/etc/upgrade/4.1.5/schema.Pg new file mode 100644 index 0000000..3a12d4d --- /dev/null +++ b/etc/upgrade/4.1.5/schema.Pg @@ -0,0 +1,2 @@ +# Template colum +ALTER TABLE Scrips ALTER COLUMN Template TYPE varchar(200); diff --git a/etc/upgrade/4.1.5/schema.mysql b/etc/upgrade/4.1.5/schema.mysql new file mode 100644 index 0000000..d35d730 --- /dev/null +++ b/etc/upgrade/4.1.5/schema.mysql @@ -0,0 +1,2 @@ +# Template column +ALTER TABLE Scrips CHANGE Template Template varchar(200) NOT NULL; diff --git a/etc/upgrade/4.1.6/content b/etc/upgrade/4.1.6/content new file mode 100644 index 0000000..d27014c --- /dev/null +++ b/etc/upgrade/4.1.6/content @@ -0,0 +1,43 @@ +use strict; +use warnings; + +our @Initial = (sub { + my $users = RT::Users->new(RT->SystemUser); + $users->FindAllRows; + + my $attributes = $users->Join( + ALIAS1 => "main", + FIELD1 => "id", + TABLE2 => RT::Attributes->Table, + FIELD2 => "ObjectId", + ); + $users->Limit( + ALIAS => $attributes, + FIELD => "ObjectType", + VALUE => "RT::User", + ); + $users->Limit( + ALIAS => $attributes, + FIELD => "Name", + VALUE => RT::User::_PrefName( RT->System ), + ); + + # Iterate all users (including disabled), with config preferences set. + # Avoids running a query for every user in the system by only selecting + # those known to have preferences. + while (my $user = $users->Next) { + RT->Logger->debug(sprintf "User #%d has config preferences", $user->id); + + my $config = $user->Preferences( RT->System ) + or next; + next unless exists $config->{DeferTransactionLoading}; + + $config->{ShowHistory} = delete $config->{DeferTransactionLoading} + ? "click" : "delay"; + + $user->SetPreferences( RT->System, $config ); + RT->Logger->debug(sprintf "Updated config Preferences for user %s (#%d)", $user->Name, $user->id); + } +}); + +1; diff --git a/etc/upgrade/4.1.7/schema.Oracle b/etc/upgrade/4.1.7/schema.Oracle new file mode 100644 index 0000000..b53dceb --- /dev/null +++ b/etc/upgrade/4.1.7/schema.Oracle @@ -0,0 +1,5 @@ +UPDATE Transactions +SET TimeTaken + = COALESCE(TO_NUMBER(REGEXP_SUBSTR(NewValue, '^-?\d+$')), 0) + - COALESCE(TO_NUMBER(OldValue),0) +WHERE ObjectType = 'RT::Ticket' AND Type = 'Set' AND Field = 'TimeWorked'; \ No newline at end of file diff --git a/etc/upgrade/4.1.7/schema.Pg b/etc/upgrade/4.1.7/schema.Pg new file mode 100644 index 0000000..2949e32 --- /dev/null +++ b/etc/upgrade/4.1.7/schema.Pg @@ -0,0 +1,5 @@ +UPDATE Transactions +SET TimeTaken + = (CASE WHEN NewValue~E'^-?\\d+$' THEN NewValue::integer ELSE 0 END) + - COALESCE(OldValue::integer, 0) +WHERE ObjectType = 'RT::Ticket' AND Type = 'Set' AND Field = 'TimeWorked'; \ No newline at end of file diff --git a/etc/upgrade/4.1.7/schema.SQLite b/etc/upgrade/4.1.7/schema.SQLite new file mode 100644 index 0000000..8d6680d --- /dev/null +++ b/etc/upgrade/4.1.7/schema.SQLite @@ -0,0 +1,2 @@ +UPDATE Transactions SET TimeTaken = NewValue - OldValue +WHERE ObjectType = 'RT::Ticket' AND Type = 'Set' AND Field = 'TimeWorked'; \ No newline at end of file diff --git a/etc/upgrade/4.1.7/schema.mysql b/etc/upgrade/4.1.7/schema.mysql new file mode 100644 index 0000000..95013f3 --- /dev/null +++ b/etc/upgrade/4.1.7/schema.mysql @@ -0,0 +1,5 @@ +UPDATE Transactions +SET TimeTaken + = COALESCE(NewValue,0) + - COALESCE(OldValue,0) +WHERE ObjectType = 'RT::Ticket' AND Type = 'Set' AND Field = 'TimeWorked'; \ No newline at end of file diff --git a/etc/upgrade/4.1.8/schema.Oracle b/etc/upgrade/4.1.8/schema.Oracle new file mode 100644 index 0000000..07cd148 --- /dev/null +++ b/etc/upgrade/4.1.8/schema.Oracle @@ -0,0 +1,2 @@ +ALTER TABLE Tickets ADD IsMerged NUMBER(11,0) DEFAULT NULL NULL; +UPDATE Tickets SET IsMerged = 1 WHERE id != EffectiveId; diff --git a/etc/upgrade/4.1.8/schema.Pg b/etc/upgrade/4.1.8/schema.Pg new file mode 100644 index 0000000..a35287e --- /dev/null +++ b/etc/upgrade/4.1.8/schema.Pg @@ -0,0 +1,2 @@ +ALTER TABLE Tickets ADD COLUMN IsMerged smallint NULL DEFAULT NULL; +UPDATE Tickets SET IsMerged = 1 WHERE id != EffectiveId; diff --git a/etc/upgrade/4.1.8/schema.SQLite b/etc/upgrade/4.1.8/schema.SQLite new file mode 100644 index 0000000..4e28e3b --- /dev/null +++ b/etc/upgrade/4.1.8/schema.SQLite @@ -0,0 +1,3 @@ +ALTER TABLE Tickets ADD COLUMN IsMerged int2 NULL DEFAULT NULL; +UPDATE Tickets SET IsMerged = 1 WHERE id != EffectiveId; + diff --git a/etc/upgrade/4.1.8/schema.mysql b/etc/upgrade/4.1.8/schema.mysql new file mode 100644 index 0000000..8977c10 --- /dev/null +++ b/etc/upgrade/4.1.8/schema.mysql @@ -0,0 +1,2 @@ +ALTER TABLE Tickets ADD COLUMN IsMerged int2 NULL DEFAULT NULL; +UPDATE Tickets SET IsMerged = 1 WHERE id != EffectiveId; diff --git a/etc/upgrade/4.1.9/content b/etc/upgrade/4.1.9/content new file mode 100644 index 0000000..3c68b69 --- /dev/null +++ b/etc/upgrade/4.1.9/content @@ -0,0 +1,190 @@ +use strict; +use warnings; + +# New HTML templates + +our @Templates = ( + { Queue => '0', + Name => 'Autoreply in HTML', # loc + Description => 'HTML Autoresponse template', # loc + Content => q[Subject: AutoReply: {$Ticket->Subject} +Content-Type: text/html + +

    Greetings,

    + +

    This message has been automatically generated in response to the +creation of a trouble ticket regarding {$Ticket->Subject()}, +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 {$Ticket->SubjectTag}.

    + +

    Please include the string {$Ticket->SubjectTag} +in the subject line of all future correspondence about this issue. To do so, +you may reply to this message.

    + +

    Thank you,
    +{$Ticket->QueueObj->CorrespondAddress()}

    + +
    +{$Transaction->Content(Type => 'text/html')} +], + }, + { Queue => '0', + Name => 'Transaction in HTML', # loc + Description => 'HTML transaction template', # loc + Content => 'RT-Attach-Message: yes +Content-Type: text/html + +{$Transaction->CreatedAsString}: Request id}">{$Ticket->id} was acted upon by {$Transaction->CreatorObj->Name}. +
    + + + + + + + + +
    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 URL:id}">{RT->Config->Get("WebURL")}Ticket/Display.html?id={$Ticket->id}
    +
    +
    +{$Transaction->Content( Type => "text/html")} +' + }, + { Queue => '0', + Name => 'Admin Correspondence in HTML', # loc + Description => 'HTML admin correspondence template', # loc + Content => 'RT-Attach-Message: yes +Content-Type: text/html + +Ticket URL: id}">{RT->Config->Get("WebURL")}Ticket/Display.html?id={$Ticket->id} +
    +
    +{$Transaction->Content(Type => "text/html");} +' + }, + { Queue => '0', + Name => 'Correspondence in HTML', # loc + Description => 'HTML correspondence template', # loc + Content => 'RT-Attach-Message: yes +Content-Type: text/html + +{$Transaction->Content( Type => "text/html")} +' + }, + { Queue => '0', + Name => 'Admin Comment in HTML', # loc + Description => 'HTML admin comment template', # loc + Content => +'Subject: [Comment] {my $s=($Transaction->Subject||$Ticket->Subject); $s =~ s/\\[Comment\\]\\s*//g; $s =~ s/^Re:\\s*//i; $s;} +RT-Attach-Message: yes +Content-Type: text/html + +

    This is a comment about id}">ticket {$Ticket->id}. It is not sent to the Requestor(s):

    + +{$Transaction->Content(Type => "text/html")} +' + }, + { Queue => '0', + Name => 'Status Change in HTML', # loc + Description => 'HTML Ticket status changed', # loc + Content => 'Subject: Status Changed to: {$Transaction->NewValue} +Content-Type: text/html + +id}">{RT->Config->Get("WebURL")}Ticket/Display.html?id={$Ticket->id} +
    +
    +{$Transaction->Content(Type => "text/html")} +' + }, + { Queue => '0', + Name => 'Resolved in HTML', # loc + Description => 'HTML Ticket Resolved', # loc + Content => 'Subject: Resolved: {$Ticket->Subject} +Content-Type: text/html + +

    According to our records, your request has been resolved. If you have any further questions or concerns, please respond to this message.

    +' + }, + { Queue => '___Approvals', + Name => "New Pending Approval in HTML", # loc + Description => "Notify Owners and AdminCcs of new items pending their approval", # loc + Content => 'Subject: New Pending Approval: {$Ticket->Subject} +Content-Type: text/html + +

    Greetings,

    + +

    There is a new item pending your approval: {$Ticket->Subject()}, +a summary of which appears below.

    + +

    Please approve +or reject this ticket, or visit the approvals +overview to batch-process all your pending approvals.

    + +
    +{$Transaction->Content()} +' + }, + { Queue => '___Approvals', + Name => "Approval Passed in HTML", # loc + Description => + "Notify Requestor of their ticket has been approved by some approver", # loc + Content => 'Subject: Ticket Approved: {$Ticket->Subject} +Content-Type: text/html + +

    Greetings,

    + +

    Your ticket has been approved by { eval { $Approver->Name } }. +Other approvals may be pending.

    + +

    Approver\'s notes:

    +
    { $Notes }
    +' + }, + { Queue => '___Approvals', + Name => "All Approvals Passed in HTML", # loc + Description => + "Notify Requestor of their ticket has been approved by all approvers", # loc + Content => 'Subject: Ticket Approved: {$Ticket->Subject} +Content-Type: text/html + +

    Greetings,

    + +

    Your ticket has been approved by { eval { $Approver->Name } }. +Its Owner may now start to act on it.

    + +

    Approver\'s notes:

    +
    { $Notes }
    +' + }, + { Queue => '___Approvals', + Name => "Approval Rejected in HTML", # loc + Description => + "Notify Owner of their rejected ticket", # loc + Content => 'Subject: Ticket Rejected: {$Ticket->Subject} +Content-Type: text/html + +

    Greetings,

    + +

    Your ticket has been rejected by { eval { $Approver->Name } }.

    + +

    Approver\'s notes:

    +
    { $Notes }
    +' + }, + { Queue => '___Approvals', + Name => "Approval Ready for Owner in HTML", # loc + Description => + "Notify Owner of their ticket has been approved and is ready to be acted on", # loc + Content => 'Subject: Ticket Approved: {$Ticket->Subject} +Content-Type: text/html + +

    Greetings,

    + +

    The ticket has been approved, you may now start to act on it.

    + +' + }, +); + diff --git a/etc/upgrade/generate-rtaddressregexp b/etc/upgrade/generate-rtaddressregexp index 3e517c4..1dcff87 100644 --- a/etc/upgrade/generate-rtaddressregexp +++ b/etc/upgrade/generate-rtaddressregexp @@ -54,7 +54,7 @@ use lib "lib"; use RT; RT::LoadConfig(); -RT->Config->Set('LogToScreen' => 'debug'); +RT->Config->Set('LogToSTDERR' => 'debug'); RT::Init(); $| = 1; diff --git a/etc/upgrade/sanity-check-stylesheets.pl b/etc/upgrade/sanity-check-stylesheets.pl index 6ae1cc6..aa4faaf 100644 --- a/etc/upgrade/sanity-check-stylesheets.pl +++ b/etc/upgrade/sanity-check-stylesheets.pl @@ -50,7 +50,7 @@ use warnings; use RT; RT::LoadConfig(); -RT->Config->Set('LogToScreen' => 'debug'); +RT->Config->Set('LogToSTDERR' => 'debug'); RT::Init(); $| = 1; @@ -59,20 +59,20 @@ use RT::Users; my $users = RT::Users->new( $RT::SystemUser ); $users->UnLimit(); -my @comp_roots = RT::Interface::Web->ComponentRoots; -my %comp_root_check_cache; +my @static_roots = RT::Interface::Web->StaticRoots; +my %static_root_check_cache; sub stylesheet_exists { my $stylesheet = shift; - return $comp_root_check_cache{$stylesheet} - if exists $comp_root_check_cache{$stylesheet}; + return $static_root_check_cache{$stylesheet} + if exists $static_root_check_cache{$stylesheet}; - for my $comp_root (@comp_roots) { - return ++$comp_root_check_cache{$stylesheet} - if -d "$comp_root/NoAuth/css/$stylesheet"; + for my $static_root (@static_roots) { + return ++$static_root_check_cache{$stylesheet} + if -d "$static_root/css/$stylesheet"; } - return $comp_root_check_cache{$stylesheet} = 0; + return $static_root_check_cache{$stylesheet} = 0; } my $system_stylesheet = RT->Config->Get('WebDefaultStylesheet'); diff --git a/etc/upgrade/shrink_cgm_table.pl b/etc/upgrade/shrink_cgm_table.pl index bb6c8d4..9b453ba 100644 --- a/etc/upgrade/shrink_cgm_table.pl +++ b/etc/upgrade/shrink_cgm_table.pl @@ -46,13 +46,13 @@ # those contributions and any derivatives thereof. # # END BPS TAGGED BLOCK }}} -use 5.8.3; +use 5.10.1; use strict; use warnings; use RT; RT::LoadConfig(); -RT->Config->Set('LogToScreen' => 'debug'); +RT->Config->Set('LogToSTDERR' => 'debug'); RT::Init(); use RT::CachedGroupMembers; diff --git a/etc/upgrade/shrink_transactions_table.pl b/etc/upgrade/shrink_transactions_table.pl index b4f07f0..601c297 100644 --- a/etc/upgrade/shrink_transactions_table.pl +++ b/etc/upgrade/shrink_transactions_table.pl @@ -46,13 +46,13 @@ # those contributions and any derivatives thereof. # # END BPS TAGGED BLOCK }}} -use 5.8.3; +use 5.10.1; use strict; use warnings; use RT; RT::LoadConfig(); -RT->Config->Set('LogToScreen' => 'debug'); +RT->Config->Set('LogToSTDERR' => 'debug'); RT::Init(); use RT::Transactions; @@ -76,6 +76,7 @@ $txns->Limit( FIELD => 'Domain', OPERATOR => '=', VALUE => 'ACLEquivalence', + CASESENSITIVE => 0, QUOTEVALUE => 1, ENTRYAGGREGATOR => 'AND', ); diff --git a/etc/upgrade/split-out-cf-categories b/etc/upgrade/split-out-cf-categories index f2751bb..c39d6d4 100644 --- a/etc/upgrade/split-out-cf-categories +++ b/etc/upgrade/split-out-cf-categories @@ -54,7 +54,7 @@ use lib "lib"; use RT; RT::LoadConfig(); -RT->Config->Set('LogToScreen' => 'debug'); +RT->Config->Set('LogToSTDERR' => 'debug'); RT::Init(); $| = 1; diff --git a/etc/upgrade/switch-templates-to b/etc/upgrade/switch-templates-to new file mode 100644 index 0000000..c6270c8 --- /dev/null +++ b/etc/upgrade/switch-templates-to @@ -0,0 +1,148 @@ +#!/usr/bin/perl +# BEGIN BPS TAGGED BLOCK {{{ +# +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2013 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; + +my $to = shift || ''; +my $from; + +if ($to =~ /html|text/i) { + $to = $to =~ /html/i ? 'html' : 'text'; + $from = $to eq 'html' ? 'text' : 'html'; +} else { + print "Usage: $0 [html|text]\n"; + warn "Please specify if you'd like to switch to HTML or text templates.\n"; + exit 1; +} + +RT::LoadConfig(); +RT->Config->Set('LogToSTDERR' => 'info'); +RT::Init(); + +$| = 1; + +my @templates = ( + "Autoreply", + "Transaction", + "Admin Correspondence", + "Correspondence", + "Admin Comment", + "Status Change", + "Resolved", + "New Pending Approval", + "Approval Passed", + "All Approvals Passed", + "Approval Rejected", + "Approval Ready for Owner", +); + +$RT::Handle->BeginTransaction(); + +use RT::Scrips; +my $scrips = RT::Scrips->new( RT->SystemUser ); +$scrips->UnLimit; + +for (@templates) { + $scrips->Limit( + FIELD => 'Template', + VALUE => ($to eq 'html' ? $_ : "$_ in HTML"), + ENTRYAGGREGATOR => 'OR' + ); +} + +my $switched = 0; +while ( my $s = $scrips->Next ) { + my $new = $s->TemplateObj->Name; + + if ($to eq 'html') { + $new .= ' in HTML'; + } else { + $new =~ s/ in HTML$//; + } + + print $s->id, ": ", $s->Description, "\n"; + print " ", $s->TemplateObj->Name, " -> $new\n\n"; + + my ($ok, $msg) = $s->SetTemplate($new); + + if ($ok) { + $switched++; + } else { + warn " Couldn't switch templates: $msg\n"; + } +} + +$RT::Handle->Commit; + +if ($switched) { + print <<" EOT"; +Switched $switched scrips to $to templates. You should now manually port any +customizations from the old templates to the new templates. + EOT + exit 1 if $switched != $scrips->Count; +} +elsif ($scrips->Count) { + print <<" EOT"; +@{[$scrips->Count]} scrips using $from templates were found, but none were +successfully switched to $to. See the errors above. + EOT + exit 1; +} +else { + print <<" EOT"; +No scrips were found using the $from templates, so none were switched to +$to templates. + EOT +} + diff --git a/etc/upgrade/time-worked-history.pl b/etc/upgrade/time-worked-history.pl new file mode 100644 index 0000000..8c8a696 --- /dev/null +++ b/etc/upgrade/time-worked-history.pl @@ -0,0 +1,110 @@ +#!/usr/bin/env perl +# BEGIN BPS TAGGED BLOCK {{{ +# +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2013 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 5.10.1; +use strict; +use warnings; + +use RT; +RT::LoadConfig(); +RT->Config->Set('LogToSTDERR' => 'info'); +RT::Init(); + +my $dbh = $RT::Handle->dbh; +my $ids = $dbh->selectcol_arrayref( + "SELECT t1.id FROM Tickets t1, Tickets t2 WHERE t1.id = t2.EffectiveId" + ." AND t2.id != t2.EffectiveId AND t2.EffectiveId = t1.id" +); +foreach my $id ( @$ids ) { + my $t = RT::Ticket->new( RT->SystemUser ); + $t->Load( $id ); + unless ( $t->id ) { + $RT::Logger->error("Couldn't load ticket #$id"); + next; + } + + fix_time_worked_history($t); +} + +sub fix_time_worked_history { + my ($t) = (@_); + + my $history = 0; + my $candidate = undef; + my @delete = (); + my $delete_time = 0; + + my $txns = $t->Transactions; + while ( my $txn = $txns->Next ) { + if ( $txn->Type =~ /^(Create|Correspond|Comment)$/ ) { + $history += $txn->TimeTaken || 0; + } elsif ( $txn->Type eq 'Set' && $txn->Field eq 'TimeWorked' ) { + $history += $txn->NewValue - $txn->OldValue; + $candidate = $txn; + } elsif ( $candidate && $txn->Field eq 'MergedInto' ) { + if ($candidate->Creator eq $txn->Creator ) { + push @delete, $candidate; + $delete_time += $candidate->NewValue - $candidate->OldValue; + } + + $candidate = undef; + } + } + + if ( $history == $t->TimeWorked ) { + $RT::Logger->info("Ticket #". $t->id . " has TimeWorked matching history. Skipping"); + } elsif ( $history - $delete_time == $t->TimeWorked ) { + $RT::Logger->warn( "Ticket #". $t->id ." has TimeWorked mismatch. Deleting transactions" ); + foreach my $dtxn ( @delete ) { + my ($status, $msg) = $dtxn->Delete; + $RT::Logger->error("Couldn't delete transaction: $msg") unless $status; + } + } else { + $RT::Logger->error( "Ticket #". $t->id ." has TimeWorked mismatch, but we couldn't find correct transactions to delete. Skipping" ); + } +} diff --git a/etc/upgrade/upgrade-articles b/etc/upgrade/upgrade-articles index ed347e8..2d338b0 100644 --- a/etc/upgrade/upgrade-articles +++ b/etc/upgrade/upgrade-articles @@ -54,7 +54,7 @@ use lib "lib"; use RT; RT::LoadConfig(); -RT->Config->Set('LogToScreen' => 'debug'); +RT->Config->Set('LogToSTDERR' => 'debug'); RT::Init(); $| = 1; diff --git a/lib/RT.pm b/lib/RT.pm index 0599cc3..92e15f3 100644 --- a/lib/RT.pm +++ b/lib/RT.pm @@ -48,12 +48,14 @@ use strict; use warnings; +use 5.010; package RT; use File::Spec (); use Cwd (); +use Scalar::Util qw(blessed); use vars qw($Config $System $SystemUser $Nobody $Handle $Logger $_Privileged $_Unprivileged $_INSTALL_MODE); @@ -63,11 +65,13 @@ use vars qw($BasePath $SbinPath $VarPath $LexiconPath + $StaticPath $PluginPath $LocalPath $LocalEtcPath $LocalLibPath $LocalLexiconPath + $LocalStaticPath $LocalPluginPath $MasonComponentRoot $MasonLocalComponentRoot @@ -158,17 +162,13 @@ sub LoadConfig { # If the user does that, do what they mean. $RT::WebPath = '' if ($RT::WebPath eq '/'); - # fix relative LogDir and GnuPG homedir + # Fix relative LogDir; It cannot be fixed in a PostLoadCheck, as + # they are run after logging is enabled. 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} ); - } - return $Config; } @@ -192,9 +192,10 @@ sub Init { InitClasses(); InitLogging(); InitPlugins(); + _BuildTableAttributes(); RT::I18N->Init; RT->Config->PostLoadCheck; - + RT::Lifecycle->new->FillCache; } =head2 ConnectToDatabase @@ -260,7 +261,7 @@ sub InitLogging { my ($package, $filename, $line) = caller($frame); $p{'message'} =~ s/(?:\r*\n)+$//; - return "[". gmtime(time) ."] [". $p{'level'} ."]: " + return "[$$] [". gmtime(time) ."] [". $p{'level'} ."]: " . $p{'message'} ." ($filename:$line)\n"; }; @@ -279,9 +280,9 @@ sub InitLogging { $p{message} =~ s/(?:\r*\n)+$//; if ($p{level} eq 'debug') { - return "$p{message}\n"; + return "[$$] $p{message} ($filename:$line)\n"; } else { - return "$p{message} ($filename:$line)\n"; + return "[$$] $p{message}\n"; } }; @@ -332,11 +333,11 @@ sub InitLogging { callbacks => [ $simple_cb, $stack_cb ], )); } - if ( $Config->Get('LogToScreen') ) { + if ( $Config->Get('LogToSTDERR') ) { require Log::Dispatch::Screen; $RT::Logger->add( Log::Dispatch::Screen->new ( name => 'screen', - min_level => $Config->Get('LogToScreen'), + min_level => $Config->Get('LogToSTDERR'), callbacks => [ $simple_cb, $stack_cb ], stderr => 1, )); @@ -356,16 +357,6 @@ sub InitLogging { InitSignalHandlers(); } -{ # Work around bug in Log::Dispatch < 2.30, wherein the short forms - # of ->warn, ->err, and ->crit do not usefully propagate out, unlike - # ->warning, ->error, and ->critical - package Log::Dispatch; - no warnings 'redefine'; - sub warn { shift->warning(@_) } - sub err { shift->error(@_) } - sub crit { shift->critical(@_) } -} - sub InitSignalHandlers { # Signal handlers @@ -405,8 +396,9 @@ sub InitSignalHandlers { sub CheckPerlRequirements { - if ($^V < 5.008003) { - die sprintf "RT requires Perl v5.8.3 or newer. Your current Perl is v%vd\n", $^V; + eval {require 5.010_001}; + if ($@) { + die sprintf "RT requires Perl v5.10.1 or newer. Your current Perl is v%vd\n", $^V; } # use $error here so the following "die" can still affect the global $@ @@ -485,7 +477,33 @@ sub InitClasses { require RT::ObjectTopics; require RT::Topic; require RT::Topics; + require RT::Link; + require RT::Links; + + _BuildTableAttributes(); + + if ( $args{'Heavy'} ) { + # load scrips' modules + my $scrips = RT::Scrips->new(RT->SystemUser); + while ( my $scrip = $scrips->Next ) { + local $@; + eval { $scrip->LoadModules } or + $RT::Logger->error("Invalid Scrip ".$scrip->Id.". Unable to load the Action or Condition. ". + "You should delete or repair this Scrip in the admin UI.\n$@\n"); + } + + foreach my $class ( grep $_, RT->Config->Get('CustomFieldValuesSources') ) { + local $@; + eval "require $class; 1" or $RT::Logger->error( + "Class '$class' is listed in CustomFieldValuesSources option" + ." in the config, but we failed to load it:\n$@\n" + ); + } + } +} + +sub _BuildTableAttributes { # on a cold server (just after restart) people could have an object # in the session, as we deserialize it so we never call constructor # of the class, so the list of accessible fields is empty and we die @@ -512,34 +530,13 @@ sub InitClasses { RT::ObjectCustomFieldValue RT::Attribute RT::ACE - RT::Link RT::Article RT::Class + RT::Link RT::ObjectClass RT::ObjectTopic RT::Topic ); - - if ( $args{'Heavy'} ) { - # load scrips' modules - my $scrips = RT::Scrips->new(RT->SystemUser); - $scrips->Limit( FIELD => 'Stage', OPERATOR => '!=', VALUE => 'Disabled' ); - while ( my $scrip = $scrips->Next ) { - local $@; - eval { $scrip->LoadModules } or - $RT::Logger->error("Invalid Scrip ".$scrip->Id.". Unable to load the Action or Condition. ". - "You should delete or repair this Scrip in the admin UI.\n$@\n"); - } - - foreach my $class ( grep $_, RT->Config->Get('CustomFieldValuesSources') ) { - local $@; - eval "require $class; 1" or $RT::Logger->error( - "Class '$class' is listed in CustomFieldValuesSources option" - ." in the config, but we failed to load it:\n$@\n" - ); - } - - } } =head2 InitSystemObjects @@ -649,14 +646,17 @@ You can define plugins by adding them to the @Plugins list in your RT_SiteConfig =cut -our @PLUGINS = (); sub Plugins { + state @PLUGINS; + state $DID_INIT = 0; + my $self = shift; - unless (@PLUGINS) { + unless ($DID_INIT) { $self->InitPluginPaths; @PLUGINS = $self->InitPlugins; + $DID_INIT++; } - return \@PLUGINS; + return [@PLUGINS]; } =head2 PluginDirs @@ -786,9 +786,9 @@ sub CanonicalizeGeneratedPaths { $BasePath = Cwd::realpath($BasePath); for my $path ( - qw/EtcPath BinPath SbinPath VarPath LocalPath LocalEtcPath + qw/EtcPath BinPath SbinPath VarPath LocalPath StaticPath LocalEtcPath LocalLibPath LexiconPath LocalLexiconPath PluginPath - LocalPluginPath MasonComponentRoot MasonLocalComponentRoot + LocalPluginPath LocalStaticPath MasonComponentRoot MasonLocalComponentRoot MasonDataDir MasonSessionDir/ ) { @@ -804,12 +804,16 @@ sub CanonicalizeGeneratedPaths { =head2 AddJavaScript -helper method to add js files to C config. -to add extra js files, you can add the following line -in the plugin's main file: +Helper method to add JS files to the C<@JSFiles> config at runtime. + +To add files, you can add the following line to your extension's main C<.pm> +file: RT->AddJavaScript( 'foo.js', 'bar.js' ); +Files are expected to be in a static root in a F directory, such as +F in your extension or F for local overlays. + =cut sub AddJavaScript { @@ -822,13 +826,17 @@ sub AddJavaScript { =head2 AddStyleSheets -helper method to add css files to C config +Helper method to add CSS files to the C<@CSSFiles> config at runtime. -to add extra css files, you can add the following line -in the plugin's main file: +To add files, you can add the following line to your extension's main C<.pm> +file: RT->AddStyleSheets( 'foo.css', 'bar.css' ); +Files are expected to be in a static root in a F directory, such as +F in your extension or F for local +overlays. + =cut sub AddStyleSheets { @@ -858,6 +866,91 @@ sub StyleSheets { return RT->Config->Get('CSSFiles'); } +=head2 Deprecated + +Notes that a particular call path is deprecated, and will be removed in +a particular release. Puts a warning in the logs indicating such, along +with a stack trace. + +Optional arguments include: + +=over + +=item Remove + +The release which is slated to remove the method or component + +=item Instead + +A suggestion of what to use in place of the deprecated API + +=item Arguments + +Used if not the entire method is being removed, merely a manner of +calling it; names the arguments which are deprecated. + +=item Message + +Overrides the auto-built phrasing of C with a custom message. + +=item Object + +An L object to print the class and numeric id of. Useful if the +admin will need to hunt down a particular object to fix the deprecation +warning. + +=back + +=cut + +sub Deprecated { + my $class = shift; + my %args = ( + Arguments => undef, + Remove => undef, + Instead => undef, + Message => undef, + Stack => 1, + @_, + ); + + my ($function) = (caller(1))[3]; + my $stack; + if ($function eq "HTML::Mason::Commands::__ANON__") { + eval { HTML::Mason::Exception->throw() }; + my $error = $@; + my $info = $error->analyze_error; + $function = "Mason component ".$info->{frames}[0]->filename; + $stack = join("\n", map { sprintf("\t[%s:%d]", $_->filename, $_->line) } @{$info->{frames}}); + } else { + $function = "function $function"; + $stack = Carp::longmess(); + } + $stack =~ s/^.*?\n//; # Strip off call to ->Deprecated + + my $msg; + if ($args{Message}) { + $msg = $args{Message}; + } elsif ($args{Arguments}) { + $msg = "Calling $function with $args{Arguments} is deprecated"; + } else { + $msg = "The $function is deprecated"; + } + $msg .= ", and will be removed in RT $args{Remove}" + if $args{Remove}; + $msg .= "."; + + $msg .= " You should use $args{Instead} instead." + if $args{Instead}; + + $msg .= sprintf " Object: %s #%d.", blessed($args{Object}), $args{Object}->id + if $args{Object}; + + $msg .= " Call stack:\n$stack" if $args{Stack}; + RT->Logger->warn($msg); +} + =head1 BUGS Please report them to rt-bugs@bestpractical.com, if you know what's diff --git a/lib/RT/ACE.pm b/lib/RT/ACE.pm index c752aa2..01e1795 100644 --- a/lib/RT/ACE.pm +++ b/lib/RT/ACE.pm @@ -71,15 +71,13 @@ sub Table {'ACL'} use strict; use warnings; -use RT::Principals; -use RT::Queues; -use RT::Groups; +require RT::Principals; +require RT::Queues; +require RT::Groups; -use vars qw ( - %LOWERCASERIGHTNAMES - %OBJECT_TYPES - %TICKET_METAPRINCIPALS -); +our %RIGHTS; + +my (@_ACL_CACHE_HANDLERS); @@ -90,37 +88,22 @@ use vars qw ( =cut - - - - - -%TICKET_METAPRINCIPALS = ( - Owner => 'The owner of a ticket', # loc_pair - Requestor => 'The requestor of a ticket', # loc_pair - Cc => 'The CC of a ticket', # loc_pair - AdminCc => 'The administrative CC of a ticket', # loc_pair -); - - - - =head2 LoadByValues PARAMHASH Load an ACE by specifying a paramhash with the following fields: PrincipalId => undef, PrincipalType => undef, - RightName => undef, + RightName => undef, And either: - Object => undef, + Object => undef, OR - ObjectType => undef, - ObjectId => undef + ObjectType => undef, + ObjectId => undef =cut @@ -137,7 +120,7 @@ sub LoadByValues { 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'}) ); + return wantarray ? ( 0, $self->loc("Invalid right. Couldn't canonicalize right '[_1]'", $args{'RightName'}) ) : 0; } $args{'RightName'} = $canonic_name; } @@ -148,14 +131,14 @@ sub LoadByValues { $args{'PrincipalType'} ); unless ( $princ_obj->id ) { - return ( 0, + return wantarray ? ( 0, $self->loc( 'Principal [_1] not found.', $args{'PrincipalId'} ) - ); + ) : 0; } my ($object, $object_type, $object_id) = $self->_ParseObjectArg( %args ); unless( $object ) { - return ( 0, $self->loc("System error. Right not granted.") ); + return wantarray ? ( 0, $self->loc("System error. Right not granted.")) : 0; } $self->LoadByCols( PrincipalId => $princ_obj->Id, @@ -166,11 +149,11 @@ sub LoadByValues { #If we couldn't load it. unless ( $self->Id ) { - return ( 0, $self->loc("ACE not found") ); + return wantarray ? ( 0, $self->loc("ACE not found") ) : 0; } # if we could - return ( $self->Id, $self->loc("Right Loaded") ); + return wantarray ? ( $self->Id, $self->loc("Right Loaded") ) : $self->Id; } @@ -223,7 +206,7 @@ sub Create { } ($args{'Object'}, $args{'ObjectType'}, $args{'ObjectId'}) = $self->_ParseObjectArg( %args ); unless( $args{'Object'} ) { - return ( 0, $self->loc("System error. Right not granted.") ); + return ( 0, $self->loc("System error. Right not granted.") ); } # Validate the principal @@ -266,7 +249,7 @@ sub Create { #check if it's a valid RightName if ( $args{'Object'}->can('AvailableRights') ) { - my $available = $args{'Object'}->AvailableRights; + my $available = $args{'Object'}->AvailableRights($princ_obj); unless ( grep $_ eq $args{'RightName'}, map $self->CanonicalizeRightName( $_ ), keys %$available ) { $RT::Logger->warning( "Couldn't validate right name '$args{'RightName'}'" @@ -296,10 +279,12 @@ sub Create { ObjectId => $args{'Object'}->id, ); - #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space. - RT::Principal->InvalidateACLCache(); - if ( $id ) { + RT::ACE->InvalidateCaches( + Action => "Grant", + RightName => $self->RightName, + ACE => $self, + ); return ( $id, $self->loc('Right Granted') ); } else { @@ -344,12 +329,12 @@ sub _Delete { $RT::Handle->BeginTransaction() unless $InsideTransaction; + my $right = $self->RightName; + my ( $val, $msg ) = $self->SUPER::Delete(@_); if ($val) { - #Clear the key cache. TODO someday we may want to just clear a little bit of the keycache space. - # TODO what about the groups key cache? - RT::Principal->InvalidateACLCache(); + RT::ACE->InvalidateCaches( Action => "Revoke", RightName => $right ); $RT::Handle->Commit() unless $InsideTransaction; return ( $val, $self->loc('Right revoked') ); } @@ -396,7 +381,67 @@ sub _BootstrapCreate { } +=head2 InvalidateCaches + +Calls any registered ACL cache handlers (see L). + +Usually called from L and L. + +=cut + +sub InvalidateCaches { + my $class = shift; + + for my $handler (@_ACL_CACHE_HANDLERS) { + next unless ref($handler) eq "CODE"; + $handler->(@_); + } +} + +=head2 RegisterCacheHandler + +Class method. Takes a coderef and adds it to the ACL cache handlers. These +handlers are called by L, usually called itself from +L and L. + +The handlers are passed a hash which may contain any (or none) of these +optional keys: +=over + +=item Action + +A string indicating the action that (may have) invalidated the cache. Expected +values are currently: + +=over + +=item Grant + +=item Revoke + +=back + +However, other values may be passed in the future. + +=item RightName + +The (canonicalized) right being granted or revoked. + +=item ACE + +The L object just created. + +=back + +Your handler should be flexible enough to account for additional arguments +being passed in the future. + +=cut + +sub RegisterCacheHandler { + push @_ACL_CACHE_HANDLERS, $_[1]; +} sub RightName { my $self = shift; @@ -420,13 +465,17 @@ the correct case. If it's not found, will return undef. =cut sub CanonicalizeRightName { - my $self = shift; - return $LOWERCASERIGHTNAMES{ lc shift }; + my $self = shift; + my $name = shift; + for my $class (sort keys %RIGHTS) { + return $RIGHTS{$class}{ lc $name }{Name} + if $RIGHTS{$class}{ lc $name }; + } + return undef; } - =head2 Object If the object this ACE applies to is a queue, returns the queue object. @@ -445,7 +494,7 @@ sub Object { my $appliesto_obj; - if ($self->__Value('ObjectType') && $OBJECT_TYPES{$self->__Value('ObjectType')} ) { + if ($self->__Value('ObjectType') && $self->__Value('ObjectType')->DOES('RT::Record::Role::Rights') ) { $appliesto_obj = $self->__Value('ObjectType')->new($self->CurrentUser); unless (ref( $appliesto_obj) eq $self->__Value('ObjectType')) { return undef; @@ -563,21 +612,21 @@ sub _ParseObjectArg { @_ ); if( $args{'Object'} && ($args{'ObjectId'} || $args{'ObjectType'}) ) { - $RT::Logger->crit( "Method called with an ObjectType or an ObjectId and Object args" ); - return (); + $RT::Logger->crit( "Method called with an ObjectType or an ObjectId and Object args" ); + return (); } elsif( $args{'Object'} && ref($args{'Object'}) && !$args{'Object'}->can('id') ) { - $RT::Logger->crit( "Method called called Object that has no id method" ); - return (); + $RT::Logger->crit( "Method called called Object that has no id method" ); + return (); } elsif( $args{'Object'} ) { - my $obj = $args{'Object'}; - return ($obj, ref $obj, $obj->id); + my $obj = $args{'Object'}; + return ($obj, ref $obj, $obj->id); } elsif ( $args{'ObjectType'} ) { - my $obj = $args{'ObjectType'}->new( $self->CurrentUser ); - $obj->Load( $args{'ObjectId'} ); - return ($obj, ref $obj, $obj->id); + my $obj = $args{'ObjectType'}->new( $self->CurrentUser ); + $obj->Load( $args{'ObjectId'} ); + return ($obj, ref $obj, $obj->id); } else { - $RT::Logger->crit( "Method called with wrong args" ); - return (); + $RT::Logger->crit( "Method called with wrong args" ); + return (); } } @@ -722,29 +771,39 @@ sub _CoreAccessible { { id => - {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, + {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, PrincipalType => - {read => 1, write => 1, sql_type => 12, length => 25, is_blob => 0, is_numeric => 0, type => 'varchar(25)', default => ''}, + {read => 1, write => 1, sql_type => 12, length => 25, is_blob => 0, is_numeric => 0, type => 'varchar(25)', default => ''}, PrincipalId => - {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, + {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, RightName => - {read => 1, write => 1, sql_type => 12, length => 25, is_blob => 0, is_numeric => 0, type => 'varchar(25)', default => ''}, + {read => 1, write => 1, sql_type => 12, length => 25, is_blob => 0, is_numeric => 0, type => 'varchar(25)', default => ''}, ObjectType => - {read => 1, write => 1, sql_type => 12, length => 25, is_blob => 0, is_numeric => 0, type => 'varchar(25)', default => ''}, + {read => 1, write => 1, sql_type => 12, length => 25, is_blob => 0, is_numeric => 0, type => 'varchar(25)', default => ''}, ObjectId => - {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, + {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Creator => - {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, + {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Created => - {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, + {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, LastUpdatedBy => - {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, + {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, LastUpdated => - {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, + {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, } }; +sub FindDependencies { + my $self = shift; + my ($walker, $deps) = @_; + + $self->SUPER::FindDependencies($walker, $deps); + + $deps->Add( out => $self->PrincipalObj->Object ); + $deps->Add( out => $self->Object ); +} + RT::Base->_ImportOverlays(); 1; diff --git a/lib/RT/ACL.pm b/lib/RT/ACL.pm index d1e0df5..9995b80 100644 --- a/lib/RT/ACL.pm +++ b/lib/RT/ACL.pm @@ -65,10 +65,10 @@ my $ACL = RT::ACL->new($CurrentUser); package RT::ACL; -use RT::ACE; - use base 'RT::SearchBuilder'; +use RT::ACE; + sub Table { 'ACL'} use strict; @@ -122,40 +122,6 @@ sub LimitToObject { -=head2 LimitNotObject $object - -Limit the ACL to rights NOT on the object $object. $object needs to be -an RT::Record class. - -=cut - -sub LimitNotObject { - my $self = shift; - my $obj = shift; - unless ( defined($obj) - && ref($obj) - && UNIVERSAL::can( $obj, 'id' ) - && $obj->id ) - { - return undef; - } - $self->Limit( FIELD => 'ObjectType', - OPERATOR => '!=', - VALUE => ref($obj), - ENTRYAGGREGATOR => 'OR', - SUBCLAUSE => $obj->id - ); - $self->Limit( FIELD => 'ObjectId', - OPERATOR => '!=', - VALUE => $obj->id, - ENTRYAGGREGATOR => 'OR', - QUOTEVALUE => 0, - SUBCLAUSE => $obj->id - ); -} - - - =head2 LimitToPrincipal { Type => undef, Id => undef, IncludeGroupMembership => undef } Limit the ACL to the principal with PrincipalId Id and PrincipalType Type @@ -252,86 +218,9 @@ sub Next { } +# The singular of ACL is ACE. +sub _SingularClass { "RT::ACE" } - - -#wrap around _DoSearch so that we can build the hash of returned -#values -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"); - if ( $self->{'must_redo_search'} ) { - $RT::Logger->crit( -"_DoSearch is not so successful as it still needs redo search, won't call _BuildHash" - ); - } - else { - $self->_BuildHash(); - } - return ($return); -} - - -#Build a hash of this ACL's entries. -sub _BuildHash { - my $self = shift; - - while (my $entry = $self->Next) { - my $hashkey = join '-', map $entry->__Value( $_ ), - qw(ObjectType ObjectId RightName PrincipalId PrincipalType); - - $self->{'as_hash'}->{"$hashkey"} =1; - - } -} - - - -=head2 HasEntry - -=cut - -sub HasEntry { - - my $self = shift; - my %args = ( RightScope => undef, - RightAppliesTo => undef, - RightName => undef, - PrincipalId => undef, - PrincipalType => undef, - @_ ); - - #if we haven't done the search yet, do it now. - $self->_DoSearch(); - - if ($self->{'as_hash'}->{ $args{'RightScope'} . "-" . - $args{'RightAppliesTo'} . "-" . - $args{'RightName'} . "-" . - $args{'PrincipalId'} . "-" . - $args{'PrincipalType'} - } == 1) { - return(1); - } - else { - return(undef); - } -} - -# }}} - - -=head2 NewItem - -Returns an empty new RT::ACE item - -=cut - -sub NewItem { - my $self = shift; - return(RT::ACE->new($self->CurrentUser)); -} RT::Base->_ImportOverlays(); 1; diff --git a/lib/RT/Action.pm b/lib/RT/Action.pm index 395805b..d02159b 100644 --- a/lib/RT/Action.pm +++ b/lib/RT/Action.pm @@ -172,26 +172,6 @@ sub Prepare { } -#If this rule applies to this transaction, return true. - -sub IsApplicable { - my $self = shift; - return(undef); -} - -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; -} - - RT::Base->_ImportOverlays(); 1; diff --git a/lib/RT/Action/AutoOpen.pm b/lib/RT/Action/AutoOpen.pm index 8566c62..30a58a7 100644 --- a/lib/RT/Action/AutoOpen.pm +++ b/lib/RT/Action/AutoOpen.pm @@ -46,7 +46,6 @@ # # END BPS TAGGED BLOCK }}} -# This Action will open the BASE if a dependent is resolved. package RT::Action::AutoOpen; use strict; @@ -87,7 +86,7 @@ sub Prepare { # no change if the ticket is in initial status and the message is a mail # from a requestor - return 1 if $ticket->QueueObj->Lifecycle->IsInitial($ticket->Status) + return 1 if $ticket->LifecycleObj->IsInitial($ticket->Status) && $self->TransactionObj->IsInbound; if ( my $msg = $self->TransactionObj->Message->First ) { diff --git a/lib/RT/Action/AutoOpenInactive.pm b/lib/RT/Action/AutoOpenInactive.pm new file mode 100644 index 0000000..58c55cb --- /dev/null +++ b/lib/RT/Action/AutoOpenInactive.pm @@ -0,0 +1,105 @@ +# BEGIN BPS TAGGED BLOCK {{{ +# +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2013 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::AutoOpenInactive; + +use strict; +use warnings; +use base qw(RT::Action); + +=head1 DESCRIPTION + +This action automatically moves an inactive ticket to an active status. + +Status is not changed if there is no active statuses in the lifecycle. + +Status is not changed if message's head has field C with +C substring. + +Status is set to the first possible active status. If the ticket's status is +C then RT finds all possible transitions from C status and +selects first one that results in the ticket having an active status. + +=cut + +sub Prepare { + my $self = shift; + + my $ticket = $self->TicketObj; + return 0 if $ticket->LifecycleObj->IsActive( $ticket->Status ); + + if ( my $msg = $self->TransactionObj->Message->First ) { + return 0 + if ( $msg->GetHeader('RT-Control') || '' ) =~ + /\bno-autoopen\b/i; + } + + my $next = $ticket->FirstActiveStatus; + return 0 unless defined $next; + + $self->{'set_status_to'} = $next; + + return 1; +} + +sub Commit { + my $self = shift; + + return 1 unless my $new_status = $self->{'set_status_to'}; + + my ($val, $msg) = $self->TicketObj->SetStatus( $new_status ); + unless ( $val ) { + $RT::Logger->error( "Couldn't auto-open-inactive ticket: ". $msg ); + return 0; + } + return 1; +} + +RT::Base->_ImportOverlays(); + +1; diff --git a/lib/RT/Action/Autoreply.pm b/lib/RT/Action/Autoreply.pm index 89b7536..7390b43 100644 --- a/lib/RT/Action/Autoreply.pm +++ b/lib/RT/Action/Autoreply.pm @@ -93,18 +93,18 @@ Set this message's return address to the apropriate queue address sub SetReturnAddress { my $self = shift; - + my $friendly_name; - if (RT->Config->Get('UseFriendlyFromLine')) { - $friendly_name = $self->TicketObj->QueueObj->Description || - $self->TicketObj->QueueObj->Name; - } + if (RT->Config->Get('UseFriendlyFromLine')) { + $friendly_name = $self->TicketObj->QueueObj->Description || + $self->TicketObj->QueueObj->Name; + } $self->SUPER::SetReturnAddress( @_, friendly_name => $friendly_name ); - + } - + =head2 SetRTSpecialHeaders diff --git a/lib/RT/Action/CreateTickets.pm b/lib/RT/Action/CreateTickets.pm index 64f427e..8817adb 100644 --- a/lib/RT/Action/CreateTickets.pm +++ b/lib/RT/Action/CreateTickets.pm @@ -53,6 +53,7 @@ use strict; use warnings; use MIME::Entity; +use RT::Link; =head1 NAME @@ -128,18 +129,18 @@ A convoluted example: my $groups = RT::Groups->new(RT->SystemUser); $groups->LimitToUserDefinedGroups(); - $groups->Limit(FIELD => "Name", OPERATOR => "=", VALUE => "$name"); + $groups->Limit(FIELD => "Name", OPERATOR => "=", VALUE => $name, CASESENSITIVE => 0); $groups->WithMember($TransactionObj->CreatorObj->Id); my $groupid = $groups->First->Id; my $adminccs = RT::Users->new(RT->SystemUser); $adminccs->WhoHaveRight( - Right => "AdminGroup", - Object =>$groups->First, - IncludeSystemRights => undef, - IncludeSuperusers => 0, - IncludeSubgroupMembers => 0, + Right => "AdminGroup", + Object =>$groups->First, + IncludeSystemRights => undef, + IncludeSuperusers => 0, + IncludeSubgroupMembers => 0, ); our @admins; @@ -241,47 +242,6 @@ all be treated as the same thing. =cut -my %LINKTYPEMAP = ( - MemberOf => { - Type => 'MemberOf', - Mode => 'Target', - }, - Parents => { - Type => 'MemberOf', - Mode => 'Target', - }, - Members => { - Type => 'MemberOf', - Mode => 'Base', - }, - Children => { - Type => 'MemberOf', - Mode => 'Base', - }, - HasMember => { - Type => 'MemberOf', - Mode => 'Base', - }, - RefersTo => { - Type => 'RefersTo', - Mode => 'Target', - }, - ReferredToBy => { - Type => 'RefersTo', - Mode => 'Base', - }, - DependsOn => { - Type => 'DependsOn', - Mode => 'Target', - }, - DependedOnBy => { - Type => 'DependsOn', - Mode => 'Base', - }, - -); - - #Do what we need to do and send it out. sub Commit { my $self = shift; @@ -388,10 +348,6 @@ sub CreateByTemplate { } $RT::Logger->debug("Assigned $template_id with $id"); - $T::Tickets{$template_id}->SetOriginObj( $self->TicketObj ) - if $self->TicketObj - && $T::Tickets{$template_id}->can('SetOriginObj'); - } $self->PostProcess( \@links, \@postponed ); @@ -673,11 +629,6 @@ sub ParseLines { if ($err) { $RT::Logger->error( "Ticket creation failed: " . $err ); - while ( my ( $k, $v ) = each %T::X ) { - $RT::Logger->debug( - "Eliminating $template_id from ${k}'s parents."); - delete $v->{$template_id}; - } next; } } @@ -722,7 +673,7 @@ sub ParseLines { } if ( ($tag =~ /^(requestor|cc|admincc)(group)?$/i - or grep {lc $_ eq $tag} keys %LINKTYPEMAP) + or grep {lc $_ eq $tag} keys %RT::Link::TYPEMAP) and $args{$tag} =~ /,/ ) { $args{$tag} = [ split /,\s*/, $args{$tag} ]; @@ -1008,19 +959,11 @@ sub GetUpdateTemplate { $string .= "InitialPriority: " . $t->Priority . "\n"; $string .= "FinalPriority: " . $t->FinalPriority . "\n"; - foreach my $type ( sort keys %LINKTYPEMAP ) { - - # don't display duplicates - if ( $type eq "HasMember" - || $type eq "Members" - || $type eq "MemberOf" ) - { - next; - } + foreach my $type ( RT::Link->DisplayTypes ) { $string .= "$type: "; - my $mode = $LINKTYPEMAP{$type}->{Mode}; - my $method = $LINKTYPEMAP{$type}->{Type}; + my $mode = $RT::Link::TYPEMAP{$type}->{Mode}; + my $method = $RT::Link::TYPEMAP{$type}->{Type}; my $links = ''; while ( my $link = $t->$method->Next ) { @@ -1086,15 +1029,7 @@ sub GetCreateTemplate { $string .= "InitialPriority: \n"; $string .= "FinalPriority: \n"; - foreach my $type ( keys %LINKTYPEMAP ) { - - # don't display duplicates - if ( $type eq "HasMember" - || $type eq 'Members' - || $type eq 'MemberOf' ) - { - next; - } + foreach my $type ( RT::Link->DisplayTypes ) { $string .= "$type: \n"; } return $string; @@ -1216,7 +1151,7 @@ sub PostProcess { $RT::Logger->debug( "Handling links for " . $ticket->Id ); my %args = %{ shift(@$links) }; - foreach my $type ( keys %LINKTYPEMAP ) { + foreach my $type ( keys %RT::Link::TYPEMAP ) { next unless ( defined $args{$type} ); foreach my $link ( ref( $args{$type} ) ? @{ $args{$type} } : ( $args{$type} ) ) @@ -1243,8 +1178,8 @@ sub PostProcess { } my ( $wval, $wmsg ) = $ticket->AddLink( - Type => $LINKTYPEMAP{$type}->{'Type'}, - $LINKTYPEMAP{$type}->{'Mode'} => $link, + Type => $RT::Link::TYPEMAP{$type}->{'Type'}, + $RT::Link::TYPEMAP{$type}->{'Mode'} => $link, Silent => 1 ); diff --git a/lib/RT/Action/EscalatePriority.pm b/lib/RT/Action/EscalatePriority.pm index ec60458..6e8d880 100644 --- a/lib/RT/Action/EscalatePriority.pm +++ b/lib/RT/Action/EscalatePriority.pm @@ -88,62 +88,62 @@ sub Describe { my $self = shift; return (ref $self . " will move a ticket's priority toward its final priority."); } - + sub Prepare { my $self = shift; - + if ($self->TicketObj->Priority() == $self->TicketObj->FinalPriority()) { - # no update necessary. - return 0; + # no update necessary. + return 0; } - + #compute the number of days until the ticket is due my $due = $self->TicketObj->DueObj(); - + # If we don't have a due date, adjust the priority by one # until we hit the final priority if ($due->Unix() < 1) { - if ( $self->TicketObj->Priority > $self->TicketObj->FinalPriority ){ - $self->{'prio'} = ($self->TicketObj->Priority - 1); - return 1; - } - elsif ( $self->TicketObj->Priority < $self->TicketObj->FinalPriority ){ - $self->{'prio'} = ($self->TicketObj->Priority + 1); - return 1; - } - # otherwise the priority is at the final priority. we don't need to - # Continue - else { - return 0; - } + if ( $self->TicketObj->Priority > $self->TicketObj->FinalPriority ){ + $self->{'prio'} = ($self->TicketObj->Priority - 1); + return 1; + } + elsif ( $self->TicketObj->Priority < $self->TicketObj->FinalPriority ){ + $self->{'prio'} = ($self->TicketObj->Priority + 1); + return 1; + } + # otherwise the priority is at the final priority. we don't need to + # Continue + else { + return 0; + } } # we've got a due date. now there are other things we should do - else { - my $diff_in_seconds = $due->Diff(time()); - my $diff_in_days = int( $diff_in_seconds / 86400); - - #if we haven't hit the due date yet - if ($diff_in_days > 0 ) { - - # compute the difference between the current priority and the - # final priority - - my $prio_delta = - $self->TicketObj->FinalPriority() - $self->TicketObj->Priority; - - my $inc_priority_by = int( $prio_delta / $diff_in_days ); - - #set the ticket's priority to that amount - $self->{'prio'} = $self->TicketObj->Priority + $inc_priority_by; - - } - #if $days is less than 1, set priority to final_priority - else { - $self->{'prio'} = $self->TicketObj->FinalPriority(); - } + else { + my $diff_in_seconds = $due->Diff(time()); + my $diff_in_days = int( $diff_in_seconds / 86400); + + #if we haven't hit the due date yet + if ($diff_in_days > 0 ) { + + # compute the difference between the current priority and the + # final priority + + my $prio_delta = + $self->TicketObj->FinalPriority() - $self->TicketObj->Priority; + + my $inc_priority_by = int( $prio_delta / $diff_in_days ); + + #set the ticket's priority to that amount + $self->{'prio'} = $self->TicketObj->Priority + $inc_priority_by; + + } + #if $days is less than 1, set priority to final_priority + else { + $self->{'prio'} = $self->TicketObj->FinalPriority(); + } } return 1; @@ -154,7 +154,7 @@ sub Commit { my ($val, $msg) = $self->TicketObj->SetPriority($self->{'prio'}); unless ($val) { - $RT::Logger->debug($self . " $msg"); + $RT::Logger->debug($self . " $msg"); } } diff --git a/lib/RT/Action/LinearEscalate.pm b/lib/RT/Action/LinearEscalate.pm index 13913e6..d0529a4 100644 --- a/lib/RT/Action/LinearEscalate.pm +++ b/lib/RT/Action/LinearEscalate.pm @@ -98,7 +98,7 @@ 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 +and can be defined using the web UI via Admin tab. This action should handle correctly situations when initial priority is greater than final. @@ -140,8 +140,6 @@ 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 @@ -198,7 +196,7 @@ sub Prepare { 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; + $new_priority = $ticket->FinalPriority if $new_priority > $ticket->FinalPriority; $self->{'new_priority'} = $new_priority; return 1; diff --git a/lib/RT/Action/Notify.pm b/lib/RT/Action/Notify.pm index 3553cbc..41a992b 100644 --- a/lib/RT/Action/Notify.pm +++ b/lib/RT/Action/Notify.pm @@ -71,8 +71,8 @@ sub Prepare { =head2 SetRecipients -Sets the recipients of this meesage to Owner, Requestor, AdminCc, Cc or All. -Explicitly B notify the creator of the transaction by default +Sets the recipients of this message to Owner, Requestor, AdminCc, Cc or All. +Explicitly B notify the creator of the transaction by default. =cut @@ -131,24 +131,9 @@ sub SetRecipients { } } - my $creatorObj = $self->TransactionObj->CreatorObj; - my $creator = $creatorObj->EmailAddress() || ''; - - #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 - my $TransactionCurrentUser = RT::CurrentUser->new; - $TransactionCurrentUser->LoadByName($creatorObj->Name); - if (RT->Config->Get('NotifyActor',$TransactionCurrentUser)) { - @{ $self->{'To'} } = @To; - @{ $self->{'Cc'} } = @Cc; - @{ $self->{'Bcc'} } = @Bcc; - } - else { - @{ $self->{'To'} } = grep ( lc $_ ne lc $creator, @To ); - @{ $self->{'Cc'} } = grep ( lc $_ ne lc $creator, @Cc ); - @{ $self->{'Bcc'} } = grep ( lc $_ ne lc $creator, @Bcc ); - } + @{ $self->{'To'} } = @To; + @{ $self->{'Cc'} } = @Cc; + @{ $self->{'Bcc'} } = @Bcc; @{ $self->{'PseudoTo'} } = @PseudoTo; if ( $arg =~ /\bOtherRecipients\b/ ) { @@ -161,6 +146,34 @@ sub SetRecipients { } } +=head2 RemoveInappropriateRecipients + +Remove transaction creator as appropriate for the NotifyActor setting. + +To send email to the selected receipients regardless of RT's NotifyActor +configuration, include AlwaysNotifyActor in the list of arguments. + +=cut + +sub RemoveInappropriateRecipients { + my $self = shift; + + my $creatorObj = $self->TransactionObj->CreatorObj; + my $creator = $creatorObj->EmailAddress() || ''; + my $TransactionCurrentUser = RT::CurrentUser->new; + $TransactionCurrentUser->LoadByName($creatorObj->Name); + + $self->RecipientFilter( + Callback => sub { + return unless lc $_[0] eq lc $creator; + return "not sending to $creator, creator of the transaction, due to NotifyActor setting"; + }, + ) unless RT->Config->Get('NotifyActor',$TransactionCurrentUser) + || $self->Argument =~ /\bAlwaysNotifyActor\b/; + + $self->SUPER::RemoveInappropriateRecipients(); +} + RT::Base->_ImportOverlays(); 1; diff --git a/lib/RT/Action/NotifyGroupAsComment.pm b/lib/RT/Action/NotifyGroupAsComment.pm index cf6952a..6218061 100644 --- a/lib/RT/Action/NotifyGroupAsComment.pm +++ b/lib/RT/Action/NotifyGroupAsComment.pm @@ -62,14 +62,12 @@ 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 ); + my $self = shift; + $self->{'comment'} = 1; + return $self->SUPER::SetReturnAddress( @_, is_comment => 1 ); } =head1 AUTHOR diff --git a/lib/RT/Action/OpenOnStarted.pm b/lib/RT/Action/OpenOnStarted.pm new file mode 100644 index 0000000..f7551c3 --- /dev/null +++ b/lib/RT/Action/OpenOnStarted.pm @@ -0,0 +1,87 @@ +# BEGIN BPS TAGGED BLOCK {{{ +# +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2013 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::OpenOnStarted + +=head1 DESCRIPTION + +OpenOnStarted is a ScripAction which sets a ticket status to open when the +ticket is given a Started value. Before this commit, this functionality used to +happen in RT::Ticket::SetStarted which made the functionality the policy for +setting started. Moving the functionality to a scrip allows for it to be +disabled if it is not desired. + +=cut + +package RT::Action::OpenOnStarted; +use base 'RT::Action'; +use strict; +use warnings; + +sub Prepare { + my $self = shift; + return 0 unless $self->TransactionObj->Type eq "Set"; + return 0 unless $self->TransactionObj->Field eq "Started"; + return 1; +} + +sub Commit { + my $self = shift; + my $ticket = $self->TicketObj; + + my $next = $ticket->FirstActiveStatus; + $ticket->SetStatus( $next ) if defined $next; + + return 1; +} + +RT::Base->_ImportOverlays(); + +1; diff --git a/lib/RT/Action/RecordComment.pm b/lib/RT/Action/RecordComment.pm index a384af3..2092196 100644 --- a/lib/RT/Action/RecordComment.pm +++ b/lib/RT/Action/RecordComment.pm @@ -59,11 +59,12 @@ been started, to make a comment on the ticket. =head1 SYNOPSIS -my $action_obj = RT::Action::RecordComment->new('TicketObj' => $ticket_obj, - 'TemplateObj' => $template_obj, - ); -my $result = $action_obj->Prepare(); -$action_obj->Commit() if $result; + my $action_obj = RT::Action::RecordComment->new( + 'TicketObj' => $ticket_obj, + 'TemplateObj' => $template_obj, + ); + my $result = $action_obj->Prepare(); + $action_obj->Commit() if $result; =head1 METHODS @@ -79,8 +80,8 @@ will give us a loop. sub Prepare { my $self = shift; if (defined $self->{'TransactionObj'} && - $self->{'TransactionObj'}->Type =~ /^(Comment|Correspond)$/) { - return undef; + $self->{'TransactionObj'}->Type =~ /^(Comment|Correspond)$/) { + return undef; } return 1; } @@ -103,14 +104,14 @@ sub CreateTransaction { my $self = shift; my ($result, $msg) = $self->{'TemplateObj'}->Parse( - TicketObj => $self->{'TicketObj'}); + TicketObj => $self->{'TicketObj'}); return undef unless $result; - + my ($trans, $desc, $transaction) = $self->{'TicketObj'}->Comment( - MIMEObj => $self->TemplateObj->MIMEObj); + MIMEObj => $self->TemplateObj->MIMEObj); $self->{'TransactionObj'} = $transaction; } - + RT::Base->_ImportOverlays(); diff --git a/lib/RT/Action/RecordCorrespondence.pm b/lib/RT/Action/RecordCorrespondence.pm index cc21503..058a88b 100644 --- a/lib/RT/Action/RecordCorrespondence.pm +++ b/lib/RT/Action/RecordCorrespondence.pm @@ -59,12 +59,12 @@ been started, to create a correspondence on the ticket. =head1 SYNOPSIS -my $action_obj = RT::Action::RecordCorrespondence->new( - 'TicketObj' => $ticket_obj, - 'TemplateObj' => $template_obj, - ); -my $result = $action_obj->Prepare(); -$action_obj->Commit() if $result; + my $action_obj = RT::Action::RecordCorrespondence->new( + 'TicketObj' => $ticket_obj, + 'TemplateObj' => $template_obj, + ); + my $result = $action_obj->Prepare(); + $action_obj->Commit() if $result; =head1 METHODS @@ -80,8 +80,8 @@ will give us a loop. sub Prepare { my $self = shift; if (defined $self->{'TransactionObj'} && - $self->{'TransactionObj'}->Type =~ /^(Comment|Correspond)$/) { - return undef; + $self->{'TransactionObj'}->Type =~ /^(Comment|Correspond)$/) { + return undef; } return 1; } @@ -104,14 +104,14 @@ sub CreateTransaction { my $self = shift; my ($result, $msg) = $self->{'TemplateObj'}->Parse( - TicketObj => $self->{'TicketObj'}); + TicketObj => $self->{'TicketObj'}); return undef unless $result; - + my ($trans, $desc, $transaction) = $self->{'TicketObj'}->Correspond( - MIMEObj => $self->TemplateObj->MIMEObj); + MIMEObj => $self->TemplateObj->MIMEObj); $self->{'TransactionObj'} = $transaction; } - + RT::Base->_ImportOverlays(); diff --git a/lib/RT/Action/SendEmail.pm b/lib/RT/Action/SendEmail.pm index 0ff7e6d..5f02e43 100644 --- a/lib/RT/Action/SendEmail.pm +++ b/lib/RT/Action/SendEmail.pm @@ -135,13 +135,15 @@ Builds an outgoing email we're going to send using scrip's template. sub Prepare { my $self = shift; - my ( $result, $message ) = $self->TemplateObj->Parse( - Argument => $self->Argument, - TicketObj => $self->TicketObj, - TransactionObj => $self->TransactionObj - ); - if ( !$result ) { - return (undef); + unless ( $self->TemplateObj->MIMEObj ) { + my ( $result, $message ) = $self->TemplateObj->Parse( + Argument => $self->Argument, + TicketObj => $self->TicketObj, + TransactionObj => $self->TransactionObj + ); + if ( !$result ) { + return (undef); + } } my $MIMEObj = $self->TemplateObj->MIMEObj; @@ -179,12 +181,6 @@ sub Prepare { && !$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' ); - # For security reasons, we only send out textual mails. foreach my $part ( grep !$_->is_multipart, $MIMEObj->parts_DFS ) { my $type = $part->mime_type || 'text/plain'; @@ -195,9 +191,12 @@ sub Prepare { $part->head->mime_attr( "Content-Type.charset" => 'utf-8' ); } - RT::I18N::SetMIMEEntityToEncoding( $MIMEObj, - RT->Config->Get('EmailOutputEncoding'), - 'mime_words_ok', ); + RT::I18N::SetMIMEEntityToEncoding( + Entity => $MIMEObj, + Encoding => RT->Config->Get('EmailOutputEncoding'), + PreserveWords => 1, + IsOut => 1, + ); # Build up a MIME::Entity that looks like the original message. $self->AddAttachments if ( $MIMEObj->head->get('RT-Attach-Message') @@ -218,7 +217,7 @@ sub Prepare { 'Success'; } - return $result; + return 1; } =head2 To @@ -407,6 +406,7 @@ sub AddAttachment { Data => $attach->OriginalContent, Disposition => $disp, Filename => $self->MIMEEncodeString( $attach->Filename ), + Id => $attach->GetHeader('Content-ID'), 'RT-Attachment:' => $self->TicketObj->Id . "/" . $self->TransactionObj->Id . "/" . $attach->id, @@ -601,16 +601,10 @@ sub SetRTSpecialHeaders { } } - if (my $precedence = RT->Config->Get('DefaultMailPrecedence') - and !$self->TemplateObj->MIMEObj->head->get("Precedence") - ) { - $self->SetHeader( 'Precedence', $precedence ); - } - $self->SetHeader( 'X-RT-Loop-Prevention', RT->Config->Get('rtname') ); - $self->SetHeader( 'RT-Ticket', + $self->SetHeader( 'X-RT-Ticket', RT->Config->Get('rtname') . " #" . $self->TicketObj->id() ); - $self->SetHeader( 'Managed-by', + $self->SetHeader( 'X-Managed-by', "RT $RT::VERSION (http://www.bestpractical.com/rt/)" ); # XXX, TODO: use /ShowUser/ShowUserEntry(or something like that) when it would be @@ -618,7 +612,7 @@ sub SetRTSpecialHeaders { if ( my $email = $self->TransactionObj->CreatorObj->EmailAddress and RT->Config->Get('UseOriginatorHeader') ) { - $self->SetHeader( 'RT-Originator', $email ); + $self->SetHeader( 'X-RT-Originator', $email ); } } @@ -738,15 +732,29 @@ Remove addresses that are RT addresses or that are on this transaction's blackli =cut +my %squelch_reasons = ( + 'not privileged' + => "because autogenerated messages are configured to only be sent to privileged users (RedistributeAutoGeneratedMessages)", + 'squelch:attachment' + => "by RT-Squelch-Replies-To header in the incoming message", + 'squelch:transaction' + => "by notification checkboxes for this transaction", + 'squelch:ticket' + => "by notification checkboxes on this ticket's People page", +); + + sub RemoveInappropriateRecipients { my $self = shift; - my @blacklist = (); + 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 my $msgid = $self->TemplateObj->MIMEObj->head->get('Message-Id'); + chomp $msgid; + if ( my $attachment = $self->TransactionObj->Attachments->First ) { if ( $attachment->GetHeader('RT-DetectedAutoGenerated') ) { @@ -755,7 +763,9 @@ sub RemoveInappropriateRecipients { # caused by one of the watcher addresses being broken. # Default ("true") is to redistribute, for historical reasons. - if ( !RT->Config->Get('RedistributeAutoGeneratedMessages') ) { + my $redistribute = RT->Config->Get('RedistributeAutoGeneratedMessages'); + + if ( !$redistribute ) { # Don't send to any watchers. @{ $self->{$_} } = () for (@EMAIL_RECIPIENT_HEADERS); @@ -763,16 +773,15 @@ sub RemoveInappropriateRecipients { . " The incoming message was autogenerated. " . "Not redistributing this message based on site configuration." ); - } elsif ( RT->Config->Get('RedistributeAutoGeneratedMessages') eq - 'privileged' ) - { + } elsif ( $redistribute eq 'privileged' ) { # Only send to "privileged" watchers. foreach my $type (@EMAIL_RECIPIENT_HEADERS) { foreach my $addr ( @{ $self->{$type} } ) { my $user = RT::User->new(RT->SystemUser); $user->LoadByEmail($addr); - push @blacklist, $addr unless $user->id && $user->Privileged; + $blacklist{ $addr } ||= 'not privileged' + unless $user->id && $user->Privileged; } } $RT::Logger->info( $msgid @@ -783,48 +792,88 @@ sub RemoveInappropriateRecipients { } if ( my $squelch = $attachment->GetHeader('RT-Squelch-Replies-To') ) { - push @blacklist, split( /,/, $squelch ); + $blacklist{ $_->address } ||= 'squelch:attachment' + foreach Email::Address->parse( $squelch ); } } - # Let's grab the SquelchMailTo attributes and push those entries into the @blacklisted - push @blacklist, map $_->Content, $self->TicketObj->SquelchMailTo, $self->TransactionObj->SquelchMailTo; + # Let's grab the SquelchMailTo attributes and push those entries + # into the blacklisted + $blacklist{ $_->Content } ||= 'squelch:transaction' + foreach $self->TransactionObj->SquelchMailTo; + $blacklist{ $_->Content } ||= 'squelch:ticket' + foreach $self->TicketObj->SquelchMailTo; + + # canonicalize emails + foreach my $address ( keys %blacklist ) { + my $reason = delete $blacklist{ $address }; + $blacklist{ lc $_ } = $reason + foreach map RT::User->CanonicalizeEmailAddress( $_->address ), + Email::Address->parse( $address ); + } - # Cycle through the people we're sending to and pull out anyone on the - # system blacklist + $self->RecipientFilter( + Callback => sub { + return unless RT::EmailParser->IsRTAddress( $_[0] ); + return "$_[0] appears to point to this RT instance. Skipping"; + }, + All => 1, + ); - # Trim leading and trailing spaces. - @blacklist = map { RT::User->CanonicalizeEmailAddress( $_->address ) } - Email::Address->parse( join ', ', grep defined, @blacklist ); + $self->RecipientFilter( + Callback => sub { + return unless $blacklist{ lc $_[0] }; + return "$_[0] is blacklisted $squelch_reasons{ $blacklist{ lc $_[0] } }. Skipping"; + }, + ); - foreach my $type (@EMAIL_RECIPIENT_HEADERS) { + + # Cycle through the people we're sending to and pull out anyone that meets any of the callbacks + for 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 $addr eq $_, @blacklist ) { - $RT::Logger->info( $msgid . "$addr was blacklisted for outbound mail on this transaction. Skipping"); - next; + ADDRESS: + for my $addr ( @{ $self->{$type} } ) { + for my $filter ( map {$_->{Callback}} @{$self->{RecipientFilter}} ) { + my $skip = $filter->($addr); + next unless $skip; + $RT::Logger->info( "$msgid $skip" ); + next ADDRESS; } push @addrs, $addr; } - foreach my $addr ( @{ $self->{'NoSquelch'}{$type} || [] } ) { - # never send email to itself - if ( !RT::EmailParser->CullRTAddresses($addr) ) { - $RT::Logger->info( $msgid . "$addr appears to point to this RT instance. Skipping" ); - next; + + NOSQUELCH_ADDRESS: + for my $addr ( @{ $self->{NoSquelch}{$type} } ) { + for my $filter ( map {$_->{Callback}} grep {$_->{All}} @{$self->{RecipientFilter}} ) { + my $skip = $filter->($addr); + next unless $skip; + $RT::Logger->info( "$msgid $skip" ); + next NOSQUELCH_ADDRESS; } push @addrs, $addr; } + @{ $self->{$type} } = @addrs; } } +=head2 RecipientFilter Callback => SUB, [All => 1] + +Registers a filter to be applied to addresses by +L. The C will be called with +one address at a time, and should return false if the address should +receive mail, or a message explaining why it should not be. Passing a +true value for C will cause the filter to also be applied to +NoSquelch (one-time Cc and Bcc) recipients as well. + +=cut + +sub RecipientFilter { + my $self = shift; + push @{ $self->{RecipientFilter}}, {@_}; +} + =head2 SetReturnAddress is_comment => BOOLEAN Calculate and set From and Reply-To headers based on the is_comment flag. @@ -972,7 +1021,7 @@ sub SetSubject { $subject =~ s/(\r\n|\n|\s)/ /g; - $self->SetHeader( 'Subject', $subject ); + $self->SetHeader( 'Subject', Encode::encode_utf8( $subject ) ); } @@ -986,11 +1035,14 @@ sub SetSubjectToken { my $self = shift; my $head = $self->TemplateObj->MIMEObj->head; - $head->replace( - Subject => RT::Interface::Email::AddSubjectTag( - Encode::decode_utf8( $head->get('Subject') ), - $self->TicketObj, - ), + $self->SetHeader( + Subject => + Encode::encode_utf8( + RT::Interface::Email::AddSubjectTag( + Encode::decode_utf8( $head->get('Subject') ), + $self->TicketObj, + ), + ), ); } @@ -1072,13 +1124,8 @@ Returns a fake Message-ID: header for the ticket to allow a base level of thread =cut sub PseudoReference { - my $self = shift; - my $pseudo_ref - = 'TicketObj->id . '@' - . RT->Config->Get('Organization') . '>'; - return $pseudo_ref; + return RT::Interface::Email::PseudoReference( $self->TicketObj ); } =head2 SetHeaderAsEncoding($field_name, $charset_encoding) @@ -1093,11 +1140,6 @@ sub SetHeaderAsEncoding { my $head = $self->TemplateObj->MIMEObj->head; - 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 ); diff --git a/lib/RT/Action/SendForward.pm b/lib/RT/Action/SendForward.pm new file mode 100644 index 0000000..f6570da --- /dev/null +++ b/lib/RT/Action/SendForward.pm @@ -0,0 +1,137 @@ +# BEGIN BPS TAGGED BLOCK {{{ +# +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2013 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::SendForward; + +use strict; +use warnings; + +use base qw(RT::Action::SendEmail); + +use Email::Address; + +=head2 Prepare + +=cut + +sub Prepare { + my $self = shift; + + my $txn = $self->TransactionObj; + + if ( $txn->Type eq 'Forward Transaction' ) { + my $forwarded_txn = RT::Transaction->new( $self->CurrentUser ); + $forwarded_txn->Load( $txn->Field ); + $self->{ForwardedTransactionObj} = $forwarded_txn; + } + + my ( $result, $message ) = $self->TemplateObj->Parse( + Argument => $self->Argument, + Ticket => $self->TicketObj, + Transaction => $self->ForwardedTransactionObj, + ForwardTransaction => $self->TransactionObj, + ); + + if ( !$result ) { + return (undef); + } + + my $mime = $self->TemplateObj->MIMEObj; + $mime->make_multipart unless $mime->is_multipart; + + my $entity; + if ( $txn->Type eq 'Forward Transaction' ) { + $entity = $self->ForwardedTransactionObj->ContentAsMIME; + } + else { + my $txns = $self->TicketObj->Transactions; + $txns->Limit( + FIELD => 'Type', + VALUE => $_, + ) for qw(Create Correspond); + + $entity = MIME::Entity->build( + Type => 'multipart/mixed', + Description => 'forwarded ticket', + ); + $entity->add_part($_) foreach + map $_->ContentAsMIME, + @{ $txns->ItemsArrayRef }; + } + + $mime->add_part($entity); + + my $txn_attachment = $self->TransactionObj->Attachments->First; + for my $header (qw/From To Cc Bcc/) { + if ( $txn_attachment->GetHeader( $header ) ) { + $mime->head->set( $header => $txn_attachment->GetHeader($header) ); + } + } + + if ( RT->Config->Get('ForwardFromUser') ) { + $mime->head->set( 'X-RT-Sign' => 0 ); + } + + $self->SUPER::Prepare(); +} + +sub SetSubjectToken { + my $self = shift; + return if RT->Config->Get('ForwardFromUser'); + $self->SUPER::SetSubjectToken(@_); +} + +sub ForwardedTransactionObj { + my $self = shift; + return $self->{'ForwardedTransactionObj'}; +} + +RT::Base->_ImportOverlays(); + +1; diff --git a/lib/RT/Action/SetStatus.pm b/lib/RT/Action/SetStatus.pm index be00396..ba7652d 100644 --- a/lib/RT/Action/SetStatus.pm +++ b/lib/RT/Action/SetStatus.pm @@ -101,7 +101,7 @@ sub Prepare { my $self = shift; my $ticket = $self->TicketObj; - my $lifecycle = $ticket->QueueObj->Lifecycle; + my $lifecycle = $ticket->LifecycleObj; my $status = $ticket->Status; my $argument = $self->Argument; diff --git a/lib/RT/Approval/Rule/NewPending.pm b/lib/RT/Approval/Rule/NewPending.pm index 97d3cfb..2054c53 100644 --- a/lib/RT/Approval/Rule/NewPending.pm +++ b/lib/RT/Approval/Rule/NewPending.pm @@ -75,7 +75,7 @@ sub Commit { # first txn entry of the approval ticket local $self->{TransactionObj} = $to; - $self->RunScripAction('Notify Owner', 'New Pending Approval', @_); + $self->RunScripAction('Notify Owner and AdminCcs', 'New Pending Approval', @_); return; diff --git a/lib/RT/Approval/Rule/Passed.pm b/lib/RT/Approval/Rule/Passed.pm index acc4916..45a64e3 100644 --- a/lib/RT/Approval/Rule/Passed.pm +++ b/lib/RT/Approval/Rule/Passed.pm @@ -96,7 +96,7 @@ sub Commit { $top->Correspond( MIMEObj => $template->MIMEObj ); if ($passed) { - my $new_status = $top->QueueObj->Lifecycle->DefaultStatus('approved') || 'open'; + my $new_status = $top->LifecycleObj->DefaultStatus('approved') || 'open'; if ( $new_status ne $top->Status ) { $top->SetStatus( $new_status ); } diff --git a/lib/RT/Approval/Rule/Rejected.pm b/lib/RT/Approval/Rule/Rejected.pm index 0a02568..7e0c4a8 100644 --- a/lib/RT/Approval/Rule/Rejected.pm +++ b/lib/RT/Approval/Rule/Rejected.pm @@ -75,7 +75,7 @@ sub Commit { # XXX: from custom prepare code $rejected->Correspond( MIMEObj => $template->MIMEObj ); $rejected->SetStatus( - Status => $rejected->QueueObj->Lifecycle->DefaultStatus('denied') || 'rejected', + Status => $rejected->LifecycleObj->DefaultStatus('denied') || 'rejected', Force => 1, ); } diff --git a/lib/RT/Article.pm b/lib/RT/Article.pm index ec1ae3c..05f209d 100644 --- a/lib/RT/Article.pm +++ b/lib/RT/Article.pm @@ -50,9 +50,11 @@ use strict; use warnings; package RT::Article; - use base 'RT::Record'; +use Role::Basic 'with'; +with "RT::Record::Role::Links" => { -excludes => ["AddLink", "_AddLinksOnCreate"] }; + use RT::Articles; use RT::ObjectTopics; use RT::Classes; @@ -67,8 +69,7 @@ sub Table {'Articles'} # This object takes custom fields use RT::CustomField; -RT::CustomField->_ForObjectType( CustomFieldLookupType() => 'Articles' ) - ; #loc +RT::CustomField->RegisterLookupType( CustomFieldLookupType() => 'Articles' ); #loc # {{{ Create @@ -352,27 +353,11 @@ sub Children { =head2 AddLink -Takes a paramhash of Type and one of Base or Target. Adds that link to this tick -et. - -=cut - -sub DeleteLink { - my $self = shift; - my %args = ( - Target => '', - Base => '', - Type => '', - Silent => undef, - @_ - ); +Takes a paramhash of Type and one of Base or Target. Adds that link to this article. - unless ( $self->CurrentUserHasRight('ModifyArticle') ) { - return ( 0, $self->loc("Permission Denied") ); - } +Prevents the use of plain numbers to avoid confusing behaviour. - $self->_DeleteLink(%args); -} +=cut sub AddLink { my $self = shift; @@ -397,15 +382,6 @@ sub AddLink { return ( 0, $self->loc("Cannot add link to plain number") ); } - # Check that we're actually getting a valid URI - my $uri_obj = RT::URI->new( $self->CurrentUser ); - unless ( $uri_obj->FromURI( $args{'Target'}||$args{'Base'} )) { - my $msg = $self->loc( "Couldn't resolve '[_1]' into a Link.", $args{'Target'} || $args{'Base'} ); - $RT::Logger->warning( $msg ); - return( 0, $msg ); - } - - $self->_AddLink(%args); } @@ -522,26 +498,6 @@ sub DeleteTopic { } } -=head2 CurrentUserHasRight - -Returns true if the current user has the right for this article, for the whole system or for this article's class - -=cut - -sub CurrentUserHasRight { - my $self = shift; - my $right = shift; - - return ( - $self->CurrentUser->HasRight( - Right => $right, - Object => $self, - EquivObjects => [ $RT::System, $RT::System, $self->ClassObj ] - ) - ); - -} - =head2 CurrentUserCanSee Returns true if the current user can see the article, using ShowArticle @@ -610,6 +566,14 @@ sub CustomFieldLookupType { "RT::Class-RT::Article"; } + +sub ACLEquivalenceObjects { + my $self = shift; + return $self->ClassObj; +} + +sub ModifyLinkRight { "ModifyArticle" } + =head2 LoadByInclude Field Value Takes the name of a form field from "Include Article" @@ -646,11 +610,11 @@ sub LoadByInclude { } unless ($ok) { # load failed, don't check Class - return ($ok, $msg); + return wantarray ? ($ok, $msg) : $ok; } unless ($Queue) { # we haven't requested extra sanity checking - return ($ok, $msg); + return wantarray ? ($ok, $msg) : $ok; } # ensure that this article is available for the Queue we're @@ -658,10 +622,10 @@ sub LoadByInclude { my $class = $self->ClassObj; unless ($class->IsApplied(0) || $class->IsApplied($Queue)) { $self->LoadById(0); - return (0, $self->loc("The Class of the Article identified by [_1] is not applied to the current Queue",$Value)); + return wantarray ? (0, $self->loc("The Class of the Article identified by [_1] is not applied to the current Queue",$Value)) : 0; } - return ($ok, $msg); + return wantarray ? ($ok, $msg) : $ok; } @@ -755,10 +719,10 @@ Returns the Class Object which has the id returned by Class =cut sub ClassObj { - my $self = shift; - my $Class = RT::Class->new($self->CurrentUser); - $Class->Load($self->Class()); - return($Class); + my $self = shift; + my $Class = RT::Class->new($self->CurrentUser); + $Class->Load($self->Class()); + return($Class); } =head2 Parent @@ -838,31 +802,57 @@ sub _CoreAccessible { { id => - {read => 1, type => 'int(11)', default => ''}, + {read => 1, type => 'int(11)', default => ''}, Name => - {read => 1, write => 1, type => 'varchar(255)', default => ''}, + {read => 1, write => 1, type => 'varchar(255)', default => ''}, Summary => - {read => 1, write => 1, type => 'varchar(255)', default => ''}, + {read => 1, write => 1, type => 'varchar(255)', default => ''}, SortOrder => - {read => 1, write => 1, type => 'int(11)', default => '0'}, + {read => 1, write => 1, type => 'int(11)', default => '0'}, Class => - {read => 1, write => 1, type => 'int(11)', default => '0'}, + {read => 1, write => 1, type => 'int(11)', default => '0'}, Parent => - {read => 1, write => 1, type => 'int(11)', default => '0'}, + {read => 1, write => 1, type => 'int(11)', default => '0'}, URI => - {read => 1, write => 1, type => 'varchar(255)', default => ''}, + {read => 1, write => 1, type => 'varchar(255)', default => ''}, Creator => - {read => 1, auto => 1, type => 'int(11)', default => '0'}, + {read => 1, auto => 1, type => 'int(11)', default => '0'}, Created => - {read => 1, auto => 1, type => 'datetime', default => ''}, + {read => 1, auto => 1, type => 'datetime', default => ''}, LastUpdatedBy => - {read => 1, auto => 1, type => 'int(11)', default => '0'}, + {read => 1, auto => 1, type => 'int(11)', default => '0'}, LastUpdated => - {read => 1, auto => 1, type => 'datetime', default => ''}, + {read => 1, auto => 1, type => 'datetime', default => ''}, } }; +sub FindDependencies { + my $self = shift; + my ($walker, $deps) = @_; + + $self->SUPER::FindDependencies($walker, $deps); + + # Links + my $links = RT::Links->new( $self->CurrentUser ); + $links->Limit( + SUBCLAUSE => "either", + FIELD => $_, + VALUE => $self->URI, + ENTRYAGGREGATOR => 'OR' + ) for qw/Base Target/; + $deps->Add( in => $links ); + + $deps->Add( out => $self->ClassObj ); + $deps->Add( in => $self->Topics ); +} + +sub PostInflate { + my $self = shift; + + $self->__Set( Field => 'URI', Value => $self->URI ); +} + RT::Base->_ImportOverlays(); 1; diff --git a/lib/RT/Articles.pm b/lib/RT/Articles.pm index d69eabf..8521432 100644 --- a/lib/RT/Articles.pm +++ b/lib/RT/Articles.pm @@ -314,7 +314,8 @@ sub LimitCustomField { $self->Limit( ALIAS => $fields, FIELD => 'Name', VALUE => $args{'FIELD'}, - ENTRYAGGREGATOR => 'OR'); + ENTRYAGGREGATOR => 'OR', + CASESENSITIVE => 0); $self->Limit( ALIAS => $fields, FIELD => 'LookupType', @@ -597,7 +598,11 @@ sub Search { require Time::ParseDate; foreach my $date (qw(Created< Created> LastUpdated< LastUpdated>)) { next unless ( $args{$date} ); - my $seconds = Time::ParseDate::parsedate( $args{$date}, FUZZY => 1, PREFER_PAST => 1 ); + my ($seconds, $error) = Time::ParseDate::parsedate( $args{$date}, FUZZY => 1, PREFER_PAST => 1 ); + unless ( defined $seconds ) { + $RT::Logger->warning( + "Couldn't parse date '$args{$date}' by Time::ParseDate" ); + } my $date_obj = RT::Date->new( $self->CurrentUser ); $date_obj->Set( Format => 'unix', Value => $seconds ); $dates->{$date} = $date_obj; @@ -907,22 +912,6 @@ sub Search { return 1; } - -=head2 NewItem - -Returns an empty new RT::Article item - -=cut - -sub NewItem { - my $self = shift; - return(RT::Article->new($self->CurrentUser)); -} - - - RT::Base->_ImportOverlays(); 1; - -1; diff --git a/lib/RT/Attachment.pm b/lib/RT/Attachment.pm index 54217b3..ee10301 100644 --- a/lib/RT/Attachment.pm +++ b/lib/RT/Attachment.pm @@ -127,28 +127,40 @@ sub Create { # If we possibly can, collapse it to a singlepart $Attachment->make_singlepart; + my $head = $Attachment->head; + # Get the subject - my $Subject = $Attachment->head->get( 'subject', 0 ); + my $Subject = $head->get( 'subject', 0 ); $Subject = '' unless defined $Subject; chomp $Subject; utf8::decode( $Subject ) unless utf8::is_utf8( $Subject ); #Get the Message-ID - my $MessageId = $Attachment->head->get( 'Message-ID', 0 ); + my $MessageId = $head->get( 'Message-ID', 0 ); defined($MessageId) or $MessageId = ''; chomp ($MessageId); $MessageId =~ s/^<(.*?)>$/$1/o; #Get the filename - my $Filename = mime_recommended_filename($Attachment); # remove path part. $Filename =~ s!.*/!! if $Filename; + my $content; + unless ( $head->get('Content-Length') ) { + my $length = 0; + if ( defined $Attachment->bodyhandle ) { + $content = $Attachment->bodyhandle->as_string; + utf8::encode( $content ) if utf8::is_utf8( $content ); + $length = length $content; + } + $head->replace( 'Content-Length' => $length ); + } + $head = $head->as_string; + # 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 ) unless utf8::is_utf8( $head ); # If a message has no bodyhandle, that means that it has subparts (or appears to) @@ -165,6 +177,7 @@ sub Create { unless ($id) { $RT::Logger->crit("Attachment insert failed - ". $RT::Handle->dbh->errstr); + return ($id); } foreach my $part ( $Attachment->parts ) { @@ -176,6 +189,7 @@ sub Create { ); unless ($id) { $RT::Logger->crit("Attachment insert failed: ". $RT::Handle->dbh->errstr); + return ($id); } } return ($id); @@ -184,7 +198,8 @@ sub Create { #If it's not multipart else { - my ($ContentEncoding, $Body, $ContentType, $Filename) = $self->_EncodeLOB( + my ($encoding, $type); + ($encoding, $content, $type, $Filename) = $self->_EncodeLOB( $Attachment->bodyhandle->as_string, $Attachment->mime_type, $Filename @@ -192,12 +207,12 @@ sub Create { my $id = $self->SUPER::Create( TransactionId => $args{'TransactionId'}, - ContentType => $ContentType, - ContentEncoding => $ContentEncoding, + ContentType => $type, + ContentEncoding => $encoding, Parent => $args{'Parent'}, Headers => $head, Subject => $Subject, - Content => $Body, + Content => $content, Filename => $Filename, MessageId => $MessageId, ); @@ -209,22 +224,6 @@ sub Create { } } -=head2 Import - -Create an attachment exactly as specified in the named parameters. - -=cut - -sub Import { - my $self = shift; - my %args = ( ContentEncoding => 'none', @_ ); - - ( $args{'ContentEncoding'}, $args{'Content'} ) = - $self->_EncodeLOB( $args{'Content'}, $args{'MimeType'} ); - - return ( $self->SUPER::Create(%args) ); -} - =head2 TransactionObj Returns the transaction object asscoiated with this attachment. @@ -263,6 +262,35 @@ sub ParentObj { return $parent; } +=head2 Closest + +Takes a MIME type as a string or regex. Returns an L object +for the nearest containing part with a matching L. Strings must +match exactly and all matches are done case insensitively. Strings ending in a +C must only match the first part of the MIME type. For example: + + # Find the nearest multipart/* container + my $container = $attachment->Closest("multipart/"); + +Returns undef if no such object is found. + +=cut + +sub Closest { + my $self = shift; + my $type = shift; + my $part = $self->ParentObj or return undef; + + $type = qr/^\Q$type\E$/ + unless ref $type eq "REGEX"; + + while (lc($part->ContentType) !~ $type) { + $part = $part->ParentObj or last; + } + + return ($part and $part->id) ? $part : undef; +} + =head2 Children Returns an L object which is preloaded with @@ -279,6 +307,30 @@ sub Children { return($kids); } +=head2 Siblings + +Returns an L object containing all the attachments sharing +the same immediate parent as the current object, excluding the current +attachment itself. + +If the current attachment is a top-level part (i.e. Parent == 0) then a +guaranteed empty L object is returned. + +=cut + +sub Siblings { + my $self = shift; + my $siblings = RT::Attachments->new( $self->CurrentUser ); + if ($self->Parent) { + $siblings->ChildrenOf( $self->Parent ); + $siblings->Limit( FIELD => 'id', OPERATOR => '!=', VALUE => $self->Id ); + } else { + # Ensure emptiness + $siblings->Limit( SUBCLAUSE => 'empty', FIELD => 'id', VALUE => 0 ); + } + return $siblings; +} + =head2 Content Returns the attachment's content. if it's base64 encoded, decode it @@ -381,59 +433,32 @@ sub ContentLength { return $len; } -=head2 Quote - -=cut +=head2 FriendlyContentLength -sub Quote { - my $self=shift; - my %args=(Reply=>undef, # Prefilled reply (i.e. from the KB/FAQ system) - @_); +Returns L in bytes, kilobytes, or megabytes as most +appropriate. The size is suffixed with C, C, and C and the returned +string is localized. - my ($quoted_content, $body, $headers); - my $max=0; +Returns the empty string if the L is 0 or undefined. - # TODO: Handle Multipart/Mixed (eventually fix the link in the - # ShowHistory web template?) - if (RT::I18N::IsTextualContentType($self->ContentType)) { - $body=$self->Content; - - # Do we need any preformatting (wrapping, that is) of the message? - - # Remove quoted signature. - $body =~ s/\n-- \n(.*)$//s; - - # What's the longest line like? - foreach (split (/\n/,$body)) { - $max=length if ( length > $max); - } - - if ($max>76) { - require Text::Wrapper; - my $wrapper = Text::Wrapper->new - ( - columns => 70, - body_start => ($max > 70*3 ? ' ' : ''), - par_start => '' - ); - $body=$wrapper->wrap($body); - } - - $body =~ s/^/> /gm; +=cut - $body = '[' . $self->TransactionObj->CreatorObj->Name() . ' - ' . $self->TransactionObj->CreatedAsString() - . "]:\n\n" - . $body . "\n\n"; +sub FriendlyContentLength { + my $self = shift; + my $size = $self->ContentLength; + return '' unless $size; - } else { - $body = "[Non-text message not quoted]\n\n"; + my $res = ''; + if ( $size > 1024*1024 ) { + $res = $self->loc( "[_1]M", int( $size / 1024 / 102.4 ) / 10 ); } - - $max=60 if $max<60; - $max=70 if $max>78; - $max+=2; - - return (\$body, $max); + elsif ( $size > 1024 ) { + $res = $self->loc( "[_1]k", int( $size / 102.4 ) / 10 ); + } + else { + $res = $self->loc( "[_1]b", $size ); + } + return $res; } =head2 ContentAsMIME [Children => 1] @@ -533,9 +558,9 @@ sub NiceHeaders { my $hdrs = ""; my @hdrs = $self->_SplitHeaders; while (my $str = shift @hdrs) { - next unless $str =~ /^(To|From|RT-Send-Cc|Cc|Bcc|Date|Subject):/i; - $hdrs .= $str . "\n"; - $hdrs .= shift( @hdrs ) . "\n" while ($hdrs[0] =~ /^[ \t]+/); + next unless $str =~ /^(To|From|RT-Send-Cc|Cc|Bcc|Date|Subject):/i; + $hdrs .= $str . "\n"; + $hdrs .= shift( @hdrs ) . "\n" while ($hdrs[0] =~ /^[ \t]+/); } return $hdrs; } @@ -712,20 +737,16 @@ sub Encrypt { 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('Cryptography is disabled')) + unless RT->Config->Get('Crypt')->{'Enable'}; return (0, $self->loc('Attachments encryption is disabled')) - unless RT->Config->Get('GnuPG')->{'AllowEncryptDataInDB'}; - - require RT::Crypt::GnuPG; + unless RT->Config->Get('Crypt')->{'AllowEncryptDataInDB'}; my $type = $self->ContentType; - if ( $type =~ /^x-application-rt\/gpg-encrypted/i ) { + if ( $type =~ /^x-application-rt\/[^-]+-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"}; } my $queue = $txn->TicketObj->QueueObj; @@ -736,9 +757,9 @@ sub Encrypt { RT->Config->Get('CorrespondAddress'), RT->Config->Get('CommentAddress'), ) { - my %res = RT::Crypt::GnuPG::GetKeysInfo( $address, 'private' ); + my %res = RT::Crypt->GetKeysInfo( Key => $address, Type => 'private' ); next if $res{'exit_code'} || !$res{'info'}; - %res = RT::Crypt::GnuPG::GetKeysForEncryption( $address ); + %res = RT::Crypt->GetKeysForEncryption( $address ); next if $res{'exit_code'} || !$res{'info'}; $encrypt_for = $address; } @@ -746,24 +767,26 @@ sub Encrypt { return (0, $self->loc('No key suitable for encryption')); } - $self->__Set( Field => 'ContentType', Value => $type ); - $self->SetHeader( 'Content-Type' => $type ); - my $content = $self->Content; - my %res = RT::Crypt::GnuPG::SignEncryptContent( + my %res = RT::Crypt->SignEncryptContent( Content => \$content, Sign => 0, Encrypt => 1, Recipients => [ $encrypt_for ], ); if ( $res{'exit_code'} ) { - return (0, $self->loc('GnuPG error. Contact with administrator')); + return (0, $self->loc('Encryption error; contact the 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)); } + + $type = qq{x-application-rt\/$res{'Protocol'}-encrypted; original-type="$type"}; + $self->__Set( Field => 'ContentType', Value => $type ); + $self->SetHeader( 'Content-Type' => $type ); + return (1, $self->loc('Successfuly encrypted data')); } @@ -774,31 +797,45 @@ sub Decrypt { 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; + return (0, $self->loc('Cryptography is disabled')) + unless RT->Config->Get('Crypt')->{'Enable'}; my $type = $self->ContentType; - if ( $type =~ /^x-application-rt\/gpg-encrypted/i ) { + my $protocol; + if ( $type =~ /^x-application-rt\/([^-]+)-encrypted/i ) { + $protocol = $1; + $protocol =~ s/gpg/gnupg/; # backwards compatibility ($type) = ($type =~ /original-type="(.*)"/i); $type ||= 'application/octet-stream'; } else { return (1, $self->loc('Is not encrypted')); } - $self->__Set( Field => 'ContentType', Value => $type ); - $self->SetHeader( 'Content-Type' => $type ); + + my $queue = $txn->TicketObj->QueueObj; + my @addresses = + $queue->CorrespondAddress, + $queue->CommentAddress, + RT->Config->Get('CorrespondAddress'), + RT->Config->Get('CommentAddress') + ; my $content = $self->Content; - my %res = RT::Crypt::GnuPG::DecryptContent( Content => \$content, ); + my %res = RT::Crypt->DecryptContent( + Protocol => $protocol, + Content => \$content, + Recipients => \@addresses, + ); if ( $res{'exit_code'} ) { - return (0, $self->loc('GnuPG error. Contact with administrator')); + return (0, $self->loc('Decryption error; contact the 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)); } + $self->__Set( Field => 'ContentType', Value => $type ); + $self->SetHeader( 'Content-Type' => $type ); + return (1, $self->loc('Successfuly decrypted data')); } @@ -1029,33 +1066,41 @@ sub _CoreAccessible { { id => - {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, + {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, TransactionId => - {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, + {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Parent => - {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, + {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, MessageId => - {read => 1, write => 1, sql_type => 12, length => 160, is_blob => 0, is_numeric => 0, type => 'varchar(160)', default => ''}, + {read => 1, write => 1, sql_type => 12, length => 160, is_blob => 0, is_numeric => 0, type => 'varchar(160)', default => ''}, Subject => - {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 => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, Filename => - {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 => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, ContentType => - {read => 1, write => 1, sql_type => 12, length => 80, is_blob => 0, is_numeric => 0, type => 'varchar(80)', default => ''}, + {read => 1, write => 1, sql_type => 12, length => 80, is_blob => 0, is_numeric => 0, type => 'varchar(80)', default => ''}, ContentEncoding => - {read => 1, write => 1, sql_type => 12, length => 80, is_blob => 0, is_numeric => 0, type => 'varchar(80)', default => ''}, + {read => 1, write => 1, sql_type => 12, length => 80, is_blob => 0, is_numeric => 0, type => 'varchar(80)', default => ''}, Content => - {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'longblob', default => ''}, + {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'longblob', default => ''}, Headers => - {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'longtext', default => ''}, + {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'longtext', default => ''}, Creator => - {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, + {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Created => - {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, + {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, } }; +sub FindDependencies { + my $self = shift; + my ($walker, $deps) = @_; + + $self->SUPER::FindDependencies($walker, $deps); + $deps->Add( out => $self->TransactionObj ); +} + RT::Base->_ImportOverlays(); 1; diff --git a/lib/RT/Attachments.pm b/lib/RT/Attachments.pm index 5b087a4..60fda95 100644 --- a/lib/RT/Attachments.pm +++ b/lib/RT/Attachments.pm @@ -71,11 +71,10 @@ package RT::Attachments; use strict; use warnings; +use base 'RT::SearchBuilder'; use RT::Attachment; -use base 'RT::SearchBuilder'; - sub Table { 'Attachments'} @@ -112,14 +111,12 @@ sub TransactionAlias { 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'} = $self->Join( + ALIAS1 => 'main', + FIELD1 => 'TransactionId', + TABLE2 => 'Transactions', + FIELD2 => 'id', ); - return $self->{'_sql_transaction_alias'} = $res; } =head2 ContentType (VALUE => 'text/plain', ENTRYAGGREGATOR => 'OR', OPERATOR => '=' ) @@ -133,9 +130,9 @@ sub ContentType { my $self = shift; my %args = ( VALUE => 'text/plain', - OPERATOR => '=', - ENTRYAGGREGATOR => 'OR', - @_ + OPERATOR => '=', + ENTRYAGGREGATOR => 'OR', + @_ ); return $self->Limit ( %args, FIELD => 'ContentType' ); @@ -203,13 +200,11 @@ sub LimitByTicket { VALUE => 'RT::Ticket', ); - my $tickets = $self->NewAlias('Tickets'); - $self->Limit( - ENTRYAGGREGATOR => 'AND', - ALIAS => $tickets, - FIELD => 'id', - VALUE => $transactions . '.ObjectId', - QUOTEVALUE => 0, + my $tickets = $self->Join( + ALIAS1 => $transactions, + FIELD1 => 'ObjectId', + TABLE2 => 'Tickets', + FIELD2 => 'id', ); $self->Limit( ENTRYAGGREGATOR => 'AND', @@ -235,18 +230,6 @@ sub Next { } } - -=head2 NewItem - -Returns an empty new RT::Attachment item - -=cut - -sub NewItem { - my $self = shift; - return(RT::Attachment->new($self->CurrentUser)); -} - RT::Base->_ImportOverlays(); 1; diff --git a/lib/RT/Attribute.pm b/lib/RT/Attribute.pm index 4350df0..39987ad 100644 --- a/lib/RT/Attribute.pm +++ b/lib/RT/Attribute.pm @@ -145,11 +145,11 @@ sub Create { Content => '', ContentType => '', Object => undef, - @_); + @_); if ($args{Object} and UNIVERSAL::can($args{Object}, 'Id')) { - $args{ObjectType} = $args{Object}->isa("RT::CurrentUser") ? "RT::User" : ref($args{Object}); - $args{ObjectId} = $args{Object}->Id; + $args{ObjectType} = $args{Object}->isa("RT::CurrentUser") ? "RT::User" : ref($args{Object}); + $args{ObjectId} = $args{Object}->Id; } else { return(0, $self->loc("Required parameter '[_1]' not specified", 'Object')); @@ -181,7 +181,9 @@ sub Create { $args{'ContentType'} = 'storable'; } - + delete $RT::User::PREFERENCES_CACHE{ $args{'ObjectId'} }{ $args{'Name'} } + if $args{'ObjectType'} eq 'RT::User'; + $self->SUPER::Create( Name => $args{'Name'}, Content => $args{'Content'}, @@ -210,11 +212,11 @@ sub LoadByNameAndObject { ); return ( - $self->LoadByCols( - Name => $args{'Name'}, - ObjectType => ref($args{'Object'}), - ObjectId => $args{'Object'}->Id, - ) + $self->LoadByCols( + Name => $args{'Name'}, + ObjectType => ref($args{'Object'}), + ObjectId => $args{'Object'}->Id, + ) ); } @@ -276,6 +278,11 @@ sub SetContent { my $self = shift; my $content = shift; + if ( $self->__Value('ObjectType') eq 'RT::User' ) { + delete $RT::User::PREFERENCES_CACHE + { $self->__Value('ObjectId') }{ $self->__Value('Name') }; + } + # Call __Value to avoid ACL check. if ( ($self->__Value('ContentType')||'') eq 'storable' ) { # We eval the serialization because it will lose on a coderef. @@ -602,31 +609,50 @@ sub _CoreAccessible { { id => - {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, + {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Name => - {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 => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, Description => - {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 => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''}, Content => - {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'blob', default => ''}, + {read => 1, write => 1, sql_type => -4, length => 0, is_blob => 1, is_numeric => 0, type => 'blob', default => ''}, ContentType => - {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''}, + {read => 1, write => 1, sql_type => 12, length => 16, is_blob => 0, is_numeric => 0, type => 'varchar(16)', default => ''}, ObjectType => - {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''}, + {read => 1, write => 1, sql_type => 12, length => 64, is_blob => 0, is_numeric => 0, type => 'varchar(64)', default => ''}, ObjectId => - {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, + {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Creator => - {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, + {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, Created => - {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, + {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, LastUpdatedBy => - {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, + {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'}, LastUpdated => - {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, + {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''}, } }; +sub FindDependencies { + my $self = shift; + my ($walker, $deps) = @_; + + $self->SUPER::FindDependencies($walker, $deps); + $deps->Add( out => $self->Object ); +} + +sub PreInflate { + my $class = shift; + my ($importer, $uid, $data) = @_; + + if ($data->{Object} and ref $data->{Object}) { + my $on_uid = ${ $data->{Object} }; + return if $importer->ShouldSkipTransaction($on_uid); + } + return $class->SUPER::PreInflate( $importer, $uid, $data ); +} + RT::Base->_ImportOverlays(); 1; diff --git a/lib/RT/Attributes.pm b/lib/RT/Attributes.pm index 997e376..43748b3 100644 --- a/lib/RT/Attributes.pm +++ b/lib/RT/Attributes.pm @@ -68,11 +68,10 @@ package RT::Attributes; use strict; use warnings; +use base 'RT::SearchBuilder'; use RT::Attribute; -use base 'RT::SearchBuilder'; - sub Table { 'Attributes'} @@ -140,23 +139,6 @@ sub Named { return (@attributes); } -=head2 WithId ID - -Returns the RT::Attribute objects with the id ID - -XXX TODO XXX THIS NEEDS A BETTER ACL CHECK - -=cut - -sub WithId { - my $self = shift; - my $id = shift; - - my $attr = RT::Attribute->new($self->CurrentUser); - $attr->LoadByCols( id => $id ); - return($attr); -} - =head2 DeleteEntry { Name => Content => , id => } Deletes attributes with @@ -218,18 +200,6 @@ sub LimitToObject { } - - -=head2 NewItem - -Returns an empty new RT::Attribute item - -=cut - -sub NewItem { - my $self = shift; - return(RT::Attribute->new($self->CurrentUser)); -} RT::Base->_ImportOverlays(); 1; diff --git a/lib/RT/CachedGroupMember.pm b/lib/RT/CachedGroupMember.pm index b334d4d..04cdb22 100644 --- a/lib/RT/CachedGroupMember.pm +++ b/lib/RT/CachedGroupMember.pm @@ -418,21 +418,25 @@ sub _CoreAccessible { { id => - {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, + {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, GroupId => - {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, + {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, MemberId => - {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, + {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Via => - {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, + {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, ImmediateParentId => - {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, + {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''}, Disabled => - {read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'}, + {read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'}, } }; +sub Serialize { + die "CachedGroupMembers should never be serialized"; +} + RT::Base->_ImportOverlays(); 1; diff --git a/lib/RT/CachedGroupMembers.pm b/lib/RT/CachedGroupMembers.pm index 4d8f356..072f489 100644 --- a/lib/RT/CachedGroupMembers.pm +++ b/lib/RT/CachedGroupMembers.pm @@ -69,11 +69,10 @@ package RT::CachedGroupMembers; use strict; use warnings; +use base 'RT::SearchBuilder'; use RT::CachedGroupMember; -use base 'RT::SearchBuilder'; - sub Table { 'CachedGroupMembers'} # {{{ LimitToUsers @@ -89,9 +88,10 @@ groups from users for display purposes sub LimitToUsers { my $self = shift; - my $principals = $self->NewAlias('Principals'); - $self->Join( ALIAS1 => 'main', FIELD1 => 'MemberId', - ALIAS2 => $principals, FIELD2 =>'id'); + my $principals = $self->Join( + ALIAS1 => 'main', FIELD1 => 'MemberId', + TABLE2 => 'Principals', FIELD2 =>'id' + ); $self->Limit( ALIAS => $principals, FIELD => 'PrincipalType', @@ -114,9 +114,11 @@ groups from users for display purposes sub LimitToGroups { my $self = shift; - my $principals = $self->NewAlias('Principals'); - $self->Join( ALIAS1 => 'main', FIELD1 => 'MemberId', - ALIAS2 => $principals, FIELD2 =>'id'); + my $principals = $self->Join( + ALIAS1 => 'main', FIELD1 => 'MemberId', + TABLE2 => 'Principals', FIELD2 =>'id' + ); + $self->Limit( ALIAS => $principals, FIELD => 'PrincipalType', @@ -166,23 +168,13 @@ sub LimitToGroupsWithMember { VALUE => $member || '0', FIELD => 'MemberId', ENTRYAGGREGATOR => 'OR', - QUOTEVALUE => 0 + QUOTEVALUE => 0 )); } # }}} -=head2 NewItem - -Returns an empty new RT::CachedGroupMember item - -=cut - -sub NewItem { - my $self = shift; - return(RT::CachedGroupMember->new($self->CurrentUser)); -} RT::Base->_ImportOverlays(); 1; diff --git a/lib/RT/Class.pm b/lib/RT/Class.pm index dfe8eb3..c0a5f43 100644 --- a/lib/RT/Class.pm +++ b/lib/RT/Class.pm @@ -60,6 +60,9 @@ use RT::Articles; use RT::ObjectClass; use RT::ObjectClasses; +use Role::Basic 'with'; +with "RT::Record::Role::Rights"; + sub Table {'Classes'} =head2 Load IDENTIFIER @@ -81,94 +84,19 @@ sub Load { } } -# {{{ This object provides ACLs - -use vars qw/$RIGHTS/; -$RIGHTS = { - SeeClass => 'See that this class exists', #loc_pair - CreateArticle => 'Create articles in this class', #loc_pair - ShowArticle => 'See articles in this class', #loc_pair - ShowArticleHistory => 'See changes to articles in this class', #loc_pair - ModifyArticle => 'Modify or delete articles in this class', #loc_pair - ModifyArticleTopics => 'Modify topics for articles in this class', #loc_pair - AdminClass => 'Modify metadata and custom fields for this class', #loc_pair - AdminTopics => 'Modify topic hierarchy associated with this class', #loc_pair - ShowACL => 'Display Access Control List', #loc_pair - ModifyACL => 'Create, modify and delete Access Control List entries', #loc_pair - DeleteArticle => 'Delete articles in this class', #loc_pair -}; - -our $RIGHT_CATEGORIES = { - SeeClass => 'Staff', - CreateArticle => 'Staff', - ShowArticle => 'General', - ShowArticleHistory => 'Staff', - ModifyArticle => 'Staff', - ModifyArticleTopics => 'Staff', - AdminClass => 'Admin', - AdminTopics => 'Admin', - ShowACL => 'Admin', - ModifyACL => 'Admin', - DeleteArticle => 'Staff', -}; - -# TODO: This should be refactored out into an RT::ACLedObject or something -# stuff the rights into a hash of rights that can exist. - -# Tell RT::ACE that this sort of object can get acls granted -$RT::ACE::OBJECT_TYPES{'RT::Class'} = 1; - -# TODO this is ripe for a refacor, since this is stolen from Queue -__PACKAGE__->AddRights(%$RIGHTS); -__PACKAGE__->AddRightCategories(%$RIGHT_CATEGORIES); - -=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); -} - -=head2 AddRightCategories C, C [, ...] - -Adds the given right and category pairs to the list of right categories. This -method should be called during server startup, not at runtime. - -=cut - -sub AddRightCategories { - my $self = shift if ref $_[0] or $_[0] eq __PACKAGE__; - my %new = @_; - $RIGHT_CATEGORIES = { %$RIGHT_CATEGORIES, %new }; -} - -=head2 AvailableRights - -Returns a hash of available rights for this object. The keys are the right names and the values are a description of what t -he rights do - -=cut - -sub AvailableRights { - my $self = shift; - return ($RIGHTS); -} - -sub RightCategories { - return $RIGHT_CATEGORIES; -} - - -# }}} - +__PACKAGE__->AddRight( Staff => SeeClass => 'See that this class exists'); # loc_pair +__PACKAGE__->AddRight( Staff => CreateArticle => 'Create articles in this class'); # loc_pair +__PACKAGE__->AddRight( General => ShowArticle => 'See articles in this class'); # loc_pair +__PACKAGE__->AddRight( Staff => ShowArticleHistory => 'See changes to articles in this class'); # loc_pair +__PACKAGE__->AddRight( General => SeeCustomField => 'View custom field values' ); # loc_pair +__PACKAGE__->AddRight( Staff => ModifyArticle => 'Modify or delete articles in this class'); # loc_pair +__PACKAGE__->AddRight( Staff => ModifyArticleTopics => 'Modify topics for articles in this class'); # loc_pair +__PACKAGE__->AddRight( Staff => ModifyCustomField => 'Modify custom field values' ); # loc_pair +__PACKAGE__->AddRight( Admin => AdminClass => 'Modify metadata and custom fields for this class'); # loc_pair +__PACKAGE__->AddRight( Admin => AdminTopics => 'Modify topic hierarchy associated with this class'); # loc_pair +__PACKAGE__->AddRight( Admin => ShowACL => 'Display Access Control List'); # loc_pair +__PACKAGE__->AddRight( Admin => ModifyACL => 'Create, modify and delete Access Control List entries'); # loc_pair +__PACKAGE__->AddRight( Staff => DeleteArticle => 'Delete articles in this class'); # loc_pair # {{{ Create @@ -255,20 +183,6 @@ sub _Value { # }}} -sub CurrentUserHasRight { - my $self = shift; - my $right = shift; - - return ( - $self->CurrentUser->HasRight( - Right => $right, - Object => ( $self->Id ? $self : $RT::System ), - EquivObjects => [ $RT::System, $RT::System ] - ) - ); - -} - sub ArticleCustomFields { my $self = shift; @@ -450,7 +364,33 @@ sub RemoveFromObject { return ( $oid, $msg ); } +sub SubjectOverride { + my $self = shift; + my $override = $self->FirstAttribute('SubjectOverride'); + return $override ? $override->Content : 0; +} +sub SetSubjectOverride { + my $self = shift; + my $override = shift; + + if ( $override == $self->SubjectOverride ) { + return (0, "SubjectOverride is already set to that"); + } + + my $cf = RT::CustomField->new($self->CurrentUser); + $cf->Load($override); + + if ( $override ) { + my ($ok, $msg) = $self->SetAttribute( Name => 'SubjectOverride', Content => $override ); + return ($ok, $ok ? $self->loc('Added Subject Override: [_1]', $cf->Name) : + $self->loc('Unable to add Subject Override: [_1] [_2]', $cf->Name, $msg)); + } else { + my ($ok, $msg) = $self->DeleteAttribute('SubjectOverride'); + return ($ok, $ok ? $self->loc('Removed Subject Override') : + $self->loc('Unable to add Subject Override: [_1] [_2]', $cf->Name, $msg)); + } +} =head2 id @@ -592,29 +532,82 @@ sub _CoreAccessible { { id => - {read => 1, type => 'int(11)', default => ''}, + {read => 1, type => 'int(11)', default => ''}, Name => - {read => 1, write => 1, type => 'varchar(255)', default => ''}, + {read => 1, write => 1, type => 'varchar(255)', default => ''}, Description => - {read => 1, write => 1, type => 'varchar(255)', default => ''}, + {read => 1, write => 1, type => 'varchar(255)', default => ''}, SortOrder => - {read => 1, write => 1, type => 'int(11)', default => '0'}, + {read => 1, write => 1, type => 'int(11)', default => '0'}, Disabled => - {read => 1, write => 1, type => 'int(2)', default => '0'}, + {read => 1, write => 1, type => 'int(2)', default => '0'}, HotList => - {read => 1, write => 1, type => 'int(2)', default => '0'}, + {read => 1, write => 1, type => 'int(2)', default => '0'}, Creator => - {read => 1, auto => 1, type => 'int(11)', default => '0'}, + {read => 1, auto => 1, type => 'int(11)', default => '0'}, Created => - {read => 1, auto => 1, type => 'datetime', default => ''}, + {read => 1, auto => 1, type => 'datetime', default => ''}, LastUpdatedBy => - {read => 1, auto => 1, type => 'int(11)', default => '0'}, + {read => 1, auto => 1, type => 'int(11)', default => '0'}, LastUpdated => - {read => 1, auto => 1, type => 'datetime', default => ''}, + {read => 1, auto => 1, type => 'datetime', default => ''}, } }; +sub FindDependencies { + my $self = shift; + my ($walker, $deps) = @_; + + $self->SUPER::FindDependencies($walker, $deps); + + my $articles = RT::Articles->new( $self->CurrentUser ); + $articles->Limit( FIELD => "Class", VALUE => $self->Id ); + $deps->Add( in => $articles ); + + my $topics = RT::Topics->new( $self->CurrentUser ); + $topics->LimitToObject( $self ); + $deps->Add( in => $topics ); + + my $objectclasses = RT::ObjectClasses->new( $self->CurrentUser ); + $objectclasses->LimitToClass( $self->Id ); + $deps->Add( in => $objectclasses ); + + # Custom Fields on things _in_ this class (CFs on the class itself + # have already been dealt with) + my $ocfs = RT::ObjectCustomFields->new( $self->CurrentUser ); + $ocfs->Limit( FIELD => 'ObjectId', + OPERATOR => '=', + VALUE => $self->id, + ENTRYAGGREGATOR => 'OR' ); + $ocfs->Limit( FIELD => 'ObjectId', + OPERATOR => '=', + VALUE => 0, + ENTRYAGGREGATOR => 'OR' ); + my $cfs = $ocfs->Join( + ALIAS1 => 'main', + FIELD1 => 'CustomField', + TABLE2 => 'CustomFields', + FIELD2 => 'id', + ); + $ocfs->Limit( ALIAS => $cfs, + FIELD => 'LookupType', + OPERATOR => 'STARTSWITH', + VALUE => 'RT::Class-' ); + $deps->Add( in => $ocfs ); +} + +sub PreInflate { + my $class = shift; + my ($importer, $uid, $data) = @_; + + $class->SUPER::PreInflate( $importer, $uid, $data ); + + return if $importer->MergeBy( "Name", $class, $uid, $data ); + + return 1; +} + RT::Base->_ImportOverlays(); 1; diff --git a/lib/RT/Classes.pm b/lib/RT/Classes.pm index 8949d9b..112ab98 100644 --- a/lib/RT/Classes.pm +++ b/lib/RT/Classes.pm @@ -92,21 +92,7 @@ sub Next { } -sub ColumnMapClassName { - return 'RT__Class'; -} - -=head2 NewItem - -Returns an empty new RT::Class item - -=cut - -sub NewItem { - my $self = shift; - return(RT::Class->new($self->CurrentUser)); -} - +sub _SingularClass { "RT::Class" } RT::Base->_ImportOverlays(); diff --git a/lib/RT/Condition.pm b/lib/RT/Condition.pm index 3826c56..ab70972 100644 --- a/lib/RT/Condition.pm +++ b/lib/RT/Condition.pm @@ -54,14 +54,14 @@ use RT::Condition; my $foo = RT::Condition->new( - TransactionObj => $tr, - TicketObj => $ti, - ScripObj => $scr, - Argument => $arg, - Type => $type); + TransactionObj => $tr, + TicketObj => $ti, + ScripObj => $scr, + Argument => $arg, + Type => $type); if ($foo->IsApplicable) { - # do something + # do something } @@ -95,14 +95,14 @@ sub new { sub _Init { my $self = shift; my %args = ( TransactionObj => undef, - TicketObj => undef, - ScripObj => undef, - TemplateObj => undef, - Argument => undef, - ApplicableTransTypes => 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'}; diff --git a/lib/RT/Condition/BeforeDue.pm b/lib/RT/Condition/BeforeDue.pm index 8df73ca..1e27865 100644 --- a/lib/RT/Condition/BeforeDue.pm +++ b/lib/RT/Condition/BeforeDue.pm @@ -46,6 +46,23 @@ # # END BPS TAGGED BLOCK }}} +=head1 NAME + +RT::Condition::BeforeDue + +=head1 DESCRIPTION + +Returns true if the ticket we're operating on is within the +amount of time defined by the passed in argument. + +The passed in value is a date in the format "1d2h3m4s" +for 1 day and 2 hours and 3 minutes and 4 seconds. Single +units can also be passed such as 1d for just one day. + + +=cut + + package RT::Condition::BeforeDue; use base 'RT::Condition'; @@ -61,8 +78,8 @@ sub IsApplicable { # and 3 minutes and 4 seconds. my %e; foreach (qw(d h m s)) { - my @vals = $self->Argument =~ m/(\d+)$_/; - $e{$_} = pop @vals || 0; + my @vals = $self->Argument =~ m/(\d+)$_/; + $e{$_} = pop @vals || 0; } my $elapse = $e{'d'} * 24*60*60 + $e{'h'} * 60*60 + $e{'m'} * 60 + $e{'s'}; diff --git a/lib/RT/Condition/Overdue.pm b/lib/RT/Condition/Overdue.pm index 547aea2..8e12f23 100644 --- a/lib/RT/Condition/Overdue.pm +++ b/lib/RT/Condition/Overdue.pm @@ -71,11 +71,11 @@ If the due date is before "now" return true sub IsApplicable { my $self = shift; if ($self->TicketObj->DueObj->Unix > 0 and - $self->TicketObj->DueObj->Unix < time()) { - return(1); - } + $self->TicketObj->DueObj->Unix < time()) { + return(1); + } else { - return(undef); + return(undef); } } diff --git a/lib/RT/Condition/OwnerChange.pm b/lib/RT/Condition/OwnerChange.pm index 8500548..51f482e 100644 --- a/lib/RT/Condition/OwnerChange.pm +++ b/lib/RT/Condition/OwnerChange.pm @@ -62,12 +62,16 @@ If we're changing the owner return true, otherwise return false sub IsApplicable { my $self = shift; - if ( ( $self->TransactionObj->Field || '' ) eq 'Owner' ) { - return(1); - } - else { - return(undef); - } + return unless ( $self->TransactionObj->Field || '' ) eq 'Owner'; + + # For tickets, there is both a Set txn (for the column) and a + # SetWatcher txn (for the group); we fire on the former for + # historical consistency. Non-ticket objects will not have a + # denormalized Owner column, and thus need fire on the SetWatcher. + return if $self->TransactionObj->Type eq "SetWatcher" + and $self->TransactionObj->ObjectType eq "RT::Ticket"; + + return(1); } RT::Base->_ImportOverlays(); diff --git a/lib/RT/Condition/PriorityChange.pm b/lib/RT/Condition/PriorityChange.pm index a600453..be79c01 100644 --- a/lib/RT/Condition/PriorityChange.pm +++ b/lib/RT/Condition/PriorityChange.pm @@ -62,10 +62,10 @@ the Priority Obj sub IsApplicable { my $self = shift; if ($self->TransactionObj->Field eq 'Priority') { - return(1); - } + return(1); + } else { - return(undef); + return(undef); } } diff --git a/lib/RT/Condition/PriorityExceeds.pm b/lib/RT/Condition/PriorityExceeds.pm index a28d6df..b43ef83 100644 --- a/lib/RT/Condition/PriorityExceeds.pm +++ b/lib/RT/Condition/PriorityExceeds.pm @@ -60,10 +60,10 @@ If the priority exceeds the argument value sub IsApplicable { my $self = shift; if ($self->TicketObj->Priority > $self->Argument) { - return(1); - } + return(1); + } else { - return(undef); + return(undef); } } diff --git a/lib/RT/Condition/QueueChange.pm b/lib/RT/Condition/QueueChange.pm index ba7a8a4..ed3d9ff 100644 --- a/lib/RT/Condition/QueueChange.pm +++ b/lib/RT/Condition/QueueChange.pm @@ -60,10 +60,10 @@ If the queue has changed. sub IsApplicable { my $self = shift; if ($self->TransactionObj->Field eq 'Queue') { - return(1); - } + return(1); + } else { - return(undef); + return(undef); } } diff --git a/lib/RT/Condition/StatusChange.pm b/lib/RT/Condition/StatusChange.pm index e84915d..f3a87cd 100644 --- a/lib/RT/Condition/StatusChange.pm +++ b/lib/RT/Condition/StatusChange.pm @@ -114,11 +114,11 @@ sub IsApplicable { } else { $RT::Logger->error("Argument '$argument' is incorrect.") - unless RT::Lifecycle->Load('')->IsValid( $argument ); + unless RT::Lifecycle->Load(Type => 'ticket')->IsValid( $argument ); return 0; } - my $lifecycle = $self->TicketObj->QueueObj->Lifecycle; + my $lifecycle = $self->TicketObj->LifecycleObj; if ( $new_must_be ) { return 0 unless grep lc($new) eq lc($_), map {m/^(initial|active|inactive)$/i? $lifecycle->Valid(lc $_): $_ } diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm index b1319c0..7ea5433 100644 --- a/lib/RT/Config.pm +++ b/lib/RT/Config.pm @@ -51,8 +51,8 @@ package RT::Config; use strict; use warnings; - use File::Spec (); +use Symbol::Global::Name; =head1 NAME @@ -107,7 +107,7 @@ 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 + under on the user Preferences page Overridable - Can users change this option SortOrder - Within a Section, how should the options be sorted for display to the user @@ -122,6 +122,11 @@ can be set for each config optin: Callback - subref that receives no arguments. It returns a hashref of items that are added to the rest of the WidgetArguments + PostSet - subref passed the RT::Config object and the current and + previous setting of the config option. This is called well + before much of RT's subsystems are initialized, so what you + can do here is pretty limited. It's mostly useful for + effecting the value of other config options early. 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 @@ -133,7 +138,8 @@ can be set for each config optin: =cut -our %META = ( +our %META; +%META = ( # General user overridable options DefaultQueue => { Section => 'General', @@ -196,15 +202,15 @@ our %META = ( WidgetArguments => { Description => 'Theme', #loc # XXX: we need support for 'get values callback' - Values => [qw(web2 aileron ballard)], + Values => [qw(rudder web2 aileron ballard)], }, PostLoadCheck => sub { my $self = shift; my $value = $self->Get('WebDefaultStylesheet'); - my @comp_roots = RT::Interface::Web->ComponentRoots; - for my $comp_root (@comp_roots) { - return if -d $comp_root.'/NoAuth/css/'.$value; + my @roots = RT::Interface::Web->StaticRoots; + for my $root (@roots) { + return if -d "$root/css/$value"; } $RT::Logger->warning( @@ -215,6 +221,16 @@ our %META = ( $self->Set('WebDefaultStylesheet', 'aileron'); }, }, + TimeInICal => { + Section => 'General', + Overridable => 1, + SortOrder => 5, + Widget => '/Widgets/Form/Boolean', + WidgetArguments => { + Description => 'Include time in iCal feed events?', # loc + Hints => 'Formats iCal feed events with date and time' #loc + } + }, UseSideBySideLayout => { Section => 'Ticket composition', Overridable => 1, @@ -260,17 +276,6 @@ our %META = ( Description => 'Message box height', #loc }, }, - MessageBoxWrap => { - Section => 'Ticket composition', #loc - Overridable => 1, - SortOrder => 8.1, - Widget => '/Widgets/Form/Select', - WidgetArguments => { - Description => 'Message box wrapping', #loc - Values => [qw(SOFT HARD)], - Hints => "When the WYSIWYG editor is not enabled, this setting determines whether automatic line wraps in the ticket message box are sent to RT or not.", # loc - }, - }, DefaultTimeUnitsToHours => { Section => 'Ticket composition', #loc Overridable => 1, @@ -323,6 +328,16 @@ our %META = ( }, # User overridable options for Ticket displays + PreferRichText => { + Section => 'Ticket display', # loc + Overridable => 1, + SortOrder => 0.9, + Widget => '/Widgets/Form/Boolean', + WidgetArguments => { + Description => 'Display messages in rich text if available', # loc + Hints => 'Rich text (HTML) shows formatting such as colored text, bold, italics, and more', # loc + }, + }, MaxInlineBody => { Section => 'Ticket display', #loc Overridable => 1, @@ -343,13 +358,19 @@ our %META = ( Description => 'Show oldest history first', #loc }, }, - DeferTransactionLoading => { + ShowHistory => { Section => 'Ticket display', Overridable => 1, SortOrder => 3, - Widget => '/Widgets/Form/Boolean', + Widget => '/Widgets/Form/Select', WidgetArguments => { - Description => 'Hide ticket history by default', #loc + Description => 'Show history', #loc + Values => [qw(delay click always)], + ValuesLabel => { + delay => "after the rest of the page loads", #loc + click => "after clicking a link", #loc + always => "immediately", #loc + }, }, }, ShowUnreadMessageNotifications => { @@ -363,13 +384,20 @@ our %META = ( }, PlainTextPre => { - Section => 'Ticket display', - Overridable => 1, - SortOrder => 5, - Widget => '/Widgets/Form/Boolean', - WidgetArguments => { - Description => 'add
     tag around plain text attachments', #loc
    -            Hints       => "Use this to protect the format of plain text" #loc
    +        PostSet => sub {
    +            my $self  = shift;
    +            my $value = shift;
    +            $self->SetFromConfig(
    +                Option => \'PlainTextMono',
    +                Value  => [$value],
    +                %{$self->Meta('PlainTextPre')->{'Source'}}
    +            );
    +        },
    +        PostLoadCheck => sub {
    +            my $self = shift;
    +            # XXX: deprecated, remove in 4.4
    +            $RT::Logger->info("You set \$PlainTextPre in your config, which has been removed in favor of \$PlainTextMono.  Please update your config.")
    +                if $self->Meta('PlainTextPre')->{'Source'}{'Package'};
             },
         },
         PlainTextMono => {
    @@ -378,8 +406,8 @@ our %META = (
             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
    +            Description => 'Display plain-text attachments in fixed-width font', #loc
    +            Hints => 'Display all plain-text attachments in a monospace font with formatting preserved, but wrapping as needed.', #loc
             },
         },
         MoreAboutRequestorTicketList => {
    @@ -388,7 +416,7 @@ our %META = (
             SortOrder       => 6,
             Widget          => '/Widgets/Form/Select',
             WidgetArguments => {
    -            Description => q|What tickets to display in the 'More about requestor' box|,                #loc
    +            Description => 'What tickets to display in the "More about requestor" box',                #loc
                 Values      => [qw(Active Inactive All None)],
                 ValuesLabel => {
                     Active   => "Show the Requestor's 10 highest priority active tickets",                  #loc
    @@ -404,7 +432,7 @@ our %META = (
             SortOrder       => 7,
             Widget          => '/Widgets/Form/Boolean',
             WidgetArguments => {
    -            Description => q|Show simplified recipient list on ticket update|,                #loc
    +            Description => "Show simplified recipient list on ticket update",                #loc
             },
         },
         DisplayTicketAfterQuickCreate => {
    @@ -413,9 +441,18 @@ our %META = (
             SortOrder       => 8,
             Widget          => '/Widgets/Form/Boolean',
             WidgetArguments => {
    -            Description => q{Display ticket after "Quick Create"}, #loc
    +            Description => 'Display ticket after "Quick Create"', #loc
             },
         },
    +    QuoteFolding => {
    +        Section => 'Ticket display',
    +        Overridable => 1,
    +        SortOrder => 9,
    +        Widget => '/Widgets/Form/Boolean',
    +        WidgetArguments => {
    +            Description => 'Enable quote folding?' # loc
    +        }
    +    },
     
         # User overridable locale options
         DateTimeFormat => {
    @@ -567,53 +604,144 @@ our %META = (
                 $self->Set( DisableGD => 1 );
             },
         },
    -    MailPlugins  => { Type => 'ARRAY' },
    -    Plugins      => {
    +    MailCommand => {
    +        Type    => 'SCALAR',
    +        PostLoadCheck => sub {
    +            my $self = shift;
    +            my $value = $self->Get('MailCommand');
    +            return if ref($value) eq "CODE"
    +                or $value =~/^(sendmail|sendmailpipe|qmail|testfile)$/;
    +            $RT::Logger->error("Unknown value for \$MailCommand: $value; defaulting to sendmailpipe");
    +            $self->Set( MailCommand => 'sendmailpipe' );
    +        },
    +    },
    +    MailPlugins  => {
             Type => 'ARRAY',
             PostLoadCheck => sub {
                 my $self = shift;
    -            my $value = $self->Get('Plugins');
    -            # XXX Remove in RT 4.2
    -            return unless $value and grep {$_ eq "RT::FM"} @{$value};
    -            warn 'RTFM has been integrated into core RT, and must be removed from your @Plugins';
    +            my @plugins = $self->Get('MailPlugins');
    +            if ( grep $_ eq 'Auth::GnuPG' || $_ eq 'Auth::SMIME', @plugins ) {
    +                $RT::Logger->warning(
    +                    'Auth::GnuPG and Auth::SMIME (from an extension) have been'
    +                    .' replaced with Auth::Crypt.  @MailPlugins has been adjusted,'
    +                    .' but should be updated to replace both with Auth::Crypt to'
    +                    .' silence this warning.'
    +                );
    +                my %seen;
    +                @plugins =
    +                    grep !$seen{$_}++,
    +                    grep {
    +                        $_ eq 'Auth::GnuPG' || $_ eq 'Auth::SMIME'
    +                        ? 'Auth::Crypt' : $_
    +                    } @plugins;
    +                $self->Set( MailPlugins => @plugins );
    +            }
             },
         },
    -    GnuPG        => { Type => 'HASH' },
    -    GnuPGOptions => { Type => 'HASH',
    +    Crypt        => {
    +        Type => 'HASH',
    +        PostLoadCheck => sub {
    +            my $self = shift;
    +            require RT::Crypt;
    +
    +            for my $proto (RT::Crypt->EnabledProtocols) {
    +                my $opt = $self->Get($proto);
    +                if (not RT::Crypt->LoadImplementation($proto)) {
    +                    $RT::Logger->error("You enabled $proto, but we couldn't load module RT::Crypt::$proto");
    +                    $opt->{'Enable'} = 0;
    +                } elsif (not RT::Crypt->LoadImplementation($proto)->Probe) {
    +                    $opt->{'Enable'} = 0;
    +                } elsif ($META{$proto}{'PostLoadCheck'}) {
    +                    $META{$proto}{'PostLoadCheck'}->( $self, $self->Get( $proto ) );
    +                }
    +
    +            }
    +
    +            my $opt = $self->Get('Crypt');
    +            my @enabled = RT::Crypt->EnabledProtocols;
    +            my %enabled;
    +            $enabled{$_} = 1 for @enabled;
    +            $opt->{'Enable'} = scalar @enabled;
    +            $opt->{'Incoming'} = [ $opt->{'Incoming'} ]
    +                if $opt->{'Incoming'} and not ref $opt->{'Incoming'};
    +            if ( $opt->{'Incoming'} && @{ $opt->{'Incoming'} } ) {
    +                $opt->{'Incoming'} = [ grep {$enabled{$_}} @{$opt->{'Incoming'}} ];
    +            } else {
    +                $opt->{'Incoming'} = \@enabled;
    +            }
    +            if ( $opt->{'Outgoing'} ) {
    +                $opt->{'Outgoing'} = $enabled[0] unless $enabled{$opt->{'Outgoing'}};
    +            } else {
    +                $opt->{'Outgoing'} = $enabled[0];
    +            }
    +        },
    +    },
    +    SMIME        => {
    +        Type => 'HASH',
    +        PostLoadCheck => sub {
    +            my $self = shift;
    +            my $opt = $self->Get('SMIME');
    +            return unless $opt->{'Enable'};
    +
    +            if (exists $opt->{Keyring}) {
    +                unless ( File::Spec->file_name_is_absolute( $opt->{Keyring} ) ) {
    +                    $opt->{Keyring} = File::Spec->catfile( $RT::BasePath, $opt->{Keyring} );
    +                }
    +                unless (-d $opt->{Keyring} and -r _) {
    +                    $RT::Logger->info(
    +                        "RT's SMIME libraries couldn't successfully read your".
    +                        " configured SMIME keyring directory (".$opt->{Keyring}
    +                        .").");
    +                    delete $opt->{Keyring};
    +                }
    +            }
    +
    +            if (defined $opt->{CAPath}) {
    +                if (-d $opt->{CAPath} and -r _) {
    +                    # directory, all set
    +                } elsif (-f $opt->{CAPath} and -r _) {
    +                    # file, all set
    +                } else {
    +                    $RT::Logger->warn(
    +                        "RT's SMIME libraries could not read your configured CAPath (".$opt->{CAPath}.")"
    +                    );
    +                    delete $opt->{CAPath};
    +                }
    +            }
    +        },
    +    },
    +    GnuPG        => {
    +        Type => 'HASH',
             PostLoadCheck => sub {
                 my $self = shift;
                 my $gpg = $self->Get('GnuPG');
                 return unless $gpg->{'Enable'};
    +
                 my $gpgopts = $self->Get('GnuPGOptions');
    +            unless ( File::Spec->file_name_is_absolute( $gpgopts->{homedir} ) ) {
    +                $gpgopts->{homedir} = File::Spec->catfile( $RT::BasePath, $gpgopts->{homedir} );
    +            }
                 unless (-d $gpgopts->{homedir}  && -r _ ) { # no homedir, no gpg
    -                $RT::Logger->debug(
    +                $RT::Logger->info(
                         "RT's GnuPG libraries couldn't successfully read your".
                         " configured GnuPG home directory (".$gpgopts->{homedir}
    -                    ."). PGP support has been disabled");
    +                    ."). GnuPG 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;
    +            if ( grep exists $gpg->{$_}, qw(RejectOnMissingPrivateKey RejectOnBadData AllowEncryptDataInDB) ) {
    +                $RT::Logger->warning(
    +                    "The RejectOnMissingPrivateKey, RejectOnBadData and AllowEncryptDataInDB"
    +                    ." GnuPG options are now properties of the generic Crypt configuration. You"
    +                    ." should set them there instead."
    +                );
    +                delete $gpg->{$_} for qw(RejectOnMissingPrivateKey RejectOnBadData AllowEncryptDataInDB);
                 }
             }
         },
    +    GnuPGOptions => { Type => 'HASH' },
         ReferrerWhitelist => { Type => 'ARRAY' },
    -    ResolveDefaultUpdateType => {
    -        PostLoadCheck => sub {
    -            my $self  = shift;
    -            my $value = shift;
    -            return unless $value;
    -            $RT::Logger->info('The ResolveDefaultUpdateType config option has been deprecated.  '.
    -                              'You can change the site default in your %Lifecycles config.');
    -        }
    -    },
         WebPath => {
             PostLoadCheck => sub {
                 my $self  = shift;
    @@ -751,35 +879,69 @@ our %META = (
                 }
             },
         },
    -
    -    ActiveStatus => {
    -        Type => 'ARRAY',
    -        PostLoadCheck => sub {
    -            my $self  = shift;
    -            return unless shift;
    -            # XXX Remove in RT 4.2
    -            warn < {
    +        Deprecated => {
    +            Instead => 'LogToSTDERR',
    +            Remove  => '4.4',
             },
         },
    -    InactiveStatus => {
    -        Type => 'ARRAY',
    -        PostLoadCheck => sub {
    -            my $self  = shift;
    -            return unless shift;
    -            # XXX Remove in RT 4.2
    -            warn < {
    +        Deprecated => {
    +            Instead => 'UserSearchFields',
    +            Remove  => '4.4',
    +        },
    +    },
    +    CustomFieldGroupings => {
    +        Type            => 'HASH',
    +        PostLoadCheck   => sub {
    +            my $config = shift;
    +            # use scalar context intentionally to avoid not a hash error
    +            my $groups = $config->Get('CustomFieldGroupings') || {};
    +
    +            unless (ref($groups) eq 'HASH') {
    +                RT->Logger->error("Config option \%CustomFieldGroupings is a @{[ref $groups]} not a HASH; ignoring");
    +                $groups = {};
    +            }
    +
    +            for my $class (keys %$groups) {
    +                my @h;
    +                if (ref($groups->{$class}) eq 'HASH') {
    +                    push @h, $_, $groups->{$class}->{$_}
    +                        for sort {lc($a) cmp lc($b)} keys %{ $groups->{$class} };
    +                } elsif (ref($groups->{$class}) eq 'ARRAY') {
    +                    @h = @{ $groups->{$class} };
    +                } else {
    +                    RT->Logger->error("Config option \%CustomFieldGroupings{$class} is not a HASH or ARRAY; ignoring");
    +                    delete $groups->{$class};
    +                    next;
    +                }
    +
    +                $groups->{$class} = [];
    +                while (@h) {
    +                    my $group = shift @h;
    +                    my $ref   = shift @h;
    +                    if (ref($ref) eq 'ARRAY') {
    +                        push @{$groups->{$class}}, $group => $ref;
    +                    } else {
    +                        RT->Logger->error("Config option \%CustomFieldGroupings{$class}{$group} is not an ARRAY; ignoring");
    +                    }
    +                }
    +            }
    +            $config->Set( CustomFieldGroupings => %$groups );
             },
         },
    +    ChartColors => {
    +        Type    => 'ARRAY',
    +    },
    +    WebExternalAuth           => { Deprecated => { Instead => 'WebRemoteUserAuth',             Remove => '4.4' }},
    +    WebExternalAuthContinuous => { Deprecated => { Instead => 'WebRemoteUserContinuous',       Remove => '4.4' }},
    +    WebFallbackToInternalAuth => { Deprecated => { Instead => 'WebFallbackToRTLogin',          Remove => '4.4' }},
    +    WebExternalGecos          => { Deprecated => { Instead => 'WebRemoteUserGecos',            Remove => '4.4' }},
    +    WebExternalAuto           => { Deprecated => { Instead => 'WebRemoteUserAutocreate',       Remove => '4.4' }},
    +    AutoCreate                => { Deprecated => { Instead => 'UserAutocreateDefaultsOnLogin', Remove => '4.4' }},
     );
     my %OPTIONS = ();
    +my @LOADED_CONFIGS = ();
     
     =head1 METHODS
     
    @@ -801,19 +963,6 @@ 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
    @@ -825,11 +974,9 @@ Takes no arguments.
     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;
     }
    @@ -857,9 +1004,13 @@ sub LoadConfig {
             and my $site_config = $ENV{RT_SITE_CONFIG} )
         {
             $self->_LoadConfig( %args, File => $site_config );
    +        # to allow load siteconfig again and again in case it's updated
    +        delete $INC{ $site_config };
         } else {
             $self->_LoadConfig(%args);
    +        delete $INC{$args{'File'}};
         }
    +
         $args{'File'} =~ s/Site(?=Config\.pm$)//;
         $self->_LoadConfig(%args);
         return 1;
    @@ -892,6 +1043,19 @@ sub _LoadConfig {
                     Extension  => $is_ext,
                 );
             };
    +        local *Plugin = sub {
    +            my (@new_plugins) = @_;
    +            my ( $pack, $file, $line ) = caller;
    +            return $self->SetFromConfig(
    +                Option     => \@RT::Plugins,
    +                Value      => [@RT::Plugins, @new_plugins],
    +                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;
    @@ -942,6 +1106,14 @@ EOF
             my $errormessage = sprintf( $message,
                 $file_path, $fileusername, $filegroup, $filegroup );
             die "$errormessage\n$@";
    +    } else {
    +        # Loaded successfully
    +        push @LOADED_CONFIGS, {
    +            as          => $args{'File'},
    +            filename    => $INC{ $args{'File'} },
    +            extension   => $is_ext,
    +            site        => $is_site,
    +        };
         }
         return 1;
     }
    @@ -978,6 +1150,40 @@ sub Configs {
         return @configs;
     }
     
    +=head2 LoadedConfigs
    +
    +Returns a list of hashrefs, one for each config file loaded.  The keys of the
    +hashes are:
    +
    +=over 4
    +
    +=item as
    +
    +Name this config file was loaded as (relative filename usually).
    +
    +=item filename
    +
    +The full path and filename.
    +
    +=item extension
    +
    +The "extension" part of the filename.  For example, the file C
    +will have an C value of C.
    +
    +=item site
    +
    +True if the file is considered a site-level override.  For example, C
    +will be false for C and true for C.
    +
    +=back
    +
    +=cut
    +
    +sub LoadedConfigs {
    +    # Copy to avoid the caller changing our internal data
    +    return map { \%$_ } @LOADED_CONFIGS
    +}
    +
     =head2 Get
     
     Takes name of the option as argument and returns its current value.
    @@ -1070,6 +1276,24 @@ sub Set {
             {no warnings 'once'; no strict 'refs'; ${"RT::$name"} = $OPTIONS{$name}; }
         }
         $META{$name}->{'Type'} = $type;
    +    $META{$name}->{'PostSet'}->($self, $OPTIONS{$name}, $old)
    +        if $META{$name}->{'PostSet'};
    +    if ($META{$name}->{'Deprecated'}) {
    +        my %deprecated = %{$META{$name}->{'Deprecated'}};
    +        my $new_var = $deprecated{Instead} || '';
    +        $self->SetFromConfig(
    +            Option => \$new_var,
    +            Value  => [$OPTIONS{$name}],
    +            %{$self->Meta($name)->{'Source'}}
    +        ) if $new_var;
    +        $META{$name}->{'PostLoadCheck'} ||= sub {
    +            RT->Deprecated(
    +                Message => "Configuration option $name is deprecated",
    +                Stack   => 0,
    +                %deprecated,
    +            );
    +        };
    +    }
         return $self->_ReturnValue( $old, $type );
     }
     
    @@ -1105,7 +1329,7 @@ sub SetFromConfig {
         my $opt = $args{'Option'};
     
         my $type;
    -    my $name = $self->__GetNameByRef($opt);
    +    my $name = Symbol::Global::Name->find($opt);
         if ($name) {
             $type = ref $opt;
             $name =~ s/.*:://;
    @@ -1165,74 +1389,6 @@ sub SetFromConfig {
         return 1;
     }
     
    -    our %REF_SYMBOLS = (
    -            SCALAR => '$',
    -            ARRAY  => '@',
    -            HASH   => '%',
    -            CODE   => '&',
    -        );
    -
    -{
    -    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 '::';
    -
    -        no strict 'refs';
    -        my $name = undef;
    -
    -        # scan $pack's nametable(hash)
    -        foreach my $k ( keys %{$pack} ) {
    -
    -            # The hash for main:: has a reference to itself
    -            next if $k eq 'main::';
    -
    -            # if the entry has a trailing '::' then
    -            # it is a link to another name space
    -            if ( substr( $k, -2 ) eq '::') {
    -                $name = $self->__GetNameByRef( $ref, $pack eq 'main::'? $k : $pack.$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. maybe we should skip any
    -            # reference
    -            next if ref($entry) eq 'SCALAR' || ref($entry) eq 'REF';
    -
    -            my $ref_type = ref($ref);
    -
    -            # regex/arrayref/hashref/coderef are stored in SCALAR glob
    -            $ref_type = 'SCALAR' if $ref_type eq 'REF';
    -
    -            my $entry_ref = *{$entry}{ $ref_type };
    -            next if ref $entry_ref && ref $entry_ref ne ref $ref;
    -            next unless $entry_ref;
    -
    -            # if references are equal then we've found
    -            if ( $entry_ref == $ref ) {
    -                $last_pack = $pack;
    -                return ( $REF_SYMBOLS{ $ref_type } || '*' ) . $pack . $k;
    -            }
    -        }
    -        return '';
    -    }
    -}
    -
     =head2 Metadata
     
     
    @@ -1257,7 +1413,7 @@ sub Sections {
     sub Options {
         my $self = shift;
         my %args = ( Section => undef, Overridable => 1, Sorted => 1, @_ );
    -    my @res  = keys %META;
    +    my @res  = sort keys %META;
         
         @res = grep( ( $META{$_}->{'Section'} || 'General' ) eq $args{'Section'},
             @res 
    diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
    new file mode 100644
    index 0000000..7554aab
    --- /dev/null
    +++ b/lib/RT/Crypt.pm
    @@ -0,0 +1,835 @@
    +# BEGIN BPS TAGGED BLOCK {{{
    +#
    +# COPYRIGHT:
    +#
    +# This software is Copyright (c) 1996-2013 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;
    +use 5.010;
    +
    +=head1 NAME
    +
    +RT::Crypt - encrypt/decrypt and sign/verify subsystem for RT
    +
    +=head1 DESCRIPTION
    +
    +This module provides support for encryption and signing of outgoing
    +messages, as well as the decryption and verification of incoming emails
    +using various encryption standards. Currently, L
    +and L protocols are supported.
    +
    +=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.
    +
    +For each protocol there is a hash with the same name in the configuration file.
    +This hash controls RT-specific options regarding the protocol. It allows you to
    +enable/disable each facility or change the format of messages; for example, GnuPG
    +uses the following config:
    +
    +    Set( %GnuPG,
    +        Enable => 1,
    +        ... other options ...
    +    );
    +
    +C is the only key that is generic for all protocols. A protocol may have
    +additional options to fine-tune behaviour.
    +
    +However, note that you B add the
    +L email filter to enable
    +the handling of incoming encrypted/signed messages.
    +
    +=head2 %Crypt
    +
    +This config option hash chooses which protocols are decrypted and
    +verified in incoming messages, which protocol is used for outgoing
    +emails, and RT's behaviour on errors during decrypting and verification.
    +
    +RT will provide sane defaults for all of these options.  By default, all
    +enabled encryption protocols are decrypted on incoming mail; if you wish
    +to limit this to a subset, you may, via:
    +
    +    Set( %Crypt,
    +        ...
    +        Incoming => ['SMIME'],
    +        ...
    +    );
    +
    +RT can currently only use one protocol to encrypt and sign outgoing
    +email; this defaults to the first enabled protocol.  You many specify it
    +explicitly via:
    +
    +    Set( %Crypt,
    +        ...
    +        Outgoing => 'GnuPG',
    +        ...
    +    );
    +
    +You can allow users to encrypt data in the database by setting the
    +C key to a true value; by default, this is
    +disabled.  Be aware that users must have rights to see and modify
    +tickets to use this feature.
    +
    +=head2 Per-queue options
    +
    +Using the web interface, it is possible to enable signing and/or
    +encrypting by default. As an administrative user of RT, navigate to the
    +'Admin' and 'Queues' menus, and select a queue.  If at least one
    +encryption protocol is enabled, information concerning available keys
    +will be displayed, as well as options to enable signing and encryption.
    +
    +=head2 Handling incoming messages
    +
    +To enable handling of encrypted and signed message in the RT you must
    +enable the L mail plugin:
    +
    +    Set(@MailPlugins, 'Auth::MailFrom', 'Auth::Crypt', ...other filter...);
    +
    +=head2 Error 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 an 'Error:' or 'Error to RT owner:' prefix
    +in the name. You can adjust the text of the messages using the web
    +interface.
    +
    +Note that while C<$TicketObj>, C<$TransactionObj> and other variables
    +usually available in RT's templates are not available in these
    +templates, but each is passed alternate data structures can be used to
    +build better messages; see the default templates and descriptions below.
    +
    +You can disable any particular notification by simply deleting the
    +content of a template.  Deleting the templates entirely is not
    +suggested, as RT will log error messages when attempting to send mail
    +usign them.
    +
    +=head3 Problems with public keys
    +
    +The 'Error: public key' template is used to inform the user that RT had
    +problems with their public key, and thus will not be able to send
    +encrypted content. There are several reasons why RT might fail to use a
    +key; by default, the actual reason is not sent to the user, but sent to
    +the RT owner using the 'Error to RT owner: public key' template.
    +
    +Possible reasons include "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".
    +
    +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
    +
    +As a message may 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:
    +
    +    @BadRecipients = (
    +        { Message => '...', Reason => '...', Recipient => '...', ...},
    +        { Message => '...', Reason => '...', Recipient => '...', ...},
    +        ...
    +    )
    +
    +=head3 Private key doesn't exist
    +
    +The 'Error: no private key' template is used to inform the user that
    +they sent an encrypted email to RT, but RT does not have the private key
    +to decrypt it.
    +
    +In this template L object C<$Message> is available, which
    +is the originally received message.
    +
    +=head3 Invalid data
    +
    +The 'Error: bad encrypted data' template is used to inform the user that
    +a message they sent had invalid data, and could not be handled.  There
    +are several possible reasons for this error, but most of them are data
    +corruption or absence of expected information.
    +
    +In this template, the C<@Messages> array is available, and will contain
    +a list of error messages.
    +
    +=head1 METHODS
    +
    +=head2 Protocols
    +
    +Returns the complete set of encryption protocols that RT implements; not
    +all may be supported by this installation.
    +
    +=cut
    +
    +our @PROTOCOLS = ('GnuPG', 'SMIME');
    +our %PROTOCOLS = map { lc $_ => $_ } @PROTOCOLS;
    +
    +sub Protocols {
    +    return @PROTOCOLS;
    +}
    +
    +=head2 EnabledProtocols
    +
    +Returns the set of enabled and available encryption protocols.
    +
    +=cut
    +
    +sub EnabledProtocols {
    +    my $self = shift;
    +    return grep RT->Config->Get($_)->{'Enable'}, $self->Protocols;
    +}
    +
    +=head2 UseForOutgoing
    +
    +Returns the configured outgoing encryption protocol; see
    +L.
    +
    +=cut
    +
    +sub UseForOutgoing {
    +    return RT->Config->Get('Crypt')->{'Outgoing'};
    +}
    +
    +=head2 EnabledOnIncoming
    +
    +Returns the list of encryption protocols that should be used for
    +decryption and verification of incoming email; see L.
    +This list is irrelevant unless L is
    +enabled in L.
    +
    +=cut
    +
    +sub EnabledOnIncoming {
    +    return @{ scalar RT->Config->Get('Crypt')->{'Incoming'} };
    +}
    +
    +=head2 LoadImplementation CLASS
    +
    +Given the name of an encryption implementation (e.g. "GnuPG"), loads the
    +L class associated with it; return the classname on success,
    +and undef on failure.
    +
    +=cut
    +
    +sub LoadImplementation {
    +    state %cache;
    +    my $proto = $PROTOCOLS{ lc $_[1] } or die "Unknown protocol '$_[1]'";
    +    my $class = 'RT::Crypt::'. $proto;
    +    return $cache{ $class } if exists $cache{ $class };
    +
    +    if (eval "require $class; 1") {
    +        return $cache{ $class } = $class;
    +    } else {
    +        RT->Logger->warn( "Could not load $class: $@" );
    +        return $cache{ $class } = undef;
    +    }
    +}
    +
    +=head2 SimpleImplementationCall Protocol => NAME, [...]
    +
    +Examines the caller of this method, and dispatches to the method of the
    +same name on the correct L class based on the provided
    +C.
    +
    +=cut
    +
    +sub SimpleImplementationCall {
    +    my $self = shift;
    +    my %args = (@_);
    +    my $protocol = delete $args{'Protocol'} || $self->UseForOutgoing;
    +
    +    my $method = (caller(1))[3];
    +    $method =~ s/.*:://;
    +
    +    my %res = $self->LoadImplementation( $protocol )->$method( %args );
    +    $res{'Protocol'} = $protocol if keys %res;
    +    return %res;
    +}
    +
    +=head2 FindProtectedParts Entity => MIME::Entity
    +
    +Looks for encrypted or signed parts of the given C, using all
    +L encryption protocols.  For each node in the MIME
    +hierarchy, L for that L
    +is called on each L protocol.  Any multipart nodes
    +not claimed by those protocols are recursed into.
    +
    +Finally, L is called on the top-most
    +entity for each L protocol.
    +
    +Returns a list of hash references; each hash reference is guaranteed to
    +contain a C key describing the protocol of the found part, and
    +a C which is either C or C.  The remaining keys
    +are protocol-dependent; the hashref will be provided to
    +L.
    +
    +=cut
    +
    +sub FindProtectedParts {
    +    my $self = shift;
    +    my %args = (
    +        Entity => undef,
    +        Skip => {},
    +        Scattered => 1,
    +        @_
    +    );
    +
    +    my $entity = $args{'Entity'};
    +    return () if $args{'Skip'}{ $entity };
    +
    +    $args{'TopEntity'} ||= $entity;
    +
    +    my @protocols = $self->EnabledOnIncoming;
    +
    +    foreach my $protocol ( @protocols ) {
    +        my $class = $self->LoadImplementation( $protocol );
    +        my %info = $class->CheckIfProtected(
    +            TopEntity => $args{'TopEntity'},
    +            Entity    => $entity,
    +        );
    +        next unless keys %info;
    +
    +        $args{'Skip'}{ $entity } = 1;
    +        $info{'Protocol'} = $protocol;
    +        return \%info;
    +    }
    +
    +    if ( $entity->effective_type =~ /^multipart\/(?:signed|encrypted)/ ) {
    +        # if no module claimed that it supports these types then
    +        # we don't dive in and check sub-parts
    +        $args{'Skip'}{ $entity } = 1;
    +        return ();
    +    }
    +
    +    my @res;
    +
    +    # not protected itself, look inside
    +    push @res, $self->FindProtectedParts(
    +        %args, Entity => $_, Scattered => 0,
    +    ) foreach grep !$args{'Skip'}{$_}, $entity->parts;
    +
    +    if ( $args{'Scattered'} ) {
    +        my %parent;
    +        my $filter; $filter = sub {
    +            $parent{$_[0]} = $_[1];
    +            unless ( $_[0]->is_multipart ) {
    +                return () if $args{'Skip'}{$_[0]};
    +                return $_[0];
    +            }
    +            return map $filter->($_, $_[0]), grep !$args{'Skip'}{$_}, $_[0]->parts;
    +        };
    +        my @parts = $filter->($entity);
    +        return @res unless @parts;
    +
    +        foreach my $protocol ( @protocols ) {
    +            my $class = $self->LoadImplementation( $protocol );
    +            my @list = $class->FindScatteredParts(
    +                Entity  => $args{'TopEntity'},
    +                Parts   => \@parts,
    +                Parents => \%parent,
    +                Skip    => $args{'Skip'}
    +            );
    +            next unless @list;
    +
    +            $_->{'Protocol'} = $protocol foreach @list;
    +            push @res, @list;
    +            @parts = grep !$args{'Skip'}{$_}, @parts;
    +        }
    +    }
    +
    +    return @res;
    +}
    +
    +=head2 SignEncrypt Entity => ENTITY, [Sign => 1], [Encrypt => 1],
    +[Recipients => ARRAYREF], [Signer => NAME], [Protocol => NAME],
    +[Passphrase => VALUE]
    +
    +Takes a L object, and signs and/or encrypts it using the
    +given C.  If not set, C for encryption will be set
    +by examining the C, C, and C headers of the MIME entity.
    +If not set, C defaults to the C of the MIME entity.
    +
    +C, if not provided, will be retrieved using
    +L.
    +
    +Returns a hash with at least the following keys:
    +
    +=over
    +
    +=item exit_code
    +
    +True if there was an error encrypting or signing.
    +
    +=item message
    +
    +An un-localized error message desribing the problem.
    +
    +=back
    +
    +=cut
    +
    +sub SignEncrypt {
    +    my $self = shift;
    +    my %args = (@_);
    +
    +    my $entity = $args{'Entity'};
    +    if ( $args{'Sign'} && !defined $args{'Signer'} ) {
    +        $args{'Signer'} =
    +            $self->UseKeyForSigning
    +            || do {
    +                my $addr = (Email::Address->parse( $entity->head->get( 'From' ) ))[0];
    +                $addr? $addr->address : undef
    +            };
    +    }
    +    if ( $args{'Encrypt'} && !$args{'Recipients'} ) {
    +        my %seen;
    +        $args{'Recipients'} = [
    +            grep $_ && !$seen{ $_ }++, map $_->address,
    +            map Email::Address->parse( $entity->head->get( $_ ) ),
    +            qw(To Cc Bcc)
    +        ];
    +    }
    +    return $self->SimpleImplementationCall( %args );
    +}
    +
    +=head2 SignEncryptContent Content => STRINGREF, [Sign => 1], [Encrypt => 1],
    +[Recipients => ARRAYREF], [Signer => NAME], [Protocol => NAME],
    +[Passphrase => VALUE]
    +
    +Signs and/or encrypts a string, which is passed by reference.
    +C defaults to C, and C
    +defaults to the global L.  All other
    +arguments and return values are identical to L.
    +
    +=cut
    +
    +sub SignEncryptContent {
    +    my $self = shift;
    +    my %args = (@_);
    +
    +    if ( $args{'Sign'} && !defined $args{'Signer'} ) {
    +        $args{'Signer'} = $self->UseKeyForSigning;
    +    }
    +    if ( $args{'Encrypt'} && !$args{'Recipients'} ) {
    +        $args{'Recipients'} = [ RT->Config->Get('CorrespondAddress') ];
    +    }
    +
    +    return $self->SimpleImplementationCall( %args );
    +}
    +
    +=head2 DrySign Signer => KEY
    +
    +Signs a small message with the key, to make sure the key exists and we
    +have a useable passphrase. The Signer 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 $self = shift;
    +
    +    my $mime = MIME::Entity->build(
    +        Type    => "text/plain",
    +        From    => 'nobody@localhost',
    +        To      => 'nobody@localhost',
    +        Subject => "dry sign",
    +        Data    => ['t'],
    +    );
    +
    +    my %res = $self->SignEncrypt(
    +        @_,
    +        Sign    => 1,
    +        Encrypt => 0,
    +        Entity  => $mime,
    +    );
    +
    +    return $res{exit_code} == 0;
    +}
    +
    +=head2 VerifyDecrypt Entity => ENTITY [, Passphrase => undef ]
    +
    +Locates all protected parts of the L object C, as
    +found by L, and calls
    +L from the appropriate L
    +class on each.
    +
    +C, if not provided, will be retrieved using
    +L.
    +
    +Returns a list of the hash references returned from
    +L.
    +
    +=cut
    +
    +sub VerifyDecrypt {
    +    my $self = shift;
    +    my %args = (
    +        Entity    => undef,
    +        Recursive => 1,
    +        @_
    +    );
    +
    +    my @res;
    +
    +    my @protected = $self->FindProtectedParts( Entity => $args{'Entity'} );
    +    foreach my $protected ( @protected ) {
    +        my %res = $self->SimpleImplementationCall(
    +            %args, Protocol => $protected->{'Protocol'}, Info => $protected
    +        );
    +
    +        # Let the header be modified so continuations are handled
    +        my $modify = $res{status_on}->head->modify;
    +        $res{status_on}->head->modify(1);
    +        $res{status_on}->head->add(
    +            "X-RT-" . $protected->{'Protocol'} . "-Status" => $res{'status'}
    +        );
    +        $res{status_on}->head->modify($modify);
    +
    +        push @res, \%res;
    +    }
    +
    +    push @res, $self->VerifyDecrypt( %args )
    +        if $args{Recursive} and @res and not grep {$_->{'exit_code'}} @res;
    +
    +    return @res;
    +}
    +
    +=head2 DecryptContent Protocol => NAME, Content => STRINGREF, [Passphrase => undef]
    +
    +Decrypts the content in the string reference in-place.  All other
    +arguments and return values are identical to L.
    +
    +=cut
    +
    +sub DecryptContent {
    +    return shift->SimpleImplementationCall( @_ );
    +}
    +
    +=head2 ParseStatus Protocol => NAME, Status => STRING
    +
    +Takes a C describing the status of verification/decryption,
    +usually as stored in a MIME header.  Parses it and returns array of hash
    +references, one for each operation.  Each hashref contains at least
    +three keys:
    +
    +=over
    +
    +=item Operation
    +
    +The classification of the process whose status is being reported upon.
    +Valid values include C, C, C, C,
    +C, C and C.
    +
    +=item Status
    +
    +Whether the operation was successful; contains C on success.
    +Other possible values include C, C, or C.
    +
    +=item Message
    +
    +An un-localized user friendly message.
    +
    +=back
    +
    +=cut
    +
    +sub ParseStatus {
    +    my $self = shift;
    +    my %args = (
    +        Protocol => undef,
    +        Status   => '',
    +        @_
    +    );
    +    return $self->LoadImplementation( $args{'Protocol'} )->ParseStatus( $args{'Status'} );
    +}
    +
    +=head2 UseKeyForSigning [KEY]
    +
    +Returns or sets the identifier of the key that should be used for
    +signing.  Returns the current value when called without arguments; sets
    +the new value when called with one argument and unsets if it's undef.
    +
    +This cache is cleared at the end of every request.
    +
    +=cut
    +
    +sub UseKeyForSigning {
    +    my $self = shift;
    +    state $key;
    +    if ( @_ ) {
    +        $key = $_[0];
    +    }
    +    return $key;
    +}
    +
    +=head2 UseKeyForEncryption [KEY [, VALUE]]
    +
    +Gets or sets keys to use for encryption.  When passed no arguments,
    +clears the cache.  When passed just a key, returns the encryption key
    +previously stored for that key.  When passed two (or more) keys, stores
    +them associatively.
    +
    +This cache is reset at the end of every request.
    +
    +=cut
    +
    +sub UseKeyForEncryption {
    +    my $self = shift;
    +    state %key;
    +    unless ( @_ ) {
    +        %key = ();
    +    } elsif ( @_ > 1 ) {
    +        %key = (%key, @_);
    +        $key{ lc($_) } = delete $key{ $_ } foreach grep lc ne $_, keys %key;
    +    } else {
    +        return $key{ $_[0] };
    +    }
    +    return ();
    +}
    +
    +=head2 GetKeysForEncryption Recipient => EMAIL, Protocol => NAME
    +
    +Returns the list of keys which are suitable for encrypting mail to the
    +given C.  Generally this is equivalent to L
    +with a C of , but encryption protocols may further limit
    +which keys can be used for encryption, as opposed to signing.
    +
    +=cut
    +
    +sub CheckRecipients {
    +    my $self = shift;
    +    my @recipients = (@_);
    +
    +    my ($status, @issues) = (1, ());
    +
    +    my $trust = sub { 1 };
    +    if ( $self->UseForOutgoing eq 'SMIME' ) {
    +        $trust = sub { $_[0]->{'TrustLevel'} > 0 or RT->Config->Get('SMIME')->{AcceptUntrustedCAs} };
    +    } elsif ( $self->UseForOutgoing eq 'GnuPG' ) {
    +        $trust = sub { $_[0]->{'TrustLevel'} > 0 };
    +    }
    +
    +    my %seen;
    +    foreach my $address ( grep !$seen{ lc $_ }++, map $_->address, @recipients ) {
    +        my %res = $self->GetKeysForEncryption( Recipient => $address );
    +        if ( $res{'info'} && @{ $res{'info'} } == 1 and $trust->($res{'info'}[0]) ) {
    +            # One key, which is trusted, or we can sign with an
    +            # untrusted key (aka SMIME with AcceptUntrustedCAs)
    +            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 = RT::Crypt->UseKeyForEncryption( $address ) ) {
    +            if ( $res{'info'} && @{ $res{'info'} } ) {
    +                next if
    +                    grep lc $_->{'Fingerprint'} eq lc $fpr,
    +                    grep $trust->($_),
    +                    @{ $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 GetKeysForEncryption {
    +    my $self = shift;
    +    my %args = @_%2? (Recipient => @_) : (Protocol => undef, Recipient => undef, @_ );
    +    return $self->SimpleImplementationCall( %args );
    +}
    +
    +=head2 GetKeysForSigning Signer => EMAIL, Protocol => NAME
    +
    +Returns the list of keys which are suitable for signing mail from the
    +given C.  Generally this is equivalent to L
    +with a C of , but encryption protocols may further limit
    +which keys can be used for signing, as opposed to encryption.
    +
    +=cut
    +
    +sub GetKeysForSigning {
    +    my $self = shift;
    +    my %args = @_%2? (Signer => @_) : (Protocol => undef, Signer => undef, @_);
    +    return $self->SimpleImplementationCall( %args );
    +}
    +
    +=head2 GetPublicKeyInfo Protocol => NAME, KEY => EMAIL
    +
    +As per L, but the C is forced to C.
    +
    +=cut
    +
    +sub GetPublicKeyInfo {
    +    return (shift)->GetKeyInfo( @_, Type => 'public' );
    +}
    +
    +=head2 GetPrivateKeyInfo Protocol => NAME, KEY => EMAIL
    +
    +As per L, but the C is forced to C.
    +
    +=cut
    +
    +sub GetPrivateKeyInfo {
    +    return (shift)->GetKeyInfo( @_, Type => 'private' );
    +}
    +
    +=head2 GetKeyInfo Protocol => NAME, Type => ('public'|'private'), KEY => EMAIL
    +
    +As per L, but only the first matching key is returned in
    +the C value of the result.
    +
    +=cut
    +
    +sub GetKeyInfo {
    +    my $self = shift;
    +    my %res = $self->GetKeysInfo( @_ );
    +    $res{'info'} = $res{'info'}->[0];
    +    return %res;
    +}
    +
    +=head2 GetKeysInfo Protocol => NAME, Type => ('public'|'private'), Key => EMAIL
    +
    +Looks up information about the public or private keys (as determined by
    +C) for the email address C.  As each protocol has its own key
    +store, C is also required.  If no C is provided and a
    +true value for C is given, returns all keys.
    +
    +The return value is a hash containing C and C in the
    +case of failure, or C, which is an array reference of key
    +information.  Each key is represented as a hash reference; the keys are
    +protocol-dependent, but will at least contain:
    +
    +=over
    +
    +=item Protocol
    +
    +The name of the protocol of this key
    +
    +=item Created
    +
    +An L of the date the key was created; undef if unset.
    +
    +=item Expire
    +
    +An L of the date the key expires; undef if the key does not expire.
    +
    +=item Fingerprint
    +
    +A fingerprint unique to this key
    +
    +=item User
    +
    +An array reference of associated user data, each of which is a hashref
    +containing at least a C value, which is a C<< Alice Example
    + >> style email address.  Each may also contain
    +C and C keys, which are L objects.
    +
    +=back
    +
    +=cut
    +
    +sub GetKeysInfo {
    +    my $self = shift;
    +    my %args = @_%2 ? (Key => @_) : ( Protocol => undef, Key => undef, @_ );
    +    return $self->SimpleImplementationCall( %args );
    +}
    +
    +1;
    diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
    index 6164a42..44d6518 100644
    --- a/lib/RT/Crypt/GnuPG.pm
    +++ b/lib/RT/Crypt/GnuPG.pm
    @@ -48,34 +48,39 @@
     
     use strict;
     use warnings;
    +use 5.010;
     
     package RT::Crypt::GnuPG;
     
    +use Role::Basic 'with';
    +with 'RT::Crypt::Role';
    +
     use IO::Handle;
    +use File::Which qw();
    +use RT::Crypt::GnuPG::CRLFHandle;
     use GnuPG::Interface;
     use RT::EmailParser ();
     use RT::Util 'safe_run_child', 'mime_recommended_filename';
     
     =head1 NAME
     
    -RT::Crypt::GnuPG - encrypt/decrypt and sign/verify email messages with the GNU Privacy Guard (GPG)
    +RT::Crypt::GnuPG - GNU Privacy Guard encryption/decryption/verification/signing
     
     =head1 DESCRIPTION
     
    -This module provides support for encryption and signing of outgoing messages, 
    -as well as the decryption and verification of incoming email.
    +This module provides support for encryption and signing of outgoing
    +messages using GnuPG, 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.
    +There are two reveant configuration options, both of which are hashes:
    +C and C. The first one controls RT specific
    +options; it enables you to enable/disable the GPG protocol or change the
    +format of messages. The second one is a hash with options which are
    +passed to the C utility. You can use it to define a keyserver,
    +enable auto-retrieval of keys, or set almost any option which C
    +supports on your system.
     
     =head2 %GnuPG
     
    @@ -88,13 +93,13 @@ Set to true value to enable this subsystem:
             ... other options ...
         );
     
    -However, note that you B add the 'Auth::GnuPG' email filter to enable
    +However, note that you B add the 'Auth::Crypt' 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:
    +The format of outgoing messages can be controlled using the
    +C option in the RT config:
     
         Set( %GnuPG,
             ... other options ...
    @@ -110,50 +115,49 @@ or
             ... other options ...
         );
     
    -This framework implements two formats of signing and encrypting of email messages:
    +The two formats for GPG mail are as follows:
     
     =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).
    +This format, the default, is also known as GPG/MIME, and is described in
    +RFC3156 and RFC1847.  The technique described in these RFCs is well
    +supported by many mail user agents (MUA); however, some older MUAs only
    +support inline signatures and encryption.
     
     =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.
    +This format doesn't take advantage of MIME, but some mail clients do not
    +support GPG/MIME.  In general, this format is discouraged because modern
    +mail clients typically do not support it well.
     
    -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.
    +Text parts are signed using clear-text signatures. For each attachment,
    +the signature is attached separately as a file with a '.sig' extension
    +added to the filename.  Encryption of text parts is implemented using
    +inline format, while other parts are replaced with attachments with the
    +filename extension '.pgp'.
     
     =back
     
    -=head3 Encrypting data in the database
    +=head3 Passphrases
     
    -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.
    +Passphrases for keys may be set by passing C.  It may be set
    +to a scalar (to use for all keys), an anonymous function, or a hash (to
    +look up by address).  If the hash is used, the '' key is used as a
    +default.
     
     =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.
    +Use this hash to set additional options of the 'gnupg' program.  The
    +only options which are diallowed are options which alter the output
    +format or attempt to run commands; thiss includes C<--sign>,
    +C<--list-options>, etc.
     
    -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
    +Some GnuPG options take arguments, while others take none. (Such as
    +C<--use-agent>).  For options without specific value use C as
    +hash value.  To disable these options, you may comment them out or
    +delete them from the hash:
     
         Set(%GnuPGOptions,
             'option-with-value' => 'value',
    @@ -161,62 +165,69 @@ To disable these option just comment them out or delete them from the hash
             # '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"'.
    +B that options may contain the '-' character and such options
    +B be quoted, otherwise you will see the quite cryptic error C.
    +
    +Common options include:
     
     =over
     
     =item --homedir
     
    -The GnuPG home directory, by default it is set to F.
    +The GnuPG home directory where the keyrings are stored; 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.
    +You can manage this data with the 'gpg' commandline utility using the
    +GNUPGHOME environment variable or C<--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. 
    +In a standard installation, access to this directory should be granted
    +to the web server user which is running RT's web interface; however, if
    +you are 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.
    +Be aware that granting access to the directory to many users makes the
    +keys less secure -- and some features, such as auto-import of keys, may
    +not be available if directory permissions are too permissive.  To enable
    +these features and suppress warnings about permissions on the directory,
    +add the C<--no-permission-warning> option to C.
     
     =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.
    +This option is required when the C format for outgoing messages is
    +used.  RT defaults to 'SHA1' by default, but you may wish to override
    +it.  C will list the algorithms supported by your
    +C installation under 'hash functions'; these generally include
    +MD5, SHA1, RIPEMD160, and SHA256.
     
     =item --use-agent
     
    -This option lets you use GPG Agent to cache the passphrase of RT's key. See
    +This option lets you use GPG Agent to cache the passphrase of secret
    +keys. 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.
    +This option lets you set the passphrase of RT's key directly. This
    +option is special in that it is not passed directly to GPG; rather, it
    +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
    +whenever possible.
     
     =item other
     
    -Read `man gpg` to get list of all options this program support.
    +Read C to get list of all options this program supports.
     
     =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',
    +default. As an administrative user of RT, open 'Admin' 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.
     
    @@ -227,99 +238,35 @@ 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...);
    +'Auth::Crypt' mail plugin.
     
    -See also `perldoc lib/RT/Interface/Email/Auth/GnuPG.pm`.
    +    Set(@MailPlugins, 'Auth::MailFrom', 'Auth::Crypt', ...other filter...);
     
    -=head2 Errors handling
    +See also `perldoc lib/RT/Interface/Email/Auth/Crypt.pm`.
     
    -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".
    +=head2 Encrypting to untrusted keys
     
     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.
    +=head1 FOR DEVELOPERS
     
    -=head3 Invalid data
    +=head2 Documentation and references
     
    -Template 'Error: bad GnuPG data' used to inform the user that a
    -message he sent has invalid data and can not be handled.
    +=over
     
    -There are several reasons for this error, but most of them are data
    -corruption or absence of expected information.
    +=item RFC1847
     
    -In this template C<@Messages> array is available and contains list
    -of error messages.
    +Security Multiparts for MIME: Multipart/Signed and Multipart/Encrypted.
    +Describes generic MIME security framework, "mulitpart/signed" and
    +"multipart/encrypted" MIME types.
     
    -=head1 FOR DEVELOPERS
     
    -=head2 Documentation and references
    +=item RFC3156
     
    -* RFC1847 - Security Multiparts for MIME: Multipart/Signed and Multipart/Encrypted.
    -Describes generic MIME security framework, "mulitpart/signed" and "multipart/encrypted"
    -MIME types.
    +MIME Security with Pretty Good Privacy (PGP), updates RFC2015.
     
    -* RFC3156 - MIME Security with Pretty Good Privacy (PGP),
    -updates RFC2015.
    +=back
     
     =cut
     
    @@ -364,64 +311,133 @@ our $RE_FILE_EXTENSIONS = qr/pgp|asc/i;
     #            ...
     #        );
     
    -=head2 SignEncrypt Entity => MIME::Entity, [ Encrypt => 1, Sign => 1, ... ]
    -
    -Signs and/or encrypts an email message with GnuPG utility.
    -
    -=over
    +sub CallGnuPG {
    +    my $self = shift;
    +    my %args = (
    +        Options     => undef,
    +        Signer      => undef,
    +        Recipients  => [],
    +        Passphrase  => undef,
    +
    +        Command     => undef,
    +        CommandArgs => [],
    +
    +        Content     => undef,
    +        Handles     => {},
    +        Direct      => undef,
    +        Output      => undef,
    +        @_
    +    );
     
    -=item Signing
    +    my %handle = %{$args{Handles}};
    +    my ($handles, $handle_list) = _make_gpg_handles( %handle );
    +    $handles->options( $_ )->{'direct'} = 1
    +        for @{$args{Direct} || [keys %handle] };
    +    %handle = %$handle_list;
     
    -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.
    +    my $content = $args{Content};
    +    my $command = $args{Command};
     
    -As well you can pass C, but if value is undefined then L
    -called to get it.
    +    my %GnuPGOptions = RT->Config->Get('GnuPGOptions');
    +    my %opt = (
    +        'digest-algo' => 'SHA1',
    +        %GnuPGOptions,
    +        %{ $args{Options} || {} },
    +    );
    +    my $gnupg = GnuPG::Interface->new;
    +    $gnupg->call( $self->GnuPGPath );
    +    $gnupg->options->hash_init(
    +        _PrepareGnuPGOptions( %opt ),
    +    );
    +    $gnupg->options->armor( 1 );
    +    $gnupg->options->meta_interactive( 0 );
    +    $gnupg->options->default_key( $args{Signer} )
    +        if defined $args{Signer};
     
    -=item Encrypting
    +    my %seen;
    +    $gnupg->options->push_recipients( $_ ) for
    +        map { RT::Crypt->UseKeyForEncryption($_) || $_ }
    +        grep { !$seen{ $_ }++ }
    +            @{ $args{Recipients} || [] };
     
    -During encryption you can pass a C array, otherwise C, C and
    -C fields of the message are used to fetch the list.
    +    $args{Passphrase} = $GnuPGOptions{passphrase}
    +        unless defined $args{'Passphrase'};
    +    $args{Passphrase} = $self->GetPassphrase( Address => $args{Signer} )
    +        unless defined $args{'Passphrase'};
    +    $gnupg->passphrase( $args{'Passphrase'} )
    +        if defined $args{Passphrase};
     
    -=back
    +    eval {
    +        local $SIG{'CHLD'} = 'DEFAULT';
    +        my $pid = safe_run_child {
    +            if ($command =~ /^--/) {
    +                $gnupg->wrap_call(
    +                    handles      => $handles,
    +                    commands     => [$command],
    +                    command_args => $args{CommandArgs},
    +                );
    +            } else {
    +                $gnupg->$command(
    +                    handles      => $handles,
    +                    command_args => $args{CommandArgs},
    +                );
    +            }
    +        };
    +        {
    +            local $SIG{'PIPE'} = 'IGNORE';
    +            if (Scalar::Util::blessed($content) and $content->can("print")) {
    +                $content->print( $handle{'stdin'} );
    +            } elsif (ref($content) eq "SCALAR") {
    +                $handle{'stdin'}->print( ${ $content } );
    +            } elsif (defined $content) {
    +                $handle{'stdin'}->print( $content );
    +            }
    +            close $handle{'stdin'} or die "Can't close gnupg input handle: $!";
    +            $args{Callback}->(%handle) if $args{Callback};
    +        }
    +        waitpid $pid, 0;
    +    };
    +    my $err = $@;
    +    if ($args{Output}) {
    +        push @{$args{Output}}, readline $handle{stdout};
    +        if (not close $handle{stdout}) {
    +            $err ||= "Can't close gnupg output handle: $!";
    +        }
    +    }
     
    -Returns a hash with the following keys:
    +    my %res;
    +    $res{'exit_code'} = $?;
     
    -* exit_code
    -* error
    -* logger
    -* status
    -* message
    +    foreach ( qw(stderr logger status) ) {
    +        $res{$_} = do { local $/ = undef; readline $handle{$_} };
    +        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
    +        if (not close $handle{$_}) {
    +            $err ||= "Can't close gnupg $_ 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 exited with error code ". ($res{'exit_code'} >> 8);
    +    }
     
    -=cut
    +    return %res;
    +}
     
     sub SignEncrypt {
    -    my %args = (@_);
    +    my $self = shift;
     
    -    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 );
    +        return $self->SignEncryptInline( @_ );
         } else {
    -        return SignEncryptRFC3156( %args );
    +        return $self->SignEncryptRFC3156( @_ );
         }
     }
     
     sub SignEncryptRFC3156 {
    +    my $self = shift;
         my %args = (
             Entity => undef,
     
    @@ -435,28 +451,7 @@ sub SignEncryptRFC3156 {
             @_
         );
     
    -    my $gnupg = GnuPG::Interface->new();
    -    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)
    @@ -468,46 +463,28 @@ sub SignEncryptRFC3156 {
                     );
                 }
             }
    -
    -        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;
    -        }
    +        $entity->make_multipart( 'mixed', Force => 1 );
    +
    +        my @signature;
    +        # We use RT::Crypt::GnuPG::CRLFHandle to canonicalize the
    +        # MIME::Entity output to use \r\n instead of \n for its newlines
    +        %res = $self->CallGnuPG(
    +            Signer     => $args{'Signer'},
    +            Command    => "detach_sign",
    +            Handles    => { stdin => RT::Crypt::GnuPG::CRLFHandle->new },
    +            Direct     => [],
    +            Passphrase => $args{'Passphrase'},
    +            Content    => $entity->parts(0),
    +            Output     => \@signature,
    +        );
    +        return %res if $res{message};
     
             # setup RFC1847(Ch.2.1) requirements
             my $protocol = 'application/pgp-signature';
    +        my $algo = RT->Config->Get('GnuPGOptions')->{'digest-algo'} || 'SHA1';
             $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->head->mime_attr( 'Content-Type.micalg'   => 'pgp-'. lc $algo );
             $entity->attach(
                 Type        => $protocol,
                 Disposition => 'inline',
    @@ -516,48 +493,23 @@ sub SignEncryptRFC3156 {
             );
         }
         if ( $args{'Encrypt'} ) {
    -        my %seen;
    -        $gnupg->options->push_recipients( $_ ) foreach 
    -            map UseKeyForEncryption($_) || $_,
    -            grep !$seen{ $_ }++, map $_->address,
    +        my @recipients = 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;
    -        }
    +        $entity->make_multipart( 'mixed', Force => 1 );
    +        %res = $self->CallGnuPG(
    +            Signer     => $args{'Signer'},
    +            Recipients => \@recipients,
    +            Command    => ( $args{'Sign'} ? "sign_and_encrypt" : "encrypt" ),
    +            Handles    => { stdout => $tmp_fh },
    +            Passphrase => $args{'Passphrase'},
    +            Content    => $entity->parts(0),
    +        );
    +        return %res if $res{message};
     
             my $protocol = 'application/pgp-encrypted';
             $entity->parts([]);
    @@ -582,6 +534,7 @@ sub SignEncryptRFC3156 {
     }
     
     sub SignEncryptInline {
    +    my $self = shift;
         my %args = ( @_ );
     
         my $entity = $args{'Entity'};
    @@ -590,19 +543,20 @@ sub SignEncryptInline {
         $entity->make_singlepart;
         if ( $entity->is_multipart ) {
             foreach ( $entity->parts ) {
    -            %res = SignEncryptInline( @_, Entity => $_ );
    +            %res = $self->SignEncryptInline( @_, Entity => $_ );
                 return %res if $res{'exit_code'};
             }
             return %res;
         }
     
    -    return _SignEncryptTextInline( @_ )
    +    return $self->_SignEncryptTextInline( @_ )
             if $entity->effective_type =~ /^text\//i;
     
    -    return _SignEncryptAttachmentInline( @_ );
    +    return $self->_SignEncryptAttachmentInline( @_ );
     }
     
     sub _SignEncryptTextInline {
    +    my $self = shift;
         my %args = (
             Entity => undef,
     
    @@ -617,72 +571,23 @@ sub _SignEncryptTextInline {
         );
         return unless $args{'Sign'} || $args{'Encrypt'};
     
    -    my $gnupg = GnuPG::Interface->new();
    -    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;
    -    }
    +    my %res = $self->CallGnuPG(
    +        Signer     => $args{'Signer'},
    +        Recipients => $args{'Recipients'},
    +        Command    => ( $args{'Sign'} && $args{'Encrypt'}
    +                      ? 'sign_and_encrypt'
    +                      : ( $args{'Sign'}
    +                        ? 'clearsign'
    +                        : 'encrypt' ) ),
    +        Handles    => { stdout => $tmp_fh },
    +        Passphrase => $args{'Passphrase'},
    +        Content    => $entity->bodyhandle,
    +    );
    +    return %res if $res{message};
     
         $entity->bodyhandle( MIME::Body::File->new( $tmp_fn) );
         $entity->{'__store_tmp_handle_to_avoid_early_cleanup'} = $tmp_fh;
    @@ -691,6 +596,7 @@ sub _SignEncryptTextInline {
     }
     
     sub _SignEncryptAttachmentInline {
    +    my $self = shift;
         my %args = (
             Entity => undef,
     
    @@ -705,71 +611,25 @@ sub _SignEncryptAttachmentInline {
         );
         return unless $args{'Sign'} || $args{'Encrypt'};
     
    -    my $gnupg = GnuPG::Interface->new();
    -    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 %res = $self->CallGnuPG(
    +        Signer     => $args{'Signer'},
    +        Recipients => $args{'Recipients'},
    +        Command    => ( $args{'Sign'} && $args{'Encrypt'}
    +                      ? 'sign_and_encrypt'
    +                      : ( $args{'Sign'}
    +                        ? 'detach_sign'
    +                        : 'encrypt' ) ),
    +        Handles    => { stdout => $tmp_fh },
    +        Passphrase => $args{'Passphrase'},
    +        Content    => $entity->bodyhandle,
    +    );
    +    return %res if $res{message};
     
         my $filename = mime_recommended_filename( $entity ) || 'no_name';
         if ( $args{'Sign'} && !$args{'Encrypt'} ) {
    @@ -793,6 +653,7 @@ sub _SignEncryptAttachmentInline {
     }
     
     sub SignEncryptContent {
    +    my $self = shift;
         my %args = (
             Content => undef,
     
    @@ -807,70 +668,22 @@ sub SignEncryptContent {
         );
         return unless $args{'Sign'} || $args{'Encrypt'};
     
    -    my $gnupg = GnuPG::Interface->new();
    -    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;
    -    }
    +    my %res = $self->CallGnuPG(
    +        Signer     => $args{'Signer'},
    +        Recipients => $args{'Recipients'},
    +        Command    => ( $args{'Sign'} && $args{'Encrypt'}
    +                      ? 'sign_and_encrypt'
    +                      : ( $args{'Sign'}
    +                        ? 'clearsign'
    +                        : 'encrypt' ) ),
    +        Handles    => { stdout => $tmp_fh },
    +        Passphrase => $args{'Passphrase'},
    +        Content    => $args{'Content'},
    +    );
    +    return %res if $res{message};
     
         ${ $args{'Content'} } = '';
         seek $tmp_fh, 0, 0;
    @@ -887,257 +700,276 @@ sub SignEncryptContent {
         return %res;
     }
     
    -sub FindProtectedParts {
    -    my %args = ( Entity => undef, CheckBody => 1, @_ );
    -    my $entity = $args{'Entity'};
    +sub CheckIfProtected {
    +    my $self = shift;
    +    my %args = ( Entity => undef, @_ );
     
    -    # inline PGP block, only in singlepart
    -    unless ( $entity->is_multipart ) {
    -        my $file = ($entity->head->recommended_filename||'') =~ /\.${RE_FILE_EXTENSIONS}$/;
    +    my $entity = $args{'Entity'};
     
    -        my $io = $entity->open('r');
    -        unless ( $io ) {
    -            $RT::Logger->warning( "Entity of type ". $entity->effective_type ." has no body" );
    -            return ();
    -        }
    +    # we check inline PGP block later in another sub
    +    return () unless $entity->is_multipart;
     
    -        # Deal with "partitioned" PGP mail, which (contrary to common
    -        # sense) unnecessarily applies a base64 transfer encoding to PGP
    -        # mail (whose content is already base64-encoded).
    -        if ( $entity->bodyhandle->is_encoded and $entity->head->mime_encoding ) {
    -            my $decoder = MIME::Decoder->new( $entity->head->mime_encoding );
    -            if ($decoder) {
    -                local $@;
    -                eval {
    -                    my $buf = '';
    -                    open my $fh, '>', \$buf
    -                        or die "Couldn't open scalar for writing: $!";
    -                    binmode $fh, ":raw";
    -                    $decoder->decode($io, $fh);
    -                    close $fh or die "Couldn't close scalar: $!";
    -
    -                    open $fh, '<', \$buf
    -                        or die "Couldn't re-open scalar for reading: $!";
    -                    binmode $fh, ":raw";
    -                    $io = $fh;
    -                    1;
    -                } or do {
    -                    $RT::Logger->error("Couldn't decode body: $@");
    -                }
    -            }
    -        }
    +    # RFC3156, multipart/{signed,encrypted}
    +    my $type = $entity->effective_type;
    +    return () unless $type =~ /^multipart\/(?:encrypted|signed)$/;
     
    -        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  => !$file || $type eq 'signed'? 'Inline' : 'Attachment',
    -                Data    => $entity,
    -            };
    -        }
    -        $io->close;
    +    unless ( $entity->parts == 2 ) {
    +        $RT::Logger->error( "Encrypted or signed entity must has two subparts. Skipped" );
             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 ();
    -        }
    +    my $protocol = $entity->head->mime_attr( 'Content-Type.protocol' );
    +    unless ( $protocol ) {
    +        # if protocol is not set then we can check second part for PGP message
    +        $RT::Logger->error( "Entity is '$type', but has no protocol defined. Checking for PGP part" );
    +        my $protected = $self->_CheckIfProtectedInline( $entity->parts(1), 1 );
    +        return () unless $protected;
     
    -        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 {
    +        if ( $protected eq 'signature' ) {
    +            $RT::Logger->debug("Found part signed according to RFC3156");
    +            return (
                     Type      => 'signed',
                     Format    => 'RFC3156',
    -                Top     => $entity,
    -                Data    => $entity->parts(0),
    +                Top       => $entity,
    +                Data      => $entity->parts(0),
                     Signature => $entity->parts(1),
    -            };
    +            );
    +        } else {
    +            $RT::Logger->debug("Found part encrypted according to RFC3156");
    +            return (
    +                Type   => 'encrypted',
    +                Format => 'RFC3156',
    +                Top    => $entity,
    +                Data   => $entity->parts(1),
    +                Info   => $entity->parts(0),
    +            );
    +        }
    +    }
    +    elsif ( $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 part encrypted according to RFC3156");
    +        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 part signed according to RFC3156");
    +        return (
    +            Type      => 'signed',
    +            Format    => 'RFC3156',
    +            Top       => $entity,
    +            Data      => $entity->parts(0),
    +            Signature => $entity->parts(1),
    +        );
         }
    +    return ();
    +}
    +
    +
    +sub FindScatteredParts {
    +    my $self = shift;
    +    my %args = ( Parts => [], Skip => {}, @_ );
    +
    +    my @res;
    +
    +    my @parts = @{ $args{'Parts'} };
     
         # attachments signed with signature in another part
    -    my @file_indices;
    -    foreach my $i ( 0 .. $entity->parts - 1 ) {
    -        my $part = $entity->parts($i);
    +    {
    +        my @file_indices;
    +        for (my $i = 0; $i < @parts; $i++ ) {
    +            my $part = $parts[ $i ];
     
    -        # we can not associate a signature within an attachment
    -        # without file names
    -        my $fname = $part->head->recommended_filename;
    -        next unless $fname;
    +            # 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;
    +            my $type = $part->effective_type;
    +
    +            if ( $type eq 'application/pgp-signature' ) {
    +                push @file_indices, $i;
    +            }
    +            elsif ( $type eq 'application/octet-stream' && $fname =~ /\.sig$/i ) {
    +                push @file_indices, $i;
    +            }
             }
    -        elsif ( $fname =~ /\.sig$/i && $part->effective_type eq 'application/octet-stream' ) {
    -            push @file_indices, $i;
    +
    +        foreach my $i ( @file_indices ) {
    +            my $sig_part = $parts[ $i ];
    +            my $sig_name = $sig_part->head->recommended_filename;
    +            my ($file_name) = $sig_name =~ /^(.*?)(?:\.sig)?$/;
    +
    +            my ($data_part_idx) =
    +                grep $file_name eq ($parts[$_]->head->recommended_filename||''),
    +                grep $sig_part  ne  $parts[$_],
    +                    0 .. @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 = $parts[ $data_part_idx ];
    +
    +            $RT::Logger->debug("Found signature (in '$sig_name') of attachment '$file_name'");
    +
    +            $args{'Skip'}{$data_part_in} = 1;
    +            $args{'Skip'}{$sig_part} = 1;
    +            push @res, {
    +                Type      => 'signed',
    +                Format    => 'Attachment',
    +                Top       => $args{'Parents'}{$sig_part},
    +                Data      => $data_part_in,
    +                Signature => $sig_part,
    +            };
             }
         }
     
    -    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);
    +    # attachments with inline encryption
    +    foreach my $part ( @parts ) {
    +        next if $args{'Skip'}{$part};
    +
    +        my $fname = $part->head->recommended_filename || '';
    +        next unless $fname =~ /\.${RE_FILE_EXTENSIONS}$/;
     
    -        $skip{"$data_part_in"}++;
    -        $RT::Logger->debug("Found signature (in '$sig_name') of attachment '$file_name'");
    +        $RT::Logger->debug("Found encrypted attachment '$fname'");
    +
    +        $args{'Skip'}{$part} = 1;
             push @res, {
    -            Type      => 'signed',
    -            Format    => 'Attachment',
    -            Top       => $entity,
    -            Data      => $data_part_in,
    -            Signature => $sig_part,
    +            Type    => 'encrypted',
    +            Format  => 'Attachment',
    +            Data    => $part,
             };
         }
     
    -    # attachments with inline encryption
    -    my @encrypted_indices =
    -        grep {($entity->parts($_)->head->recommended_filename || '') =~ /\.${RE_FILE_EXTENSIONS}$/}
    -            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 ."'");
    +    # inline PGP block
    +    foreach my $part ( @parts ) {
    +        next if $args{'Skip'}{$part};
    +
    +        my $type = $self->_CheckIfProtectedInline( $part );
    +        next unless $type;
    +
    +        my $file = ($part->head->recommended_filename||'') =~ /\.${RE_FILE_EXTENSIONS}$/;
    +
    +        $args{'Skip'}{$part} = 1;
             push @res, {
    -            Type      => 'encrypted',
    -            Format    => 'Attachment',
    -            Top     => $entity,
    -            Data    => $part,
    +            Type      => $type,
    +            Format    => !$file || $type eq 'signed'? 'Inline' : 'Attachment',
    +            Data      => $part,
             };
         }
     
    -    push @res, FindProtectedParts( Entity => $_ )
    -        foreach grep !$skip{"$_"}, $entity->parts;
    -
         return @res;
     }
     
    -=head2 VerifyDecrypt Entity => undef, [ Detach => 1, Passphrase => undef, SetStatus => 1 ]
    +sub _CheckIfProtectedInline {
    +    my $self = shift;
    +    my $entity = shift;
    +    my $check_for_signature = shift || 0;
     
    -=cut
    +    my $io = $entity->open('r');
    +    unless ( $io ) {
    +        $RT::Logger->warning( "Entity of type ". $entity->effective_type ." has no body" );
    +        return '';
    +    }
    +
    +    # Deal with "partitioned" PGP mail, which (contrary to common
    +    # sense) unnecessarily applies a base64 transfer encoding to PGP
    +    # mail (whose content is already base64-encoded).
    +    if ( $entity->bodyhandle->is_encoded and $entity->head->mime_encoding ) {
    +        my $decoder = MIME::Decoder->new( $entity->head->mime_encoding );
    +        if ($decoder) {
    +            local $@;
    +            eval {
    +                my $buf = '';
    +                open my $fh, '>', \$buf
    +                    or die "Couldn't open scalar for writing: $!";
    +                binmode $fh, ":raw";
    +                $decoder->decode($io, $fh);
    +                close $fh or die "Couldn't close scalar: $!";
    +
    +                open $fh, '<', \$buf
    +                    or die "Couldn't re-open scalar for reading: $!";
    +                binmode $fh, ":raw";
    +                $io = $fh;
    +                1;
    +            } or do {
    +                $RT::Logger->error("Couldn't decode body: $@");
    +            }
    +        }
    +    }
    +
    +    while ( defined($_ = $io->getline) ) {
    +        if ( /^-----BEGIN PGP (SIGNED )?MESSAGE-----/ ) {
    +            return $1? 'signed': 'encrypted';
    +        }
    +        elsif ( $check_for_signature && !/^-----BEGIN PGP SIGNATURE-----/ ) {
    +            return 'signature';
    +        }
    +    }
    +    $io->close;
    +    return '';
    +}
     
     sub VerifyDecrypt {
    +    my $self = shift;
         my %args = (
    -        Entity    => undef,
    -        Detach    => 1,
    -        SetStatus => 1,
    -        AddStatus => 0,
    +        Info      => undef,
             @_
         );
    -    my @protected = FindProtectedParts( Entity => $args{'Entity'} );
    -    my @res;
    -    # XXX: detaching may brake nested signatures
    -    foreach my $item( grep $_->{'Type'} eq 'signed', @protected ) {
    -        my $status_on;
    +
    +    my %res;
    +
    +    my $item = $args{'Info'};
    +    my $status_on;
    +    if ( $item->{'Type'} eq 'signed' ) {
             if ( $item->{'Format'} eq 'RFC3156' ) {
    -            push @res, { VerifyRFC3156( %$item, SetStatus => $args{'SetStatus'} ) };
    -            if ( $args{'Detach'} ) {
    -                $item->{'Top'}->parts( [ $item->{'Data'} ] );
    -                $item->{'Top'}->make_singlepart;
    -            }
    +            %res = $self->VerifyRFC3156( %$item );
                 $status_on = $item->{'Top'};
             } elsif ( $item->{'Format'} eq 'Inline' ) {
    -            push @res, { VerifyInline( %$item ) };
    +            %res = $self->VerifyInline( %$item );
                 $status_on = $item->{'Data'};
             } 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;
    -            }
    +            %res = $self->VerifyAttachment( %$item );
                 $status_on = $item->{'Data'};
    +        } else {
    +            die "Unknown format '".$item->{'Format'} . "' of GnuPG signed part";
             }
    -        if ( $args{'SetStatus'} || $args{'AddStatus'} ) {
    -            my $method = $args{'AddStatus'} ? 'add' : 'set';
    -            # Let the header be modified so continuations are handled
    -            my $modify = $status_on->head->modify;
    -            $status_on->head->modify(1);
    -            $status_on->head->$method(
    -                'X-RT-GnuPG-Status' => $res[-1]->{'status'}
    -            );
    -            $status_on->head->modify($modify);
    -        }
    -    }
    -    foreach my $item( grep $_->{'Type'} eq 'encrypted', @protected ) {
    -        my $status_on;
    +    } elsif ( $item->{'Type'} eq 'encrypted' ) {
             if ( $item->{'Format'} eq 'RFC3156' ) {
    -            push @res, { DecryptRFC3156( %$item ) };
    +            %res = $self->DecryptRFC3156( %$item );
                 $status_on = $item->{'Top'};
             } elsif ( $item->{'Format'} eq 'Inline' ) {
    -            push @res, { DecryptInline( %$item ) };
    +            %res = $self->DecryptInline( %$item );
                 $status_on = $item->{'Data'};
             } elsif ( $item->{'Format'} eq 'Attachment' ) {
    -            push @res, { DecryptAttachment( %$item ) };
    +            %res = $self->DecryptAttachment( %$item );
                 $status_on = $item->{'Data'};
    +        } else {
    +            die "Unknown format '".$item->{'Format'} . "' of GnuPG encrypted part";
             }
    -        if ( $args{'SetStatus'} || $args{'AddStatus'} ) {
    -            my $method = $args{'AddStatus'} ? 'add' : 'set';
    -            # Let the header be modified so continuations are handled
    -            my $modify = $status_on->head->modify;
    -            $status_on->head->modify(1);
    -            $status_on->head->$method(
    -                'X-RT-GnuPG-Status' => $res[-1]->{'status'}
    -            );
    -            $status_on->head->modify($modify);
    -        }
    +    } else {
    +        die "Unknown type '".$item->{'Type'} . "' of protected item";
         }
    -    return @res;
    +
    +    return (%res, status_on => $status_on);
     }
     
    -sub VerifyInline { return DecryptInline( @_ ) }
    +sub VerifyInline { return (shift)->DecryptInline( @_ ) }
     
     sub VerifyAttachment {
    -    my %args = ( Data => undef, Signature => undef, Top => undef, @_ );
    -
    -    my $gnupg = GnuPG::Interface->new();
    -    my %opt = RT->Config->Get('GnuPGOptions');
    -    $opt{'digest-algo'} ||= 'SHA1';
    -    $gnupg->options->hash_init(
    -        _PrepareGnuPGOptions( %opt ),
    -        meta_interactive => 0,
    -    );
    +    my $self = shift;
    +    my %args = ( Data => undef, Signature => undef, @_ );
     
         foreach ( $args{'Data'}, $args{'Signature'} ) {
             next unless $_->bodyhandle->is_encoded;
    @@ -1151,85 +983,45 @@ sub VerifyAttachment {
         $args{'Data'}->bodyhandle->print( $tmp_fh );
         $tmp_fh->flush;
     
    -    my ($handles, $handle_list) = _make_gpg_handles();
    -    my %handle = %$handle_list;
    +    my %res = $self->CallGnuPG(
    +        Command     => "verify",
    +        CommandArgs => [ '-', $tmp_fn ],
    +        Passphrase  => $args{'Passphrase'},
    +        Content     => $args{'Signature'}->bodyhandle,
    +    );
    +
    +    $args{'Top'}->parts( [
    +        grep "$_" ne $args{'Signature'}, $args{'Top'}->parts
    +    ] );
    +    $args{'Top'}->make_singlepart;
     
    -    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 = GnuPG::Interface->new();
    -    my %opt = RT->Config->Get('GnuPGOptions');
    -    $opt{'digest-algo'} ||= 'SHA1';
    -    $gnupg->options->hash_init(
    -        _PrepareGnuPGOptions( %opt ),
    -        meta_interactive => 0,
    -    );
    +    my $self = shift;
    +    my %args = ( Data => undef, Signature => undef, @_ );
     
         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 = $self->CallGnuPG(
    +        Command     => "verify",
    +        CommandArgs => [ '-', $tmp_fn ],
    +        Passphrase  => $args{'Passphrase'},
    +        Content     => $args{'Signature'}->bodyhandle,
    +    );
    +
    +    $args{'Top'}->parts( [ $args{'Data'} ] );
    +    $args{'Top'}->make_singlepart;
     
    -    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 $self = shift;
         my %args = (
             Data => undef,
             Info => undef,
    @@ -1238,105 +1030,52 @@ sub DecryptRFC3156 {
             @_
         );
     
    -    my $gnupg = GnuPG::Interface->new();
    -    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'} && $?;
    +
    +    my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
    +    binmode $tmp_fh, ':raw';
    +
    +    my %res = $self->CallGnuPG(
    +        Command     => "decrypt",
    +        Handles     => { stdout => $tmp_fh },
    +        Passphrase  => $args{'Passphrase'},
    +        Content     => $args{'Data'}->bodyhandle,
    +    );
     
         # 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;
    -        }
    -    }
    +    delete $res{'message'} if $res{'status'} =~ /DECRYPTION_OKAY/;
    +
    +    return %res if $res{message};
     
         seek $tmp_fh, 0, 0;
         my $parser = RT::EmailParser->new();
         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'}->parts( [$decrypted] );
         $args{'Top'}->make_singlepart;
    +
         return %res;
     }
     
     sub DecryptInline {
    +    my $self = shift;
         my %args = (
             Data => undef,
             Passphrase => undef,
             @_
         );
     
    -    my $gnupg = GnuPG::Interface->new();
    -    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';
     
    @@ -1360,9 +1099,8 @@ sub DecryptInline {
                 seek $block_fh, 0, 0;
     
                 my ($res_fh, $res_fn);
    -            ($res_fh, $res_fn, %res) = _DecryptInlineBlock(
    +            ($res_fh, $res_fn, %res) = $self->_DecryptInlineBlock(
                     %args,
    -                GnuPG => $gnupg,
                     BlockHandle => $block_fh,
                 );
                 return %res unless $res_fh;
    @@ -1397,9 +1135,8 @@ sub DecryptInline {
             seek $block_fh, 0, 0;
     
             my ($res_fh, $res_fn);
    -        ($res_fh, $res_fn, %res) = _DecryptInlineBlock(
    +        ($res_fh, $res_fn, %res) = $self->_DecryptInlineBlock(
                 %args,
    -            GnuPG => $gnupg,
                 BlockHandle => $block_fh,
             );
             return %res unless $res_fh;
    @@ -1418,92 +1155,53 @@ sub DecryptInline {
     }
     
     sub _DecryptInlineBlock {
    +    my $self = shift;
         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'} && $?;
    +    my %res = $self->CallGnuPG(
    +        Command     => "decrypt",
    +        Handles     => { stdout => $tmp_fh, stdin => $args{'BlockHandle'} },
    +        Passphrase  => $args{'Passphrase'},
    +    );
     
         # 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);
    -        }
    -    }
    +    delete $res{'message'} if $res{'status'} =~ /DECRYPTION_OKAY/;
    +
    +    return (undef, undef, %res) if $res{message};
     
         seek $tmp_fh, 0, 0;
         return ($tmp_fh, $tmp_fn, %res);
     }
     
     sub DecryptAttachment {
    +    my $self = shift;
         my %args = (
    -        Top  => undef,
             Data => undef,
             Passphrase => undef,
             @_
         );
     
    -    my $gnupg = GnuPG::Interface->new();
    -    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(
    +    my ($res_fh, $res_fn, %res) = $self->_DecryptInlineBlock(
             %args,
    -        GnuPG => $gnupg,
             BlockHandle => $tmp_fh,
         );
         return %res unless $res_fh;
    @@ -1527,68 +1225,29 @@ sub DecryptAttachment {
     }
     
     sub DecryptContent {
    +    my $self = shift;
         my %args = (
             Content => undef,
             Passphrase => undef,
             @_
         );
     
    -    my $gnupg = GnuPG::Interface->new();
    -    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'} && $?;
    +    my %res = $self->CallGnuPG(
    +        Command     => "decrypt",
    +        Handles     => { stdout => $tmp_fh },
    +        Passphrase  => $args{'Passphrase'},
    +        Content     => $args{'Content'},
    +    );
     
         # 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;
    -        }
    -    }
    +    delete $res{'message'} if $res{'status'} =~ /DECRYPTION_OKAY/;
    +
    +    return %res if $res{'message'};
     
         ${ $args{'Content'} } = '';
         seek $tmp_fh, 0, 0;
    @@ -1605,48 +1264,6 @@ sub DecryptContent {
         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.
    -
    -=cut
    -
     my %REASON_CODE_TO_TEXT = (
         NODATA => {
             1 => "No armored data",
    @@ -1722,6 +1339,7 @@ my %ignore_keyword = map { $_ => 1 } qw(
     );
     
     sub ParseStatus {
    +    my $self = shift;
         my $status = shift;
         return () unless $status;
     
    @@ -1965,52 +1583,10 @@ sub _PrepareGnuPGOptions {
         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', @_ );
    +    my $self = shift;
    +    my %args = (Recipient => undef, @_);
    +    my %res = $self->GetKeysInfo( Key => delete $args{'Recipient'}, %args, Type => 'public' );
         return %res if $res{'exit_code'};
         return %res unless $res{'info'};
     
    @@ -2019,7 +1595,7 @@ sub GetKeysForEncryption {
             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,
    +        # skip disabled, expired, revoked and keys with no trust,
             # but leave keys with unknown trust level
             next if $key->{'TrustLevel'} < 0;
     
    @@ -2030,148 +1606,54 @@ sub GetKeysForEncryption {
     }
     
     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;
    +    my $self = shift;
    +    my %args = (Signer => undef, @_);
    +    return $self->GetKeysInfo( Key => delete $args{'Signer'}, %args, Type => 'private' );
     }
     
     sub GetKeysInfo {
    -    my $email = shift;
    -    my $type = shift || 'public';
    -    my $force = shift;
    +    my $self = shift;
    +    my %args = (
    +        Key   => undef,
    +        Type  => 'public',
    +        Force => 0,
    +        @_
    +    );
     
    +    my $email = $args{'Key'};
    +    my $type = $args{'Type'};
         unless ( $email ) {
    -        return (exit_code => 0) unless $force;
    -    }
    -
    -    my $gnupg = GnuPG::Interface->new();
    -    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,
    +        return (exit_code => 0) unless $args{'Force'};
    +    }
    +
    +    my @info;
    +    my $method = $type eq 'private'? 'list_secret_keys': 'list_public_keys';
    +    my %res = $self->CallGnuPG(
    +        Options     => {
    +            'with-colons'     => undef, # parseable format
    +            'fingerprint'     => undef, # show fingerprint
    +            'fixed-list-mode' => undef, # don't merge uid with keys
    +        },
    +        Command     => $method,
    +        ( $email ? (CommandArgs => ['--', $email]) : () ),
    +        Output      => \@info,
         );
     
    -    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;
    +    # Asking for a non-existent key is not an error
    +    if ($res{message} and $res{logger} =~ /(secret key not available|public key not found)/) {
    +        delete $res{exit_code};
    +        delete $res{message};
         }
     
    -    @info = ParseKeysInfo( @info );
    +    return %res if $res{'message'};
    +
    +    @info = $self->ParseKeysInfo( @info );
         $res{'info'} = \@info;
         return %res;
     }
     
     sub ParseKeysInfo {
    +    my $self = shift;
         my @lines = @_;
     
         my %gpg_opt = RT->Config->Get('GnuPGOptions');
    @@ -2205,7 +1687,7 @@ sub ParseKeysInfo {
     
                 @info{qw(OwnerTrust OwnerTrustTerse OwnerTrustLevel)} = 
                     _ConvertTrustChar( $info{'OwnerTrustChar'} );
    -            $info{ $_ } = _ParseDate( $info{ $_ } )
    +            $info{ $_ } = $self->ParseDate( $info{ $_ } )
                     foreach qw(Created Expire);
                 push @res, \%info;
             }
    @@ -2218,7 +1700,7 @@ sub ParseKeysInfo {
                 ) } = split /:/, $line, 12;
                 @info{qw(OwnerTrust OwnerTrustTerse OwnerTrustLevel)} = 
                     _ConvertTrustChar( $info{'OwnerTrustChar'} );
    -            $info{ $_ } = _ParseDate( $info{ $_ } )
    +            $info{ $_ } = $self->ParseDate( $info{ $_ } )
                     foreach qw(Created Expire);
                 push @res, \%info;
             }
    @@ -2226,7 +1708,7 @@ sub ParseKeysInfo {
                 my %info;
                 @info{ qw(Trust Created Expire String) }
                     = (split /:/, $line)[0,4,5,8];
    -            $info{ $_ } = _ParseDate( $info{ $_ } )
    +            $info{ $_ } = $self->ParseDate( $info{ $_ } )
                     foreach qw(Created Expire);
                 push @{ $res[-1]{'User'} ||= [] }, \%info;
             }
    @@ -2304,173 +1786,95 @@ sub ParseKeysInfo {
         }
     }
     
    -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 $self = shift;
         my $key = shift;
     
    -    my $gnupg = GnuPG::Interface->new();
    -    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';
    -        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";
    +    return $self->CallGnuPG(
    +        Command     => "--delete-secret-and-public-key",
    +        CommandArgs => ["--", $key],
    +        Callback    => sub {
    +            my %handle = @_;
    +            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 $self = shift;
         my $key = shift;
     
    -    my $gnupg = GnuPG::Interface->new();
    -    my %opt = RT->Config->Get('GnuPGOptions');
    -    $gnupg->options->hash_init(
    -        _PrepareGnuPGOptions( %opt ),
    -        meta_interactive => 0,
    +    return $self->CallGnuPG(
    +        Command     => "import_keys",
    +        Content     => $key,
         );
    -
    -    my ($handles, $handle_list) = _make_gpg_handles();
    -    my %handle = %$handle_list;
    -
    -    eval {
    -        local $SIG{'CHLD'} = 'DEFAULT';
    -        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;
    +sub GnuPGPath {
    +    state $cache = RT->Config->Get('GnuPG')->{'GnuPG'};
    +    $cache = $_[1] if @_ > 1;
    +    return $cache;
     }
     
    -1;
    -
    -=head2 Probe
    -
    -This routine returns true if RT's GnuPG support is configured and working 
    -properly (and false otherwise).
    -
    -
    -=cut
    +sub Probe {
    +    my $self = shift;
    +    my $gnupg = GnuPG::Interface->new;
    +
    +    my $bin = $self->GnuPGPath();
    +    unless ($bin) {
    +        $RT::Logger->warning(
    +            "No gpg path set; GnuPG support has been disabled.  ".
    +            "Check the 'GnuPG' configuration in %GnuPG");
    +        return 0;
    +    }
     
    +    if ($bin =~ m{^/}) {
    +        unless (-f $bin and -x _) {
    +            $RT::Logger->warning(
    +                "Invalid gpg path $bin; GnuPG support has been disabled.  ".
    +                "Check the 'GnuPG' configuration in %GnuPG");
    +            return 0;
    +        }
    +    } else {
    +        my $path = File::Which::which( $bin );
    +        unless ($path) {
    +            $RT::Logger->warning(
    +                "Can't find gpg binary '$bin' in PATH; GnuPG support has been disabled.  ".
    +                "Check the 'GnuPG' configuration in %GnuPG");
    +            return 0;
    +        }
    +        $self->GnuPGPath( $bin = $path );
    +    }
     
    -sub Probe {
    -    my $gnupg = GnuPG::Interface->new();
    -    my %opt = RT->Config->Get('GnuPGOptions');
    +    $gnupg->call( $bin );
         $gnupg->options->hash_init(
    -        _PrepareGnuPGOptions( %opt ),
    -        armor => 1,
    -        meta_interactive => 0,
    +        _PrepareGnuPGOptions( RT->Config->Get('GnuPGOptions') )
         );
    +    $gnupg->options->meta_interactive( 0 );
     
         my ($handles, $handle_list) = _make_gpg_handles();
         my %handle = %$handle_list;
     
    -    local $@;
    +    local $@ = undef;
         eval {
             local $SIG{'CHLD'} = 'DEFAULT';
    -        my $pid = safe_run_child { $gnupg->wrap_call( commands => ['--version' ], handles => $handles ) };
    -        close $handle{'stdin'};
    +        my $pid = safe_run_child {
    +            $gnupg->wrap_call(
    +                commands => ['--version' ],
    +                handles  => $handles
    +            )
    +        };
    +        close $handle{'stdin'} or die "Can't close gnupg input handle: $!";
             waitpid $pid, 0;
         };
         if ( $@ ) {
    +        $RT::Logger->warning(
    +            "RT's GnuPG libraries couldn't successfully execute gpg.".
    +                " GnuPG support has been disabled");
             $RT::Logger->debug(
                 "Probe for GPG failed."
                 ." Couldn't run `gpg --version`: ". $@
    @@ -2483,15 +1887,18 @@ sub Probe {
     # but there is no way to get actuall error
         if ( $? && ($? >> 8) != 2 ) {
             my $msg = "Probe for GPG failed."
    -            ." Process exitted with code ". ($? >> 8)
    +            ." Process exited with code ". ($? >> 8)
                 . ($? & 127 ? (" as recieved signal ". ($? & 127)) : '')
                 . ".";
             foreach ( qw(stderr logger status) ) {
    -            my $tmp = do { local $/; readline $handle{$_} };
    +            my $tmp = do { local $/ = undef; readline $handle{$_} };
                 next unless $tmp && $tmp =~ /\S/s;
    -            close $handle{$_};
    +            close $handle{$_} or $tmp .= "\nFailed to close: $!";
                 $msg .= "\n$_:\n$tmp\n";
             }
    +        $RT::Logger->warning(
    +            "RT's GnuPG libraries couldn't successfully execute gpg.".
    +                " GnuPG support has been disabled");
             $RT::Logger->debug( $msg );
             return 0;
         }
    @@ -2511,15 +1918,4 @@ sub _make_gpg_handles {
     
     RT::Base->_ImportOverlays();
     
    -# 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/lib/RT/Crypt/GnuPG/CRLFHandle.pm b/lib/RT/Crypt/GnuPG/CRLFHandle.pm
    new file mode 100644
    index 0000000..0ed93f5
    --- /dev/null
    +++ b/lib/RT/Crypt/GnuPG/CRLFHandle.pm
    @@ -0,0 +1,70 @@
    +# BEGIN BPS TAGGED BLOCK {{{
    +#
    +# COPYRIGHT:
    +#
    +# This software is Copyright (c) 1996-2013 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::Crypt::GnuPG::CRLFHandle;
    +use strict;
    +use warnings;
    +
    +use base qw(IO::Handle);
    +
    +# https://metacpan.org/module/MIME::Tools#Fuzzing-of-CRLF-and-newline-when-encoding-composing
    +# means that the output of $entity->print contains lines terminated by
    +# "\n"; however, signatures are generated off of the "correct" form of
    +# the MIME entity, which uses "\r\n" as the newline separator.  This
    +# class, used only when generating signatures, transparently munges "\n"
    +# newlines into "\r\n" newlines such that the generated signature is
    +# correct for the "\r\n"-newline version of the MIME entity which will
    +# eventually be sent over the wire.
    +
    +sub print {
    +    my ($self, @args) = (@_);
    +    s/\r*\n/\x0D\x0A/g foreach @args;
    +    return $self->SUPER::print( @args );
    +}
    +
    +1;
    diff --git a/lib/RT/Crypt/Role.pm b/lib/RT/Crypt/Role.pm
    new file mode 100644
    index 0000000..df53438
    --- /dev/null
    +++ b/lib/RT/Crypt/Role.pm
    @@ -0,0 +1,254 @@
    +# BEGIN BPS TAGGED BLOCK {{{
    +#
    +# COPYRIGHT:
    +#
    +# This software is Copyright (c) 1996-2013 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::Role;
    +use Role::Basic;
    +
    +=head1 NAME
    +
    +RT::Crypt::Role - Common requirements for encryption implementations
    +
    +=head1 METHODS
    +
    +=head2 Probe
    +
    +This routine is called only if the protocol is enabled, and should
    +return true if all binaries required by the protocol are installed.  It
    +should produce any warnings necessary to describe any issues it
    +encounters.
    +
    +=cut
    +
    +requires 'Probe';
    +
    +=head2 GetPassphrase Address => ADDRESS
    +
    +Returns the passphrase for the given address.  It looks at the relevant
    +configuration option for the encryption protocol
    +(e.g. L for GnuPG), and examines the Passphrase key.
    +It it does not exist, returns the empty string.  If it is a scalar, it
    +returns that value.  If it is an anonymous subroutine, it calls it.  If
    +it is a hash, it looks up the address (using '' as a fallback key).
    +
    +=cut
    +
    +sub GetPassphrase {
    +    my $self = shift;
    +    my %args = ( Address => undef, @_ );
    +
    +    my $class = ref($self) || $self;
    +    $class =~ s/^RT::Crypt:://;
    +
    +    my $config = RT->Config->Get($class)->{Passphrase};
    +
    +    return '' unless defined $config;
    +
    +    if (not ref $config) {
    +        return $config;
    +    } elsif (ref $config eq "HASH") {
    +        return $config->{$args{Address}}
    +            || $config->{''};
    +    } elsif (ref $config eq "CODE") {
    +        return $config->( @_ );
    +    } else {
    +        warn "Unknown Passphrase type for $class: ".ref($config);
    +    }
    +}
    +
    +=head2 SignEncrypt Entity => MIME::Entity, [ Encrypt => 1, Sign => 1, ... ]
    +
    +Signs and/or encrypts a MIME entity.  All arguments and return values
    +are identical to L, with the omission of
    +C.
    +
    +=cut
    +
    +requires 'SignEncrypt';
    +
    +=head2 SignEncryptContent Content => STRINGREF, [ Encrypt => 1, Sign => 1, ... ]
    +
    +Signs and/or encrypts a string, which is passed by reference.  All
    +arguments and return values are identical to
    +L, with the omission of C.
    +
    +=cut
    +
    +requires 'SignEncryptContent';
    +
    +=head2 VerifyDecrypt Info => HASHREF, [ Passphrase => undef ]
    +
    +The C key is a hashref as returned from L or
    +L.  This method should alter the mime objects
    +in-place as necessary during signing and decryption.
    +
    +Returns a hash with at least the following keys:
    +
    +=over
    +
    +=item exit_code
    +
    +True if there was an error encrypting or signing.
    +
    +=item message
    +
    +An un-localized error message desribing the problem.
    +
    +=back
    +
    +=cut
    +
    +requires 'VerifyDecrypt';
    +
    +=head2 DecryptContent Content => STRINGREF, [ Passphrase => undef ]
    +
    +Decrypts the content in the string reference in-place.  All arguments
    +and return values are identical to L, with the
    +omission of C.
    +
    +=cut
    +
    +requires 'DecryptContent';
    +
    +=head2 ParseStatus STRING
    +
    +Takes a string describing the status of verification/decryption, usually
    +as stored in a MIME header.  Parses and returns it as described in
    +L.
    +
    +=cut
    +
    +requires 'ParseStatus';
    +
    +=head2 FindScatteredParts Parts => ARRAYREF, Parents => HASHREF, Skip => HASHREF
    +
    +Passed the list of unclaimed L objects in C, this
    +method should examine them as a whole to determine if there are any that
    +could not be claimed by the single-entity-at-a-time L
    +method.  This is generally only necessary in the case of signatures
    +manually attached in parallel, and the like.
    +
    +If found, the relevant entities should be inserted into C with a
    +true value, to signify to other encryption protols that they have been
    +claimed.  The method should return a list of hash references, each
    +containing a C key which is either C or C.  The
    +remaining keys are protocol-dependent; the hashref will be provided to
    +L.
    +
    +=cut
    +
    +requires 'FindScatteredParts';
    +
    +=head2 CheckIfProtected Entity => MIME::Entity
    +
    +Examines the provided L, and returns an empty list if it
    +is not signed or encrypted using the protocol.  If it is, returns a hash
    +reference containing a C which is either C or
    +C.  The remaining keys are protocol-dependent; the hashref will
    +be provided to L.
    +
    +=cut
    +
    +requires 'CheckIfProtected';
    +
    +=head2 GetKeysInfo Type => ('public'|'private'), Key => EMAIL
    +
    +Returns a list of keys matching the email C, as described in
    +L.
    +
    +=cut
    +
    +requires 'GetKeysInfo';
    +
    +=head2 GetKeysForEncryption Recipient => EMAIL
    +
    +Returns a list of keys suitable for encryption, as described in
    +L.
    +
    +=cut
    +
    +requires 'GetKeysForEncryption';
    +
    +=head2 GetKeysForSigning Signer => EMAIL
    +
    +Returns a list of keys suitable for encryption, as described in
    +L.
    +
    +=cut
    +
    +requires 'GetKeysForSigning';
    +
    +=head2 ParseDate STRING
    +
    +Takes a string, and parses and returns a L; if the string is
    +purely numeric, assumes is a epoch timestamp.
    +
    +=cut
    +
    +sub ParseDate {
    +    my $self = shift;
    +    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;
    +}
    +
    +
    +1;
    diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
    new file mode 100644
    index 0000000..cb05e33
    --- /dev/null
    +++ b/lib/RT/Crypt/SMIME.pm
    @@ -0,0 +1,941 @@
    +# BEGIN BPS TAGGED BLOCK {{{
    +#
    +# COPYRIGHT:
    +#
    +# This software is Copyright (c) 1996-2013 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 5.010;
    +
    +package RT::Crypt::SMIME;
    +
    +use Role::Basic 'with';
    +with 'RT::Crypt::Role';
    +
    +use RT::Crypt;
    +use File::Which qw();
    +use IPC::Run3 0.036 'run3';
    +use RT::Util 'safe_run_child';
    +use Crypt::X509;
    +use String::ShellQuote 'shell_quote';
    +
    +=head1 NAME
    +
    +RT::Crypt::SMIME - encrypt/decrypt and sign/verify email messages with the SMIME
    +
    +=head1 CONFIGURATION
    +
    +You should start from reading L.
    +
    +=head2 %SMIME
    +
    +    Set( %SMIME,
    +        Enable => 1,
    +        OpenSSL => '/usr/bin/openssl',
    +        Keyring => '/opt/rt4/var/data/smime',
    +        CAPath  => '/opt/rt4/var/data/smime/signing-ca.pem',
    +        Passphrase => {
    +            'queue.address@example.com' => 'passphrase',
    +            '' => 'fallback',
    +        },
    +    );
    +
    +=head3 OpenSSL
    +
    +Path to openssl executable.
    +
    +=head3 Keyring
    +
    +Path to directory with keys and certificates for queues. Key and
    +certificates should be stored in a PEM file named, e.g.,
    +F.  See L.
    +
    +=head3 CAPath
    +
    +C should be set to either a PEM-formatted certificate of a
    +single signing certificate authority, or a directory of such (including
    +hash symlinks as created by the openssl tool C).  Only SMIME
    +certificates signed by these certificate authorities will be treated as
    +valid signatures.  If left unset (and C is unset, as
    +it is by default), no signatures will be marked as valid!
    +
    +=head3 AcceptUntrustedCAs
    +
    +Allows arbitrary SMIME certificates, no matter their signing entities.
    +Such mails will be marked as untrusted, but signed; C will be
    +used to mark which mails are signed by trusted certificate authorities.
    +This configuration is generally insecure, as it allows the possibility
    +of accepting forged mail signed by an untrusted certificate authority.
    +
    +Setting this option also allows encryption to users with certificates
    +created by untrusted CAs.
    +
    +=head3 Passphrase
    +
    +C may be set to a scalar (to use for all keys), an anonymous
    +function, or a hash (to look up by address).  If the hash is used, the
    +'' key is used as a default.
    +
    +=head2 Keyring configuration
    +
    +RT looks for keys in the directory configured in the L option
    +of the L.  While public certificates are also stored
    +on users, private SSL keys are only loaded from disk.  Keys and
    +certificates should be concatenated, in in PEM format, in files named
    +C, for example.
    +
    +These files need be readable by the web server user which is running
    +RT's web interface; however, if you are 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.
    +
    +The keyring on disk will be checked before the user with the email
    +address is examined.  If the file exists, it will be used in preference
    +to the certificate on the user.
    +
    +=cut
    +
    +sub OpenSSLPath {
    +    state $cache = RT->Config->Get('SMIME')->{'OpenSSL'};
    +    $cache = $_[1] if @_ > 1;
    +    return $cache;
    +}
    +
    +sub Probe {
    +    my $self = shift;
    +    my $bin = $self->OpenSSLPath();
    +    unless ($bin) {
    +        $RT::Logger->warning(
    +            "No openssl path set; SMIME support has been disabled.  ".
    +            "Check the 'OpenSSL' configuration in %OpenSSL");
    +        return 0;
    +    }
    +
    +    if ($bin =~ m{^/}) {
    +        unless (-f $bin and -x _) {
    +            $RT::Logger->warning(
    +                "Invalid openssl path $bin; SMIME support has been disabled.  ".
    +                "Check the 'OpenSSL' configuration in %OpenSSL");
    +            return 0;
    +        }
    +    } else {
    +        my $path = File::Which::which( $bin );
    +        unless ($path) {
    +            $RT::Logger->warning(
    +                "Can't find openssl binary '$bin' in PATH; SMIME support has been disabled.  ".
    +                "Check the 'OpenSSL' configuration in %OpenSSL");
    +            return 0;
    +        }
    +        $self->OpenSSLPath( $bin = $path );
    +    }
    +
    +    {
    +        my ($buf, $err) = ('', '');
    +
    +        local $SIG{'CHLD'} = 'DEFAULT';
    +        safe_run_child { run3( [$bin, "list-standard-commands"],
    +            \undef,
    +            \$buf, \$err
    +        ) };
    +
    +        if ($? or $err) {
    +            $RT::Logger->warning(
    +                "RT's SMIME libraries couldn't successfully execute openssl.".
    +                    " SMIME support has been disabled") ;
    +            return;
    +        } elsif ($buf !~ /\bsmime\b/) {
    +            $RT::Logger->warning(
    +                "openssl does not include smime support.".
    +                    " SMIME support has been disabled");
    +            return;
    +        } else {
    +            return 1;
    +        }
    +    }
    +}
    +
    +sub SignEncrypt {
    +    my $self = shift;
    +    my %args = (
    +        Entity => undef,
    +
    +        Sign => 1,
    +        Signer => undef,
    +        Passphrase => undef,
    +
    +        Encrypt => 1,
    +        Recipients => undef,
    +
    +        @_
    +    );
    +
    +    my $entity = $args{'Entity'};
    +
    +    if ( $args{'Encrypt'} ) {
    +        my %seen;
    +        $args{'Recipients'} = [
    +            grep !$seen{$_}++, map $_->address, map Email::Address->parse($_),
    +            grep defined && length, map $entity->head->get($_), qw(To Cc Bcc)
    +        ];
    +    }
    +
    +    $entity->make_multipart('mixed', Force => 1);
    +    my ($buf, %res) = $self->_SignEncrypt(
    +        %args,
    +        Content => \$entity->parts(0)->stringify,
    +    );
    +    unless ( $buf ) {
    +        $entity->make_singlepart;
    +        return %res;
    +    }
    +
    +    my $tmpdir = File::Temp::tempdir( TMPDIR => 1, CLEANUP => 1 );
    +    my $parser = MIME::Parser->new();
    +    $parser->output_dir($tmpdir);
    +    my $newmime = $parser->parse_data($$buf);
    +
    +    # Work around https://rt.cpan.org/Public/Bug/Display.html?id=87835
    +    for my $part (grep {$_->is_multipart and $_->preamble and @{$_->preamble}} $newmime->parts_DFS) {
    +        $part->preamble->[-1] .= "\n"
    +            if $part->preamble->[-1] =~ /\r$/;
    +    }
    +
    +    $entity->parts([$newmime]);
    +    $entity->make_singlepart;
    +
    +    return %res;
    +}
    +
    +sub SignEncryptContent {
    +    my $self = shift;
    +    my %args = (
    +        Content => undef,
    +        @_
    +    );
    +
    +    my ($buf, %res) = $self->_SignEncrypt(%args);
    +    ${ $args{'Content'} } = $$buf if $buf;
    +    return %res;
    +}
    +
    +sub _SignEncrypt {
    +    my $self = shift;
    +    my %args = (
    +        Content => undef,
    +
    +        Sign => 1,
    +        Signer => undef,
    +        Passphrase => undef,
    +
    +        Encrypt => 1,
    +        Recipients => [],
    +
    +        @_
    +    );
    +
    +    my %res = (exit_code => 0, status => '');
    +
    +    my @keys;
    +    if ( $args{'Encrypt'} ) {
    +        my @addresses = @{ $args{'Recipients'} };
    +
    +        foreach my $address ( @addresses ) {
    +            $RT::Logger->debug( "Considering encrypting message to " . $address );
    +
    +            my %key_info = $self->GetKeysInfo( Key => $address );
    +            unless ( defined $key_info{'info'} ) {
    +                $res{'exit_code'} = 1;
    +                my $reason = 'Key not found';
    +                $res{'status'} .= $self->FormatStatus({
    +                    Operation => "RecipientsCheck", Status => "ERROR",
    +                    Message => "Recipient '$address' is unusable, the reason is '$reason'",
    +                    Recipient => $address,
    +                    Reason => $reason,
    +                });
    +                next;
    +            }
    +
    +            if ( not $key_info{'info'}[0]{'Expire'} ) {
    +                # we continue here as it's most probably a problem with the key,
    +                # so later during encryption we'll get verbose errors
    +                $RT::Logger->error(
    +                    "Trying to send an encrypted message to ". $address
    +                    .", but we couldn't get expiration date of the key."
    +                );
    +            }
    +            elsif ( $key_info{'info'}[0]{'Expire'}->Diff( time ) < 0 ) {
    +                $res{'exit_code'} = 1;
    +                my $reason = 'Key expired';
    +                $res{'status'} .= $self->FormatStatus({
    +                    Operation => "RecipientsCheck", Status => "ERROR",
    +                    Message => "Recipient '$address' is unusable, the reason is '$reason'",
    +                    Recipient => $address,
    +                    Reason => $reason,
    +                });
    +                next;
    +            }
    +            push @keys, $key_info{'info'}[0]{'Content'};
    +        }
    +    }
    +    return (undef, %res) if $res{'exit_code'};
    +
    +    my $opts = RT->Config->Get('SMIME');
    +
    +    my @command;
    +    if ( $args{'Sign'} ) {
    +        my $file = $self->CheckKeyring( Key => $args{'Signer'} );
    +        unless ($file) {
    +            $res{'status'} .= $self->FormatStatus({
    +                Operation => "KeyCheck", Status => "MISSING",
    +                Message   => "Secret key for $args{Signer} is not available",
    +                Key       => $args{Signer},
    +                KeyType   => "secret",
    +            });
    +            $res{exit_code} = 1;
    +            return %res;
    +        }
    +        $args{'Passphrase'} = $self->GetPassphrase( Address => $args{'Signer'} )
    +            unless defined $args{'Passphrase'};
    +
    +        push @command, join ' ', shell_quote(
    +            $self->OpenSSLPath, qw(smime -sign),
    +            -signer => $file,
    +            -inkey  => $file,
    +            (defined $args{'Passphrase'} && length $args{'Passphrase'})
    +                ? (qw(-passin env:SMIME_PASS))
    +                : (),
    +        );
    +    }
    +    if ( $args{'Encrypt'} ) {
    +        foreach my $key ( @keys ) {
    +            my $key_file = File::Temp->new;
    +            print $key_file $key;
    +            close $key_file;
    +            $key = $key_file;
    +        }
    +        push @command, join ' ', shell_quote(
    +            $self->OpenSSLPath, qw(smime -encrypt -des3),
    +            map { $_->filename } @keys
    +        );
    +    }
    +
    +    my ($buf, $err) = ('', '');
    +    {
    +        local $ENV{'SMIME_PASS'} = $args{'Passphrase'};
    +        local $SIG{'CHLD'} = 'DEFAULT';
    +        safe_run_child { run3(
    +            join( ' | ', @command ),
    +            $args{'Content'},
    +            \$buf, \$err
    +        ) };
    +    }
    +    $RT::Logger->debug( "openssl stderr: " . $err ) if length $err;
    +
    +    if ($buf) {
    +        $res{'status'} .= $self->FormatStatus({
    +            Operation => "Sign", Status => "DONE",
    +            Message => "Signed message",
    +        }) if $args{'Sign'};
    +        $res{'status'} .= $self->FormatStatus({
    +            Operation => "Encrypt", Status => "DONE",
    +            Message => "Data has been encrypted",
    +        }) if $args{'Encrypt'};
    +    }
    +
    +    return (\$buf, %res);
    +}
    +
    +sub VerifyDecrypt {
    +    my $self = shift;
    +    my %args = ( Info => undef, @_ );
    +
    +    my %res;
    +    my $item = $args{'Info'};
    +    if ( $item->{'Type'} eq 'signed' ) {
    +        %res = $self->Verify( %$item );
    +    } elsif ( $item->{'Type'} eq 'encrypted' ) {
    +        %res = $self->Decrypt( %args, %$item );
    +    } else {
    +        die "Unknown type '". $item->{'Type'} ."' of protected item";
    +    }
    +
    +    return (%res, status_on => $item->{'Data'});
    +}
    +
    +sub Verify {
    +    my $self = shift;
    +    my %args = (Data => undef, @_ );
    +
    +    my $msg = $args{'Data'}->as_string;
    +
    +    my %res;
    +    my $buf;
    +    my $keyfh = File::Temp->new;
    +    {
    +        local $SIG{CHLD} = 'DEFAULT';
    +        my $cmd = [
    +            $self->OpenSSLPath, qw(smime -verify -noverify),
    +            '-signer', $keyfh->filename,
    +        ];
    +        safe_run_child { run3( $cmd, \$msg, \$buf, \$res{'stderr'} ) };
    +        $res{'exit_code'} = $?;
    +    }
    +    if ( $res{'exit_code'} ) {
    +        if ($res{stderr} =~ /(signature|digest) failure/) {
    +            $res{'message'} = "Validation failed";
    +            $res{'status'} = $self->FormatStatus({
    +                Operation => "Verify", Status => "BAD",
    +                Message => "The signature did not verify",
    +            });
    +        } else {
    +            $res{'message'} = "openssl exited with error code ". ($? >> 8)
    +                ." and error: $res{stderr}";
    +            $res{'status'} = $self->FormatStatus({
    +                Operation => "Verify", Status => "ERROR",
    +                Message => "There was an error verifying: $res{stderr}",
    +            });
    +            $RT::Logger->error($res{'message'});
    +        }
    +        return %res;
    +    }
    +
    +    my $signer;
    +    if ( my $key = do { $keyfh->seek(0, 0); local $/; readline $keyfh } ) {{
    +        my %info = $self->GetCertificateInfo( Certificate => $key );
    +
    +        $signer = $info{info}[0];
    +        last unless $signer and $signer->{User}[0]{String};
    +
    +        unless ( $info{info}[0]{TrustLevel} > 0 or RT->Config->Get('SMIME')->{AcceptUntrustedCAs}) {
    +            # We don't trust it; give it the finger
    +            $res{exit_code} = 1;
    +            $res{'message'} = "Validation failed";
    +            $res{'status'} = $self->FormatStatus({
    +                Operation => "Verify", Status => "BAD",
    +                Message => "The signing CA was not trusted",
    +                UserString => $signer->{User}[0]{String},
    +                Trust => "NONE",
    +            });
    +            return %res;
    +        }
    +
    +        my $user = RT::User->new( $RT::SystemUser );
    +        $user->LoadOrCreateByEmail( $signer->{User}[0]{String} );
    +        my $current_key = $user->SMIMECertificate;
    +        last if $current_key && $current_key eq $key;
    +
    +        # Never over-write existing keys with untrusted ones.
    +        last if $current_key and not $info{info}[0]{TrustLevel} > 0;
    +
    +        my ($status, $msg) = $user->SetSMIMECertificate( $key );
    +        $RT::Logger->error("Couldn't set SMIME certificate for user #". $user->id .": $msg")
    +            unless $status;
    +    }}
    +
    +    my $res_entity = _extract_msg_from_buf( \$buf );
    +    unless ( $res_entity ) {
    +        $res{'exit_code'} = 1;
    +        $res{'message'} = "verified message, but couldn't parse result";
    +        $res{'status'} = $self->FormatStatus({
    +            Operation => "Verify", Status => "DONE",
    +            Message => "The signature is good, unknown signer",
    +            Trust => "UNKNOWN",
    +        });
    +        return %res;
    +    }
    +
    +    $res_entity->make_multipart( 'mixed', Force => 1 );
    +
    +    $args{'Data'}->make_multipart( 'mixed', Force => 1 );
    +    $args{'Data'}->parts([ $res_entity->parts ]);
    +    $args{'Data'}->make_singlepart;
    +
    +    $res{'status'} = $self->FormatStatus({
    +        Operation => "Verify", Status => "DONE",
    +        Message => "The signature is good, signed by ".$signer->{User}[0]{String}.", trust is ".$signer->{TrustTerse},
    +        UserString => $signer->{User}[0]{String},
    +        Trust => uc($signer->{TrustTerse}),
    +    });
    +
    +    return %res;
    +}
    +
    +sub Decrypt {
    +    my $self = shift;
    +    my %args = (Data => undef, Queue => undef, @_ );
    +
    +    my $msg = $args{'Data'}->as_string;
    +
    +    push @{ $args{'Recipients'} ||= [] },
    +        $args{'Queue'}->CorrespondAddress, RT->Config->Get('CorrespondAddress'),
    +        $args{'Queue'}->CommentAddress, RT->Config->Get('CommentAddress')
    +    ;
    +
    +    my ($buf, %res) = $self->_Decrypt( %args, Content => \$args{'Data'}->as_string );
    +    return %res unless $buf;
    +
    +    my $res_entity = _extract_msg_from_buf( $buf );
    +    $res_entity->make_multipart( 'mixed', Force => 1 );
    +
    +    # Work around https://rt.cpan.org/Public/Bug/Display.html?id=87835
    +    for my $part (grep {$_->is_multipart and $_->preamble and @{$_->preamble}} $res_entity->parts_DFS) {
    +        $part->preamble->[-1] .= "\n"
    +            if $part->preamble->[-1] =~ /\r$/;
    +    }
    +
    +    $args{'Data'}->make_multipart( 'mixed', Force => 1 );
    +    $args{'Data'}->parts([ $res_entity->parts ]);
    +    $args{'Data'}->make_singlepart;
    +
    +    return %res;
    +}
    +
    +sub DecryptContent {
    +    my $self = shift;
    +    my %args = (
    +        Content => undef,
    +        @_
    +    );
    +
    +    my ($buf, %res) = $self->_Decrypt( %args );
    +    ${ $args{'Content'} } = $$buf if $buf;
    +    return %res;
    +}
    +
    +sub _Decrypt {
    +    my $self = shift;
    +    my %args = (Content => undef, @_ );
    +
    +    my %seen;
    +    my @addresses =
    +        grep !$seen{lc $_}++, map $_->address, map Email::Address->parse($_),
    +        grep length && defined, @{$args{'Recipients'}};
    +
    +    my ($buf, $encrypted_to, %res);
    +
    +    foreach my $address ( @addresses ) {
    +        my $file = $self->CheckKeyring( Key => $address );
    +        unless ( $file ) {
    +            my $keyring = RT->Config->Get('SMIME')->{'Keyring'};
    +            $RT::Logger->debug("No key found for $address in $keyring directory");
    +            next;
    +        }
    +
    +        local $ENV{SMIME_PASS} = $self->GetPassphrase( Address => $address );
    +        local $SIG{CHLD} = 'DEFAULT';
    +        my $cmd = [
    +            $self->OpenSSLPath,
    +            qw(smime -decrypt),
    +            -recip => $file,
    +            (defined $ENV{'SMIME_PASS'} && length $ENV{'SMIME_PASS'})
    +                ? (qw(-passin env:SMIME_PASS))
    +                : (),
    +        ];
    +        safe_run_child { run3( $cmd, $args{'Content'}, \$buf, \$res{'stderr'} ) };
    +        unless ( $? ) {
    +            $encrypted_to = $address;
    +            $RT::Logger->debug("Message encrypted for $encrypted_to");
    +            last;
    +        }
    +
    +        if ( index($res{'stderr'}, 'no recipient matches key') >= 0 ) {
    +            $RT::Logger->debug("Although we have a key for $address, it is not the one that encrypted this message");
    +            next;
    +        }
    +
    +        $res{'exit_code'} = $?;
    +        $res{'message'} = "openssl exited with error code ". ($? >> 8)
    +            ." and error: $res{stderr}";
    +        $RT::Logger->error( $res{'message'} );
    +        $res{'status'} = $self->FormatStatus({
    +            Operation => 'Decrypt', Status => 'ERROR',
    +            Message => 'Decryption failed',
    +            EncryptedTo => $address,
    +        });
    +        return (undef, %res);
    +    }
    +    unless ( $encrypted_to ) {
    +        $RT::Logger->error("Couldn't find SMIME key for addresses: ". join ', ', @addresses);
    +        $res{'exit_code'} = 1;
    +        $res{'status'} = $self->FormatStatus({
    +            Operation => 'KeyCheck',
    +            Status    => 'MISSING',
    +            Message   => "Secret key is not available",
    +            KeyType   => 'secret',
    +        });
    +        return (undef, %res);
    +    }
    +
    +    $res{'status'} = $self->FormatStatus({
    +        Operation => 'Decrypt', Status => 'DONE',
    +        Message => 'Decryption process succeeded',
    +        EncryptedTo => $encrypted_to,
    +    });
    +
    +    return (\$buf, %res);
    +}
    +
    +sub FormatStatus {
    +    my $self = shift;
    +    my @status = @_;
    +
    +    my $res = '';
    +    foreach ( @status ) {
    +        while ( my ($k, $v) = each %$_ ) {
    +            $res .= "[SMIME:]". $k .": ". $v ."\n";
    +        }
    +        $res .= "[SMIME:]\n";
    +    }
    +
    +    return $res;
    +}
    +
    +sub ParseStatus {
    +    my $self = shift;
    +    my $status = shift;
    +    return () unless $status;
    +
    +    my @status = split /\s*(?:\[SMIME:\]\s*){2}/, $status;
    +    foreach my $block ( grep length, @status ) {
    +        chomp $block;
    +        $block = { map { s/^\s+//; s/\s+$//; $_ } map split(/:/, $_, 2), split /\s*\[SMIME:\]/, $block };
    +    }
    +    foreach my $block ( grep $_->{'EncryptedTo'}, @status ) {
    +        $block->{'EncryptedTo'} = [{
    +            EmailAddress => $block->{'EncryptedTo'},  
    +        }];
    +    }
    +
    +    return @status;
    +}
    +
    +sub _extract_msg_from_buf {
    +    my $buf = shift;
    +    my $rtparser = RT::EmailParser->new();
    +    my $parser   = MIME::Parser->new();
    +    $rtparser->_SetupMIMEParser($parser);
    +    $parser->decode_bodies(0);
    +    $parser->output_to_core(1);
    +    unless ( $rtparser->{'entity'} = $parser->parse_data($$buf) ) {
    +        $RT::Logger->crit("Couldn't parse MIME stream and extract the submessages");
    +
    +        # Try again, this time without extracting nested messages
    +        $parser->extract_nested_messages(0);
    +        unless ( $rtparser->{'entity'} = $parser->parse_data($$buf) ) {
    +            $RT::Logger->crit("couldn't parse MIME stream");
    +            return (undef);
    +        }
    +    }
    +    return $rtparser->Entity;
    +}
    +
    +sub FindScatteredParts { return () }
    +
    +sub CheckIfProtected {
    +    my $self = shift;
    +    my %args = ( Entity => undef, @_ );
    +
    +    my $entity = $args{'Entity'};
    +
    +    my $type = $entity->effective_type;
    +    if ( $type =~ m{^application/(?:x-)?pkcs7-mime$} || $type eq 'application/octet-stream' ) {
    +        # RFC3851 ch.3.9 variant 1 and 3
    +
    +        my $security_type;
    +
    +        my $smime_type = $entity->head->mime_attr('Content-Type.smime-type');
    +        if ( $smime_type ) { # it's optional according to RFC3851
    +            if ( $smime_type eq 'enveloped-data' ) {
    +                $security_type = 'encrypted';
    +            }
    +            elsif ( $smime_type eq 'signed-data' ) {
    +                $security_type = 'signed';
    +            }
    +            elsif ( $smime_type eq 'certs-only' ) {
    +                $security_type = 'certificate management';
    +            }
    +            elsif ( $smime_type eq 'compressed-data' ) {
    +                $security_type = 'compressed';
    +            }
    +            else {
    +                $security_type = $smime_type;
    +            }
    +        }
    +
    +        unless ( $security_type ) {
    +            my $fname = $entity->head->recommended_filename || '';
    +            if ( $fname =~ /\.p7([czsm])$/ ) {
    +                my $type_char = $1;
    +                if ( $type_char eq 'm' ) {
    +                    # RFC3851, ch3.4.2
    +                    # it can be both encrypted and signed
    +                    $security_type = 'encrypted';
    +                }
    +                elsif ( $type_char eq 's' ) {
    +                    # RFC3851, ch3.4.3, multipart/signed, XXX we should never be here
    +                    # unless message is changed by some gateway
    +                    $security_type = 'signed';
    +                }
    +                elsif ( $type_char eq 'c' ) {
    +                    # RFC3851, ch3.7
    +                    $security_type = 'certificate management';
    +                }
    +                elsif ( $type_char eq 'z' ) {
    +                    # RFC3851, ch3.5
    +                    $security_type = 'compressed';
    +                }
    +            }
    +        }
    +        return () unless $security_type;
    +
    +        my %res = (
    +            Type   => $security_type,
    +            Format => 'RFC3851',
    +            Data   => $entity,
    +        );
    +
    +        if ( $security_type eq 'encrypted' ) {
    +            my $top = $args{'TopEntity'}->head;
    +            $res{'Recipients'} = [grep defined && length, map $top->get($_), 'To', 'Cc'];
    +        }
    +
    +        return %res;
    +    }
    +    elsif ( $type eq 'multipart/signed' ) {
    +        # RFC3156, multipart/signed
    +        # RFC3851, ch.3.9 variant 2
    +
    +        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 ();
    +        }
    +
    +        unless ( $protocol =~ m{^application/(x-)?pkcs7-signature$} ) {
    +            $RT::Logger->info( "Skipping protocol '$protocol', only 'application/x-pkcs7-signature' is supported" );
    +            return ();
    +        }
    +        $RT::Logger->debug("Found part signed according to RFC3156");
    +        return (
    +            Type      => 'signed',
    +            Format    => 'RFC3156',
    +            Data      => $entity,
    +        );
    +    }
    +    return ();
    +}
    +
    +sub GetKeysForEncryption {
    +    my $self = shift;
    +    my %args = (Recipient => undef, @_);
    +    return $self->GetKeysInfo( Key => delete $args{'Recipient'}, %args, Type => 'public' );
    +}
    +
    +sub GetKeysForSigning {
    +    my $self = shift;
    +    my %args = (Signer => undef, @_);
    +    return $self->GetKeysInfo( Key => delete $args{'Signer'}, %args, Type => 'private' );
    +}
    +
    +sub GetKeysInfo {
    +    my $self = shift;
    +    my %args = (
    +        Key   => undef,
    +        Type  => 'public',
    +        Force => 0,
    +        @_
    +    );
    +
    +    my $email = $args{'Key'};
    +    unless ( $email ) {
    +        return (exit_code => 0); # unless $args{'Force'};
    +    }
    +
    +    my $key = $self->GetKeyContent( %args );
    +    return (exit_code => 0) unless $key;
    +
    +    return $self->GetCertificateInfo( Certificate => $key );
    +}
    +
    +sub GetKeyContent {
    +    my $self = shift;
    +    my %args = ( Key => undef, @_ );
    +
    +    my $key;
    +    if ( my $file = $self->CheckKeyring( %args ) ) {
    +        open my $fh, '<:raw', $file
    +            or die "Couldn't open file '$file': $!";
    +        $key = do { local $/; readline $fh };
    +        close $fh;
    +    }
    +    else {
    +        my $user = RT::User->new( RT->SystemUser );
    +        $user->LoadByEmail( $args{'Key'} );
    +        $key = $user->SMIMECertificate if $user->id;
    +    }
    +    return $key;
    +}
    +
    +sub CheckKeyring {
    +    my $self = shift;
    +    my %args = (
    +        Key => undef,
    +        @_,
    +    );
    +    my $keyring = RT->Config->Get('SMIME')->{'Keyring'};
    +    return undef unless $keyring;
    +
    +    my $file = File::Spec->catfile( $keyring, $args{'Key'} .'.pem' );
    +    return undef unless -f $file;
    +
    +    return $file;
    +}
    +
    +sub GetCertificateInfo {
    +    my $self = shift;
    +    my %args = (
    +        Certificate => undef,
    +        @_,
    +    );
    +
    +    if ($args{Certificate} =~ /^-----BEGIN \s+ CERTIFICATE----- \s* $
    +                                (.*?)
    +                               ^-----END \s+ CERTIFICATE----- \s* $/smx) {
    +        $args{Certificate} = MIME::Base64::decode_base64($1);
    +    }
    +
    +    my $cert = Crypt::X509->new( cert => $args{Certificate} );
    +    return ( exit_code => 1, stderr => $cert->error ) if $cert->error;
    +
    +    my %USER_MAP = (
    +        Country          => 'country',
    +        StateOrProvince  => 'state',
    +        Organization     => 'org',
    +        OrganizationUnit => 'ou',
    +        Name             => 'cn',
    +        EmailAddress     => 'email',
    +    );
    +    my $canonicalize = sub {
    +        my $type = shift;
    +        my %data;
    +        for (keys %USER_MAP) {
    +            my $method = $type . "_" . $USER_MAP{$_};
    +            $data{$_} = $cert->$method if $cert->can($method);
    +        }
    +        $data{String} = Email::Address->new( @data{'Name', 'EmailAddress'} )->format
    +            if $data{EmailAddress};
    +        return \%data;
    +    };
    +
    +    my $PEM = "-----BEGIN CERTIFICATE-----\n"
    +        . MIME::Base64::encode_base64( $args{Certificate} )
    +        . "-----END CERTIFICATE-----\n";
    +
    +    my %res = (
    +        exit_code => 0,
    +        info => [ {
    +            Content         => $PEM,
    +            Fingerprint     => Digest::SHA::sha1_hex($args{Certificate}),
    +            'Serial Number' => $cert->serial,
    +            Created         => $self->ParseDate( $cert->not_before ),
    +            Expire          => $self->ParseDate( $cert->not_after ),
    +            Version         => sprintf("%d (0x%x)",hex($cert->version || 0)+1, hex($cert->version || 0)),
    +            Issuer          => [ $canonicalize->( 'issuer' ) ],
    +            User            => [ $canonicalize->( 'subject' ) ],
    +        } ],
    +        stderr => ''
    +    );
    +
    +    # Check the validity
    +    my $ca = RT->Config->Get('SMIME')->{'CAPath'};
    +    if ($ca) {
    +        my @ca_verify;
    +        if (-d $ca) {
    +            @ca_verify = ('-CApath', $ca);
    +        } elsif (-f $ca) {
    +            @ca_verify = ('-CAfile', $ca);
    +        }
    +
    +        local $SIG{CHLD} = 'DEFAULT';
    +        my $cmd = [
    +            $self->OpenSSLPath,
    +            'verify', @ca_verify,
    +        ];
    +        my $buf = '';
    +        safe_run_child { run3( $cmd, \$PEM, \$buf, \$res{stderr} ) };
    +
    +        if ($buf =~ /^stdin: OK$/) {
    +            $res{info}[0]{Trust} = "Signed by trusted CA $res{info}[0]{Issuer}[0]{String}";
    +            $res{info}[0]{TrustTerse} = "full";
    +            $res{info}[0]{TrustLevel} = 2;
    +        } elsif ($? == 0 or ($? >> 8) == 2) {
    +            $res{info}[0]{Trust} = "UNTRUSTED signing CA $res{info}[0]{Issuer}[0]{String}";
    +            $res{info}[0]{TrustTerse} = "none";
    +            $res{info}[0]{TrustLevel} = -1;
    +        } else {
    +            $res{exit_code} = $?;
    +            $res{message} = "openssl exited with error code ". ($? >> 8)
    +                ." and stout: $buf";
    +            $res{info}[0]{Trust} = "unknown (openssl failed)";
    +            $res{info}[0]{TrustTerse} = "unknown";
    +            $res{info}[0]{TrustLevel} = 0;
    +        }
    +    } else {
    +        $res{info}[0]{Trust} = "unknown (no CAPath set)";
    +        $res{info}[0]{TrustTerse} = "unknown";
    +        $res{info}[0]{TrustLevel} = 0;
    +    }
    +
    +    return %res;
    +}
    +
    +1;
    diff --git a/lib/RT/CurrentUser.pm b/lib/RT/CurrentUser.pm
    index fa0d4ca..a87cec3 100644
    --- a/lib/RT/CurrentUser.pm
    +++ b/lib/RT/CurrentUser.pm
    @@ -88,14 +88,13 @@ passed to Load method.
     
     package RT::CurrentUser;
     
    -use RT::I18N;
    -
     use strict;
     use warnings;
     
    -
     use base qw/RT::User/;
     
    +use RT::I18N;
    +
     #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
     # the CurrentUser object
    @@ -271,44 +270,8 @@ sub CurrentUser {
         return shift;
     }
     
    -=head2 Authenticate
    -
    -Takes $password, $created and $nonce, and returns a boolean value
    -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" )
    -    ))
    -
    -where $server_pass is the md5_hex(password) digest stored in the
    -database, $created is in ISO time format, and $nonce is a random
    -string no longer than 32 bytes.
    -
    -=cut
    -
    -sub Authenticate { 
    -    my ($self, $password, $created, $nonce, $realm) = @_;
    -
    -    require Digest::MD5;
    -    require Digest::SHA1;
    -    require MIME::Base64;
    -
    -    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")
    -    ));
    -
    -    chomp($password);
    -    chomp($auth_digest);
    -
    -    return ($password eq $auth_digest);
    +sub CustomFieldLookupType {
    +    return "RT::User";
     }
     
     RT::Base->_ImportOverlays();
    diff --git a/lib/RT/CustomField.pm b/lib/RT/CustomField.pm
    index 0211815..da6e16a 100644
    --- a/lib/RT/CustomField.pm
    +++ b/lib/RT/CustomField.pm
    @@ -50,14 +50,18 @@ package RT::CustomField;
     
     use strict;
     use warnings;
    +use 5.010;
     
     use Scalar::Util 'blessed';
     
     use base 'RT::Record';
     
    -sub Table {'CustomFields'}
    +use Role::Basic 'with';
    +with "RT::Record::Role::Rights";
     
    +sub Table {'CustomFields'}
     
    +use Scalar::Util qw(blessed);
     use RT::CustomFieldValues;
     use RT::ObjectCustomFields;
     use RT::ObjectCustomFieldValues;
    @@ -192,78 +196,24 @@ our %FieldTypes = (
     );
     
     
    -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
    -
    -our $RIGHTS = {
    -    SeeCustomField            => 'View custom fields',                                    # loc_pair
    -    AdminCustomField          => 'Create, modify and delete custom fields',               # loc_pair
    -    AdminCustomFieldValues    => 'Create, modify and delete custom fields values',        # loc_pair
    -    ModifyCustomField         => 'Add, modify and delete custom field values for objects' # loc_pair
    -};
    -
    -our $RIGHT_CATEGORIES = {
    -    SeeCustomField          => 'General',
    -    AdminCustomField        => 'Admin',
    -    AdminCustomFieldValues  => 'Admin',
    -    ModifyCustomField       => 'Staff',
    -};
    -
    -# Tell RT::ACE that this sort of object can get acls granted
    -$RT::ACE::OBJECT_TYPES{'RT::CustomField'} = 1;
    -
    -__PACKAGE__->AddRights(%$RIGHTS);
    -__PACKAGE__->AddRightCategories(%$RIGHT_CATEGORIES);
    -
    -=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;
    -}
    +my %BUILTIN_GROUPINGS;
    +my %FRIENDLY_LOOKUP_TYPES = ();
     
    -=head2 RightCategories
    +__PACKAGE__->RegisterLookupType( 'RT::Queue-RT::Ticket' => "Tickets", );    #loc
    +__PACKAGE__->RegisterLookupType( 'RT::Queue-RT::Ticket-RT::Transaction' => "Ticket Transactions", ); #loc
    +__PACKAGE__->RegisterLookupType( 'RT::User'  => "Users", );                           #loc
    +__PACKAGE__->RegisterLookupType( 'RT::Queue'  => "Queues", );                         #loc
    +__PACKAGE__->RegisterLookupType( 'RT::Group' => "Groups", );                          #loc
     
    -Returns a hashref where the keys are rights for this type of object and the
    -values are the category (General, Staff, Admin) the right falls into.
    -
    -=cut
    -
    -sub RightCategories {
    -    return $RIGHT_CATEGORIES;
    -}
    -
    -=head2 AddRightCategories C, C [, ...]
    -
    -Adds the given right and category pairs to the list of right categories.  This
    -method should be called during server startup, not at runtime.
    -
    -=cut
    +__PACKAGE__->RegisterBuiltInGroupings(
    +    'RT::Ticket'    => [ qw(Basics Dates Links People) ],
    +    'RT::User'      => [ 'Identity', 'Access control', 'Location', 'Phones' ],
    +);
     
    -sub AddRightCategories {
    -    my $self = shift if ref $_[0] or $_[0] eq __PACKAGE__;
    -    my %new = @_;
    -    $RIGHT_CATEGORIES = { %$RIGHT_CATEGORIES, %new };
    -}
    +__PACKAGE__->AddRight( General => SeeCustomField         => 'View custom fields'); # loc_pair
    +__PACKAGE__->AddRight( Admin   => AdminCustomField       => 'Create, modify and delete custom fields'); # loc_pair
    +__PACKAGE__->AddRight( Admin   => AdminCustomFieldValues => 'Create, modify and delete custom fields values'); # loc_pair
    +__PACKAGE__->AddRight( Staff   => ModifyCustomField      => 'Add, modify and delete custom field values for objects'); # loc_pair
     
     =head1 NAME
     
    @@ -281,7 +231,6 @@ Create takes a hash of values and creates a row in the database:
       varchar(200) 'Type'.
       int(11) 'MaxValues'.
       varchar(255) 'Pattern'.
    -  smallint(6) 'Repeated'.
       varchar(255) 'Description'.
       int(11) 'SortOrder'.
       varchar(255) 'LookupType'.
    @@ -302,7 +251,6 @@ sub Create {
             Description => '',
             Disabled    => 0,
             LookupType  => '',
    -        Repeated    => 0,
             LinkValueTo => '',
             IncludeContentForValue => '',
             @_,
    @@ -374,6 +322,8 @@ sub Create {
             }
         }
     
    +    $args{'Disabled'} ||= 0;
    +
         (my $rv, $msg) = $self->SUPER::Create(
             Name        => $args{'Name'},
             Type        => $args{'Type'},
    @@ -385,7 +335,6 @@ sub Create {
             Description => $args{'Description'},
             Disabled    => $args{'Disabled'},
             LookupType  => $args{'LookupType'},
    -        Repeated    => $args{'Repeated'},
         );
     
         if ($rv) {
    @@ -456,6 +405,7 @@ sub LoadByName {
         my %args = (
             Queue => undef,
             Name  => undef,
    +        LookupType => undef,
             @_,
         );
     
    @@ -479,6 +429,20 @@ sub LoadByName {
         $CFs->SetContextObject( $self->ContextObject );
         my $field = $args{'Name'} =~ /\D/? 'Name' : 'id';
         $CFs->Limit( FIELD => $field, VALUE => $args{'Name'}, CASESENSITIVE => 0);
    +
    +    # The context object may be a ticket, for example, as context for a
    +    # queue CF.  The valid lookup types are thus the entire set of
    +    # ACLEquivalenceObjects for the context object.
    +    $args{LookupType} ||= [
    +        map {$_->CustomFieldLookupType}
    +            ($self->ContextObject, $self->ContextObject->ACLEquivalenceObjects) ]
    +        if $self->ContextObject;
    +
    +    $args{LookupType} = [ $args{LookupType} ]
    +        if $args{LookupType} and not ref($args{LookupType});
    +    $CFs->Limit( FIELD => "LookupType", OPERATOR => "IN", VALUE => $args{LookupType} )
    +        if $args{LookupType};
    +
         # Don't limit to queue if queue is 0.  Trying to do so breaks
         # RT::Group type CFs.
         if ( defined $args{'Queue'} ) {
    @@ -741,7 +705,11 @@ sub ValidateType {
         my $type = shift;
     
         if ( $type =~ s/(?:Single|Multiple)$// ) {
    -        $RT::Logger->warning( "Prefix 'Single' and 'Multiple' to Type deprecated, use MaxValues instead at (". join(":",caller).")");
    +        RT->Deprecated(
    +            Arguments => "suffix 'Single' or 'Multiple'",
    +            Instead   => "MaxValues",
    +            Remove    => "4.4",
    +        );
         }
     
         if ( $FieldTypes{$type} ) {
    @@ -757,7 +725,11 @@ 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).")");
    +        RT->Deprecated(
    +            Arguments => "suffix 'Single' or 'Multiple'",
    +            Instead   => "MaxValues",
    +            Remove    => "4.4",
    +        );
             $self->SetMaxValues($1 ? 1 : 0);
         }
         $self->_Set(Field => 'Type', Value =>$type);
    @@ -837,22 +809,6 @@ sub UnlimitedValues {
     }
     
     
    -=head2 CurrentUserHasRight RIGHT
    -
    -Helper function to call the custom field's queue's CurrentUserHasRight with the passed in args.
    -
    -=cut
    -
    -sub CurrentUserHasRight {
    -    my $self  = shift;
    -    my $right = shift;
    -
    -    return $self->CurrentUser->HasRight(
    -        Object => $self,
    -        Right  => $right,
    -    );
    -}
    -
     =head2 ACLEquivalenceObjects
     
     Returns list of objects via which users can get rights on this custom field. For custom fields
    @@ -870,9 +826,10 @@ sub 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.
    +Set or get a context for this object. It can be ticket, queue or another
    +object this CF added to. Used for ACL control, for example
    +SeeCustomField can be granted on queue level to allow people to see all
    +fields added to the queue.
     
     =cut
     
    @@ -927,12 +884,13 @@ sub LoadContextObject {
     
     =head2 ValidateContextObject
     
    -Ensure that a given ContextObject applies to this Custom Field.
    -For custom fields that are assigned to Queues or to Classes, this checks that the Custom
    -Field is actually applied to that objects.  For Global Custom Fields, it returns true
    -as long as the Object is of the right type, because you may be using
    -your permissions on a given Queue of Class to see a Global CF.
    -For CFs that are only applied Globally, you don't need a ContextObject.
    +Ensure that a given ContextObject applies to this Custom Field.  For
    +custom fields that are assigned to Queues or to Classes, this checks
    +that the Custom Field is actually added to that object.  For Global
    +Custom Fields, it returns true as long as the Object is of the right
    +type, because you may be using your permissions on a given Queue of
    +Class to see a Global CF.  For CFs that are only added globally, you
    +don't need a ContextObject.
     
     =cut
     
    @@ -940,20 +898,20 @@ sub ValidateContextObject {
         my $self = shift;
         my $object = shift;
     
    -    return 1 if $self->IsApplied(0);
    +    return 1 if $self->IsGlobal;
     
         # global only custom fields don't have objects
         # that should be used as context objects.
    -    return if $self->ApplyGlobally;
    +    return if $self->IsOnlyGlobal;
     
         # Otherwise, make sure we weren't passed a user object that we're
         # supposed to treat as a queue.
         return unless $self->ValidContextType(ref $object);
     
    -    # Check that it is applied correctly
    -    my ($applied_to) = grep {ref($_) eq $self->RecordClassFromLookupType} ($object, $object->ACLEquivalenceObjects);
    -    return unless $applied_to;
    -    return $self->IsApplied($applied_to->id);
    +    # Check that it is added correctly
    +    my ($added_to) = grep {ref($_) eq $self->RecordClassFromLookupType} ($object, $object->ACLEquivalenceObjects);
    +    return unless $added_to;
    +    return $self->IsAdded($added_to->id);
     }
     
     
    @@ -1160,9 +1118,7 @@ sub SetLookupType {
         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};
    +        RT::ObjectCustomField->new($self->CurrentUser)->DeleteAll( CustomField => $self );
         }
         return $self->_Set(Field => 'LookupType', Value =>$lookup);
     }
    @@ -1176,15 +1132,9 @@ Returns an array of LookupTypes available
     
     sub LookupTypes {
         my $self = shift;
    -    return keys %FRIENDLY_OBJECT_TYPES;
    +    return sort keys %FRIENDLY_LOOKUP_TYPES;
     }
     
    -my @FriendlyObjectTypes = (
    -    "[_1] objects",            # loc
    -    "[_1]'s [_2] objects",        # loc
    -    "[_1]'s [_2]'s [_3] objects",   # loc
    -);
    -
     =head2 FriendlyLookupType
     
     Returns a localized description of the type of this custom field
    @@ -1194,15 +1144,21 @@ Returns a localized description of the type of this custom field
     sub FriendlyLookupType {
         my $self = shift;
         my $lookup = shift || $self->LookupType;
    -   
    -    return ($self->loc( $FRIENDLY_OBJECT_TYPES{$lookup} ))
    -                     if (defined  $FRIENDLY_OBJECT_TYPES{$lookup} );
    +
    +    return ($self->loc( $FRIENDLY_LOOKUP_TYPES{$lookup} ))
    +        if defined $FRIENDLY_LOOKUP_TYPES{$lookup};
     
         my @types = map { s/^RT::// ? $self->loc($_) : $_ }
           grep { defined and length }
           split( /-/, $lookup )
           or return;
    -    return ( $self->loc( $FriendlyObjectTypes[$#types], @types ) );
    +
    +    state $LocStrings = [
    +        "[_1] objects",            # loc
    +        "[_1]'s [_2] objects",        # loc
    +        "[_1]'s [_2]'s [_3] objects",   # loc
    +    ];
    +    return ( $self->loc( $LocStrings->[$#types], @types ) );
     }
     
     =head1 RecordClassFromLookupType
    @@ -1281,112 +1237,181 @@ sub CollectionClassFromLookupType {
         return $collection_class;
     }
     
    -=head1 ApplyGlobally
    +=head2 Groupings
    +
    +Returns a (sorted and lowercased) list of the groupings in which this custom
    +field appears.
    +
    +If called on a loaded object, the returned list is limited to groupings which
    +apply to the record class this CF applies to (L).
     
    -Certain custom fields (users, groups) should only be applied globally
    -but rather than regexing in code for LookupType =~ RT::Queue, we'll codify
    -the rules here.
    +If passed a loaded object or a class name, the returned list is limited to
    +groupings which apply to the class of the object or the specified class.
    +
    +If called on an unloaded object, all potential groupings are returned.
     
     =cut
     
    -sub ApplyGlobally {
    +sub Groupings {
         my $self = shift;
    +    my $record_class = $self->_GroupingClass(shift);
    +
    +    my $config = RT->Config->Get('CustomFieldGroupings');
    +       $config = {} unless ref($config) eq 'HASH';
    +
    +    my @groups;
    +    if ( $record_class ) {
    +        push @groups, sort {lc($a) cmp lc($b)} keys %{ $BUILTIN_GROUPINGS{$record_class} || {} };
    +        if ( ref($config->{$record_class} ||= []) eq "ARRAY") {
    +            my @order = @{ $config->{$record_class} };
    +            while (@order) {
    +                push @groups, shift(@order);
    +                shift(@order);
    +            }
    +        } else {
    +            @groups = sort {lc($a) cmp lc($b)} keys %{ $config->{$record_class} };
    +        }
    +    } else {
    +        my %all = (%$config, %BUILTIN_GROUPINGS);
    +        @groups = sort {lc($a) cmp lc($b)} map {$self->Groupings($_)} grep {$_} keys(%all);
    +    }
     
    -    return ($self->LookupType =~ /^RT::(?:Group|User)/io);
    -
    +    my %seen;
    +    return
    +        grep defined && length && !$seen{lc $_}++,
    +        @groups;
     }
     
    -=head1 AppliedTo
    -
    -Returns collection with objects this custom field is applied to.
    -Class of the collection depends on L.
    -See all L .
    +=head2 CustomGroupings
     
    -Doesn't takes into account if object is applied globally.
    +Identical to L but filters out built-in groupings from the the
    +returned list.
     
     =cut
     
    -sub AppliedTo {
    +sub CustomGroupings {
         my $self = shift;
    +    my $record_class = $self->_GroupingClass(shift);
    +    return grep !$BUILTIN_GROUPINGS{$record_class}{$_}, $self->Groupings( $record_class );
    +}
     
    -    my ($res, $ocfs_alias) = $self->_AppliedTo;
    -    return $res unless $res;
    +sub _GroupingClass {
    +    my $self    = shift;
    +    my $record  = shift;
     
    -    $res->Limit(
    -        ALIAS     => $ocfs_alias,
    -        FIELD     => 'id',
    -        OPERATOR  => 'IS NOT',
    -        VALUE     => 'NULL',
    -    );
    +    my $record_class = ref($record) || $record || '';
    +    $record_class = $self->RecordClassFromLookupType
    +        if !$record_class and blessed($self) and $self->id;
     
    -    return $res;
    +    return $record_class;
     }
     
    -=head1 NotAppliedTo
    +=head2 RegisterBuiltInGroupings
     
    -Returns collection with objects this custom field is not applied to.
    -Class of the collection depends on L.
    -See all L .
    +Registers groupings to be considered a fundamental part of RT, either via use
    +in core RT or via an extension.  These groupings must be rendered explicitly in
    +Mason by specific calls to F and
    +F.  They will not show up automatically on normal
    +display pages like configured custom groupings.
    +
    +Takes a set of key-value pairs of class names (valid L subclasses)
    +and array refs of grouping names to consider built-in.
     
    -Doesn't takes into account if object is applied globally.
    +If a class already contains built-in groupings (such as L and
    +L), new groupings are appended.
     
     =cut
     
    -sub NotAppliedTo {
    +sub RegisterBuiltInGroupings {
         my $self = shift;
    +    my %new  = @_;
     
    -    my ($res, $ocfs_alias) = $self->_AppliedTo;
    -    return $res unless $res;
    +    while (my ($k,$v) = each %new) {
    +        $v = [$v] unless ref($v) eq 'ARRAY';
    +        $BUILTIN_GROUPINGS{$k} = {
    +            %{$BUILTIN_GROUPINGS{$k} || {}},
    +            map { $_ => 1 } @$v
    +        };
    +    }
    +    $BUILTIN_GROUPINGS{''} = { map { %$_ } values %BUILTIN_GROUPINGS  };
    +}
     
    -    $res->Limit(
    -        ALIAS     => $ocfs_alias,
    -        FIELD     => 'id',
    -        OPERATOR  => 'IS',
    -        VALUE     => 'NULL',
    -    );
    +=head1 IsOnlyGlobal
     
    -    return $res;
    -}
    +Certain custom fields (users, groups) should only be added globally;
    +codify that set here for reference.
    +
    +=cut
     
    -sub _AppliedTo {
    +sub IsOnlyGlobal {
         my $self = shift;
     
    -    my ($class) = $self->CollectionClassFromLookupType;
    -    return undef unless $class;
    +    return ($self->LookupType =~ /^RT::(?:Group|User)/io);
    +
    +}
    +sub ApplyGlobally {
    +    RT->Deprecated(
    +        Instead   => "IsOnlyGlobal",
    +        Remove    => "4.4",
    +    );
    +    return shift->IsOnlyGlobal(@_);
    +}
     
    -    my $res = $class->new( $self->CurrentUser );
    +=head1 AddedTo
     
    -    # If CF is a Group CF, only display user-defined groups
    -    if ( $class eq 'RT::Groups' ) {
    -        $res->LimitToUserDefinedGroups;
    -    }
    +Returns collection with objects this custom field is added to.
    +Class of the collection depends on L.
    +See all L .
     
    -    $res->OrderBy( FIELD => 'Name' );
    -    my $ocfs_alias = $res->Join(
    -        TYPE   => 'LEFT',
    -        ALIAS1 => 'main',
    -        FIELD1 => 'id',
    -        TABLE2 => 'ObjectCustomFields',
    -        FIELD2 => 'ObjectId',
    -    );
    -    $res->Limit(
    -        LEFTJOIN => $ocfs_alias,
    -        ALIAS    => $ocfs_alias,
    -        FIELD    => 'CustomField',
    -        VALUE    => $self->id,
    +Doesn't takes into account if object is added globally.
    +
    +=cut
    +
    +sub AddedTo {
    +    my $self = shift;
    +    return RT::ObjectCustomField->new( $self->CurrentUser )
    +        ->AddedTo( CustomField => $self );
    +}
    +sub AppliedTo {
    +    RT->Deprecated(
    +        Instead   => "AddedTo",
    +        Remove    => "4.4",
         );
    -    return ($res, $ocfs_alias);
    +    shift->AddedTo(@_);
    +};
    +
    +=head1 NotAddedTo
    +
    +Returns collection with objects this custom field is not added to.
    +Class of the collection depends on L.
    +See all L .
    +
    +Doesn't take into account if the object is added globally.
    +
    +=cut
    +
    +sub NotAddedTo {
    +    my $self = shift;
    +    return RT::ObjectCustomField->new( $self->CurrentUser )
    +        ->NotAddedTo( CustomField => $self );
     }
    +sub NotAppliedTo {
    +    RT->Deprecated(
    +        Instead   => "NotAddedTo",
    +        Remove    => "4.4",
    +    );
    +    shift->NotAddedTo(@_)
    +};
     
    -=head2 IsApplied
    +=head2 IsAdded
     
     Takes object id and returns corresponding L
    -record if this custom field is applied to the object. Use 0 to check
    -if custom field is applied globally.
    +record if this custom field is added to the object. Use 0 to check
    +if custom field is added globally.
     
     =cut
     
    -sub IsApplied {
    +sub IsAdded {
         my $self = shift;
         my $id = shift;
         my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
    @@ -1394,6 +1419,29 @@ sub IsApplied {
         return undef unless $ocf->id;
         return $ocf;
     }
    +sub IsApplied {
    +    RT->Deprecated(
    +        Instead   => "IsAdded",
    +        Remove    => "4.4",
    +    );
    +    shift->IsAdded(@_);
    +};
    +
    +sub IsGlobal { return shift->IsAdded(0) }
    +
    +=head2 IsAddedToAny
    +
    +Returns true if custom field is applied to any object.
    +
    +=cut
    +
    +sub IsAddedToAny {
    +    my $self = shift;
    +    my $id = shift;
    +    my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
    +    $ocf->LoadByCols( CustomField => $self->id );
    +    return $ocf->id ? 1 : 0;
    +}
     
     =head2 AddToObject OBJECT
     
    @@ -1403,7 +1451,6 @@ Takes an object
     
     =cut
     
    -
     sub AddToObject {
         my $self  = shift;
         my $object = shift;
    @@ -1417,26 +1464,9 @@ sub AddToObject {
             return ( 0, $self->loc('Permission Denied') );
         }
     
    -    if ( $self->IsApplied( $id ) ) {
    -        return ( 0, $self->loc("Custom field is already applied to the object") );
    -    }
    -
    -    if ( $id ) {
    -        # applying locally
    -        return (0, $self->loc("Couldn't apply custom field to an object as it's global already") )
    -            if $self->IsApplied( 0 );
    -    }
    -    else {
    -        my $applied = RT::ObjectCustomFields->new( $self->CurrentUser );
    -        $applied->LimitToCustomField( $self->id );
    -        while ( my $record = $applied->Next ) {
    -            $record->Delete;
    -        }
    -    }
    -
         my $ocf = RT::ObjectCustomField->new( $self->CurrentUser );
    -    my ( $oid, $msg ) = $ocf->Create(
    -        ObjectId => $id, CustomField => $self->id,
    +    my ( $oid, $msg ) = $ocf->Add(
    +        CustomField => $self->id, ObjectId => $id,
         );
         return ( $oid, $msg );
     }
    @@ -1463,9 +1493,9 @@ sub RemoveFromObject {
             return ( 0, $self->loc('Permission Denied') );
         }
     
    -    my $ocf = $self->IsApplied( $id );
    +    my $ocf = $self->IsAdded( $id );
         unless ( $ocf ) {
    -        return ( 0, $self->loc("This custom field does not apply to that object") );
    +        return ( 0, $self->loc("This custom field cannot be added to that object") );
         }
     
         # XXX: Delete doesn't return anything
    @@ -1704,9 +1734,10 @@ sub ValuesForObject {
     }
     
     
    -=head2 _ForObjectType PATH FRIENDLYNAME
    +=head2 RegisterLookupType LOOKUPTYPE FRIENDLYNAME
     
    -Tell RT that a certain object accepts custom fields
    +Tell RT that a certain object accepts custom fields via a lookup type and
    +provide a friendly name for such CFs.
     
     Examples:
     
    @@ -1720,13 +1751,21 @@ This is a class method.
     
     =cut
     
    -sub _ForObjectType {
    +sub RegisterLookupType {
         my $self = shift;
         my $path = shift;
         my $friendly_name = shift;
     
    -    $FRIENDLY_OBJECT_TYPES{$path} = $friendly_name;
    +    $FRIENDLY_LOOKUP_TYPES{$path} = $friendly_name;
    +}
     
    +sub _ForObjectType {
    +    RT->Deprecated(
    +        Instead => 'RegisterLookupType',
    +        Remove  => '4.4',
    +    );
    +    my $self = shift;
    +    $self->RegisterLookupType(@_);
     }
     
     
    @@ -1812,7 +1851,7 @@ sub SetBasedOn {
         $cf->SetContextObject( $self->ContextObject );
         $cf->Load( ref $value ? $value->id : $value );
     
    -    return (0, "Permission denied")
    +    return (0, "Permission Denied")
             unless $cf->id && $cf->CurrentUserHasRight('SeeCustomField');
     
         # XXX: Remove this restriction once we support lists and cascaded selects
    @@ -1938,24 +1977,6 @@ Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
     =cut
     
     
    -=head2 Repeated
    -
    -Returns the current value of Repeated. 
    -(In the database, Repeated is stored as smallint(6).)
    -
    -
    -
    -=head2 SetRepeated VALUE
    -
    -
    -Set Repeated to VALUE. 
    -Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
    -(In the database, Repeated will be stored as a smallint(6).)
    -
    -
    -=cut
    -
    -
     =head2 BasedOn
     
     Returns the current value of BasedOn. 
    @@ -2098,8 +2119,6 @@ sub _CoreAccessible {
             {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 => -4, length => 0,  is_blob => 1,  is_numeric => 0,  type => 'text', default => ''},
    -        Repeated => 
    -        {read => 1, write => 1, sql_type => 5, length => 6,  is_blob => 0,  is_numeric => 1,  type => 'smallint(6)', default => '0'},
             ValuesClass => 
             {read => 1, write => 1, sql_type => 12, length => 64,  is_blob => 0,  is_numeric => 0,  type => 'varchar(64)', default => ''},
             BasedOn => 
    @@ -2124,6 +2143,22 @@ sub _CoreAccessible {
      }
     };
     
    +sub FindDependencies {
    +    my $self = shift;
    +    my ($walker, $deps) = @_;
    +
    +    $self->SUPER::FindDependencies($walker, $deps);
    +
    +    $deps->Add( out => $self->BasedOnObj )
    +        if $self->BasedOnObj->id;
    +
    +    my $applied = RT::ObjectCustomFields->new( $self->CurrentUser );
    +    $applied->LimitToCustomField( $self->id );
    +    $deps->Add( in => $applied );
    +
    +    $deps->Add( in => $self->Values ) if $self->ValuesClass eq "RT::CustomFieldValues";
    +}
    +
     
     RT::Base->_ImportOverlays();
     
    diff --git a/lib/RT/CustomFieldValue.pm b/lib/RT/CustomFieldValue.pm
    index 6dffc34..fb8d83f 100644
    --- a/lib/RT/CustomFieldValue.pm
    +++ b/lib/RT/CustomFieldValue.pm
    @@ -54,8 +54,8 @@ package RT::CustomFieldValue;
     no warnings qw/redefine/;
     
     
    -use RT::CustomField;
     use base 'RT::Record';
    +use RT::CustomField;
     
     sub Table {'CustomFieldValues'}
     
    @@ -100,37 +100,6 @@ 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(@_);
    -}
    -
     sub _Set { 
         my $self = shift; 
     
    @@ -329,6 +298,14 @@ sub _CoreAccessible {
     };
     
     
    +sub FindDependencies {
    +    my $self = shift;
    +    my ($walker, $deps) = @_;
    +
    +    $self->SUPER::FindDependencies($walker, $deps);
    +
    +    $deps->Add( out => $self->CustomFieldObj );
    +}
     
     
     RT::Base->_ImportOverlays();
    diff --git a/lib/RT/CustomFieldValues.pm b/lib/RT/CustomFieldValues.pm
    index e3380b7..37d1594 100644
    --- a/lib/RT/CustomFieldValues.pm
    +++ b/lib/RT/CustomFieldValues.pm
    @@ -51,12 +51,10 @@ package RT::CustomFieldValues;
     use strict;
     use warnings;
     
    -
    +use base 'RT::SearchBuilder';
     
     use RT::CustomFieldValue;
     
    -use base 'RT::SearchBuilder';
    -
     sub Table { 'CustomFieldValues'}
     
     sub _Init {
    @@ -64,15 +62,15 @@ sub _Init {
     
       # By default, order by SortOrder
       $self->OrderByCols(
    -	 { ALIAS => 'main',
    -	   FIELD => 'SortOrder',
    -	   ORDER => 'ASC' },
    -	 { ALIAS => 'main',
    -	   FIELD => 'Name',
    -	   ORDER => 'ASC' },
    -	 { ALIAS => 'main',
    -	   FIELD => 'id',
    -	   ORDER => 'ASC' },
    +         { ALIAS => 'main',
    +           FIELD => 'SortOrder',
    +           ORDER => 'ASC' },
    +         { ALIAS => 'main',
    +           FIELD => 'Name',
    +           ORDER => 'ASC' },
    +         { ALIAS => 'main',
    +           FIELD => 'id',
    +           ORDER => 'ASC' },
          );
     
         return ( $self->SUPER::_Init(@_) );
    @@ -95,19 +93,6 @@ sub LimitToCustomField {
         );
     }
     
    -
    -
    -
    -=head2 NewItem
    -
    -Returns an empty new RT::CustomFieldValue item
    -
    -=cut
    -
    -sub NewItem {
    -    my $self = shift;
    -    return(RT::CustomFieldValue->new($self->CurrentUser));
    -}
     RT::Base->_ImportOverlays();
     
     1;
    diff --git a/lib/RT/CustomFieldValues/External.pm b/lib/RT/CustomFieldValues/External.pm
    index e6bf2f8..4e58388 100644
    --- a/lib/RT/CustomFieldValues/External.pm
    +++ b/lib/RT/CustomFieldValues/External.pm
    @@ -214,6 +214,10 @@ sub LimitToCustomField {
         return $self->SUPER::LimitToCustomField( @_ );
     }
     
    +sub _SingularClass {
    +    "RT::CustomFieldValue"
    +}
    +
     RT::Base->_ImportOverlays();
     
     1;
    diff --git a/lib/RT/CustomFields.pm b/lib/RT/CustomFields.pm
    index 1ea75da..e30aa42 100644
    --- a/lib/RT/CustomFields.pm
    +++ b/lib/RT/CustomFields.pm
    @@ -68,12 +68,10 @@ package RT::CustomFields;
     use strict;
     use warnings;
     
    -use DBIx::SearchBuilder::Unique;
    +use base 'RT::SearchBuilder';
     
     use RT::CustomField;
     
    -use base 'RT::SearchBuilder';
    -
     sub Table { 'CustomFields'}
     
     sub _Init {
    @@ -81,21 +79,77 @@ sub _Init {
     
       # By default, order by SortOrder
       $self->OrderByCols(
    -	 { ALIAS => 'main',
    -	   FIELD => 'SortOrder',
    -	   ORDER => 'ASC' },
    -	 { ALIAS => 'main',
    -	   FIELD => 'Name',
    -	   ORDER => 'ASC' },
    -	 { ALIAS => 'main',
    -	   FIELD => 'id',
    -	   ORDER => 'ASC' },
    +         { ALIAS => 'main',
    +           FIELD => 'SortOrder',
    +           ORDER => 'ASC' },
    +         { ALIAS => 'main',
    +           FIELD => 'Name',
    +           ORDER => 'ASC' },
    +         { ALIAS => 'main',
    +           FIELD => 'id',
    +           ORDER => 'ASC' },
          );
         $self->{'with_disabled_column'} = 1;
     
         return ( $self->SUPER::_Init(@_) );
     }
     
    +=head2 LimitToGrouping
    +
    +Limits this collection object to custom fields which appear under a
    +specified grouping by calling L for each CF name as appropriate.
    +
    +Requires an L object or class name as the first argument and
    +accepts a grouping name as the second.  If the grouping name is false
    +(usually via the empty string), limits to custom fields which appear in no
    +grouping.
    +
    +I While the record object or class name is used to find the
    +available groupings, no automatic limit is placed on the lookup type of
    +the custom fields.  It's highly suggested you limit the collection by
    +queue or another lookup type first.  This is already done for you if
    +you're creating the collection via the L method on an
    +L object.
    +
    +=cut
    +
    +sub LimitToGrouping {
    +    my $self = shift;
    +    my $obj = shift;
    +    my $grouping = shift;
    +
    +    my $config = RT->Config->Get('CustomFieldGroupings');
    +       $config = {} unless ref($config) eq 'HASH';
    +       $config = $config->{ref($obj) || $obj} || [];
    +    my %h = ref $config eq "ARRAY" ? @{$config} : %{$config};
    +
    +    if ( $grouping ) {
    +        my $list = $h{$grouping};
    +        unless ( $list and ref($list) eq 'ARRAY' and @$list ) {
    +            return $self->Limit( FIELD => 'id', VALUE => 0, ENTRYAGGREGATOR => 'AND' );
    +        }
    +        foreach ( @$list ) {
    +            $self->Limit( FIELD => 'Name', VALUE => $_, CASESENSITIVE => 0 );
    +        }
    +    } else {
    +        my @list = map {@$_} grep defined && ref($_) eq 'ARRAY',
    +            values %h;
    +
    +        return unless @list;
    +        foreach ( @list ) {
    +            $self->Limit(
    +                FIELD => 'Name',
    +                OPERATOR => '!=',
    +                VALUE => $_,
    +                ENTRYAGGREGATOR => 'AND',
    +                CASESENSITIVE => 0,
    +            );
    +        }
    +
    +    }
    +    return;
    +}
    +
     
     =head2 LimitToLookupType
     
    @@ -164,7 +218,7 @@ sub LimitToObjectId {
     =head2 LimitToGlobalOrObjectId
     
     Takes list of object IDs and limits collection to custom
    -fields that are applied to these objects or globally.
    +fields that are added to these objects or globally.
     
     =cut
     
    @@ -181,29 +235,7 @@ sub LimitToGlobalOrObjectId {
         $self->LimitToObjectId(0) unless $global_only;
     }
     
    -sub _LimitToOCFs {
    -    my $self = shift;
    -    my @ids = @_;
    -
    -    my $ocfs_alias = $self->_OCFAlias( New => 1, Left => 1 );
    -    if ( @ids ) {
    -        # XXX: we need different EA in join clause, but DBIx::SB
    -        # doesn't support them, use IN (X) instead
    -        my $dbh = $self->_Handle->dbh;
    -        $self->Limit(
    -            LEFTJOIN   => $ocfs_alias,
    -            ALIAS      => $ocfs_alias,
    -            FIELD      => 'ObjectId',
    -            OPERATOR   => 'IN',
    -            QUOTEVALUE => 0,
    -            VALUE      => "(". join( ',', map $dbh->quote($_), @ids ) .")",
    -        );
    -    }
    -
    -    return $ocfs_alias;
    -}
    -
    -=head2 LimitToNotApplied
    +=head2 LimitToNotAdded
     
     Takes either list of object ids or nothing. Limits collection
     to custom fields to listed objects or any corespondingly. Use
    @@ -211,48 +243,31 @@ zero to mean global.
     
     =cut
     
    -sub LimitToNotApplied {
    +sub LimitToNotAdded {
         my $self = shift;
    -    my @ids = @_;
    -
    -    my $ocfs_alias = $self->_LimitToOCFs(@ids);
    -
    -    $self->Limit(
    -        ENTRYAGGREGATOR => 'AND',
    -        ALIAS    => $ocfs_alias,
    -        FIELD    => 'id',
    -        OPERATOR => 'IS',
    -        VALUE    => 'NULL',
    -    );
    +    return RT::ObjectCustomFields->new( $self->CurrentUser )
    +        ->LimitTargetToNotAdded( $self => @_ );
     }
     
    -=head2 LimitToApplied
    +=head2 LimitToAdded
     
     Limits collection to custom fields to listed objects or any corespondingly. Use
     zero to mean global.
     
     =cut
     
    -sub LimitToApplied {
    +sub LimitToAdded {
         my $self = shift;
    -    my @ids = @_;
    -
    -    my $ocfs_alias = $self->_LimitToOCFs(@ids);
    -
    -    $self->Limit(
    -        ENTRYAGGREGATOR => 'AND',
    -        ALIAS    => $ocfs_alias,
    -        FIELD    => 'id',
    -        OPERATOR => 'IS NOT',
    -        VALUE    => 'NULL',
    -    );
    +    return RT::ObjectCustomFields->new( $self->CurrentUser )
    +        ->LimitTargetToAdded( $self => @_ );
     }
     
     =head2 LimitToGlobalOrQueue QUEUEID
     
    -DEPRECATED since CFs are applicable not only to tickets these days.
    +Limits the set of custom fields found to global custom fields or those
    +tied to the queue C, similar to L.
     
    -Limits the set of custom fields found to global custom fields or those tied to the queue with ID QUEUEID
    +Note that this will cause the collection to only return ticket CFs.
     
     =cut
     
    @@ -266,34 +281,33 @@ sub LimitToGlobalOrQueue {
     
     =head2 LimitToQueue QUEUEID
     
    -DEPRECATED since CFs are applicable not only to tickets these days.
    +Takes a numeric C, and limits the Custom Field collection to
    +those only applied directly to it; this limit is OR'd with other
    +L and L limits.
     
    -Takes a queue id (numerical) as its only argument. Makes sure that
    -Scopes it pulls out apply to this queue (or another that you've selected with
    -another call to this method
    +Note that this will cause the collection to only return ticket CFs.
     
     =cut
     
     sub LimitToQueue  {
        my $self = shift;
    -  my $queue = shift;
    -
    -  $self->Limit (ALIAS => $self->_OCFAlias,
    -                ENTRYAGGREGATOR => 'OR',
    -		FIELD => 'ObjectId',
    -		VALUE => "$queue")
    -      if defined $queue;
    -  $self->LimitToLookupType( 'RT::Queue-RT::Ticket' );
    +   my $queue = shift;
    +
    +   $self->Limit (ALIAS => $self->_OCFAlias,
    +                 ENTRYAGGREGATOR => 'OR',
    +                 FIELD => 'ObjectId',
    +                 VALUE => "$queue")
    +       if defined $queue;
    +   $self->LimitToLookupType( 'RT::Queue-RT::Ticket' );
     }
     
     
     =head2 LimitToGlobal
     
    -DEPRECATED since CFs are applicable not only to tickets these days.
    +Limits the Custom Field collection to global ticket CFs; this limit is
    +OR'd with L limits.
     
    -Makes sure that Scopes it pulls out apply to all queues
    -(or another that you've selected with
    -another call to this method or LimitToQueue)
    +Note that this will cause the collection to only return ticket CFs.
     
     =cut
     
    @@ -302,8 +316,8 @@ sub LimitToGlobal  {
     
       $self->Limit (ALIAS => $self->_OCFAlias,
                     ENTRYAGGREGATOR => 'OR',
    -		FIELD => 'ObjectId',
    -		VALUE => 0);
    +                FIELD => 'ObjectId',
    +                VALUE => 0);
       $self->LimitToLookupType( 'RT::Queue-RT::Ticket' );
     }
     
    @@ -355,19 +369,8 @@ sub SetContextObject {
     
     sub _OCFAlias {
         my $self = shift;
    -    my %args = ( New => 0, Left => 0, @_ );
    -
    -    return $self->{'_sql_ocfalias'} if $self->{'_sql_ocfalias'} && !$args{'New'};
    -
    -    my $alias = $self->Join(
    -        $args{'Left'} ? (TYPE => 'LEFT') : (),
    -        ALIAS1 => 'main',
    -        FIELD1 => 'id',
    -        TABLE2 => 'ObjectCustomFields',
    -        FIELD2 => 'CustomField'
    -    );
    -    return $alias if $args{'New'};
    -    return $self->{'_sql_ocfalias'} = $alias;
    +    return RT::ObjectCustomFields->new( $self->CurrentUser )
    +        ->JoinTargetToThis( $self => @_ );
     }
     
     
    diff --git a/lib/RT/Dashboard.pm b/lib/RT/Dashboard.pm
    index 349864e..8fafc1c 100644
    --- a/lib/RT/Dashboard.pm
    +++ b/lib/RT/Dashboard.pm
    @@ -67,41 +67,26 @@
     
     package RT::Dashboard;
     
    -use RT::SavedSearch;
    -
     use strict;
     use warnings;
     
     use base qw/RT::SharedSetting/;
     
    +use RT::SavedSearch;
    +
     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
    -);
    -
    -RT::System::AddRightCategories(
    -    SubscribeDashboard => 'Staff',
    -
    -    SeeDashboard       => 'General',
    -    CreateDashboard    => 'Admin',
    -    ModifyDashboard    => 'Admin',
    -    DeleteDashboard    => 'Admin',
    -
    -    SeeOwnDashboard    => 'Staff',
    -    CreateOwnDashboard => 'Staff',
    -    ModifyOwnDashboard => 'Staff',
    -    DeleteOwnDashboard => 'Staff',
    -);
    +'RT::System'->AddRight( Staff   => SubscribeDashboard => 'Subscribe to dashboards'); # loc_pair
    +
    +'RT::System'->AddRight( General => SeeDashboard       => 'View system dashboards'); # loc_pair
    +'RT::System'->AddRight( Admin   => CreateDashboard    => 'Create system dashboards'); # loc_pair
    +'RT::System'->AddRight( Admin   => ModifyDashboard    => 'Modify system dashboards'); # loc_pair
    +'RT::System'->AddRight( Admin   => DeleteDashboard    => 'Delete system dashboards'); # loc_pair
    +
    +'RT::System'->AddRight( Staff   => SeeOwnDashboard    => 'View personal dashboards'); # loc_pair
    +'RT::System'->AddRight( Staff   => CreateOwnDashboard => 'Create personal dashboards'); # loc_pair
    +'RT::System'->AddRight( Staff   => ModifyOwnDashboard => 'Modify personal dashboards'); # loc_pair
    +'RT::System'->AddRight( Staff   => DeleteOwnDashboard => 'Delete personal dashboards'); # loc_pair
    +
     
     =head2 ObjectName
     
    diff --git a/lib/RT/Dashboard/Mailer.pm b/lib/RT/Dashboard/Mailer.pm
    index 9d28c49..c4de860 100644
    --- a/lib/RT/Dashboard/Mailer.pm
    +++ b/lib/RT/Dashboard/Mailer.pm
    @@ -60,6 +60,7 @@ use RT::Interface::Web::Handler;
     use RT::Interface::Web;
     use File::Temp 'tempdir';
     use HTML::Scrubber;
    +use URI::QueryParam;
     
     sub MailDashboards {
         my $self = shift;
    @@ -351,6 +352,7 @@ sub EmailDashboard {
         $RT::Logger->debug('Mailing dashboard "'.$dashboard->Name.'" to user '.$currentuser->Name." <$email>");
     
         my $ok = RT::Interface::Email::SendEmail(
    +        %{ RT->Config->Get('Crypt')->{'Dashboards'} || {} },
             Entity => $entity,
         );
     
    @@ -381,8 +383,10 @@ sub BuildEmail {
                 # already attached this object
                 return "cid:$cid_of{$uri}" if $cid_of{$uri};
     
    -            $cid_of{$uri} = time() . $$ . int(rand(1e6));
                 my ($data, $filename, $mimetype, $encoding) = GetResource($uri);
    +            return $uri unless defined $data;
    +
    +            $cid_of{$uri} = time() . $$ . int(rand(1e6));
     
                 # downgrade non-text strings, because all strings are utf8 by
                 # default, which is wrong for non-text strings.
    @@ -405,7 +409,7 @@ sub BuildEmail {
             inline_css => sub {
                 my $uri = shift;
                 my ($content) = GetResource($uri);
    -            return $content;
    +            return defined $content ? $content : "";
             },
             inline_imports => 1,
         );
    @@ -450,7 +454,7 @@ sub BuildEmail {
                     autohandler_name => '', # disable forced login and more
                     data_dir => $data_dir,
                 );
    -            $mason->set_escape( h => \&RT::Interface::Web::EscapeUTF8 );
    +            $mason->set_escape( h => \&RT::Interface::Web::EscapeHTML );
                 $mason->set_escape( u => \&RT::Interface::Web::EscapeURI  );
                 $mason->set_escape( j => \&RT::Interface::Web::EscapeJS   );
             }
    @@ -523,7 +527,13 @@ sub BuildEmail {
     
     sub GetResource {
         my $uri = URI->new(shift);
    -    my ($content, $filename, $mimetype, $encoding);
    +    my ($content, $content_type, $filename, $mimetype, $encoding);
    +
    +    # Avoid trying to inline any remote URIs.  We absolutified all URIs
    +    # using WebURL in SendDashboard() above, so choose the simpler match on
    +    # that rather than testing a bunch of URI accessors.
    +    my $WebURL = RT->Config->Get("WebURL");
    +    return unless $uri =~ /^\Q$WebURL/;
     
         $RT::Logger->debug("Getting resource $uri");
     
    @@ -536,40 +546,34 @@ sub GetResource {
         $path = "/$path"
             unless $path =~ m{^/};
     
    -    $HTML::Mason::Commands::r->path_info($path);
    -
    -    # grab the query arguments
    -    my %args;
    -    for (split /&/, ($uri->query||'')) {
    -        my ($k, $v) = /^(.*?)=(.*)$/
    -            or die "Unable to parse query parameter '$_'";
    -
    -        for ($k, $v) { s/%(..)/chr hex $1/ge }
    -
    -        # no value yet, simple key=value
    -        if (!exists $args{$k}) {
    -            $args{$k} = $v;
    -        }
    -        # already have key=value, need to upgrade it to key=[value1, value2]
    -        elsif (!ref($args{$k})) {
    -            $args{$k} = [$args{$k}, $v];
    -        }
    -        # already key=[value1, value2], just add the new value
    -        else {
    -            push @{ $args{$k} }, $v;
    -        }
    +    # Try the static handler first for non-Mason CSS, JS, etc.
    +    my $res = RT::Interface::Web::Handler->GetStatic($path);
    +    if ($res->is_success) {
    +        RT->Logger->debug("Fetched '$path' from the static handler");
    +        $content      = $res->decoded_content;
    +        $content_type = $res->headers->content_type;
    +    } else {
    +        # Try it through Mason instead...
    +        $HTML::Mason::Commands::r->path_info($path);
    +
    +        # grab the query arguments
    +        my %args = map { $_ => [ $uri->query_param($_) ] } $uri->query_param;
    +        # Convert empty and single element arrayrefs to a non-ref scalar
    +        @$_ < 2 and $_ = $_->[0]
    +            for values %args;
    +
    +        $RT::Logger->debug("Running component '$path'");
    +        $content = RunComponent($path, %args);
    +
    +        $content_type = $HTML::Mason::Commands::r->content_type;
         }
     
    -    $RT::Logger->debug("Running component '$path'");
    -    $content = RunComponent($path, %args);
    -
         # guess at the filename from the component name
         $filename = $1 if $path =~ m{^.*/(.*?)$};
     
         # the rest of this was taken from Email::MIME::CreateHTML::Resolver::LWP
         ($mimetype, $encoding) = MIME::Types::by_suffix($filename);
     
    -    my $content_type = $HTML::Mason::Commands::r->content_type;
         if ($content_type) {
             $mimetype = $content_type;
     
    diff --git a/lib/RT/Dashboards.pm b/lib/RT/Dashboards.pm
    index f9cbbe8..023745c 100644
    --- a/lib/RT/Dashboards.pm
    +++ b/lib/RT/Dashboards.pm
    @@ -67,12 +67,12 @@
     
     package RT::Dashboards;
     
    -use RT::Dashboard;
    -
     use strict;
     use warnings;
     use base 'RT::SharedSettings';
     
    +use RT::Dashboard;
    +
     sub RecordClass {
         return 'RT::Dashboard';
     }
    diff --git a/lib/RT/Date.pm b/lib/RT/Date.pm
    index d362197..28a1593 100644
    --- a/lib/RT/Date.pm
    +++ b/lib/RT/Date.pm
    @@ -166,10 +166,21 @@ sub Set {
     
         return $self->Unix(0) unless $args{'Value'} && $args{'Value'} =~ /\S/;
     
    -    if ( $args{'Format'} =~ /^unix$/i ) {
    +    my $format = lc $args{'Format'};
    +
    +    if ( $format eq 'unix' ) {
             return $self->Unix( $args{'Value'} );
         }
    -    elsif ( $args{'Format'} =~ /^(sql|datemanip|iso)$/i ) {
    +    elsif (
    +        ($format eq 'sql' || $format eq 'iso')
    +        && $args{'Value'} =~ /^(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$/
    +    ) {
    +        local $@;
    +        my $u = eval { Time::Local::timegm($6, $5, $4, $3, $2-1, $1) } || 0;
    +        $RT::Logger->warning("Invalid date $args{'Value'}: $@") if $@ && !$u;
    +        return $self->Unix( $u > 0 ? $u : 0 );
    +    }
    +    elsif ( $format =~ /^(sql|datemanip|iso)$/ ) {
             $args{'Value'} =~ s!/!-!g;
     
             if (   ( $args{'Value'} =~ /^(\d{4})?(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/ )
    @@ -201,14 +212,14 @@ sub Set {
                 return $self->Unix(0);
             }
         }
    -    elsif ( $args{'Format'} =~ /^unknown$/i ) {
    +    elsif ( $format eq 'unknown' ) {
             require Time::ParseDate;
             # 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(
    +        my ($date, $error) = Time::ParseDate::parsedate(
                 $args{'Value'},
                 GMT           => 1,
                 NOW           => $now,
    @@ -216,6 +227,13 @@ sub Set {
                 PREFER_PAST   => RT->Config->Get('AmbiguousDayInPast'),
                 PREFER_FUTURE => RT->Config->Get('AmbiguousDayInFuture'),
             );
    +        unless ( defined $date ) {
    +            $RT::Logger->warning(
    +                "Couldn't parse date '$args{'Value'}' by Time::ParseDate"
    +            );
    +            return $self->Unix(0);
    +        }
    +
             # apply timezone offset
             $date -= ($self->Localtime( $args{Timezone}, $date ))[9];
     
    @@ -223,7 +241,7 @@ sub Set {
                 "RT::Date used Time::ParseDate to make '$args{'Value'}' $date\n"
             );
     
    -        return $self->Set( Format => 'unix', Value => $date);
    +        return $self->Unix($date || 0);
         }
         else {
             $RT::Logger->error(
    @@ -323,50 +341,116 @@ sub DiffAsString {
     Takes a number of seconds. Returns a localized string describing
     that duration.
     
    +Takes optional named arguments:
    +
    +=over 4
    +
    +=item * Show
    +
    +How many elements to show, how precise it should be. Default is 1,
    +most vague variant.
    +
    +=item * Short
    +
    +Turn on short notation with one character units, for example
    +"3M 2d 1m 10s".
    +
    +=back
    +
     =cut
     
    +# loc("[_1]s")
    +# loc("[_1]m")
    +# loc("[_1]h")
    +# loc("[_1]d")
    +# loc("[_1]W")
    +# loc("[_1]M")
    +# loc("[_1]Y")
    +# loc("[quant,_1,second]")
    +# loc("[quant,_1,minute]")
    +# loc("[quant,_1,hour]")
    +# loc("[quant,_1,day]")
    +# loc("[quant,_1,week]")
    +# loc("[quant,_1,month]")
    +# loc("[quant,_1,year]")
    +
     sub DurationAsString {
         my $self     = shift;
         my $duration = int shift;
    +    my %args = ( Show => 1, Short => 0, @_ );
    +
    +    unless ( $duration ) {
    +        return $args{Short}? $self->loc("0s") : $self->loc("0 seconds");
    +    }
     
    -    my ( $negative, $s, $time_unit );
    +    my $negative;
         $negative = 1 if $duration < 0;
         $duration = abs $duration;
     
    -    if ( $duration < $MINUTE ) {
    -        $s         = $duration;
    -        $time_unit = $self->loc("sec");
    -    }
    -    elsif ( $duration < ( 2 * $HOUR ) ) {
    -        $s         = int( $duration / $MINUTE + 0.5 );
    -        $time_unit = $self->loc("min");
    -    }
    -    elsif ( $duration < ( 2 * $DAY ) ) {
    -        $s         = int( $duration / $HOUR + 0.5 );
    -        $time_unit = $self->loc("hours");
    -    }
    -    elsif ( $duration < ( 2 * $WEEK ) ) {
    -        $s         = int( $duration / $DAY + 0.5 );
    -        $time_unit = $self->loc("days");
    -    }
    -    elsif ( $duration < ( 2 * $MONTH ) ) {
    -        $s         = int( $duration / $WEEK + 0.5 );
    -        $time_unit = $self->loc("weeks");
    -    }
    -    elsif ( $duration < $YEAR ) {
    -        $s         = int( $duration / $MONTH + 0.5 );
    -        $time_unit = $self->loc("months");
    -    }
    -    else {
    -        $s         = int( $duration / $YEAR + 0.5 );
    -        $time_unit = $self->loc("years");
    +    my %units = (
    +        s => 1,
    +        m => $MINUTE,
    +        h => $HOUR,
    +        d => $DAY,
    +        W => $WEEK,
    +        M => $MONTH,
    +        Y => $YEAR,
    +    );
    +    my %long_units = (
    +        s => 'second',
    +        m => 'minute',
    +        h => 'hour',
    +        d => 'day',
    +        W => 'week',
    +        M => 'month',
    +        Y => 'year',
    +    );
    +
    +    my @res;
    +
    +    my $coef = 2;
    +    my $i = 0;
    +    while ( $duration > 0 && ++$i <= $args{'Show'} ) {
    +
    +        my $unit;
    +        if ( $duration < $MINUTE ) {
    +            $unit = 's';
    +        }
    +        elsif ( $duration < ( $coef * $HOUR ) ) {
    +            $unit = 'm';
    +        }
    +        elsif ( $duration < ( $coef * $DAY ) ) {
    +            $unit = 'h';
    +        }
    +        elsif ( $duration < ( $coef * $WEEK ) ) {
    +            $unit = 'd';
    +        }
    +        elsif ( $duration < ( $coef * $MONTH ) ) {
    +            $unit = 'W';
    +        }
    +        elsif ( $duration < $YEAR ) {
    +            $unit = 'M';
    +        }
    +        else {
    +            $unit = 'Y';
    +        }
    +        my $value = int( $duration / $units{$unit}  + ($i < $args{'Show'}? 0 : 0.5) );
    +        $duration -= int( $value * $units{$unit} );
    +
    +        if ( $args{'Short'} ) {
    +            push @res, $self->loc("[_1]$unit", $value);
    +        } else {
    +            push @res, $self->loc("[quant,_1,$long_units{$unit}]", $value);
    +        }
    +
    +        $coef = 1;
         }
     
         if ( $negative ) {
    -        return $self->loc( "[_1] [_2] ago", $s, $time_unit );
    +        return $self->loc( "[_1] ago", join ' ', @res );
         }
         else {
    -        return $self->loc( "[_1] [_2]", $s, $time_unit );
    +        return join ' ', @res;
         }
     }
     
    @@ -620,7 +704,7 @@ sub DefaultFormat
                                 $self->Localtime($args{'Timezone'});
         $wday = $self->GetWeekday($wday);
         $mon = $self->GetMonth($mon);
    -    ($mday, $hour, $min, $sec) = map { sprintf "%02d", $_ } ($mday, $hour, $min, $sec);
    +    $_ = sprintf "%02d", $_ foreach $mday, $hour, $min, $sec;
     
         if( $args{'Date'} && !$args{'Time'} ) {
             return $self->loc('[_1] [_2] [_3] [_4]',
    @@ -667,8 +751,8 @@ sub LocaleObj {
     Returns date and time as string, with user localization.
     
     Supports arguments: C and C which may contains date and
    -time format as specified in L (default to full_date_format and
    -medium_time_format), C and C which may be set to 0 if
    +time format as specified in L (default to C and
    +C), C and C which may be set to 0 if
     you want full Day/Month names instead of abbreviated ones.
     
     =cut
    @@ -888,7 +972,9 @@ sub RFC2616 {
     
     =head4 iCal
     
    -Returns the object's date and time in iCalendar format,
    +Returns the object's date and time in iCalendar format.
    +If only date requested then users timezone is used, otherwise
    +it's UTC.
     
     Supports arguments: C and C

    QcL>Su98H4u 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 Z&ViXV@!A!LC*d#dF^5`0%1m4${{!}?Eye%< literal 0 HcmV?d00001 diff --git a/docs/incremental-export/README b/docs/incremental-export/README new file mode 100644 index 0000000..86503c4 --- /dev/null +++ b/docs/incremental-export/README @@ -0,0 +1,29 @@ +To perform an incremental upgrade of your RT instance you will need to +perform the following steps on your production server. + + Copy the rt-validator command from the current version of RT and run + it against your production RT instance. The output from --help will + show you how to run it in check and then in fix mode. + + Turn off all access to RT (this usually involves stopping your web and mail servers). + Take a snapshot of the RT database. + Install Record_Local.pm into $RTHOME/lib/RT/. + Create the IncrementalRecords table using the schema file for your database. + Add Set($IncrementalExport, 1); to your RT_SiteConfig.pm + Bring your webserver back up. + Confirm that changes in RT (such as a ticket status change or reply) + result in records being added to IncrementalRecords. + + +Using the backup, upgrade on your new server to the latest release of RT. +Once this upgrade is complete, you should avoid making changes to it +until you import the incremental changes from production. + +For additional information on the following steps, please review +rt-importer --help and rt-serializer --help + +During your final cutover, you will bring down the production web and mail servers for the final time. +Run $RTHOME/sbin/rt-serializer --incremental +This will create a directory named $Organization:date +Once this has completed, you will copy this directory to the new server. +On the new server, run $RTHOME/sbin/rt-importer $Organization:date diff --git a/docs/incremental-export/Record_Local.pm b/docs/incremental-export/Record_Local.pm new file mode 100644 index 0000000..4118e9c --- /dev/null +++ b/docs/incremental-export/Record_Local.pm @@ -0,0 +1,74 @@ +use strict; +use warnings; + +package RT::Record; +no warnings 'redefine'; + +my ($update, $create, $delete); +BEGIN { + $update = RT::Record->can("__Set"); + $create = RT::Record->can("Create"); + $delete = RT::Record->can("Delete"); +} + + +sub __Set { + my $self = shift; + my %args = @_; + my $ret = $update->($self, @_); + + my $class = ref($self); + return ( $ret->return_value ) unless $RT::IncrementalExport; + return ( $ret->return_value ) unless $ret; + return ( $ret->return_value ) if $class eq "RT::CachedGroupMember"; + + $self->_Handle->SimpleQuery( <__Value("Id") ); +INSERT INTO IncrementalRecords (ObjectType, ObjectId, UpdateType, AlteredAt) + VALUES (?, ?, 1, NOW()) + ON DUPLICATE KEY UPDATE + AlteredAt = AlteredAt +EOQ + + return ( $ret->return_value ); +} + + +sub Create { + my $self = shift; + my ($id, $msg) = $create->($self, @_); + + if ($RT::IncrementalExport and $id and ref($self) ne "RT::CachedGroupMember") { + $self->_Handle->SimpleQuery( <($self,@_); + + if ($RT::IncrementalExport and $ok and ref($self) ne "RT::CachedGroupMember") { + $self->_Handle->SimpleQuery( <__Value("Id") ); +INSERT INTO IncrementalRecords (ObjectType, ObjectId, UpdateType, AlteredAt) + VALUES (?, ?, 3, NOW()) + ON DUPLICATE KEY UPDATE + UpdateType = UpdateType + 2 +EOQ + } + + if (wantarray) { + return ( $ok, $msg ); + } else { + return ( $ok ); + } +} + +1; diff --git a/docs/incremental-export/schema.mysql b/docs/incremental-export/schema.mysql new file mode 100644 index 0000000..0af8938 --- /dev/null +++ b/docs/incremental-export/schema.mysql @@ -0,0 +1,10 @@ +CREATE TABLE IncrementalRecords ( + id INTEGER NOT NULL AUTO_INCREMENT, + ObjectType VARCHAR(50) NOT NULL, + ObjectId INTEGER NOT NULL, + UpdateType TINYINT NOT NULL, + AlteredAt TIMESTAMP NOT NULL, + PRIMARY KEY(ObjectType, ObjectId), + UNIQUE KEY(id), + KEY(UpdateType) +); diff --git a/docs/initialdata.pod b/docs/initialdata.pod index c649b62..0ed73f4 100644 --- a/docs/initialdata.pod +++ b/docs/initialdata.pod @@ -78,14 +78,13 @@ For a full list of fields, read the documentation for L. =head2 C<@Groups> push @Groups, { - Domain => 'UserDefined', Name => 'Example Employees', Description => 'All of the employees of my company', }; Creates a new L for each hashref. In almost all cases you'll want to follow the example above to create a group just as if you had done it from -the admin interface. B omit the C<< Domain => 'UserDefined' >> line. +the admin interface. Additionally, the C field is specially handled to make it easier to add the new group to other groups. C may be a single value or an @@ -145,7 +144,6 @@ L for the fields you can use. =head2 C<@CustomFields> push @CustomFields, { - Queue => 0, Name => 'Favorite color', Type => 'FreeformSingle', LookupType => 'RT::Queue-RT::Ticket', @@ -164,10 +162,15 @@ The name of this CF as displayed in RT. A short summary of what this CF is for. -=item C +=item C -May be a Name or ID. The single queue or array ref of queues to apply this CF -to. This does not apply when C does not start with C. +May be a single value, or an array reference of such; each should be +either an ID or Name. If omitted, the CF is applied globally. This +should not be used for User or Group custom fields. + +This argument may also be passed via C, for backwards +compatibility, which also defaults the C to +C. =item C @@ -215,6 +218,7 @@ is for Tickets, Transactions, Users, Groups, or Queues. Possible values: RT::User # Users RT::Group # Groups RT::Queue # Queues + RT::Class-RT::Article # Articles Ticket CFs are the most common, meaning C is the most common C. @@ -247,7 +251,6 @@ field. This only makes sense for "Select" CFs. An example: my $i = 1; push @CustomFields, { - Queue => 0, # Globally applied LookupType => 'RT::Queue-RT::Ticket', # for Tickets Name => 'Type of food', Type => 'SelectSingle', # SelectSingle is the same as: Type => 'Select', MaxValues => 1 @@ -301,6 +304,7 @@ granted. This is B than the user/group/role receiving the right. =item Granted on a custom field by name (or ID), potentially a global or queue CF => 'Name', + LookupType => 'RT::User', # optional, in case you need to disambiguate =item Granted on a queue @@ -311,6 +315,11 @@ granted. This is B than the user/group/role receiving the right. CF => 'Name', Queue => 'Name', +=item Granted on some other object (article Classes, etc) + + ObjectType => 'RT::Class', + ObjectId => 'Name', + =item Granted globally Specifying none of the above will get you a global right. diff --git a/docs/reminders.pod b/docs/reminders.pod new file mode 100644 index 0000000..c2bc67d --- /dev/null +++ b/docs/reminders.pod @@ -0,0 +1,67 @@ +=head1 Reminders + +Reminders can be attached to a ticket to notify you take some action +on the ticket. Although there are fields like "Due" on tickets, some +tickets have dependencies or sub-tasks that need to be completed before you +can do the ticket. For a "Deploy New Certificate" ticket, for example, you may +need to remind yourself to order the new cert first. + +Reminders are sort of mini-tickets and in fact they are implemented as +tickets themselves. + +Each Reminder has: + +=over + +=item * Subject + +=item * Owner + +=item * Due date + +=item * Status (new, open, resolved, ...) + +=back + +=head1 Creating a Reminder + +Reminders are attached to tickets, so you create them in the Reminders section of +the ticket display. Once you give it an Owner and a Due date, the Reminder will +appear on the Owner's "At-a-glance" page by default. + +If you don't see reminders, it may be turned off. Display of reminders can be +disabled with the C<$EnableReminders> flag in C. By default, +reminders are turned on. + +=head1 Email Reminders + +While seeing reminders in the web display is handy, you may also want to send out +email based on reminders that are due or are soon to be due. You can use the +C utility to schedule a job to send these emails for you. + +To schedule the reminders, add a line like the following to your RT crontab: + + 0 6 * * * root /opt/rt4/bin/rt-crontool \ + --search RT::Search::FromSQL \ + --search-arg 'Type = "reminder" and (Status = "open" or Status = "new")' \ + --condition RT::Condition::BeforeDue \ + --condition-arg 2d \ + --action RT::Action::SendEmail \ + --action-arg Owner,AlwaysNotifyActor \ + --transaction first \ + --template 'Reminder' + +If you have modified the status values for reminders such that you have more +active statuses than "open" and "new" you should add them as part of your +"FromSQL" query. You typically won't want to send out email on "resolved" +reminders, but you could add that to the query as well. + +The argument to C is an amount of time in the form +"1d2h3m4s" for 1 day and 2 hours and 3 minutes and 4 seconds. As shown in the +example, single values can also be passed. The run frequency in your crontab +should be consistent with the time period you set to avoid missing reminders. + +The template value refers to a Template in your RT system. You can use the +default Reminder template or create your own in Admin > Global > Templates > +Create. You can look at the default template for examples of the values +you can use to populate the email. diff --git a/docs/reporting/feeds.pod b/docs/reporting/feeds.pod new file mode 100644 index 0000000..81260e9 --- /dev/null +++ b/docs/reporting/feeds.pod @@ -0,0 +1,145 @@ +=encoding utf-8 + +=head1 Feeds + +RT offers several feeds that provide RT data in formats suitable for +integrating with external applications. This document describes the +available feeds and some ways they can be used. + +The feeds are based on a ticket search you create using the RT Query +Builder, available at Tickets > New Search or, starting in RT 4.2, +Search > Tickets > New Search. After you create your search, the +search results page has a Feeds menu in the upper right-hand corner. +That menu contains the feeds described below. + +=head2 Spreadsheet + +If you click the Spreadsheet link, a tab-separated values (.tsv) file is +downloaded containing the results from the search you performed in the +browser. You can then import the file into a spreadsheet application like +the OpenOffice spreadsheet or Microsoft Excel. + +Pulling ticket data into a spreadsheet can be handy if you want to +manipulate a subset of your ticket data outside of RT. Depending on +what you're doing with the data, once you have it in the form +you need (sorted, summed, etc.) you can sometimes reproduce the report +back in RT. This has the advatage of making it dynamic and you can share +the resulting report with other users as a shared search or dashboard. + +Depending on your browser brand and settings, the downloaded spreadsheet +may be given a .xls extension, a .tsv extension, or no extension at all. +If it gets a .xls extension, the spreadsheet file will likely already be +associated with a spreadsheet application. In this case, you can +double-click on the file to open it. You may see a warning about the file +format, since it is a tsv file and not a .xls file, and then the application +will either convert the file automatically or open a dialog to guide you. + +If your system doesn't automatically associate the tsv file, the following +sections describe how to manually import a tsv file in some common +applications. + +=head3 Importing into Microsoft Excel + +If the file is given a .tsv extension or no extension, here's how to open +it in Excel 2010: + +=over + +=item 1. + +Select File > Open, locate the file on your system and click Open. + +=item 2. + +In the Text Import Wizard, select Delimited, import starting at row 1, +and leave the default File origin. Click Next. + +=item 3. + +In the Delimiters section, select Tab if it isn't selected by default. +Leave the other settings with default values. Click Next. + +=item 4. + +The last dialog lets you define the column formats for the imported columns. +You can also exclude some columns from the import. 'General' will try to +guess for you. Click Finish. + +=back + +=head3 Importing into OpenOffice + +If OpenOffice doesn't automatically open the Text Import dialog when +you try to open the file, here's how to open a tsv manually: + +=over + +=item 1. + +Open a new Spreadsheet document + +=item 2. + +Select Insert > Sheet From File... + +=item 3. + +In the file selection dialog, find the file downloaded from RT. By default, +the name is Results. + +=item 4. + +In the Separator options, click the checkbox Separated by: Tab. Uncheck any +other options that might be checked. Click OK. + +=item 5. + +On the Insert Sheet dialog, select where you want to put the new sheet and +click OK. + +=back + +=head2 RSS + +You can use the RSS feed to subscribe using your RSS feed reader of +choice. The feed entries provide the ticket subject, a link to the +ticket, and the content of the first transaction on the ticket. + +=head2 iCal + +The iCal link uses your search to provide a feed suitable +for subscribing to from a calendaring application like Mozilla Lightning +(the Thunderbird calendar), Google Calendar, Microsoft Outlook, or Apple +iCal. You can copy the link and use your calendar application's subscribe +feature to pull in and display ticket dates. + +The feed provides Starts and Due dates from the selected tickets. In +RT 4.0, the calendar events are day-long events, which can be handled +differently in different calendar applications. The events also contain +a URL linking to the ticket and some calendars will display this link +with the event. + +RT 4.2 adds the L option, which will include times +in the iCal feed so you can have calendar events at a specific time. In +addition, you can provide C as an additional query parameter +to have tickets generate a single event using the Start and Due dates/times +as the start and end values for the event. You can put the parameter on the +end of the iCal feed URL, for example: + + https://myrt.example.com/NoAuth/iCal/user/...?SingleEvent=1 + +This is useful if you have tickets with shorter durations, like scheduled +maintenance for example. + +=head2 Secret Tokens + +All of your RSS and iCal feeds embed a secret token that is specific to your RT +user account. You should never share your feed urls with other people, +otherwise they can see tickets as your user. If one of your feed urls is +accidentally shared, you can reset your token to disable all old feed urls. To +do so, start by logging into RT and go to Logged in as … > Settings > About me. +In the lower right-hand corner, there is a link "I want to reset my secret +token" and it does just that. + +Note that this will disable all of your existing feeds. After updating +the token, you'll need to update all of your feed urls. diff --git a/docs/rt_perl.pod b/docs/rt_perl.pod new file mode 100644 index 0000000..513bb59 --- /dev/null +++ b/docs/rt_perl.pod @@ -0,0 +1,163 @@ +=head1 Perl for RT + +RT runs on Perl and there are many different approaches to installing +and maintaining your Perl installation. This document reviews some of the +options and pros and cons of different approaches. + +Perl has been around for a long time, so many different versions are +installed on systems everywhere. We try to maintain a reasonable +timeframe for backward compatibility, but beyond a certain age, running +old versions of Perl is no longer safe or even possible with modern +applications. We currently require at least version 5.10.1 which is +old enough to be default on OSes from many years ago, but sufficiently +new to support RT and the modules RT depends on. + +=head1 Default System Perls + +All Linux and Unix-type variants come with a version of Perl installed +and many provide Perl and many CPAN modules as packages for easier +maintenance and management. You can run RT on the vendor Perl on your +system as long as it meets the minimum version requirement. + +When you run C as part of your RT installation, +you'll likely find that the RT will require you to upgrade some of the +dependent modules to newer versions than those provided in the +vendor packages. If you have any IT policy requirements to only use +vendor packaged versions of software, this might be an issue. If +so, you can consider installing an RT-only version of Perl. +See L<"Stand-alone Perl">. + +Occasionally vendors introduce their own changes to their packaged version +of Perl or modules and these might create issues when running RT. +Also, the system Perl is also often used by other utilities on the system +and modifying the default Perl too heavily can introduce issues for these +other applications which might rely on an older version of a module, for +example. Consider these factors before modifying your system Perl. + +Many packaging systems restore the system to the official packaged +version of software when updates are applied. Since a Perl update is +likely to have many or all packaged Perl modules as dependencies, this +means an update to the vendor Perl will restore all of the modules you +upgraded to their previous version. Therefore, if you decide to use +the vendor Perl on your system, you need to note somewhere that you'll +need to upgrade RT's dependencies any time the system Perl packages are +updated. The L tool provided in RT's sbin +directory can help with this. + +=head1 Stand-alone Perl + +To avoid having modules unexpectedly downgraded as described above, +we typically recommend installing a separate Perl to run RT. In doing so +you take on the extra responsibility to patch that Perl if necessary, +but you can plan this work as necessary rather than being surprised if +RT has issues after a security package update is applied. + +Having a Perl version installed specifically for RT gives you the flexibility +to upgrade or install a new module if needed to add a new extension or address +a bug. You can then test just RT and not worry about possible side-effects +on your system. + +You can install this Perl in an alternate location like C, or +to make it clear it's for RT, even C. To make future +upgrades easier, install in a version-specific directory like +C, then symlink C to that directory. This +makes it easy to switch to a newer version of Perl later by installing +and just moving the symlink. + +If you install a stand-alone Perl, update your shell to put the path +of the new C executable before the system Perl. You may want +to set this in your shell profile for the user account you use to manage +RT so you don't accidentally run commands or install modules in the +wrong Perl installation. + +The following sections describe several approaches to installing a +stand-alone Perl. + +=head2 Install from Source + +You can download Perl directly from L and follow +the installation instructions. Typically this involves running C, +then C. For most installations, +this C command should be sufficient: + + ./Configure -d -Dprefix=/opt/perl + +You can set the prefix to wherever you want Perl installed. Read the +documentation provided with the distribution for more options. + +=head2 Perlbrew + +L is a tool that makes it easy to manage multiple +Perl installations. Once installed, the C command provides options to +build various versions of Perl, switch between version, update installed +versions, and more. + +By default, C installs all of its Perls in your C<$HOME> directory. If +you want to install in an alternate location, you can set the C +environment variable: + + export PERLBREW_ROOT=/opt/perl5 + curl -kL http://install.perlbrew.pl | bash + +Since C has a C command to use different installed Perl +versions, you don't need to manually manage symlinks as described above. + +=head2 mod_perl + +If you plan to run RT with L on a 64-bit system, you +may need to run Configure with these options: + + ./Configure -d -Dprefix=/opt/perl -A ccflags=-fPIC + +Then make sure you use your stand-alone perl when building and installing +mod_perl. You find more details on these flags in the +L. + +=head1 CPAN Modules + +RT requires modules from the +L to run. +Below are a few of the tools available to help download and install +these modules from CPAN. These tools can work with RT's L +tool and the C and C part of the installation +process to get these modules installed. + +=head2 CPAN Shell + +The traditional tool for managing Perl modules is the CPAN shell, +accessed with the C command installed as part of Perl. To set up +C on an initial install, run the C command and follow the +prompts to set the initial configuration. You can set each option or allow +it to automatically set some sensible defaults. + +The main options you'll need to set are the list of download servers and +options for C. For download servers, you'll typically want to +select some mirrors geographically close to you. If you typically run installs +using C, set C to C<'sudo make'> and +C to C<'sudo ./Build'>. Then install +the CPAN bundle: + + cpan>install Bundle::CPAN + +This installs some additional modules to add features to C. + +Once you finish this initialization, RT's C should be able +to handle the rest. Any time you need to install a new module or upgrade +a module, you can just type C and manage it from the cpan shell. + +=head2 cpanminus + +C, or C, is a utility built to make it as easy as possible +to install modules from CPAN. You can install the L module +itself from CPAN, or have it install itself: + + curl -L http://cpanmin.us | perl - --sudo App::cpanminus + +Once installed, set the C environment variable to +have RT use C to install modules: + + export RT_FIX_DEPS_CMD=/opt/perl/bin/cpanm + +Then run C and let RT install all of its dependencies. + +=cut diff --git a/docs/security.pod b/docs/security.pod index 620f868..5bf4291 100644 --- a/docs/security.pod +++ b/docs/security.pod @@ -32,11 +32,7 @@ months before being added to the public RT repository. Protect your RT installation by making it only accessible via SSL. This will protect against users' passwords being sniffed as they go over the -wire, as well as helping prevent phishing attacks. If you use SSL, you -will need to install some additional Perl libraries so that C -can connect. You can use the C<--enable-ssl-mailgate> command to -configure to automate the installation of these dependencies. This is -documented further in step 10 of the README. +wire, as well as helping prevent phishing attacks. You should use a certificate signed by a reputable authority, or at very least a certificate signed by a consistent local CA, which you configure diff --git a/docs/web_deployment.pod b/docs/web_deployment.pod index 5a9bd93..a5684e0 100644 --- a/docs/web_deployment.pod +++ b/docs/web_deployment.pod @@ -52,7 +52,6 @@ spontaneously logged in as other users in the system. AddDefaultCharset UTF-8 - Alias /NoAuth/images/ /opt/rt4/share/html/NoAuth/images/ ScriptAlias / /opt/rt4/sbin/rt-server.fcgi/ DocumentRoot "/opt/rt4/share/html" @@ -89,7 +88,6 @@ to return to the old default. AddDefaultCharset UTF-8 - Alias /NoAuth/images/ /opt/rt4/share/html/NoAuth/images/ ScriptAlias / /opt/rt4/sbin/rt-server.fcgi/ DocumentRoot "/opt/rt4/share/html" @@ -187,10 +185,6 @@ With the nginx configuration: fastcgi_param SERVER_NAME $server_name; fastcgi_pass 127.0.0.1:9000; } - - location /NoAuth/images { - root /opt/rt4/share/html; - } } @@ -198,21 +192,16 @@ With the nginx configuration: server.modules += ( "mod_fastcgi" ) $HTTP["host"] =~ "^rt.example.com" { - alias.url = ( - "/NoAuth/images/" => "/opt/rt4/share/html/NoAuth/images/", - ) - $HTTP["url"] !~ "^/NoAuth/images/" { - fastcgi.server = ( - "/" => ( - "rt" => ( - "port" => "9000", - "bin-path" => "/opt/rt4/sbin/rt-server.fcgi", - "check-local" => "disable", - "fix-root-scriptname" => "enable", - ) + fastcgi.server = ( + "/" => ( + "rt" => ( + "port" => "9000", + "bin-path" => "/opt/rt4/sbin/rt-server.fcgi", + "check-local" => "disable", + "fix-root-scriptname" => "enable", ) ) - } + ) } @@ -226,14 +215,13 @@ F: Set($WebPath, "/rt"); Then you need to update your Apache configuration to match. Prefix any RT -related C, C and C directives with C. You +related C and C directives with C. You should also make sure C is B set to C, otherwise RT's source will be served from C. For example: if you're using the sample FastCGI config above, you might change the relevant directives to: - Alias /rt/NoAuth/images/ /opt/rt4/share/html/NoAuth/images/ ScriptAlias /rt /opt/rt4/sbin/rt-server.fcgi/ # Set DocumentRoot as appropriate for the other content you want to serve diff --git a/etc/RT_Config.pm b/etc/RT_Config.pm index fdd8874..26ca3e9 100644 --- a/etc/RT_Config.pm +++ b/etc/RT_Config.pm @@ -1,7 +1,7 @@ # # RT was configured with: # -# $ ./configure --prefix=/www/var/rt/ --with-web-user=httpd --with-web-group=httpd --with-rt-group=uio-rt --with-apachectl=/www/sbin/apachectl --with-db-type=Pg --with-db-dba=postgres --disable-gpg +# $ ./configure --prefix=/www/var/rt/ --with-web-user=httpd --with-web-group=httpd --with-rt-group=uio-rt --with-db-type=Pg --with-db-dba=postgres --disable-gpg # package RT; @@ -127,6 +127,26 @@ C Set(@Plugins, ()); +=item C<@StaticRoots> + +Set C<@StaticRoots> to serve extra paths with a static handler. The +contents of each hashref should be the the same arguments as +L takes. These paths will be checked before +any plugin or core static paths. + +Example: + + Set( @StaticRoots, + { + path => qr{^/static/}, + root => '/local/path/to/static/parent', + }, + ); + +=cut + +Set( @StaticRoots, () ); + =back @@ -203,6 +223,15 @@ database. Set($DatabaseRequireSSL, undef); +=item <$DatabaseAdmin> + +The name of the database administrator to connect to the database as +during upgrades. + +=cut + +Set($DatabaseAdmin, "postgres"); + =back @@ -221,7 +250,7 @@ message. =over 4 -=item C<$LogToSyslog>, C<$LogToScreen> +=item C<$LogToSyslog>, C<$LogToSTDERR> The minimum level error that will be logged to the specific device. From lowest to highest priority, the levels are: @@ -239,7 +268,7 @@ in your web server's error logs). =cut Set($LogToSyslog, "info"); -Set($LogToScreen, "info"); +Set($LogToSTDERR, "info"); =item C<$LogToFile>, C<$LogDir>, C<$LogToFileNamed> @@ -274,11 +303,9 @@ 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.) +Additional options to pass to L; the most +interesting flags include C, C, and possibly C. +See the L documentation for more information. =cut @@ -299,14 +326,7 @@ you're not dealing with historical C<$rtname> values, you'll likely never have to change this configuration. Be B 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 +subject token matching. The setting below would make RT behave exactly as it does without the setting enabled. @@ -318,8 +338,10 @@ setting enabled. =item C<$OwnerEmail> 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 I be an address that's managed by your RT instance. +errors generated by the mail gateway to this address; it will also be +displayed as the contact person on the RT's login page. Because RT +sends errors to this address, it should I be an address that's +managed by your RT instance, to avoid mail loops. =cut @@ -387,9 +409,10 @@ already, you can generate a naive first pass regexp by using: perl etc/upgrade/generate-rtaddressregexp -If left blank, RT will generate a regexp for you, based on your -comment and correspond address settings on your queues; this comes at -a small cost in start-up speed. +If left blank, RT will compare each address to your configured +C<$CorrespondAddress> and C<$CommentAddress> before searching for a +Queue configured with a matching "Reply Address" or "Comment Address" +on the Queue Admin page. =cut @@ -423,13 +446,16 @@ Set($CanonicalizeOnCreate, 0); =item C<$ValidateUserEmailAddresses> -If C<$ValidateUserEmailAddresses> is 1, RT will refuse to create +By default C<$ValidateUserEmailAddresses> is 1, and 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 addresses. +Set this to 0 to skip any email address validation. Doing so may open up +vulnerabilities. + =cut -Set($ValidateUserEmailAddresses, undef); +Set($ValidateUserEmailAddresses, 1); =item C<@MailPlugins> @@ -489,11 +515,8 @@ Set( $CheckMoreMSMailHeaders, 0); 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'. - -Note that you should remove the '-t' from C<$SendmailArguments> if you -use 'sendmail' rather than 'sendmailpipe' +doesn't work well for you, try 'sendmail'. 'qmail' is also a supported +value. For testing purposes, or to simply disable sending mail out into the world, you can set C<$MailCommand> to 'testfile' which writes all mail @@ -735,16 +758,14 @@ These options only take effect if C<$MailCommand> is 'sendmail' or =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 work-a-likes. +These options are good for most sendmail wrappers and work-a-likes. These arguments are good for sendmail brand sendmail 8 and newer: -C +C =cut -Set($SendmailArguments, "-oi -t"); +Set($SendmailArguments, "-oi"); =item C<$SendmailBounceArguments> @@ -766,39 +787,6 @@ your sendmail binary in C<$SendmailPath>. Set($SendmailPath, "/usr/sbin/sendmail"); -=back - -=head2 SMTP configuration - -These options only take effect if C<$MailCommand> is 'smtp' - -=over 4 - -=item C<$SMTPServer> - -C<$SMTPServer> should be set to the hostname of the SMTP server to use - -=cut - -Set($SMTPServer, undef); - -=item C<$SMTPFrom> - -C<$SMTPFrom> should be set to the 'From' address to use, if not the -email's 'From' - -=cut - -Set($SMTPFrom, undef); - -=item C<$SMTPDebug> - -C<$SMTPDebug> should be set to 1 to debug SMTP mail sending - -=cut - -Set($SMTPDebug, 0); - =back =head2 Other mailers @@ -808,7 +796,7 @@ Set($SMTPDebug, 0); =item C<@MailParams> C<@MailParams> defines a list of options passed to $MailCommand if it -is not 'sendmailpipe', 'sendmail', or 'smtp' +is not 'sendmailpipe' or 'sendmail'; =cut @@ -826,11 +814,12 @@ Set(@MailParams, ()); This determines the default stylesheet the RT web interface will use. RT ships with several themes by default: - web2 The default layout for RT 3.8 + rudder The default theme for RT 4.2 aileron The default layout for RT 4.0 + web2 The default layout for RT 3.8 ballard Theme which doesn't rely on JavaScript for menuing -This value actually specifies a directory in F +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 @@ -838,7 +827,7 @@ be overridden by users in their preferences. =cut -Set($WebDefaultStylesheet, "aileron"); +Set($WebDefaultStylesheet, "rudder"); =item C<$DefaultQueue> @@ -877,6 +866,48 @@ custom field values from external sources at runtime. Set(@CustomFieldValuesSources, ()); +=item C<%CustomFieldGroupings> + +This option affects the display of ticket and user custom fields in the +web interface. It does not address the sorting of custom fields within +the groupings; which is controlled by the Ticket Custom Fields tab in +Queue Configuration in the Admin UI. + +A nested datastructure defines how to group together custom fields +under a mix of built-in and arbitrary headings ("groupings"). + +Set C<%CustomFieldGroupings> to a nested structure similar to the following: + + Set(%CustomFieldGroupings, + 'RT::Ticket' => [ + 'Grouping Name' => ['CF Name', 'Another CF'], + 'Another Grouping' => ['Some CF'], + 'Dates' => ['Shipped date'], + ], + 'RT::User' => [ + 'Phones' => ['Fax number'], + ], + ); + +The first level keys are record types for which CFs may be used, and the +values are either hashrefs or arrayrefs -- if arrayrefs, then the +ordering is preserved during display, otherwise groupings are displayed +alphabetically. The second level keys are the grouping names and the +values are array refs containing a list of CF names. + +There are several special built-in groupings which RT displays in +specific places (usually the collapsible box of the same title). The +ordering of these standard groupings cannot be modified. You may also +only append Custom Fields to the list in these boxes, not reorder or +remove core fields. + +For C, these groupings are: C, C, C, C + +For C: C, C, C, C + +Extensions may also add their own built-in groupings, refer to the individual +extension documentation for those. + =item C<$CanonicalizeRedirectURLs> Set C<$CanonicalizeRedirectURLs> to 1 to use C<$WebURL> when @@ -893,32 +924,25 @@ enable this option. Set($CanonicalizeRedirectURLs, 0); -=item C<@JSFiles> +=item C<$CanonicalizeURLsInFeeds> -A list of JavaScript files to be included in head. Removing any of -the default entries is not suggested. +Set C<$CanonicalizeURLsInFeeds> to 1 to use C<$WebURL> in feeds +rather than the one we get from request. -If you're a plugin author, refer to RT->AddJavaScript. +If you use RT behind a reverse proxy, you almost certainly want to +enable this option. =cut -Set(@JSFiles, qw/ - jquery-1.4.2.min.js - jquery_noconflict.js - jquery-ui-1.8.4.custom.min.js - jquery-ui-timepicker-addon.js - jquery-ui-patch-datepicker.js - jquery.cookie.js - titlebox-state.js - util.js - userautocomplete.js - jquery.event.hover-1.0.js - superfish.js - supersubs.js - jquery.supposition.js - history-folding.js - late.js -/); +Set($CanonicalizeURLsInFeeds, 0); + +=item C<@JSFiles> + +A list of additional JavaScript files to be included in head. + +=cut + +Set(@JSFiles, qw//); =item C<$JSMinPath> @@ -945,14 +969,73 @@ Set(@CSSFiles, qw//); =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. +This determines how user info is displayed. 'concise' will show the +first of RealName, Name or EmailAddress that has a value. 'verbose' will +show EmailAddress, and the first of RealName or Name which is defined. + +=cut + +Set($UsernameFormat, "role"); + +=item C<$UserSearchResultFormat> + +This controls the display of lists of users returned from the User +Summary Search. The display of users in the Admin interface is +controlled by C<%AdminSearchResultFormat>. + +=cut + +Set($UserSearchResultFormat, + q{ '__id__/TITLE:#'} + .q{,'__Name__/TITLE:Name'} + .q{,__RealName__, __EmailAddress__} +); + +=item C<@UserSummaryPortlets> + +A list of portlets to be displayed on the User Summary page. +By default, we show all of the available portlets. +Extensions may provide their own portlets for this page. + +=cut + +Set(@UserSummaryPortlets, (qw/ExtraInfo CreateTicket ActiveTickets InactiveTickets/)); + +=item C<$UserSummaryExtraInfo> + +This controls what information is displayed on the User Summary +portal. By default the user's Real Name, Email Address and Username +are displayed. You can remove these or add more as needed. This +expects a Format string of user attributes. Please note that not all +the attributes are supported in this display because we're not +building a table. =cut -Set($UsernameFormat, "concise"); +Set($UserSummaryExtraInfo, "RealName, EmailAddress, Name"); + +=item C<$UserSummaryTicketListFormat> + +Control the appearance of the Active and Inactive ticket lists in the +User Summary. + +=cut + +Set($UserSummaryTicketListFormat, q{ + '__id__/TITLE:#', + '__Subject__/TITLE:Subject', + Status, + QueueName, + Owner, + Priority, + '__NEWLINE__', + '', + '__Requestors__', + '__CreatedRelative__', + '__ToldRelative__', + '__LastUpdatedRelative__', + '__TimeLeft__' +}); =item C<$WebBaseURL>, C<$WebURL> @@ -986,7 +1069,7 @@ Define the directory name to be used for images in RT web documents. =cut -Set($WebImagesURL, RT->Config->Get('WebPath') . "/NoAuth/images/"); +Set($WebImagesURL, RT->Config->Get('WebPath') . "/static/images/"); =item C<$LogoURL> @@ -1100,6 +1183,19 @@ At this time, this feature only applies to MySQL and PostgreSQL. Set($ChartsTimezonesInDB, 0); +=item C<@ChartColors> + +An array of 6-digit hexadecimal RGB color values used for chart series. By +default there are 12 distinct colors. + +=cut + +Set(@ChartColors, qw( + 66cc66 ff6666 ffcc66 663399 + 3333cc 339933 993333 996633 + 33cc33 cc3333 cc9933 6633cc +)); + =back @@ -1136,7 +1232,7 @@ user's customized homepage ("RT at a glance"). Set( $HomepageComponents, [ - qw(QuickCreate Quicksearch MyAdminQueues MySupportQueues MyReminders RefreshHomepage Dashboards SavedSearches) # loc_qw + qw(QuickCreate Quicksearch MyAdminQueues MySupportQueues MyReminders RefreshHomepage Dashboards SavedSearches FindUser) # loc_qw ] ); @@ -1152,16 +1248,13 @@ Set( =item C<$UseSQLForACLChecks> Historically, ACLs were checked on display, which could lead to empty -search pages and wrong ticket counts. Set C<$UseSQLForACLChecks> to 1 -to limit search results in SQL instead, which eliminates these -problems. - -This option is still relatively new; it may result in performance -problems in some cases, or significant speedups in others. +search pages and wrong ticket counts. Set C<$UseSQLForACLChecks> to 0 +to go back to this method; this will reduce the complexity of the +generated SQL statements, at the cost of the aforementioned bugs. =cut -Set($UseSQLForACLChecks, undef); +Set($UseSQLForACLChecks, 1); =item C<$TicketsItemMapSize> @@ -1201,7 +1294,7 @@ Set ($DefaultSearchResultFormat, qq{ '__Subject__/TITLE:Subject', Status, QueueName, - OwnerName, + Owner, Priority, '__NEWLINE__', '', @@ -1223,7 +1316,7 @@ Set($DefaultSelfServiceSearchResultFormat, qq{ '__Subject__/TITLE:Subject', Status, Requestors, - OwnerName}); + Owner}); =item C<%FullTextSearch> @@ -1303,6 +1396,20 @@ Ticket/Display.html. This option can be controlled by users also. Set($MoreAboutRequestorTicketList, "Active"); +=item C<$MoreAboutRequestorTicketListFormat> + +Control the appearance of the ticket lists in the 'More About Requestors' box. + +=cut + +Set($MoreAboutRequestorTicketListFormat, q{ + '__id__', + '__Owner__', + '__Subject__', + '__Status__', +}); + + =item C<$MoreAboutRequestorExtraInfo> By default, the 'More about requestor' box on Ticket/Display.html @@ -1364,6 +1471,12 @@ builder are replaced by text fields that autocomplete. This can alleviate the sometimes huge owner list for installations where many users have the OwnTicket right. +Autocompleter is automatically turned on if list contains more than +50 users, but penalty of executing potentially slow query is still paid. + +Drop down doesn't show unprivileged users. If your setup allows unprivileged +to own ticket then you have to enable autocompleting. + =cut Set($AutocompleteOwners, 0); @@ -1378,15 +1491,19 @@ is ignored. Helpful when owners list is huge in the query builder. Set($AutocompleteOwnersForSearch, 0); -=item C<$UserAutocompleteFields> +=item C<$UserSearchFields> -Specifies which fields of L to match against and how to -match each field when autocompleting users. Valid match methods are -LIKE, STARTSWITH, ENDSWITH, =, and !=. +Used by the User Autocompleter as well as the User Search. + +Specifies which fields of L to match against and how to match +each field when autocompleting users. Valid match methods are LIKE, +STARTSWITH, ENDSWITH, =, and !=. Valid search fields are the core User +fields, as well as custom fields, which are specified as "CF.1234" or +"CF.Name" =cut -Set($UserAutocompleteFields, { +Set($UserSearchFields, { EmailAddress => 'STARTSWITH', Name => 'STARTSWITH', RealName => 'LIKE', @@ -1402,6 +1519,23 @@ your users. Set($AllowUserAutocompleteForUnprivileged, 0); +=item C<$TicketAutocompleteFields> + +Specifies which fields of L to match against and how to match each +field when autocompleting users. Valid match methods are LIKE, STARTSWITH, +ENDSWITH, C<=>, and C. + +Not all Ticket fields are publically accessible and hence won't work for +autocomplete unless you override their accessibility using a local overlay or a +plugin. Out of the box the following fields are public: id, Subject. + +=cut + +Set( $TicketAutocompleteFields, { + id => 'STARTSWITH', + Subject => 'LIKE', +}); + =item C<$DisplayTicketAfterQuickCreate> Enable this to redirect to the created ticket display page @@ -1508,15 +1642,6 @@ for Rich Text settings. Set($MessageBoxWidth, undef); Set($MessageBoxHeight, 15); -=item C<$MessageBoxWrap> - -Wrapping is disabled when using MessageBoxRichText because of a bad -interaction between IE and wrapping with the Rich Text Editor. - -=cut - -Set($MessageBoxWrap, "SOFT"); - =item C<$MessageBoxRichText> Should "rich text" editing be enabled? This option lets your users @@ -1570,15 +1695,39 @@ option can be overridden by users in their preferences. Set($OldestTransactionsFirst, 1); -=item C<$DeferTransactionLoading> +=item C<$ShowHistory> + +This option controls how history is shown on the ticket display page. It +accepts one of three possible modes and is overrideable on a per-user +preference level. If you regularly deal with long tickets and don't care much +about the history, you may wish to change this option to C. + +=over + +=item C (the default) + +When set to C, history is loaded via javascript after the rest of the +page has been loaded. This speeds up apparent page load times and generally +provides a smoother experience. You may notice slight delays before the ticket +history appears on very long tickets. + +=item C + +When set to C, history is loaded on demand when a placeholder link is +clicked. This speeds up ticket display page loads and history is never loaded +if not requested. -When set, defers loading ticket history until the user clicks a link. -This should end up serving pages to users quicker, since generating -all the HTML for transaction history can be slow for long tickets. +=item C + +When set to C, history is loaded before showing the page. This ensures +history is always available immediately, but at the expense of longer page load +times. This behaviour was the default in RT 4.0. + +=back =cut -# Set($DeferTransactionLoading, 1); +Set($ShowHistory, 'delay'); =item C<$ShowBccHeader> @@ -1608,28 +1757,16 @@ overrides C. Set($AlwaysDownloadAttachments, undef); -=item C<$AttachmentUnits> - -Controls the units (kilobytes or bytes) that attachment sizes use for -display. The default is to display kilobytes if the attachment is -larger than 1024 bytes, bytes otherwise. If you set -C<$AttachmentUnits> to C<'k'> then attachment sizes will always be -displayed in kilobytes. If set to C<'b'>, then sizes will be bytes. - -=cut - -Set($AttachmentUnits, undef); - =item C<$PreferRichText> -If C<$PreferRichText> is set to 1, RT will show HTML/Rich text messages -in preference to their plain-text alternatives. RT "scrubs" the HTML to -show only a minimal subset of HTML to avoid possible contamination by -cross-site-scripting attacks. +By default, RT shows rich text (HTML) messages if possible. + +If C<$PreferRichText> is set to 0, RT will show plain text messages +in preference to any rich text alternatives. =cut -Set($PreferRichText, undef); +Set($PreferRichText, 1); =item C<$MaxInlineBody> @@ -1652,23 +1789,25 @@ behavior. Set($ShowTransactionImages, 1); -=item C<$PlainTextPre> +=item C<$ShowRemoteImages> -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 messages will be -displayed using

  • RA>nH(OwVXir4 zq@ez;buG1(tqPPr<5+Q_v*0|=unMCRWbu7jTMUva<-h`jCz5(*5}C$S2Mc&KYSk9Q zt!1+;itS{{NIKiuTB>)iV9f|BQMDHGYb=(0h`5<+v}CgxALX?N96a5Q{F>*L7RaEe zVpFRwn7tuf-ya`+VPRB*AugxJnE4;6w$k)%sb*i}QbqVHCH#jccgKVMe;Xk$1oMQH z68}xvKZbFr`smt*E2vs*`7L=P<)+Bin5a zzb=OVZ-;)|nAM@#7jq>5nM6F8FE2y0j>DgjXuDjQMzc{doLQ-;EN1c<$ZG!s z0sk{CQ@v zdwUIiZ@0l3<0KxJ8$AB@(Z102QY{bpf8z^)xj9>3g)2}L6~SC;aWsoeH5de-O1`+C zu8h~$>uRY27^@pzUIqo^&(_ma^Vsc}|2rDN0Mx(c!1nZf(_to`DCOnp<(lt1#?sKx z64X32E9QR}XSO(mGjB|`RNTPEMy1zNeO%I98}QskB-ly2s+Kerp~1{o>ApQ z9#!A+aE*_)YQvuF0ZB+-?E@)^ixB9Vdm^%R;vs=QXbv+WyQ}otjT}L zDNH`^r|n7*m|Kf)1_nWPy*(kb(C?!Dy%kQhy9X&}DvuTRP#8tBqePYrUTH373d()n zSmf0g-Cr&d+klj(t4Z}n{ZuU4J>>t}3J9WqHD9REGo3gAAhG=nmHO4zZ1SSEt${gj zK}5)(8u}0Zg-NDV93(DRT^Ss7oX#b_DlG^1xR@BAYHI?I+>nq7a=>F>PTV5*J>j2&JrhU8RyaK#k3-3kab|^& z^L-=*T9a=`dc3Y=ZCVH5(T^$R?)sq6j{~mGX9D%^a)5RRvE$QIBe}qK%f?Oe4a7Cjh@zlp&clOl~9f<$0zMiF0qDB4ycUg`gyU7&V!!M)!MvN^V%8k&Py^ zpuve%*LHV%ef;c#(==W{k7x_7F~=LXQ)2EIPv#HqK)DsI)57l=-e%j$e8jj-fp?5b zGhR|7D-A~aKTN82LUW$F$Z7R|%9X=gUaH3s|C^-!1h68N8(3x2{H;vsC)Cr@sC4!|P3KAsz{Iu9qb$|@`r zJ>6{ky|@UR2@|>6U&)!T6>z_#4~?#s_au2{;pfU|x2|cebG;<%^40I~fAKsZXvvMv zs)IWQ{uIdCoRS4b3fcY1kz zhPnflhu?b=y0J1=OGJ0yrLJmp>9Yg22_>8>Mpxg5A-cdYh`T3&mjh_QQ?;Q3VlA~k zu`6(!7|#VgdzNWH7q2R&E#+SPEn`nCD*$eL+L*eB(GE{u0V77A?HNc<%i{Wqy=>P} z;n*VY-k_>;c(wRAtOefmkl|~Gt7Bqp)7$$%3F##d=FL?`&byBAcyBH)ak&`R@P9Bd zm>(4FFI1qiwt(jr12;AWjfJOAjUs;N>s#sbXR**oURN=t3H*qd_`pLE(A&gm?YrhZ zBIF{=cQXm6o8IevImJOzW0Izf1OrO-cSHFb#P?q*;aC%@s0#`NH;tFRm0ew^%p^m* zctL@O=D29r>1mP#trZt1xN*H`=;T&MS3$G+(oAKYZ$i@+Ri)6t@~+3F_7vh;tjDW# z$?t{U0^0$vsCjC7;Cc&~k4mWV8#6cr^Z9a8il{alv1@1?o$Pd;9ovM=wmIDDxUW~u z2xiVT`MJCKCps?=tf5$xwCgY1*>@P09?Lb(lGR(`>7 zan%$OVM@^3#gK}|DMOvZJDpfk3O${vSDo9nsWcPpv|}f?&QfdH%26$(!q>HtU0MCt zE$tUp&pwM7=p>SV%NnTZZak2k?KtoLh2at_)PpQG{!~$Tdzr;! z?$ENkmte(}joNi%05XRU?zydku#V_1q))iwI0?l;_#(Wn5W!j{J3T@h8Li{~1P^w{ z1h>;}XN0cDTFr8zUaU)?Ain33`pHL-#X&9! zM^^~regO;CvT_w{AUx1?CrLa0E<1&_D-)86ORgWNW~a-d#i zk~-KXCe3d|GVaalL5l-Ygc0I_!6U@)DOvOcSj9i&=I&NsxMX^>qOmWpF8U4V(*BsK z^29A$+=oaEbHQ^|rjY>qEAh1_^h)S&CbZ0f&UMbss@5bhS#wY--XC%lUFVNna6c(W zd373`Ck%S}Eg*)C_mWLqGd7_{{$p&+tt^++MRaV%kTVvm?QDHYk`0q~`D|dt#-duY zcs?k?W<38UmMdH1`nt4K&|Lif6^P~>_1n&ekPXM`eTfr$GEojckA5BX4o}YOsOB!g z4#Q$TM|Usr5~e)$8_jAvT5IhSZ+V*k@qA5D7it<2fK0W6Z)!k|FQ)`idA+TI;W+UN z^=1)rV(8~m7TkaHwSKnOT=uj;c6gG8pwD9|zj~s-#!FGx zlSOi$IH(i#DxY{mZN=kAnE17yx%_yBU9mG=-CyIJ-}`P1nB~>{(mS8Uzno+8`6jW^ zQ&!zQ#}jzxXP&Y_$0 zmEct)P+TGPS!$S9@4M^LOc2^jLf{aPgMvoQfm-gy2Yam|wZw`cfo#C6LY{4cZ-xmCKv0l+TF4q?ZqlCX)+m-43qvDX+G1VlJ)pUiCATSEY4-1{pBvS zT-u@xH)#EG?+WtV3XmdPrf!&Bn0<_s>#iq|0_Y>5t}MNCHDJYR~M!-Ic*4XMuV!?K<^SF{l4h4@?%F$VN|yHMm+@KnZL{GsQyo2 z@(>q;gfwIO#q4Wz0)3*vzRAv$Yun)fE?a;p{rupVLt)(cuf16^oTwBQYxVZ~&$m!T z_(dvkY;^PRJk7(9kr_N+PjP3;ihb=<5?2^Dsq}O_2Rl&s6mpmFm13rixE+rM%wD7^ zcQA)OkwJBoY_K%#ocd%iYUw1gGlz)0AurXuJs%I;vZhp=Col5r(2h!yiAs#rYDy2a z&mZfb04~ppHIho#TlpP+vy+=NpU{Y_(PcE0jq1q(FR=l}y`UnZyG$SZuNoy!&xrl~ z{qq8=rjvNgCgW2%{8e52eWsR@{eN?nW(5d-{+KLJW|t3m`W*irjDYKYf2P+v_B}?C z&1t&1S<{3jpH@#hd!Ex{j-7*zus_|3R?QEGIgv~6FaEPH&)LG6kcFbS&+2Ae+PKnQ zF5JLFqie~n}guN(a@@+JEA z{e$=c0#xw+b--89`hy)>{=W<{dfBEX0EFye<`TN^PD3T4^*fzEba;rfGx@G(J%&ef z$(W3#C3Lq2^Br!JpV2R@{C)iYSkyH5;HXdg@jpZ$Po-7)EOxNqa zDl6A+x*e?QwRy)ss7;Us(jD~b{JosZ89u12m!ot&v0sm{brSu!&vn|_m5MA zb(K9m2TIi>2BVQ4cH__nj!yojl6DGy`{ObL5@&7{lyj=St1T8Z1CO`TM09i-6i}Qz zX>U4b1Cjn3`pffmW~*9tPcDal^sBVxjsIRr)n?$c?+1+sm`cFU7DS4=y0mcy-?@d< z)%^kj0>oz}^_i0dYo{#EY4zM0`S>`vx%0O_FXP1jF8P;Wn14h;COkGv4>rGW-}B-( zu&S$5BX>zYgEt_6XDA)qP3pz{Pj|obvAoygUf&0&IVqJ>{hQeNJ$=lR&iivRraJM( zvf0jL)}+;PZOU4s^S0>9RxdS+sVDX2F==#tCb#o!Dx2|lSP1N-3KD3;g@9{1m(@cE#=98zHctX@2&W8%VojjrbAreQYAmWXCA(;$pftKXfP$PmDp z7#|z6U0Wiir}vu5mN)RcEGPb7t?vAY-0e8^vA5U%tG&>LeN|@Brsno>rU9PBheQ4%g~v z1htsbzs_3RstJ0WPIZ!MNv92F9&Y7W8mnK0icnU4khkv1(P5LkhQ*099)}#QgU)ER zD_)#9jU%+%MH+$(Xx@D@?t2>g{a>daI?RfGmLe$tZs&!6cK zkThKMRW@TqB-6JWxg3KhB=G1fCmXY5;?Xj7OLBM0Xb2%cs z)KkASrM2~T&2CfT22<9here$92~TY0_3#6wXCXvCAHftp;_DWm3ZzR5)bLTby^!CpbskyP5ra$%JIqis)>7-OVnwuCG!SwsCyb)~p-(;O;`+_><8Wi`1QE9E=59khxjpPioJ=K3@sE@-LHHfus0qpikr zyh|R-{ncpX0Xn@x1Mo~!@GER!_;xkUAp2(W z`-53!XX^`rcgFE^#anT1&BP!$N<5bydB-heHRmc!a<|E$?%fb^c@6MTR+`T{ohC$v zQpaIVR1ctr9AgPwYtpwgOVz6u z=j+TA9-rwKAKhuTo6AS6furp1OT1Bmrh8zxpK-Y#Nsk<`H5Ruhr`j?Q2b7@*#llfG!=IRipuSXsfU19E-|#kNp&-y#0r-!QUE^*rB_sg*4#r+j{F4iW4#t43X>V+%?K8 z%Uy1sjzwq+c({1Ze-m${vT-l`wv#$`G<46kK_Wo#mEZMRfpY2YcVWZKw;UH?tu#;;B# z)k?!r>*-Br$1`I9nhP=#c2Vz~5A^tJD9M=JD~-4I`%{ra!F&6ga>RTiF15rQ)2340 z8{WpO8U^RqNes#7AKR*BYJHXPr2=`?F6#)Y1b?RxC>lF8SR59|%rD_*Ka(52br29U z>_vP|44Y{!ps%*tnbq>b%5qS7{l1j$ev{;9?a(R-VY`wv)lu2r7u!+UH zl7wc%IvsC|`{EVCn}^wa-kqZdjkX(ckaZEuQr@tq&+j2fcU(3Nj=$<`wP^GwD@va( zg~`rgUM5@~;P(kivy9$F7m>IeA4s6~A2Ca?`wh~@9HWYQG!ciRuR!Gf+pSkCf4z)M zYj&qcv&b;fX{4}TpN6ZEL_})k5P~i!CVlj%73daxqr(-8?8r;4AP00>#Gik?0I})i zuHkRR9f%Gep0bFPIvbp?k6JbXFWWMnzL*dQWuIh3;arACSB|Vi9q#;jzR@>-*C#qSdIlxO_&^LYmd6 z*d+cqhZ zxq=n)<&^LC<_Ljub^!0)x_HhC1ftf9ufMh+Pbfd;sGrEh@n_2rZy_v>|Fl&B>sMS{ z+|whB1}$0I1z~7tc-v%O|DhvTNp-J^VTJ#I8rS6}wutB}ZS`nvFDl=tY;R!ne0`F1 zmKM6$R!EY+aHN)Xis5+crF_r}4O5i(7La7)&7wG?3-xMzeZ^sriH26epA#p?)H+vHO)iP0}PZ+%NDItxS-ck9W6UAh7H5PnM~&t9G?qIa~y>xIGUHXSHpUo z;hka=9576nJ&0452Vsh7qT#d`<}({ao^E`fz9W2}D%w?N1uRS>hLHp|1jv_O)0lq$ zUT`DbGJ7MRu^0%aDb$@alr#UPu1@unB-FxXF8kI;fwO_ zbPQF3_VjvK3&TKJqwTNj)ZrULfH}_fk2?Unv06Igjge7iZaw)MoVstPagMe1Pvj<8 z@g`OC7Iml}2X#%+uT*VcLG`V=6_g z_})lujf{c~r8k+kL)d6zbJ+e++8FT3t{}nq-VScnK7<^6C zU82w49HMkjw)diAaQn9VA~zae zq}T)@&o>}j5-@dZlcxtQ=f!fpcx}5o((9%-xmgcPedP>jLz8J7L|Hn{urr`Ddc!h@MCqSsu z64b&LE7pG!;K!xf=K~HtpNnY!D+j@_GJ*^L|I{V{W>>GRuwoZ^*oE!`TP?0j?EOFN zy>(EQZS*cmDcvPVcOyu52#BO0-Cfe%ASvD5A>G{wB1m_4cXyr#KN0u-bI+bL=gjN(AfL#*{xBDeDZiJIK(3^&j6mf2pCX{Pe|Cs-9M zABhk33Ay+n?+hA2+69hRMS4o<`7#txS1{_%z|cZ?E6@dSt{}(Qx$31CHxAubWu1 zVX0(SJ<0c@A#qD$yy{&2F+`I|V)4cWL_CU&wwl)22K$X;&QaW*vao3G`ly@r4GBO= zCz{<%^~r*3?DgdeZhPAo_~`qEhha#B-O>6gF(8}Pm&ghaWtcrSA3g#{Uiy^jgBK+D z(c*>uWj#CTBl$~;B9(2hVHTd`G5aG*tmv$bxp8_Sivm9G0XvqOuRZpdn3ydzM+F%; z^W|biQ-kVzUo}i7g@D}MhueBr=Ztr`kHM$?MT0wWmGKx2XTxHDvbL)=3{0hwQQdLA z)(#u)pDnekw)ib0P)Lo#W+K=&TVt$A0&v?byGsZ~RHF2CS7IMOv#^O@x=0@GN@XLT zXy9$>A2jKnT~FviB}POvS#N}5OJouXy6EP6SkBU(M4y%jd*{F0YUiz)vrikdRQ8aF9e@er0 z;oh8twM*ZrU+UK=n3sK_N^IqkyQO3A)A|Jzh3@<29S_P1kyR7pzGx{t0HFnBYA1LrC{dQ_7ug+MvIG*uC!RItxH)yd1ehz11zP zim<^@BmQtZT!@_PpJJk3&xyx3z=Py?%nmpG}N?dqzPAZi! z+PBb>-HRF)3sKe4@xiV6{AVx_J~p|q8+3-?OQlL4%r&U_A?%Ba78e&^E6t&+@WVW9 zP+rhjOKQ|kEY_Y5`|jncNcEl`YU!ll2_NROu!m1h0wt5)M+#~AAs%SIFRUfTyx-u3zP9mvQj?{Nduyhrx1BA ziyf^uj7FU7cE+z$6fD7>yqO1I6K3fU5)0*)u(b3iNy&6^=*vBG=V08Og-nq|rv)q~ zq$eN5m&O6cg4WhOT*`aTHZ!GE6tSyO&m>}pgA+w2!4wh_Trl#gpZ~8o!4T^4Y7REG zS9pVCx#3=Zkbz=M-ckgn@18C(rja%XWy!UasC)Y4u`|gagf#dNPreldloPcy{VRkW znTYLQUrP0uPFG*EL`a}-r6|0zQ~jH_C6FcuL=v`blIc&b7zAt`P*NnAcSb*dVoL=0 zV)9+I=gJ4oN1EaP(u06p!$Ju4pT;h_L1m>(x^F9o>%$YAl5mxxJSh(aWnmr5N&E&H{-^sy>FDsK|7st z<;N2c(QqXtB@xU~1o4t8XRN*F{Fbe%fv1w0BVH{Tq{jM)|34WH2xuZ&Ev;Z)`P}e> zOfe9JQ!u;1r@XP&_irgDHdbvR#l%(dgSgo-!C&5sbKBHmb~{Tgy=~7P`4a7Y23!5Z zU@LTbaNtXzpConW{Iu24aR9Vb=i-Sw+i@Ox9f?$fzBx(qw37vIFPBSV zBo0R~Ub;6p|Bx#xC^)xDsPV3bmbBl%jO(pJK&isQWbK05Y4y{K;VZ5vD5&6hMLqd2 zA@pG}gEs_GdS`X|ZcfMfUZa`SQ(2Gu2gPSS#;iZ?-vb`n@Wkwc>FjjX_}H)Pg+nCQ zHJGZJQQSL4(|tsDSKHAzWG~;~Yuk6fyut63SWZuVM(gJF&4Q{aSME%fsqHYhE608d zd2904T?R~~a`x(e7Gs;m;wn7@57ZP&i+YD$V6t2H{eQtR2V7DH7tdjTW{0VSO(=^A_Bw&|fn@c2(~aJOq*|gkYy*5XpC@MsGufQTjX;Ip`Z4)$MVf4l)Ikm z=hanFB`xl^H9~txkw${ITu~L<6uTkxvGnh8Ijf@+cQyP&7i#tz^p>`Dw zIqvR$mmV&w%gf@#zgg&?f;#$fGOk4I;P%3@$07-aEO@I>`0Zo?9I; zwWU~;0g4YEnLn!}$Q|8D)e0Q8Gb$?I)oq!$LCX6UWd*+SDT46Ted@6&=%WKYG7*Jm zMk(J;)gcIFk`A5Of0ewmxF9YURns;s`60MP@&%yH5Pz^&Sf(8 z3pN(P_5SDNTDOM~`1GVM=A=M&@4-?i?>!GGsq^inhlhv!nlh2!{aML~SPa(?z_?ra zIs7e3W775PU(ryrSBkGS+*UBpgBzfkKHRqfaxNQwKT~I;I4yT)(_gRm%z(aCaKD9A z;tA@j^td7{{81t*%4#-Oug0*V--jcWbVx_D{C5a9>zZ)u#%{)7$?nJ3{B)k#(PJ;OL z&*B^$e8JRToYMb|tlpR;-_~rCW6aP!SWpqmh1+6Lo8R;N$Jff&tT5H4ob;S)Fw=D> zo#%UVi7Zu3klIlN6i=!$z*X)_r~M@SY-gA5T?7rSpohV)mCnL`<@-y1AO&~!TX0?r zt$2zA(ceV@0yc(`3uvtJqs3Br_Lvfc8IhdNwk7CG^Z_bf)zZpqLRN}_`GiJ_R{x!efU+`cW-|? z>Fo+WLqkJXGd$A@smH5jbBcBK(7S>rNyXg8bHfEY)+^{f3!%z9ch;C*(+$e`&dIZ& zKsFT5bapP;SOd}Oxqp0V31I6BW@E+4DxD#C5(!Mj`l3nC8ch!mA&6c?tu;`{W4Hw% zDUrk;sRf*w_NTDv_i7nKe&bH$xtfybOij1{L05Md$kr6IC2OvB6?5rSbGWv$KUq8w zFlLlDyx!cFM0Z4RWs4X~ zXjF!ua1|hG1SaO=`e+{a>R9WQ;Zwd2P@|r`=xs^vxwg%I_YBM+-nHI-ocsc2s+C?1 z^A1?=!LLz?6bOG1K3`(}_`X;g_QMU{gQASyCg+?4m&4EKu@+E&K_!T%GfW(Q+x4Y` ze2{cxgqdn%+*otdOvM1ta9-pFci_f9P!PhH3K)1!OYNGY74Yt8`whvXbivqf;!~p) z1}cCe(MrVeuT20X+j}n_M|r56ER~L0u;Q)3`CKJsv$Orddf!)VlLe90zg!A|v~cTj zjS4%b2^Uw=`GHh_Z!f(pn)&mDBb9o4tO+XSjm5vkLh0h*n2+tH9Wp+Cp7By0&=Yc$ zE^z%dxw0)KC7-e~N>9(Cx!EKtJktLXVgF$<<;T7Pa?R6u=Q6TD^M!eCrb>GtS|SUL z@c)~E1rn~0;|35_5_E}cdwU$Z?{iY`3In;O(^!~Sl%IzeLj3V@l0b?`o$M_s@;_sG zLk%GdcN&yt?|*-b_aXkyKRlcLjU<9N}P`eU0p2|KY>OyV3|@fd^p#!8OJ_ zkNf>}zFa4ZDaXx-6#Fv8iAHCzGaC&}ts3XW&)~1xua89npVhV(Vy(BfCAw>#q+$qg z`1b?3xtWrZ!H_^^kUnqyc&!XD;TOarGO#d}7X{<1&$9!hczW@DqS2wXwNX|n%cuq!H5B-WBt|uO*D=p8Cjy|_mGd)G@NPLUUP(U*QE{61Ihu(e4vG*14O1>zY z^QA>MkVlk^8{A7F{OshEl0^CO0FhT$y(wX1W8zq$1t!W7DnctqGF(aN#ZUGgH(*m* zTE6WeJ$}j=0wcf=4Lo|jHvMNTz@RW24Nu02&+{C5bguQVUtL|Kygyv>TfpT!kA(P^ zDZ;zCJiNO2J<$EC=RPWG*M1(W%;m~IBZJd7Y0VNEh?0APUI@Z)K5tQY+16Vm`IcQ1 zeSJ5f+MpmV!1#_qvlKsy7u8@^{HehrZbN{mN{b)#Ba*iT~MhQ~JZF z(eOy}5nWovU;O%*IBa1qXU5Gx5{gtBKJiK^X~-8xOUtPPGO4JBH-t<1g*-DrxF}y_ z9A;1_{_L0uqRolT=Vy?4s=t3k_VIL?Oh}69=Am|oQ z2Bh6e<3xi4QAK10z1DXJ?gPJm^(C-}0Qt`NR~nad_IZH2*M(vsNQ<`QxcT2^=X_nl zcG&pl+zhZ2(XkAv-WBC>9*HWb!QYfaB^|D}{73})dZ?#Mmrfz^UC6=74!mq_m3O}$NwZSd(wfl~0#h<= zov3lF6C_*c)-hTm^&<(3o~fI6y-*Q^at8AI#3FPTdoy$tT{EjyzVG6_!;)MCAF3@^ zgs=-Bf}fwP zy!`h(Sp_O89QKIm@@%f;o%#5l!+b8BrHht@h-5X24c>D_KXmGp2rQ_G94>vkm7i~q zgXolM3bc`*(Hs<$qNlS+eDn~+%W6Ao>*@Nl#$B!l7pZn5fZ4S_Xg)kiy^-5sfEeJp z@lT?ZMbMot$Y5hK%@4P&T6L_5NEG+HTTk3jQ^mgV8+Q4<_i{j8bXk=(>9R#m@6>azgz$e+1ot zCC)8*oW^5AFUu7$7Z=O$mW*;|faCK7V(wMe3u! z$QHO>E5^Zn&3kR=9N$YtdCd%SeQsKk3TKf@B~ks)a#u=|CoF9J^#Sboy`m$t?z`z{ zJD@4yY0M2Wo`9Y~^N*X!2V_G<(|(!H&tL&m9VXUl0zS_(m5%|ykmwFofc*l*60&6= z>?HQ~bIr73P36yWBLYpWN8{z_{r&c;0jT&WQ6y$RygMk}+{YG=%|jzDC>&9~1d0$YFINz28#tL2<*}cKz~TYI7^jWJe4mD4 zr_OqUPNP4Z?~xws=zalFnZV6i3sCC;e(#^R9$#7v7&C&_OtmdMENlu#RcB7>&+Kpd zp*T}jCKDKIRD>%f5tD`SI(0Vl|2E#)6R2ipY?s5Y=%xXyOD+IJF{abCTK~W5jz=)iO7yq} z0U|qUt8{1LLF!`w(6vaUacM#W7M4q#7EGBVn2^#34F-QC{nb1M7!<{#IpDBOCB4t;%baM}A76p$z(!q3f? zWg2KLnL?{$(oQEv%ME6WodESF9&1oEBSNcBq6%Ow$^&3nQR&3CI||SJW@Z3@1|G}H z%bfNBdgxzfY7WGcCQa|IEqQqQUbEU!s9+pCZycmj509(NQgX>G(JMf%)N(q!!g!po zAs~jwOML=KQtx`yQ36C^8hm`zu%qEL|J?WnC07nAG0fs@D+eF@Oy{C=ziFPyyZ{wQ%ub zWo>OP*DERCkIi=IX}^F=S{Sl;)tJ(C~ zvg89L|4y!AN|S)FEHtP6S1!(v|9RO7%Rk zy-Qim8f7wLYU^3@hOUx={u*`DAGc4oBK zdwJFnnl6{e5pZY8Bw!-WV7hUapRz!I?5X=%0ls@9-s?oGr8ZkxW{P_NA!X-m#B;oR zF8c!v?_)UmXm~M*03>PDaja5hCHkSB5HY35!5{4(uV73Gcs=T!(rty&d_{1jNI8t~ zCf6Iu4#9u?ErGPyZ+j0B8`PHIGS3f?7MVK4j?H|u)SYY3%M9w%qqUEwgt)pA0?Oh@ zG@1JqV!XKRcCBWr&5mv8R&4&MyhAGW2Qb0s5_MBj$+@bXPWy#B7It=OQc_M@T2}U} zpDO0A)|-Rb8n??!)uw8+GD821Heah^>#b1L7cWj=L+-UBhQ51;bK9fQ9DY`@YJ0et z4G2jQUkz?uv>dGLNkEy1_x%$C>-~1JF3?)|?zf&mMrB{);X0LupP&TTJ)}o#u{X|6 z$2br@r$}{wArur)izo+0?Ra(R(0tMlwu%wwoI)?Z$v2>hIbm0RF2?M14mXfl8z zxC$h@iDFo?n=5TKTF67ds0@mkNeEbHjVOzX={7 ztYQHxd@uc)yZD({$^{363mkko@Bc0(&uOU9&qSV4QORl!Yah(a%p#p*s9?<*78X-J zB>lEszkf-~K6*^Rx*Y+Fo;kr8aiLg|As=v?%tc5W&wGzRx(cA8CPznc9OaVp?hWPT z`JZ%-e*$D<66qDB^JIHr@znXwv9ozVCH)CeQurV0D*@~x1yQ;$i0!#V76fz$YSaO3 zynL*?s}_J1ysr0G@dqtZJ-v-nRRt4(MjZ$o!e!K*O$UAk8<@@0Fh)&Q7$oNA=FC)D zGH3lwcLN8UfV==oChr!#+JQ*Xes}W-sLfPWO~B!DI2v%DmsQLKU_`Us-nUqV5q9e@ zb92e3xFhA+f%j9@@TIAS&qbad{Av1xyRjOQr$BqogZ+%9RMZx~ zs31eRhB<&80TjDR2`DPn7nrI5PPzU)K>@vxWN!ZlsH#Gg*yoX(tbJS@px_+J)!d+< zMI=0y8NH0E!N)5omeV%KqtH{Zz(1Rsq{X2i44H&hrImbocb{BoN*pMva z;^4=x09tk|Fp9aF`Y|506e46253GRvAja+s$6~Hwk7& z@tw6XakAH4J(no|Y?_VS(V@KM|Ip2iT(!q#>ro`NC&HeTLa0ZQ!*z+?c%}z^bn5ZKA{r zJi5e|hdT#7!oqhoz9Q3)$DCUZEFk^0Elkv)~10(-!YR{Dal5TfLGdni*vceRU(;*l_` zwGia(Y+W@9#DI6aJ0m1R41cDQq*EWubCY@9C-&!fudk|hM{@Rn(rNf|#`WuAu^Avi zeZ1(UUS2#7;0BcS^;I-q-*c9v_J7*jjs{c`pHG(cEV!rIUEBN|FXCk~o9!Beq;EQ{ zXmPe-ExxoO|WY2W3&KY~?ysM3aXa9}g3 zvR@68SE1Q4`NdaQm^}vI<4Ce#>J;(k7~sDm=4y%86E8cSg;byr#`G2!4L8#pLJ0qu zQbm5CrU0f3?db#?z%GhDD(Hu*J&LVSsj!^#RLI3|8 zBR(A;#zNV5{GKe-1$-JYzmZ*}ZFlxRhy{SpsYrIeBEXM_Pi(hWYp zi4eX!Xe~@y>|-GRP?#x9`MgeSDOy!0Iez3AJ{vu2U@`T5T*3bPi6;lJn2H2l0-im| z8xdet;xHFK!@XR=NALGP5BmSo7!8hUP5EZ%=*T_>*TrhQ_iOaL&f9s#*QY(P*7xT% zD}bNQ$TUp}K48YuBMFDC`t)#AJOv?xUXx;5vJwlvpptYz+fbW$$?@JW25^!=- z2eJ~;$-+-LIKXp(BR_hAly2Yv2O}ePOuE_2>zyC7vu*6wcnuq*{{pp^7oWMk?>gvt zB4-+tl9}&rcn{lPLlLk)F&eA1IQxu0TiZ{BnmdG_KaGyOcrh)TGt7Iny1LwP6Brms zE&-FD!Tm2Yb^%t6*LJ=&B6-dCt+(2AryeNG0J06 zB4^L&WcPFhpc94k6S|HKmD1JA34bPfG!MY4d0c}>e4mxDzPomxVVAbfw)uP`Nui4x34AY_jo>Y>%Z!|RX6 zC4EjVJ(0C4K!%36P+h%mRkVVRPU0W=B!DO1ya4WtW(Q&HH=c#W z<#g)mVe@f)ek_ouGm|Gtt9f3pNP~bR>R9xj^ws~tIj?|BEAg5HjnZ zjiz_0%gg7N?urApu%9Dn`!5)LJZu*0e;V-^-2A^W5(!R%rMka7*Lrl$`nD#Ai0RDq zn~bpGpMBFCj9gyA-5@O2wze;4rY5s-LwYOf&%|J^0@xY3H}I8(`=h@q)NK)h|GAU2 z)iC>iKM3jn))_~h*A+yb@WisfNl1o+V9I;Rw=Sk`ZXdPM&--zOfD?zTt*z|sjRz^1 z9?OMt0r}wPz1v!B;P2YlRv*}jmg{XlKmdS~d*odd-!B4@cM@AT2F~nKW~i`O3}F0!jX>yaL@T^h%!Md_>@;=~@Di^b zD{SBnc?^e*WMo2det#WQrW39S60ly3 zAFc2fDckA-kybpiNk;@F%;PATKelSYDe4?(l_?Awnjs=i?d4kRhHmiWM#|3q($eR| zvn+XFD|@H`YjawN^`j`zMrTkZg`J9ld;7EPK0sK`s_k7eSL65ck?ydiYJK;GV=c!5 zXu>Nnz!d^XZW8b^z!l;0@}y}oCCEQ+`-_hrA1818#l|0>45h2P+H`mR{l|};mMe)1 zfK`u;^kT?UdCdQ-rd$Ou7E2t4(Xy87=G9Iu*< zMtqkh^u#_?7epmLvR zqeBwtFp#GozHQ_*Z;2+BbRpp8zdz8>(rsiv+9Usd0Q`|B;(@|)HtmfO{`Y1OXcCga zm*1?5R5rfnD3s8#&e_aQwD1sV**Zi#Huld?+8ye9Bj}qB%w{06z1qrQM(D&8^gB=B zcK&o@jx-+jbRBR*8YLJdt=58r&V(;~LrJ}HZybZ`*+PX;zA%>*81OEr(XlahoDddG z3W?9`V6HdkO`92k%5=Qz?|}bJht8_>=61VXWo=>KzuqC=n{(FfiD|;EPgitAG1Pz{ z&{}Z0=|jDftSm7uW3hy`;TSi%T|9~^wFEtafDYq(`*^=v?O}wzyx$No^wV&jk!n(S zNw%eZabki}p?oE{O-wwcNqB(? zK|d%G!|zA_Gj5fjU{R%}Y2k)MPZI(Br$;#72dNE4?CI6}mC%-x)kxLM98t*}6wt`d z*TaxTMmZ5NM$<%|l^)eDS1Sgi$G*P4E94x^G`LQ}Vp{8z?-sMhMQ?ibIVdy`AK-5)eJC!3vtO@j*H&VcTy$g;u2sCP&69*ag2Iz zcxV}8LNy|sPx@eP@s4rr5B->Q2KABtbhQ%7lZ9AIC9AqO>kipN-DRgJ0GZfn!ER;( zI1V9S;V{{rryY{_a6CtfmHW*)0?96-+Z(;bRyh`g9b^k; z+8$0YMmgME@nNyNN4QO@zH-$1q|dpy$<9SwMKX!aaaVD%D}04q%6Mzb2>j*g<~Z%_ zl~l?t3{h?Qu!6>dM+Fv^S}+fE*Wu<6YE>0^81#9A-QH-8IqRl=QyQ^|B!MHrq1?rO za_PgEg5t?1uax3qp79cm8uh07O@{D+biq#N)AxSP?d%&C>1w#*ujjv~8Kv^5jSLSD zejgO%u>J6wg$DeLU#c~1`&T4=>nsbp@Ev!9hrrVsa-2hBHE zo%jlfxYO1HDT2uy$=3UIO*3z>6C@vp)*yoKlEsBgOlzLR;w3bnnKmPa?q@89>Fm-R;zD^u}Kvwkz;myqo;hUj1J4e3$oO*C2K| zUU!ZI(W%QH84E}+B3YeZZPYAx%D-tcA56mw<+2*HosjVOyfYWKDu z0Y4?(Nk>>X)(?*gsNlziM7-8;wI#2|ug~bIDy3yi?|WP=gD?=A?+0PRlzw25dnDc^ z+e^;WmTC7Tm=5{4o=+_S?Y_&%I8MDkUB=XCJeyfaacGAxSfYV7%_mwJqh01Y9o*eW zso;ZnSZ6)JKDk=-%D>sax!4?XjiGJ8P9+p{>QBCrg4i~4KMIubS6FrFV_xa*xga!v ztCL8YeAq7N#5&=>tunC}Pv&qq-whuv8R{Fg6C89uQhveL@ zdZ;Zh?l3|Hq4-^@$X?b<2hxP32Q6tl?qkCe2sW4*b(QM&G1zMmF+(j1SSpr`<5w4L zx#YgQa%?ytY1$0C0SfiT?UZood9HA2l8G$4Cj*F(14h^`y2xK{<)G{={P}F5IL`a2f~6 z5dxW`kA6rE7S7D?Cvg?j*_}2AgZ#@+D`$-|Ar=P$9G&ZrETs7#hPaE6LtJZ>RzAYq z43=+lAsZlPG*oR~S@`|-&jg^JhZ5ZUBq>CmEc_tU(2GKQR~v}8d*%xrqgZOBi^82SJ~ zAWDdaK~oDsjkr6fQqU6L5%D69aR1058W+mXoXBlZ3?IIO@{&HkLQzYzGsMeSjRYNR z=qr+1oHnFiJ&KPGwVND&QH{^#E6EW^!}N4=3GBl@^AP8&wY|$AoGJU^toyl!5l}Tt z(;D}5LrBDZAD(X$NZeV@7yS-D1fXKD`RJMNQ6Rs85WRC3^Fa8S$}%`)`FoU>kr(Hf z@UVPqM8TwkiP{W35~EQg8?i; zA-?OUCX?6+ZFZ>=-u0>5EvdwC`KE25-nj-7x8ukyDzJWSXp#n3#L8Vy@XoNe>oM?H z!>|T~c^~QI55Bl{5X!G?&n5PJ-&UR3Lr9V7U1zW$_|7G8$-NS;f3i-$bQ|F2&mmX@ z2&YkU1`RAQDjMFnA{FT78oYK$PfJV>B*M9i+-a+xQEv+X&|MM>u<}NPyWnKLg1nSx zxWg}i)D$JY>#QfBE5rGIeU{2|wd&06Z1;Tti2wpcUgHYg?@(H`Y#F?^R~rtF8m>3l zWVpa~rwEfzihx}g5)2s?87Y^DAQhJr;r6UVrA!lpELe+1qKG%G-)`RpA>ilni$ky8 zU-7tm18EsY=FUVO^7l)K^xjrN4ej{Yyt$yH0SI1ZzQ)Uh-Nwvv3ixWxx7H6Kb&p=~g7IE{FIJ4jl44Z3oDFiU60QbAM}e zef**e>6)1HX4!J(ntyKD%Uh9O6dzA;>ttOGYC>f-D7GWc55Z4?R6a~z)Ofcw0+#Mo zVW7Sq`9n20*e35KH|$C~w6Ihe)tm9jwzPYLR9t})Fa2a5*SLfYzgFPl(-nNFomqC= zf{G<1Yow*tqIu{=IgolJGBRzW>bslJDsY}cutxxuL`WkjzZ1U7PO1tAyKn{uGC4-YP9&Tb|ug`1N# z;5+yeQ1z50<%4$4UyTcaKtXB6hB9xin>i?P-%LM?kJmY>S8`+~5&!WV5Qsj84KS$0 z;d~WPt&W;KG4$rOTDBYg$!}yKd6~MaDidCWh5r}6yeD7)y>260Z?E_dQU3RvQpAHA zB}+AOkl?8Wk0H?y06(LiEGhl_SrjoJbfT4nfY)|oA0t9;mR;bZ%N)Vr)@ z!r$PQuNwtu*3`FjEs`)Q2+5S&{QUg$g@^gGaTVuz=L73obvoUh#qZnzcmdRa?sv1+ zH+=oy0*WkmJtUj9^P}0WRwE~9RUPt%7HxO9uC78766ynBRFX#FfPmw%GweXvKsiCe z0UoKUK)!CgaO(VYWJ1Gv49qRNRk792b*0* zl*bC!F<<(?52%eUBf4J>EVbMA4R}!gH|;Y1z~sU(zmy>KL?Z!F0A-6%w;7Ul$>{SU z+8rwp`xtIAob|wKs{Fl&-dw@*%>^~NfvfT~vHk4PahVP%I-tfoP__B!*;kE#wLfK@ zYrfEl?>c(Fo``-Z1Dptq%YJNsF?S&>)Iq32*=2CFT+W~qLtHSFnRjOB)3#jhG*djY|lOBu%TDCx&2iB!`-3h^?QyhE;tKpUt8WaF~9G_z$}k z?NkU6n#-8yN6YuCzh0s%sy8FHr@n&e_*6Q;%NxXYD5cm`k3$Djp`W~C<94-MAsMu# zTtpUhKm;vpXWnv`Es@Amqe%9!2q`-}JdCu|jtCr)Z@&P9>AI;rb4ZZ!jn~`cE%#Nf ztsZ!TBg7)-o>}r8Lbs-dgPM3B9_BBa&nH^Un)a$GN4~)Ws<0 z3sU*2rlqM`B((p1T%@eLOgU}`VJg3u)3pnEinH0O!neU|%h1*EqBmExDiiI6C z)Acn^!?#i!z?h(sco?Zzy!KJ|{u0E3IBhans-DQ|?s(F-p>v6fjl6@dIga`AL;4;8 zgocw5XB)ZXE@FHjVJ{GjO2soOQnLK`9=820*OH2Ywph zX}DSmz2H8d9OJ$G{s~I&$FFXVgBX?jknfV4Jv+sT9$}dxQiU2VH~pMBcR)lSg5%4fi*V_&E z?Y9H>y9t_H^>?k<>a252Th}`Z5Bv59v?_KI7yGwV=o6^3X_168S$wHX94|jtd(IAHDV>Dtq2VX0yH^O42p)zKTmc#fDm*1zWW(Zk> z83>trdI|`sk1%g?r-5K<5iu<)Hvsx;P?>J#lv=&b7p%*friMc->DM3l5lZp>a&INNuyEE(_bQcN1>D<)! z?(Xfy3G6x)mlh2Zoha_R$9v7&{IC`q`o#|ffD&Xd?S~^T-^`mWfG?KHDAg4ef~ zfUJ}-GcOS;${B)HWKw@Y>aq;dZO13hpu04MO4^%^H*eS0)**OwmLx=jyCwJU;*W3u z>aBs;y|gx%l&Ef>(qH-UK+%iz8nNRp8VGu(jneh}yN#G7cBN>kI}C zCOb6Yg$p(c2AQ-KpvwStIQ3K<2w4_3n#Y7)SY~;OAfPQAfgkd)G?5adxT~PuihE); zAWJ~HB;aa+CykmOvv&r4T_yxaEWi@@O?||JknSp(fnYqcMvL9l zotj3I03{)veF*xd|H6+3;0Tg6T+hZTjoQNvo>J$P^n zsgsv3zSP_}o8VSNwj0fDUbtOngX$BFeA#LWVm`0o+~>7432@AysJhy*bQ=T^0v6+i zAN?aqIBSt+9adIR$+uJ8u3e*wF$1Ejr6Hp9BCbgMedW;7>KIPou86U}6>9OJA;9H_ zTsBy)n2FN&-uRr4QcaLvIvEE zPeA7Be31OrQ2~G@^X?DPJ#?IyW+ic+-r3-9_Kdot2;DE+am23#1mJ#XM4Z46{wf8fg+uCf))(WzyYYyK;t!I7P&W>styB4A$@w7OFuG0**Yf9 z7Y)a?NK!Et?ROY_+dH6#P^&Z;OUjjo7_QJn{zt!==>!shQ70i1Ch!cAT>Jw2l3!m7 zIrnVGzUk;^&Se=moT9d6fgfIiX+1b z4B%7xX^R5HK`36$7O=wWxv={7OA4Wl%5OL^8SU@Cv(?8n~(LO?kF?Y-b2+`mv=_c3oE7I5@@od<-B zH*oj=Jo3*lz5k!Fd|n*?2aCus62kc6V2ZAW`^H4}8<_13YoU5nbUax#Yy}35T0tkVwG2nEI#t2VHIgv_6cijJwy;1Z6S z=1!ESQMX7p-tBdy9*<#V{9(xV_F~86A`19^Sg#*gqX$Z?^d?8=vXp?@R)$24xIls4 z>7cQ&x3|zSFvY<+Hocp9MuASKr9Y`gbF2RNF8t@HnQ~ZNm!e~sqcKB$M3>=O{qc1d z#P^lEH4{wwI6uX}m|~WgZ$>v0MT#^L2DA>wd+TgtU#bkZX`;$5^O&9C!nO5G3iT_Q zi-yCu^T&%^h^S@HV`1N7a+m3cf?8Bc^g-uQFX;YQ_%uvLSMcuFg}FL6y>&``)N=K4 z{1&Zp(&wFv*cHa8WYePrh=m%<2uPEeT*S_OTDcvJoNU@T)Dl+mwsFY;W(#6a6t)*& zJW~ACQ85Kr_K6%_<4wP> zew&5CB9yFNGG^%)#;Fn(Ic6W5@cczoFBT#PTFoJq{fo$C5k(>nb54kOWvlZT8(RI+ zNz;lmKXR45(NYEYzPlJ-hpFo%`Kg&fM6^*ZXWO9sA7aHB!sknDO{9y$#L6(MnEFAC z9I9dCOUAu0k*!;(P!3kB_89kZMN?Dqjt!`U?CC0tTd|f@?77bQ>^W>M^heIeS+-Xp z7K*op;BGAg3ve$+XD>gJ_Hf2$x4r;XA=C4(YDLDd@(kc2BoNp8$qCkdded$Rrvx*T zDfJ_ez777X9+RMzB`k8yWT}sD*Y`QTys#-5pARoE;LahC%peejkzFKP7>AFiveWwBDi=vBZu-f>|)> z%T-`kAcKqc(3JYLGD`B3u!6A~iosJI?P2UBBrY<>%1PlY4r`g-eYBu*oCM#Q&nRS+ z$chhUhtr6nhpgZRD28`EMB3E${9~f5p?TbZ?UJ z5UqfaP&S=7w=>3%T>Ef*eAa(`9P)b z*<&j8R+qzM^E75V3b1tbC24GjcU%P&CQQY|W(o@&?xm=pbd%`2xPOITX1bgVmt8hG ztUqo;BupU9c|XpbwPXVuY%6=i1AGsZ@Vi+VP4q1}STRLQ4ny%MZ+NV{T5E({%d z83>O6u}$0;E$~%076SWSXj^GbZtZ|yCsE6Qiqc-NF;?n~y7D@V`UFp&hmUt~qN!kM zLb|fwLydq)J{_v5x!_)qifuYAXGzzRxqlAKiDVKAC3Emx2ye#O?Bmp z9Ti5sWu}uGC3ct_1qX^u?+}`Af)W=4#{KBSJ@WW)t)PCXF7SbVlK#7J+RlpIUtE`v5wnf(llJc9=^$AK1 z(S%h_C41?cfYnJFN4?t2F4j^izL)pnYj`e}G@V{=IK0anuVg&`pXSc{8_unL;}I=- zCs9Hoh#nEWM1%;Vgy>x`f-#urHTq<s+jv> zj^_!Gy5{*s-ja?14htjG*EKa$Ey$4L4!=3GrBI*X=0d)&ttHCA-(#9-XuG2xh@_;{ zKbjSjsx*LVuvCwbyR^JgONPvwuX<^v-tiLHzr#fVkFI2A=frUZoE!Po$hHH@1mzg- z*s0Vwvo9@lc~0;WJuuTvz?#1jU3v6Et(!7g=eUYVw?%9S{(;4iS^UApQlzHilxx;cs%%a!YMNsoq<);MT{g(q-)33{$xMv|VmM4d^t<%{g?0lWoX3`op zqA}9olh_E?UO0*szS+!?Bh^SxT#xyVp1uoDg;qbq*BOk%xhz0h7(OB-wq9k1u*JpybUw`4ikwl6?wJpl zCW*NnAue$;J!Ujvj5`R_`XVYF_%rh*ZF9x$kC=f+ROE>xm*Zvi1Fq`afelnFrM`#^ zX*&;HYoe_FyZZRDpVn)|_yHMr}{EY6h;E0G$^~huBC~2}@DRC-qQ~r#Og; zK<%?!wv9nyEIaE6Lr=Mrp=ZB~T<0?xa0A2Uao6O}ms<=HVUbeS%m;*xA6wi(gRc&< z$65JWAJ|sA8n=UNt{(MuZu~{J>9;RL+4^8akZ<03V!)}Sk;Fy^y%l*)DM9Tz4O1Gu zx_nXzD{ltW8~f}_*-XsPvit@9hOr{FLxJ@B+!Sb7u%dCnt%`-KA1@A;CQ!f)!j&Ur z%N)T)#>htF?R%;ci^t!te6@F?b1xL!N}k46=-Fkh!pwb--s($&^_@2iH{hFBM$Hsq*6&V;Mp(Ca9IHu_Z|N zrbMqzIQd%v-M1yK?wQJ#6MT^59+TK8E<@UwC7XsIqlX{;GuFt4t-`zbOe~xD9e8Pj z+X8eHcDv5-_^XH{qZ5Ux9k1?K@%tFXP$+E4?YEL}-eSuscPydGb*m_HC@PcPrPBeQ zpaynXhVk=RbrVN(e=m1UqYtQ%r0J11|SkIzMEOXf*`n`^#OY0R#& z(3b@cFXfxoe+}xAtWaBvK+-`&f-Ezb#N7(MD|_TC-#7hT{I0E3=^Wn2(_?I71hyf# zG6U189DsB1g9UrFv4~TUpUV*T3D<9g=66P4O;wao%)G)dEGR*$XW>}TEnj?n= zQ#0YnYVsM6^J4?3SVm_36!WY?Jx4Vb>>N2I8maqazlC|GI>rUcPF*bURn$9y8(mN! zUreh{^lzLi2#D4<6rO5$~bzY_?+d^TgzH|zS*)^$CSUVN{Rqa#t(?98DIvigH7 zQPhTT?%PZy{)c2^!$-T5X-mkhy+2I>Qz&RALG~v9%*ps?XOTSIaVD0-l~ucYhuwES zt2`_2N5t5+@KOZrQYe{sR8rCg+&vJdaL(ft4dOGfyP{sI{z`^Nw@#-$! zy+zyy2Yi>fSlcF%y}fs~;#AU~v&=J$&7pQkr?%^2@SWDqSzgvdQ$49T#@m~SOfk91|>>`5`X|%wS(I{-&;$k4d`mv#nt60$wlH_F??d% zZ;e zqIgpe0i3WtJmkHOM_H{C5z~U_{jPEsR=iZ(&u#<&CVhcWyDg#kB3RSW8vli}zz_v%|>l|>0yg{5WCT`HjFH+M}u+j44V zfG=5$9v(blV{H1$)N<<}nv;SZFKt9ZML&~ufal`F z*Qww_6LPE(>m89LKoi*JW4I;$<6ug~WxCNE)tBNwHt$yQ$*ijnN2qm(>DE`hStvNV z2X5fhKXvGI+I-B8|N1m!vxt?Ju1!Txx(#A`t2%_wskQGcn}UlXC9teRfoXkq4bAuC zSH&ECrP|xtnvu?%Vt&lh=1Dx0^B$?jz*H;F!*{oU53fmu04ikFz!T%=1y)tdJe+jw z?HC`xQ}lf$j(kZO1yn($Y8wfi?<0J;fgEw)7C#>6=PKe*zU-I{h)MOb+gaGQLGLCaaxr|-K{b%ik zsWj|{6gV)Vzj5*F1?Z9gFr~??AK@dU-9wsdxctS20*XmgW-?m- z24HNTr%#B({Y38)Vo7@S%K*J7-C_=->30RH$BX-R8PK+kGg&Rj>MJ+J#^nE&jNuLX!jg4QHTj%=qcQM?*@HkhS$ZOHH*FK1YO$FM`avfapyctdYDy6< zn!0Gg72&skM* zz)s&CTX;T8N{ZYsJ{KVhRu+1ALv3U?@q1?Cw9dz+b7uV9E|C#PMqxSiQ`i8kPW5D- zL;wWUk@M`$u`*nzMQd^ljGN;;j6yC`+5wT0lj@3jtgdN~)L)HLiDuKX9&7 zrN06CbLaPAQ#em?R%Ai7U}M3wl~qMrNkW@0)~jm%K5EXa9C-6VO3gEKY(_K7?j>ttIFnZc%M%XP>Oq;pMNLX$}>4WMqNjUxp^RHn)za&3OBo}R_=v_waf zJYeBs!rPG=$fqEr`i}QzYLrW%zrR@hVfrgZyCi{_ZxQt?Xc=1BXGHca#73;wzzw!r zig3}OH_FL>^^ImX`);`=Z!(mx;h2n_kmt@3P$Tyoh;hF8jN$P9I8~J12hqkyJ=F9` zwKfA=`?nLVGv*HzV8`keY9W*J@Z}48gBKhq7@cI}u`Vp@Ghcs@7+$0RRJ+Os_4JqH z=kiz;7QszXzA25ucz=kZ3jYvApZKvRepn}nIVg?F@%C@-fXFVmdow;Glx>TtLu75| zsbRBa&%OH`)2woFLLV=*ASaVDcc!!vUL;P#yxhrBr!W$tTFz`RbA6@cCnOFym!ab7 z^FSYhtU#BDSMs-UKnB7Ws%j%i(=%H6cDuv#cox%anYN43ZM!4sO89%09!07S7L`*} z@eJKhwWj|rs-K_|j9x1~y@%>I`2M0uA*+mDz;dM`?AsO-T8{A5HxmUzjdb zVHX7E^Y)Gxc-L9G=hePov91dvb2wkYOarM$0qVTIhS+;WmoC9pR>1)Qjb2!?+HuPC z?JRcDR-4MayW_Ltw<@a_Y+k1?BBBNj78HoJ?<-hX(X6vcN3k47rdKtOQK!b{BkqmO zOh?%fiMCpA&l~!FSonJ*5j5;5o*vXLN!rEnvn!nUdKxbT7!b=UZh>8FVtLZs054|} z_cuiQ5d}_)vD*`pSiwz-tAcmMff~0;BX@2ghU6OrZyIfjaY~RM#nxmV{o=UW)S_di zXK#_CvXJG!&~9O|8tM;bx{2%t0HX=`$7l5d7tdL!$q}ao_aogwfGCs2IJ?&L!=* za1ro3H;wrI@_*$!m0li#P$@B+iG1*Xs9Tti+Iz+ZE-C(se{}8tPWu1s1(*NaaPXBJ U4Aa~=xP# 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 wfi(ZemqP@^HHw*#@wd0$zcDN$Ap9$H8Mh($ literal 0 HcmV?d00001 diff --git a/docs/images/general-status-chart.png b/docs/images/general-status-chart.png new file mode 100644 index 0000000000000000000000000000000000000000..3d208f90131bcf38593a17c98f34bac45e593fab GIT binary patch literal 39175 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 z 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 literal 0 HcmV?d00001 diff --git a/docs/images/queue-created-started-chart.png b/docs/images/queue-created-started-chart.png new file mode 100644 index 0000000000000000000000000000000000000000..b0aa8338692d26df4f0a3d2b5b592415e61557b7 GIT binary patch literal 74489 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